2

在 ES6 之前,JS 中没有常量声明方式,有的仅仅是一种命名上的约定。

var PI = 3.14;
PI = 4;
console.log(PI); // 4

我们用大写变量名来标识这是一个常量,但这种约定并不能保证常量的不可变,在程序的执行过程中,依旧有可能改变该常量的值。

而在 ES6 中,给出了声明一个常量的方法:

const PI = 3.14;
PI = 4; // Uncaught TypeError: Assignment to constant variable.

通过 const 声明的常量在赋值之后不能再次进行赋值,否则会报错。

在使用 ES6 的程序中,我们提倡对变量声明时采用 const 来代替 var。在确实需要改变变量的值的时候使用 let。这样就可以最大程度上规避变量的意外变化来使程序发生异常的可能性。

有句老生常谈的话,JS 中一切皆对象。JS 中的数据类型有两类,一类是按值传递,另一类使按引用传递。

对于字符串数字等基本类型,变量保存的指向该值实际数据的内存地址。而对于对象,变量保存的是指向该对象的一个内存引用。

所以,实际上 const 绑定的不可变常量对于对象而言,是其指向的对象的内存地址绑定不可变,而非指向对象的属性不可改变。

const person = {
  name: 'John',
  age: 20
};

person.name = 'Tom';
console.log(person.name); // Tom
person = {} // Uncaught TypeError

除了对多次赋值的错误提示外,使用 let 和 const 以代替带来的好处还有实现了块级作用域。

在任何一本 es6 之前的 js 书中,变量部分几乎都会告诉我们,js 中的变量没有块级作用域。作用域是函数作用域。

顺着作用域链,在非函数内声明的变量会存在于全局作用域中,在浏览器环境中,他会变成 window 对象的一个属性。

var PI = 3.14
console.log(window.PI) // 3.14

为了避免作用域的污染,在 ES5 中需要使用一个立即执行函数来确保变量的作用域范围。

(function () {
  var PI = 3.14;
  console.log(PI); // 3.14
})();

console.log(PI); //undefined

此时形成了一个闭包。

一个经典问题,循环绑定事件:

var buttons = document.querySelectorAll('.button');

for (var i = 0; i < buttons.length; ++i) {
  buttons[i].addEnentListener('click', function(evt) {
    console.log(i)
  }, false)
}

在页面中,为三个按钮绑定 click 事件,输出相应按钮的数组下标索引。

结果我们可以看到,三个按钮绑定的 i 值均为 3。也就是说,在上述循环中,按钮绑定的值为 i 最后一次自增的值。

为了将绑定的 i 值限定在每次循环内,也就是需要在 for 循环内形成块级作用域。

在 ES5 之前,可以这样改写:

var buttons = document.querySelectorAll('.button');

for (var i = 0; i < buttons.length; ++i) {
  (function (i) {
    buttons[i].addEventListener('click', function(evt) {
      console.log(i)
    }, false)
  })(i)
}

利用闭包,我们保存来每次循环三次生成三个作用域,三个作用域的值互不影响。

闭包使得变量的生命周期得以延续。

而对于 ES6,有了 let,就可以直接用起来了。

const buttons = document.querySelectorAll('.button');

for (let i = 0; i < buttons.length; ++i) {
  buttons[i].addEventListener('click', function(evt) {
    console.log(i)
  }, false)
}

前面我们提到过,使用 var 声明的变量在全局作用域中会成为全局对象的一个属性:

window.PI = 3.14;
var PI = 4;
console.log(PI); // 4
console.log(window.PI); // 4

而 const 和 let 声明的变量并不会成为全局对象的属性。也不会更改原先的属性值,而是'遮蔽'该值。

window.PI = 3.14;
const PI = 4;
console.log(PI); // 4
console.log(window.PI); // 3.14

变量声明提升与临时死区

在使用 const 和 let 来代替 var 使用时,需要关注到不同的一点是,const 和 let 不存在变量声明提升:

const welcome = function (name) {
  console.log(welcome_text);
  var welcome_text = `hello ${name}`;
};

welcome('Rainy'); // undefined

JS 引擎在扫描代码时,遇到 var 声明的变量会将其提升至作用域顶部。因而在这里,console.log() 函数访问 welcome_text 变量,变量声明提升之后未进行赋值,返回 undefined。

而遇到 const 和 let 声明的变量时,并不会将其提升至作用域顶部,此时该变量位于临时死区(TDZ)中,直到遇到该变量声明语句才会移出 TDZ,在此之前访问该变量都会抛出错误。

const welcome = function (name) {
  console.log(welcome_text);
  const welcome_text = `hello ${name}`
};

welcome('Rainy'); // Uncaught ReferenceError: welcome_text is not defined

-EOF-


geekrainy
12 声望0 粉丝

不忘初心,记录时光,走向成长。