前端每日一题Page1

1.使用typeof bar === 'object’来确定bar是否是一个对象时有什么潜在的缺陷,如何避免?

以下几种情况使用typeof返回值会是object
在这里插入图片描述
可以通过使用Object.prototype.toString.call来判断基本类型
在这里插入图片描述

2.严格模式下变量必须先声明后赋值

(function() {
    
    
	var a = b = 3;
})()
console.log('a defined?' + (typeof a !== 'undefined'));
console.log('b defined?' + (typeof b !== 'undefined'));

var a = b = 3等价于

b = 3;
var a = b;

此时在非严格模式下不会报错,此时的b是全局变量,值为3,a的作用域在封闭函数内,在函数外部并没有定义a,所以它的值为undefined,控制台输出信息如下:

a defined? false
b defined? true

严格模式下会报错:ReferenceError: b is not defined,因为在严格模式下变量必须先声明,直接给变量赋值,不会隐式创建全局变量

3.this指向问题

var myObject = {
    
    
    foo: "bar",
    func: function() {
    
    
        var self = this;
        console.log("outer func " + this.foo);      // bar
        console.log("outer func " + self.foo);      // bar
        (function() {
    
    
            console.log("inner func " + this.foo);  // undefined
            console.log("inner funcn" + self.foo);  // bar
        }());
    }
};
myObject.func();

在外部函数中,this和self都引用myObject,因此都可以正确地引用和访问foo。但在内部函数中,this不再指向myObject。因此,this.foo在内部函数中是未定义的,而对局部变量self的引用仍然在范围内并且可以在那里访问。

我们可以通过在控制台打断点看当前的this指向的是哪里
在这里插入图片描述

4.在功能块中封装JavaScript源文件的全部内容的重要性和原因

这是一种日益普遍的做法,被许多流行的JavaScript库(jQuery,Node.js等)所采用。封装的目的是为了将信息隐藏,这种技术在文件的全部内容周围创建一个闭包,这可能最重要的是创建一个私有名称空间,避免不同JavaScript模块和库之间的潜在名称冲突。封装使得对象之间的耦合变松散,对象之间只通过暴露的API接口来通信。

这种技术的另一个特点是为全局变量提供一个容易引用(可能更短)的别名。例如,这通常用于jQuery插件。 jQuery允许您使用jQuery.noConflict()来禁用对jQuery名称空间的 引 用 。 如 果 这 样 做 了 , 你 的 代 码 仍 然 可 以 使 用 引用。如果这样做了,你的代码仍然可以使用 使使用闭包技术,如下所示:

(function($) {
    
     /* jQuery plugin code referencing $ */ } )(jQuery);

5.不遵守规范引发的问题

function foo1() {
    
    
  return {
    
    
      bar: "hello"
  };
}
 
function foo2() {
    
    
  return
  {
    
    
      bar: "hello"
  };
}

在这里插入图片描述
js语言会默认加分号,return后面如果没有紧跟着大括号,就相当于直接在return后面加了个分号,return语句结束,没有任何返回值。
所以最好是遵循规范:一行开头大括号放在行尾,而不是在新行的开头

6.浮点精度丢失

console.log(0.1 + 0.2);             // 0.30000000000000004
console.log(0.1 + 0.2 == 0.3);      // false
原因

计算机能读懂的是二进制,在进行运算的时候,实际上是把数字转换为二进制进行的,把0.1和0.2转换为二进制:

0.1 => 0.0001 1001 1001 1001..(无限循环)
0.2 => 0.0011 0011 0011 0011…(无限循环)

这里可以看出转换为二进制是一个无限循环的数字,在计算机中对于无限循环的数字会进行舍入处理的,进行双精度浮点数的小数部分最多支持52位。然后把两个2进制的数进行运算得出的也是一个二进制数值,最后再把它转换为十进制。保留17位小数,所以0.1+0.2的值就成了 0.30000000000000004。 0.1+0.1的值成了0.20000000000000000,全是0的时候可以省略,就成了0.2

几乎所有的编程语言都会出现上面类似的精度误差问题,只是大部分语言都处理封装了避免误差的方法。对于js而言,由于它是一门弱类型的语言,所以并没有对浮点数的运算有解决的封装方法。

