7个简单但容易忽视的 JavaScript 问题 - 闭包、变量提升、浮点数计算...

240 阅读4分钟

这篇文章,我将分享7个简单但是又容易犯错的 JS 问题。或许平常开发中我们也会忽视这几个问题。

意外声明的全局变量

Question

判断下面代码中 typeof atypeof b 的值

function foo() {
    let a = b = 0;
    a++;
    return a;
}

foo();
console.log(typeof a); // => ???
console.log(typeof b); // => ???

Answer

让我们仔细看一下第2行:let a = b =0。该语句确实声明了局部变量a。但是,我们也悄悄地声明了全局变量b。

在上面的代码中,没有在 foo() 范围或全局范围中声明任何变量b。因此JavaScript将 b = 0 表达式解释为 window.b = 0

下面几种不合理地写法,会声明全局变量

# 1. i 被声明为全局变量
function foo(count) {
    for(i=0; i<count; ++i) {

    }
}

# 2. i 被声明全局变量
function foo() {
    i = 0;
}

# 3. b 被声明为全局变量
function foo() {
    let a = b = 0;
    // 我们可以写成 let a=0, b=0;
}

上面问题的代码,在浏览器会被识别为

function foo() {
    let a;
    window.b = 0;
    a = window.b;
    a++;
    return a;
}

foo();
typeof a; // => 'undefined'
typeof window.b; // => 'number'

Array length property

Question

判断a[0] 的值

const a = ['jacket', 't-shirt'];
a.length = 0;

console.log(a[0]); // => undefined

Answer

数组对象的 length 属性具有特殊的行为:

减少length属性的值具有删除自己的数组元素(其数组索引在新旧长度值之间)的副作用。

由于这种length变化行为,当JavaScript执行 a.length = 0 时,将删除数组衣服的所有元素。

Eagle eye test

Question

判断 numbers 数组的值:

const length = 4;
const numbers = [];
for (var i = 0; i < length; i++);{
  numbers.push(i + 1);
}

console.log(numbers); // => ???

#### Answer

相应很多人一开始都会一直认为结果为[1,2,3,4],实际上在 for 循环一行中增加了一个分号;,因此for()遍历null语句4次(不执行任何操作),而忽略实际将项目推入数组的块:{number.push(i + 1); }

const length = 4;
const numbers = [];
var i;
for (i = 0; i < length; i++) {
  // does nothing
}
{ 
  // a simple block
  numbers.push(i + 1);
}

numbers; // => [5]

Automatic semicolon insertion

Question

arrayFromValue() 返回何值:

function arrayFromValue(item) {
  return
    [items];
}

console.log(arrayFromValue(10)); // => ???

Answer

上面代码,如果在 vscode 通过格式化插件,肯定会有规范代码,但是如果在一些控制台或者没格式化的状态下写的代码,很容易就到 return 就返回了undefined,如下:

function arrayFromValue(item) {
  return;
  [items];
}

arrayFromValue(10); // => undefined

经典问题:闭包

Question

分析代码将在控制台打印的结果:

let i;
for (i = 0; i < 3; i++) {
  const log = () => {
    console.log(i);
  }
  setTimeout(log, 100);
}

Answer

在刚开始学习 JS 的时候,都会认为输出结果为0,1,2,实际上,执行此代码段有两个阶段:

  1. 阶段一:
    1. for() 重复3次。在每次迭代过程中,都会创建一个新的函数log()来捕获变量i。然后setTimout()计划执行log()
    2. 当for()循环完成时,i变量的值为3

log() 是一个捕获变量i的闭包,该变量在for()循环的外部范围中定义

  1. 阶段二: 第二阶段发生在100ms之后
    1. 3个计划的log()回调由setTimeout()调用。 log()读取变量i的当前值3,并将其记录到控制台3

这也就是为什么最后的输出结果为3了,如果需要输出0,1,2,只需要将 let i 声明到 for 里面即可。这时候就会生成块级的作用域,然后 log 函数就会存储每次循环中 i 的值。

Floating point math

Question

O(∩_∩)O哈哈~,这个问题已经算是老生常谈了,都了解 JS 下段代码会输出什么结果,或者不会输出什么结果:

0.1 + 0.2 === 0.3 ?

Answer

0.1 + 0.2; // => 0.30000000000000004

由于 JS 是以二进制方式对浮点数进行编码,因此像浮点数相加之类的操作会产生舍入误差。

简而言之,直接比较浮点数并不精确。

Hoisting

Question

如果在声明前访问myVar和myConst,会发生什么情况?

myVar;   // => ???
myConst; // => ???

var myVar = 'value';
const myConst = 3.14;

Answer

变量提升和块级作用域是影响JavaScript变量生命周期的两个重要概念。

总结

上面几个问题,可能一般都不会问道,毕竟知识深度比较浅,但是我们也不能忽视,特别是鹰眼和闭包问题。

😉😇😉晚安,深夜放文~~~~