您的位置:首页 > Web前端 > JavaScript

理解ES6: 块作用域

2017-06-29 19:32 267 查看
这是Nicholas Zakas的新作,原文链接:https://github.com/nzakas/understandinges6/blob/master/manuscript/01-Block-Bindings.md

var声明与变量提升现象

这是前ES6时期var的问题,变量会被JS引擎处理成好像它们的声明被放在函数作用域(或者全局作用域)的顶端。

function getValue(condition) {

if (condition) {
var value = "blue";

// other code

return value;
} else {

// value exists here with a value of undefined

return null;
}

// value exists here with a value of undefined
}

经过JS引擎解释后,上面的代码等同于下面的写法:

function getValue(condition) {

var value;

if (condition) {
value = "blue";

// other code

return value;
} else {

return null;
}
}

这个现象以前讨论过很多次,我就不完全复述作者原文了。

区块级别的声明

let的声明

区块作用域的存在要符合两个要求:

在函数里;

在一对{}里。

用let关键字有以下几个作用:

只存在于定义区块内;

不会再被提升,见代码:
function getValue(condition) {

if (condition) {
let value = "blue";

// other code

return value;
} else {

// value doesn't exist here

return null;
}

// value doesn't exist here
}

不能重复使用同一个变量名;

但在不同区块里可以重复使用相同名声明变量,比如子区块,如:
var count = 30;

// Does not throw an error
if (condition) {

let count = 40;

// more code
}


常量的声明

它和let一样,也是区块作用域级别,并且不会被提升;

必须在声明时赋值;

不可以被修改,对象是特例,下面会说到;

也不能够重复使用相同的变量名,不论该名字是通过var还是let声明的。

常量对象

这个地方有点晦涩,作者解释的方法是,const关键字约束的是一个绑定本身,而不是被绑定的值,见代码:

const person = {
name: "Nicholas"
};

// works
person.name = "Greg";

// throws an error
person = {
name: "Greg"
};

这个现象很接近C++里的常量指针,指针指向的地址不能够改变,它指向一个对象后将永远指向它,但是这个对象本身的内容可以被修改。

临时无人区(TDZ)

TDZ这个概念只是在抽象层面存在的一个术语,用于方便解释一些JS引擎的行为,而且它不是ES规范里的内容,在JS引擎遇到var声明时,它的行为和ES3一样,提升变量,在遇到let或者const声明时,则是把变量先放到一个临时无人区,这样没有人能访问它,甚至是typeof,顺便提一下,在ES3里用typeof操作一个未定义变量是安全的,它会返回undefined。直到程序执行到该变量的声明,它的绑定才会被移出无人区,从此能够被访问到。

if (condition) {
console.log(typeof value);  // ReferenceError!
let value = "blue";
}

但TDZ有一个奇特的效果:

console.log(typeof value);     // "undefined"

if (condition) {
let value = "blue";
}

按说,在if区块之前不应该能够访问到value的。

循环里的区块绑定

ES3里,for循环有一个缺陷,如:

for (var i = 0; i < 10; i++) {
process(items[i]);
}

// i is still accessible here
console.log(i);                     // 10

这也是个讲过很多次的经典问题了,就不多说了。ES6里,这个问题可以用let解决

for (let i = 0; i < 10; i++) {
process(items[i]);
}

// i is not accessible here - throws an error
console.log(i);

循环里的函数

ES3里的问题如下示例代码:

var funcs = [];

for (var i = 0; i < 10; i++) {
funcs.push(function() { console.log(i); });
}

funcs.forEach(function(func) {
func();     // outputs the number "10" ten times
});

这也是个经典的老问题了,我就不复述了。以前的解决办法是用闭包:

var funcs = [];

for (var i = 0; i < 10; i++) {
funcs.push((function(value) {
return function() {
console.log(value);
}
}(i)));
}

funcs.forEach(function(func) {
func();     // outputs 0, then 1, then 2, up to 9
});

但是在ES6里,用let就行了。

var funcs = [];

for (let i = 0; i < 10; i++) {
funcs.push(function() {
console.log(i);
});
}

funcs.forEach(function(func) {
func();     // outputs 0, then 1, then 2, up to 9
})

这个行为也适用于for...in循环和for...of循环。

var funcs = [],
object = {
a: true,
b: true,
c: true
};

for (let key in object) {
funcs.push(function() {
console.log(key);
});
}

funcs.forEach(function(func) {
func();     // outputs "a", then "b", then "c"
});

在普通的for循环里,你不能用const声明循环控制变量i,但是在for...in和for...of循环里,可以用const,效果和let一样:

var funcs = [],
object = {
a: true,
b: true,
c: true
};

// doesn't cause an error
for (const key in object) {
funcs.push(function() {
console.log(key);
});
}

funcs.forEach(function(func) {
func();     // outputs "a", then "b", then "c"
});

全局作用域的绑定

不同于var,let和const在全局作用域里声明变量时,它们不再会在window对象上创建新属性,所以它们不会重写window上面的内建对象,比如Date和Math。

作者的建议

使用const,其次是let,抛弃var。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息