解决
floatAdd (arg1, arg2) {
    
    
    arg1 = (arg1 === undefined || arg1 === null) ? '' : arg1
    arg2 = (arg2 === undefined || arg2 === null) ? '' : arg2
    let r1, r2, m, c
    try {
    
    
        r1 = arg1.toString().split('.')[1].length
    } catch (e) {
    
    
        r1 = 0
    }
    try {
    
    
        r2 = arg2.toString().split('.')[1].length
    } catch (e) {
    
    
        r2 = 0
    }
    c = Math.abs(r1 - r2)
    m = Math.pow(10, Math.max(r1, r2))
    if (c > 0) {
    
    
        let cm = Math.pow(10, c)
        if (r1 > r2) {
    
    
            arg1 = Number(arg1.toString().replace('.', ''))
            arg2 = Number(arg2.toString().replace('.', '')) * cm
        } else {
    
    
            arg1 = Number(arg1.toString().replace('.', '')) * cm
            arg2 = Number(arg2.toString().replace('.', ''))
        }
    } else {
    
    
        arg1 = Number(arg1.toString().replace('.', ''))
        arg2 = Number(arg2.toString().replace('.', ''))
    }
    return (arg1 + arg2) / m
},

floatSub (arg1, arg2) {
    
    
    arg1 = (arg1 === undefined || arg1 === null) ? '' : arg1
    arg2 = (arg2 === undefined || arg2 === null) ? '' : arg2
    let r1, r2, m, n
    try {
    
    
        r1 = arg1.toString().split('.')[1].length
    } catch (e) {
    
    
        r1 = 0
    }
    try {
    
    
        r2 = arg2.toString().split('.')[1].length
    } catch (e) {
    
    
        r2 = 0
    }
    m = Math.pow(10, Math.max(r1, r2))
    n = (r1 >= r2) ? r1 : r2
    return ((arg1 * m - arg2 * m) / m).toFixed(n)
},

floatMul (arg1, arg2) {
    
    
    arg1 = (arg1 === undefined || arg1 === null) ? '' : arg1
    arg2 = (arg2 === undefined || arg2 === null) ? '' : arg2
    let [m, s1, s2] = [0, arg1.toString(), arg2.toString()]
    try {
    
    
        m += s1.split('.')[1].length
    } catch (e) {
    
    }
    try {
    
    
        m += s2.split('.')[1].length
    } catch (e) {
    
    }
    return Number(s1.replace('.', '')) * Number(s2.replace('.', '')) / Math.pow(10, m)
}

7.事件队列

(function() {
    
    
    console.log(1); 
    setTimeout(function(){
    
    console.log(2)}, 1000); 
    setTimeout(function(){
    
    console.log(3)}, 0); 
    console.log(4);
})();

顺序打印出1,4,3,2

解释:

所有同步任务都在主线程上执行,形成一个执行栈。

主线程之外,还存在一个”任务队列”(task queue)。只要异步任务有了运行结果,就在”任务队列”之中放置一个事件。

一旦”执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。

定时器是异步执行的,当1和4打印出来之后才会去执行setTimeout。

浏览器端每次setTimeout会有4ms的延迟,当连续执行多个setTimeout,有可能会阻塞进程,造成性能问题。

8.判断字符串是否是回文,返回一个布尔值

function isPalindrome(str) {
    
    
  str = str.replace(/\W/g, '').toLowerCase();
  return (str == str.split('').reverse().join(''));
}

\w表示 [^0-9a-zA-Z_],非单词字符
reverse()用于颠倒数组中元素的顺序

9.闭包

当用户点击“按钮4”时,什么被记录到控制台?

for (var i = 0; i < 5; i++) {
    
    
  var btn = document.createElement('button');
  btn.appendChild(document.createTextNode('Button ' + i));
  btn.addEventListener('click', function(){
    
     console.log(i); });
  document.body.appendChild(btn);
}

10.直接赋值一个数组,使用shift方法的后果

var a = [1,2,3];
var b = a;
b.shift();
console.log(b);  // [2,3]
console.log(a);  // [2,3]

直接赋值一个数组,使用shift方法时也会影响到拷贝后的数组,应该使用concat

var a = [1,2,3];
var c = a.concat();
c.shift();
console.log(c);  // [2,3]
console.log(a);  // [1,2,3]

猜你喜欢

转载自blog.csdn.net/callmeCassie/article/details/106973085