[关闭]
@xiaoyixy 2018-11-07T11:32:44.000000Z 字数 3179 阅读 1081

let, const

Note ES6 ECMAScript6


函数作用域,块级作用域

ES5 只有全局作用域和函数作用域,没有块级作用域

函数作用域

定义在函数中的参数和变量在函数外部是不可见的

  1. #include <stdio.h>
  2. void main() {
  3. int i=2;
  4. i--;
  5. if(i)
  6. {
  7. int j=3;
  8. }
  9. printf("%d/n",j); // use an undefined variable:j
  10. }

块级作用域

任何一对花括号 {} 中的语句集都属于一个块,在这之中定义的所有变量在代码块外都是不可见的

ES6 的块级作用域允许声明函数的规则,只在使用大括号的情况下成立,如果没有使用大括号,就会报错。

  1. /* -------------------------------------------- */
  2. // 在块外,块中定义的变量i仍然是可以访问的。也就是说,JS并不支持块级作用域。
  3. for(var i=0;i<3;i++){
  4. // do something
  5. }
  6. alert(i); // 3
  7. /* -------------------------------------------- */
  8. // 利用 JS 的闭包特性使 JS 支持块级作用域
  9. (function (){
  10. for(var i=0;i<4;i++){
  11. }
  12. })();
  13. alert(i); // i is not defined

函数声明

ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。

以下三条规则只对 ES6 的浏览器实现有效,其他环境的实现不用遵守,还是将块级作用域的函数声明当作let处理。

  1. // Browser with ES6 environment
  2. function f() { console.log('I am outside!'); }
  3. (function () {
  4. //// var f = undefined;
  5. if (false) {
  6. function f() { console.log('I am inside!'); }
  7. }
  8. f(); // TypeError: f is not a function
  9. }());

let

ES6 新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。

  1. {
  2. let a = 1;
  3. var b = 1;
  4. }
  5. a // ReferenceError: a is not defined.
  6. b // 1

for 循环有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。

  1. for (let i = 0; i < 3; i++) {
  2. let i = 'x';
  3. console.log(i); // x
  4. }

变量提升,暂时性死区(temporal dead zone | TDZ)

var命令会发生”变量提升“现象,即变量可以在声明之前使用,值为undefined。ES6 明确规定,如果区块中存在letconst命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错,在语法上,称为暂时性死区(temporal dead zone,简称 TDZ),此时,typeof不再是一个百分之百安全的操作。

  1. console.log(a); // undefined
  2. var a = 2;
  3. console.log(b); // ReferenceError: c is not defined
  4. let b = 2;
  1. if (true) {
  2. // TDZ Start
  3. tmp = 'abc'; // ReferenceError: tmp is not defined
  4. console.log(tmp); // ReferenceError: tmp is not defined
  5. let tmp;
  6. // TDZ End
  7. console.log(tmp); // undefined
  8. tmp = 123;
  9. console.log(tmp); // 123
  10. }
  11. let x = x; // ReferenceError: x is not defined
  12. typeof x; // ReferenceError: x is not defined
  13. let x;

不允许重复声明

let不允许在相同作用域内,重复声明同一个变量。

  1. function func() {
  2. let a = 1;
  3. var a = 1;
  4. }
  5. func(); // SyntaxError: Identifier 'a' has already been declared
  6. function func() {
  7. let a = 1;
  8. let a = 1;
  9. }
  10. func(); // SyntaxError: Identifier 'a' has already been declared
  11. function func(arg) {
  12. let arg;
  13. }
  14. func(); // SyntaxError: Identifier 'arg' has already been declared

const

const声明一个只读的常量。一旦声明,常量的值就不能改变。并且,const一旦声明变量,就必须立即初始化,不能留到以后赋值。

  1. const A = 1;
  2. A // 1
  3. A = 2; // TypeError: Assignment to constant variable.
  4. const B; // SyntaxError: Missing initializer in const declaration

本质

const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。

  1. const foo = {};
  2. // 为 foo 添加一个属性,可以成功
  3. foo.prop = 123;
  4. foo.prop // 123
  5. // 将 foo 指向另一个对象,就会报错
  6. foo = {}; // TypeError: "foo" is read-only

如果真的想将对象冻结,应该使用Object.freeze方法。

  1. const foo = Object.freeze({});
  2. // 常规模式时,下面一行不起作用;
  3. // 严格模式时,该行会报错
  4. foo.prop = 123;

除了将对象本身冻结,对象的属性也应该冻结。下面是一个将对象彻底冻结的函数。

  1. var constantize = (obj) => {
  2. Object.freeze(obj);
  3. Object.keys(obj).forEach( (key, i) => {
  4. if ( typeof obj[key] === 'object' ) {
  5. constantize( obj[key] );
  6. }
  7. });
  8. };

顶层对象的属性

JavaScript 语言中,顶层对象的属性与全局变量挂钩,是一大设计败笔。

ES6 规定,为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性

  1. var a = 1;
  2. // 如果在 Node 的 REPL 环境,可以写成 global.a
  3. // 或者采用通用方法,写成 this.a
  4. window.a // 1
  5. let b = 1;
  6. window.b // undefined

参考

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注