理解ES6: 块作用域

来源:互联网 发布:淘宝1秒拦截软件有哪些 编辑:程序博客网 时间:2024/05/31 00:39

这是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 errorif (condition) {    let count = 40;    // more code}


常量的声明


  • 它和let一样,也是区块作用域级别,并且不会被提升;
  • 必须在声明时赋值;
  • 不可以被修改,对象是特例,下面会说到;
  • 也不能够重复使用相同的变量名,不论该名字是通过var还是let声明的。


常量对象


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

const person = {    name: "Nicholas"};// worksperson.name = "Greg";// throws an errorperson = {    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 hereconsole.log(i);                     // 10

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

for (let i = 0; i < 10; i++) {    process(items[i]);}// i is not accessible here - throws an errorconsole.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 errorfor (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。


原创粉丝点击