深入理解ES6 - 块级作用域绑定

块级作用域绑定   

js 中通过 var 申明变量是很容易让人迷惑的,为此,ES6 引入了块级作用域来强化对变量生命周期的控制。

var 申明变量、变量提升机制

在函数作用域或全局作用域通过 var 申明的变量,无论在什么位置申明的变量,都会被当成在当前作用域顶部申明的变量,这就是变量提升机制。

function demo(a){
    if(a){
        var value = 'test1'; //此处申明 value 变量
    }
    return value; //项目编码中不要这么写,此处仅为讲解知识点
}

    上面的代码在预编译阶段,js 引擎会将由 var 申明的变量提升到函数顶部,只是初始化保留在原申明的位置执行。预编译后代码会改成如下的样子:

function demo(a){
    //变量申明会提升到函数顶部
    var value;
    if(a){
        //初始化会保留在原处
        value = 'test1';
    }
    //如果 a=false,则 value 不会被初始化,则 value=undefined
    return value;
};

块级申明

let 申明变量

let 用法与 var 相同,但 let 申明的变量不会被提升,作用域限制在当前代码块中。

function demo(a){
    if(a){
        let value = 'test1'; //此处创建并初始化 value 变量
        //当离开 if 块时,value 会立即被销毁
    }else{
        //当 a=false 时,value 将永远不会被申明
    }
    //变量 value 在此处不存在,连 undefined 都不是;
    //如果写了 return value 会直接报错。
}

同一作用域内,用 let 重复申明已存在的标识符,会报错。

function demo(){
    var a = 1;
    var a = 2; //不会报错
    let a = 3; //运行时会报错

    var b = 1;
    if(b){
        let b =2; //不会报错,因为两处 b 的作用域不同
    }
}

const 申明常量

ES6 标准提供了 const 关键字来申明常量, const 申明的常量,在申明时必须进行初始化,且一旦绑定后不可被更改。与 let 类似,const 申明的也是块级标识符,不会被提升,一旦执行到块外会立即被销毁。const 也不能重复申明已存在的标识符。

const 申明对象

ES6 中的常量与其它语言很像,但是,ES6 中的常量如果是对象,则对象中的值可以修改。因为 const 申明的常量不允许修改绑定,但允许修改值。这就意味着可以修改对象常量的属性值,只要不修改对象的内存地址。

const a; //报错,申明时必须初始化
a = 1; //报错,不能修改绑定
const b = 1; //正确

const user = { name : 'demo' };
user.name = 'test'; //可以修改常量的值
user = { name : 'ceshi' }; //报错,不能修改常量的绑定

临时死区(Temporal Dead Zone)

ECMAScript 标准中并没有提到临时死区,但我们常用其描述不提升的效果。
js 引擎在扫描代码发现变量申明时,要么将其提升到作用域顶部(var),要么将申明放到 TDZ 中(let、const)。访问 TDZ 中的变量会触发运行时错误。只有执行过变量申明语句后,变量才会从 TDZ 中移除。但在 let/const 申明的作用域外对该变量进行 typeof 运算时,则不会报错,因为 TDZ 是相对于块级作用域而言的。

console.log(typeof a); //出错,访问了临时死区中的变量
let a = 'demo';
//不会报错,因为 b 不在当前块的作用域中,故不在 TDZ 中。
console.log(typeof b); //typeof b=undefined。 b is not defined。注意:undefined != not defined
if(condition){
    let b = 'test';
}

循环中的块作用域绑定

ES6 之前的循环变量在循环体外仍能访问,但用 let 申明的变量则不可以。

for (var i = 0; i < 10; i++) {
    //todo something
}
console.log(i); //10,此处依然可以访问到变量i

for (let i = 0; i < 10; i++) {
    //todo something
}
console.log(i); //报错,循环结束后 i 即被销毁

循环中的 let 与 const

ES6 之前,在循环中创建函数是一件麻烦的事情,我们先看如下代码。

var funcs = [];
for (var i = 0; i < 10; i++) {
    funcs.push(function(){
        console.log(i);
    });
}
funcs.forEach(function(func){
    func(); //10,10,10...
});

输出10个10,这是因为每次迭代共享着变量 i,循环结束时 i=10。如果希望输出0,1,2...,可以用即时函数实现,此处不赘述。
但在 ES6 中,用 let 就不会这样,因为每次迭代都会创建一个新变量,并以之前迭代中同名变量的值将其初始化。上面的代码,将 var 换成 let,输出的便是 0,1,2...9。PS : let 申明在循环内部的这种行为是标准中专门定义的,事实上早期的 let 实现并不包含这一行为。
 
let 在 for 循环中的这种行为,在 for-in 中也存在。在 for-in 或 for-of 循环中使用 const 时的行为与 let 一致。

var funcs = [];
var obj = {a:1, b:2, c:3};
//每次迭代创建新的 key,输出1,2,3。但如果把 let 改成 var,输出 3,3,3
for(let key in obj){
    funcs.push(function(){
        console.log(key);
    });
}

//不会报错,输出1,2,3。每次迭代不会修改已有绑定,而是创建一个新的绑定。
for(const key in obj){
    funcs.push(function(){
        console.log(key);
    });
}

全局块作用域绑定

当 var 用于全局作用域时,其创建的变量作为全局对象,会覆盖 window 对象的属性。let 或 const 申明的变量会遮蔽全局的同名变量,但不会覆盖,用 window. 还可以访问到。

var RegExp = 'hello';
console.log(window.RegExp); //var 覆盖了 window 对象的属性

let RegExp = 'hi';
console.log(RegExp); //'hi',遮蔽了window.RegExp
console.log(window.RegExp === RegExp); //false,let 不会覆盖 window 属性

总结

ES6 中引入的 let 和 const 申明的块级变量没有提升机制,且禁止重复申明同名变量,访问临时死区中的变量会报错。与 var 不同,用let 和 const 申明的循环变量,每次迭代都会创建一个新的绑定。
块级变量最佳实践:默认情况下使用 const,只有在知道变量值需要更改时才用 let。

转载于:https://my.oschina.net/u/2563695/blog/1592833

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值