all-of-javascript
all-of-javascript copied to clipboard
ES6学习01 -- let 和 const 命令
ECMAScript6 即正义!
参考:
工具:
分别用上面的三个编译工具试试将阮老师书中所有的关于 ES6 中的语法特性,编译成 ES5!并进行比较分析!
peace and love ❤️ !无意比较编译器优劣,旨在学习 ES6 特性以及编译处理!
let 和 const 命令
1. let 命令
let 声明的变量只在它所在的代码块有效
let and const declarations define variables that are scoped to the running execution context’s LexicalEnvironment. 参考:规范: Let and Const Declarations
所以就是说 let 只在其身处的正在执行的上下文环境的词法作用域中有效!
experiment-let-01:
{
let a = 10
var b = 'hello world'
console.log(a, b) // 10, 'hello world'
}
console.log(a, b) // Uncaught ReferenceError: a is not defined
Traceur:
$traceurRuntime.ModuleStore.getAnonymousModule(function() {
"use strict";
{
var a$__0 = 10;
var b = 'hello world';
console.log(a$__0, b);
}
console.log(a, b);
return {};
});
//# sourceURL=traceured.js
Babel:
'use strict';
{
var _a = 10;
var b = 'hello world';
console.log(_a, b);
}
console.log(a, b);
Buble:
{
var a$1 = 10
var b = 'hello world'
console.log(a$1, b)
}
console.log(a, b)
--> 三者都是采用了在块内使用新的变量名实现其只能在其身处的块作用域内访问
for 循环中使用 var 声明计数器的时候因为变量提升而全局有效,通常会导致值一直是最后一个计数器的值(相当于不停的刷新 i 的值)。
for(var i = 0; i < 10; i++){}
console.log(i) // 10
var a = []
for(var i = 0; i < 10; i++){
a[i] = function(){
console.log(i)
}
}
a[6]() // 10
如果 i 是 let 声明的,当前的 i 只在本轮循环有效,所以每一次循环的 i 其实都是一个新的变量。引擎会自动的记住上一轮的值,计算之后给这个新变量赋值
experiment-let-02:
var a = []
for(let i = 0; i < 10; i++){
a[i] = function(){
console.log(i)
}
}
a[6]() // 6
Traceur:
$traceurRuntime.ModuleStore.getAnonymousModule(function() {
"use strict";
var a = [];
var $__0 = function(i) {
a[i] = function() {
console.log(i);
};
};
for (var i = 0; i < 10; i++) {
$__0(i);
}
a[6]();
return {};
});
//# sourceURL=traceured.js
Babel:
"use strict";
var a = [];
var _loop = function _loop(i) {
a[i] = function () {
console.log(i);
};
};
for (var i = 0; i < 10; i++) {
_loop(i);
}
a[6]();
Buble:
var a = []
var loop = function ( i ) {
a[i] = function(){
console.log(i)
}
};
for(var i = 0; i < 10; i++)loop( i );
a[6]()
--> 很一致,三者都是通过闭包实现的!
for 循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域
experiment-let-03:
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
} // 输出3次 abc,没报错,说明计数器所在的和循环体内部的是不同的作用域,是父子关系嵌套作用域
Traceur:
$traceurRuntime.ModuleStore.getAnonymousModule(function() {
"use strict";
for (var i = 0; i < 3; i++) {
var i$__0 = 'abc';
console.log(i$__0);
}
return {};
});
//# sourceURL=traceured.js
Babel:
'use strict';
for (var i = 0; i < 3; i++) {
var i = 'abc';
console.log(i);
}
Buble:
for (var i = 0; i$1 < 3; i$1++) {
var i$1 = 'abc';
console.log(i$1);
}
--> 明显只有 Traceur 编译后的结果是符合预期的!咦,好奇怪!后两者的结果都有很大的问题。
let 不存在变量提升!
experiment-let-04:
console.log(_a) // Uncaught ReferenceError: _a is not defined
let _a = 'hello world'
console.log(_b) // undefined
var _b = 'hello es6'
Traceur:
$traceurRuntime.ModuleStore.getAnonymousModule(function() {
"use strict";
console.log(_a);
var _a = 'hello world';
console.log(_b);
var _b = 'hello es6';
return {};
});
//# sourceURL=traceured.js
Babel:
'use strict';
console.log(_a);
var _a = 'hello world';
console.log(_b);
var _b = 'hello es6';
Buble:
console.log(_a)
var _a = 'hello world'
console.log(_b)
var _b = 'hello es6'
--> 三者编译后的结果都不是严格符合预期!但是这种处理是最小代价的最优方案了,就是忽略限制提升这一特性。 --> 想想也是,怎么用 ES5 的语法实现一个限制提升呢?代价太大
暂时性死区(TDZ) ES6 明确规定,如果区块中存在 let 和 const 命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。 总之,在代码块内,使用 let 命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。 “暂时性死区”也意味着typeof不再是一个百分之百安全的操作
experiment-let-05:
if(true){
temp = 'abc' // 死区,Uncaught ReferenceError
let temp
temp = 'hello world'
console.log(temp)
}
typeof undefined_val // 就算是没定义的也不会报错,输出"undefined"
if(true){
typeof _temp // 死区,Uncaught ReferenceError
let _temp
}
function bar(x = y, y = 2) { // Uncaught ReferenceError,这里在 y 初始化之前使用了 y 赋值给 x, 也是暂时性死区问题
return [x, y];
}
bar();
let x = x // 死区,Uncaught ReferenceError
--> 其实三者编译器都没有处理暂时性死区的问题,就是正常转换成 ES5 的语法,这里就不例举出来了
不允许在相同的作用域内,重复声明 直白的说就是在同一个作用域内,只要用 let 声明了的变量名,就不允许重复了
~function func(){
let a = 1
var a = 2 // 报错
}()
~function func(arg) {
let arg;
}()
~function func(){
let temp = 1
if(true){
var temp = 3 // 报错
}
}()
--> 三者都会报错
为什么要有块级作用域? 其实就是为了使得变量使用更安全,不会被意外覆盖或者泄露到全局
var tmp = new Date();
function f() {
console.log(tmp); // undefined, 因为 var temp = 'hello world' 中的 temp 提升,覆盖了初始的 temp值
if (false) {
var tmp = 'hello world';
}
}
f();
var s = 'hello';
for (var i = 0; i < s.length; i++) {
console.log(s[i]);
}
console.log(i); // 5 计数器 i 提升后泄漏到了全局
let 的主要作用之一就是声明了一个块级作用域
~function func(){
let a = 10
if(true){
let a = 20
}
console.log(a) // 10
}()
Babel:
"use strict";
~(function func() {
var a = 10;
if (true) {
var _a = 20;
}
console.log(a); // 10
})();
--> 三者编译也是通过另取变量名处理的,就不都列举了
函数声明与块级作用域 ES5 中是不允许函数声明出现在块级作用域中的,ES6 引入了块级作用域,明确允许在块级作用域之中声明函数 ES6 规定,块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可引用 ES6环境浏览器对这一实现是不严格遵守规范的。还是类似使用var,函数声明会提升,但是表达式是不会的,所以,最好的办法还是在块级作用域内使用函数表达式
// 浏览器的 ES6 环境
function f() { console.log('I am outside!'); }
(function () {
// var f = undefined; 提升之后的效果如同这里加了这么一句
if (false) {
// 重复声明一次函数f
function f() { console.log('I am inside!'); }
}
f();
}());
// Uncaught TypeError: f is not a function, 因为 f 提升了!
--> 其实三者编译的时候都会把你写的函数声明转成函数表达式。减少提升带来的影响
const 命令
const声明一个只读的常量。一旦声明,常量的值就不能改变(除了对象、数组之类的数据,其本质是保证内存指向不变) const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动 const一旦声明变量,就必须立即初始化,不能留到以后赋值 同 let 一样不可以提升,只能在所在作用域使用,同一个作用域内不可以重复声明,也存在暂时性死区
const PI = 3.1415
PI = 3 // TypeError
const ARR = []
ARR.push(123)
ARR = [] // TypeError
--> 在三者编译中都会按照预期在再度赋值的时候报错。
--> 但是编译成 var PI = 3.1415 然后 PI 赋值的时候报错 readonly,是怎么实现的呢? 通过 setter 实现?记录下 const 声明的变量?
顶层对象
就是 let const 声明的全局对象也不会挂载到 window 上
let b = 1;
window.b // undefined
--> Babel 会将上面的代码编译成 var b = 1; window.b 但是输出还是 undefined