💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[TOC] # let命令 ## 块级作用域 lei用法与var类似,但所声明的变量只在let坐在的代码块内有效。 ``` for(let i = 0; i < 10; i++){} i // ReferenceError ``` ``` var a = [] undefined for(var i = 0; i<10;i++) { a[i] = function(){ console.log(i) } } a[6]() // 10 ``` 变量i是var声明的,在全局范围有效,i的值为最后一次输出的值。 如果用let,声明的变量仅在块级作用域有效,最后输出6 ``` var a = [] undefined for(let i = 0; i<10;i++) { a[i] = function(){ console.log(i) } } a[6]() // 6 ``` ## 不存在变量提升 ``` console.log(foo) // ReferenceError let foo = 2 ``` 这也意味着typeof不再是绝对安全的操作 ``` typeof x // ReferenceError let x ``` ## 暂时性死区 只要块级作用域内存在let命令,它所声明的变量就不会受外部影响。 ``` var tmp = '123' if(true) { tmp = 'abc' //ReferenceError let tmp } ``` 上面代码中,存在全局变量`tmp`,但是块级作用域内`let`又声明了一个局部变量`tmp`,导致后者绑定这个块级作用域,所以在`let`声明变量前,对`tmp`赋值会报错。 ES6 明确规定,如果区块中存在`let`和`const`命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。 总之,在代码块内,使用`let`命令声明变量之前,该变量都是不可用的。这在语法上,称为“**暂时性死区**”(temporal dead zone,简称 TDZ)。 <br> 有些“死区”比较隐蔽。 ``` function bar(x = y, y = 2) { return [x,y] } bar() //报错 ``` 调用bar会报错是因为参数x的默认值等于另一个参数y,此时y还没声明,属于“死区”。如果y的默认值是x,则不会报错。 ``` function bar(x = 2, y = x) { return [x,y] } bar() // [2, 2] ``` ## 不允许重复声明 let不允许在相同作用域内重复声明同一个变量 ``` function b() { let a = 10 // 报错 var a = 10 } b() // 报错 ``` ``` function b() { let a = 10 // 报错 let a = 10 } ``` ``` function bar(arg) { let arg // 报错 } ``` ``` function bar(arg) { { let arg // 不报错 } } ``` # 块级作用域 ES5 只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。 第一种场景,内层变量可能会覆盖外层变量。 ~~~javascript var tmp = new Date(); function f() { console.log(tmp); if (false) { var tmp = 'hello world'; } } f(); // undefined ~~~ 第二种场景,用来计数的循环变量泄露为全局变量。 ~~~javascript var s = 'hello'; for (var i = 0; i < s.length; i++) { console.log(s[i]); } console.log(i); // 5 ~~~ ## ES6 的块级作用域 `let`实际上为 JavaScript 新增了块级作用域。 ~~~javascript function f1() { let n = 5; if (true) { let n = 10; } console.log(n); // 5 } ~~~ 上面的函数有两个代码块,都声明了变量`n`,运行后输出 5。这表示外层代码块不受内层代码块的影响。如果两次都使用`var`定义变量`n`,最后输出的值才是 10。 ES6 允许块级作用域的任意嵌套。 ~~~javascript {{{{{let insane = 'Hello World'}}}}}; ~~~ 上面代码使用了一个五层的块级作用域。外层作用域无法读取内层作用域的变量。 ~~~javascript {{{{ {let insane = 'Hello World'} console.log(insane); // 报错 }}}}; ~~~ 块级作用域的出现,实际上使得获得广泛应用的立即执行函数表达式(IIFE)不再必要了。 ~~~javascript // IIFE 写法 (function () { var tmp = ...; ... }()); // 块级作用域写法 { let tmp = ...; ... } ~~~ ### 块级作用域与函数声明 函数能不能在块级作用域之中声明?这是一个相当令人混淆的问题。 ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。 ~~~javascript // 情况一 if (true) { function f() {} } // 情况二 try { function f() {} } catch(e) { // ... } ~~~ 上面两种函数声明,根据 ES5 的规定都是非法的。 但是,浏览器没有遵守这个规定,为了兼容以前的旧代码,还是支持在块级作用域之中声明函数,因此上面两种情况实际都能运行,不会报错。 ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。ES6 规定,块级作用域之中,函数声明语句的行为类似于`let`,在块级作用域之外不可引用。 ~~~javascript function f() { console.log('I am outside!'); } (function () { if (false) { // 重复声明一次函数f function f() { console.log('I am inside!'); } } f(); }()); ~~~ 上面代码在 ES5 中运行,会得到“I am inside!”,因为在`if`内声明的函数`f`会被提升到函数头部,实际运行的代码如下。 ~~~javascript // ES5 环境 function f() { console.log('I am outside!'); } (function () { function f() { console.log('I am inside!'); } if (false) { } f(); }()); ~~~ ES6 就完全不一样了,理论上会得到“I am outside!”。因为块级作用域内声明的函数类似于`let`,对作用域之外没有影响。但是,如果你真的在 ES6 浏览器中运行一下上面的代码,是会报错的,这是为什么呢? 原来,如果改变了块级作用域内声明的函数的处理规则,显然会对老代码产生很大影响。为了减轻因此产生的不兼容问题,ES6 在[附录 B](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-block-level-function-declarations-web-legacy-compatibility-semantics)里面规定,浏览器的实现可以不遵守上面的规定,有自己的[行为方式](http://stackoverflow.com/questions/31419897/what-are-the-precise-semantics-of-block-level-functions-in-es6)。 * 允许在块级作用域内声明函数。 * 函数声明类似于`var`,即会提升到全局作用域或函数作用域的头部。 * 同时,函数声明还会提升到所在的块级作用域的头部。 注意,上面三条规则只对 ES6 的浏览器实现有效,其他环境的实现不用遵守,还是将块级作用域的函数声明当作`let`处理。 根据这三条规则,在浏览器的 ES6 环境中,块级作用域内声明的函数,行为类似于`var`声明的变量。 ~~~javascript // 浏览器的 ES6 环境 function f() { console.log('I am outside!'); } (function () { if (false) { // 重复声明一次函数f function f() { console.log('I am inside!'); } } f(); }()); // Uncaught TypeError: f is not a function ~~~ 上面的代码在符合 ES6 的浏览器中,都会报错,因为实际运行的是下面的代码。 ~~~javascript // 浏览器的 ES6 环境 function f() { console.log('I am outside!'); } (function () { var f = undefined; if (false) { function f() { console.log('I am inside!'); } } f(); }()); // Uncaught TypeError: f is not a function ~~~ 考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。 ~~~javascript // 函数声明语句 { let a = 'secret'; function f() { return a; } } // 函数表达式 { let a = 'secret'; let f = function () { return a; }; } ~~~ 另外,还有一个需要注意的地方。ES6 的块级作用域允许声明函数的规则,只在使用大括号的情况下成立,如果没有使用大括号,就会报错。 ~~~javascript // 不报错 'use strict'; if (true) { function f() {} } // 报错 'use strict'; if (true) function f() {} ~~~ <br> # const命令 const用来声明变量。一旦声明,其值就不能改变。 ``` const PI = 3.1415 PI // 3.1415 PI = 3 //TypeError: "PI" is read-only ``` const一旦声明变量,就必须立即初始化,不能以后赋值。 ``` const foo; // SyntaxError: missing = in const declaration ``` const的作用域与let相同,只在声明的块级作用域有效;声明的常亮不提升;存在暂时性死区;只能在声明后使用;不可重复声明。 对于复合类型的变量,变量名不指向数据,而是指向数据所在的地址。const命令只保证变量名指向的地址不变,不保证该地址的数据不变。 ``` const foo = {} foo.prop = 123 foo.prop // 123 foo= {} //TypeError ``` ``` const a = [] a.push('hello') // 可执行 a.length = 0 // 可执行 a = ['aa'] // 报错 ``` 若想冻结对象,应该使用Object.freeze方法。 ``` const foo = Object.freeze({}) ``` 除了冻结对象本身,对象的属性也应冻结。 ## 跨模块常量 设置跨模块的变量 ``` // constants.js export const A = 1 export const B = 1 ``` ``` // test1.js import * as constants from './constants.js' console.log(constants.A) ``` ``` // test2.js import {A, B} from './contants' console.log(A) ``` ## 全局对象的属性 let、const和class命令声明的全局变量不属于全局对象的属性 ``` var a = 1 window.a // 1 let b = 2 window.b // undefinded ``` # 参考资料 [ECMAScript 6 入门](http://es6.ruanyifeng.com/#docs/let#%E5%9D%97%E7%BA%A7%E4%BD%9C%E7%94%A8%E5%9F%9F)