块级作用域绑定

#var声明及变量提升机制
    在 函数作用域 或 全局作用域中通过 var 声明的变量,无论实际上在哪里声明的,都会被当成在  当前作用域顶部声明的变量。这就是常说的提升机制;
function func(condition){
    if(condition){
        var value="xxx";
        return value;    
    }
    else{
        //这里可以访问 value,其值为 undefined
        return null;
    }
    //此处可以访问 value,其值为 undefined
}
    上面的代码,在预编译阶段,JavaScript引擎会将上面的func函数修改成下面这样:
function func(condition){
    var value;
    if(condition){
        value="xxx";
        return value;    
    }
    else{
        return null;
    }
}
    
    变量 value 的声明被提升至函数顶部,而初始化操作依旧在原处执行,这就意味着在 else 子句中可以访问到该变量,且由于没有被初始化,所以其值为 undefined;
    正因为这个问题,ECMAScript 6 引入了块级作用域来强化对变量声明周期的控制
#块级声明
    块级声明用于声明在指定块的作用域之外无法访问的变量。块级作用域存在于:
        1,函数内部;
        2,块中(花括号{}之间);
    
    &let声明
        let用法和var相同。用let来替代var来声明变量,可以把变量的作用域限制在当前代码块中。
function func(condition){
    if(condition){
        let value="xxx";
        return value;    
    }
    else{
        //变量 value 在此处不存在
        return null;
    }
    //变量 value 在此处不存在
}
    
        变量 value 用let声明后,不再被提升至函数顶部。执行流离开if块,value 立刻被销毁。如果condition为false,就永远不会声明并初始化 value。

    &禁止重声明

        假设作用域中已经存在某个变量,此时再使用 let 关键字声明一次就会错误:
var count = 30;
// 抛出语法错误
let count = 40;
   

    &const声明

        ECMAScript 6 标准还提供了 const 关键字。使用声明的是常量,其值一旦被设定后不可更改。因此,每个通过 const 声明的常量必须进行初始化
// 有效的常量
const maxNum = 30;
// 语法错误:常量未初始化
const minNum;
    const 与 let 声明的都是块级标识符,所以常量也只在当前代码块内有效,一旦执行到块外会立即被销毁。常量同样也不会被提升至作用域顶部;
    与 let 相似,在同一作用域中用 const 声明已经存在的标识符也会导致语法错误;
 

    &用 const 声明对象

        记住,const 声明不允许修改绑定,但是允许修改值。这也就意味着用 const 声明对象后,可以修改该对象的属性值,但是不可以修改该对象的引用:
const person = {
    name : 'zhangsan'
}
// 没毛病
person.name = 'lisi';
// 抛出语法错误
person = {
    name : 'wangwu'
}
 

#循环中的块级作用域绑定

    以下的代码在JavaScript中很常见:
for(var i=0;i<10;i++){
    process(arr[i]);
}
console.log(i);    // 打印10
    由于 var 声明得到提升,变量 i 的声明提升至当前作用域顶部,在循环结束后依旧可以访问它;如果换成 let 声明变量:
for(let i=0;i<10;i++){
    process(arr[i]);
}
// i 在这里不可访问,抛出一个错误
console.log(i);    
    
    
    &循环中的函数
        var 声明让开发者在循环中创建函数变得异常困难,对于新手会造成困惑,看下面的代码:
var funcs = [];
for(var i=0;i<10;i++){
    funcs.push(function(){
        console.log(i);    
    });
}
funcs.forEach(function(func){
    func();
});
    
        预期的结果是输出数字 0 ~ 9,但是程序却一直输出 10;
        这是因为循环里的每次迭代同时共享着变量 i,循环内部创建的函数全部保留了对相同变量的引用。当循环结束时候,变量 i 的值为10,所以每次调用 console.log(i) 时就会输出10;
        为了解决上面的问题,可以在循环中使用 立即调用函数表达式(IIFE),以强制生成计数器变量的副本:
var funcs = [];
for(var i=0;i<10;i++){
    funcs.push(function(value){
        return function(){
            console.log(value);
        }  
    });
}
funcs.forEach(function(func){
    func();    //依次输出 0 ~ 9
});
        在循环内部,IIFE 表达式为接受的每一个变量 i 都创建了一个副本并存储为变量 value。这个变量的值就是相应迭代创建的函数所使用的值。
        ECMAScript 6 中的 let 和 const 提供的块级绑定就无需这么麻烦了;
    
    &循环中的let声明
        let 声明模仿上面的 IIFE 所做的一切来简化循环过程。
var funcs = [];
for(let i=0;i<10;i++){
    funcs.push(function(){
        console.log(i);
    });
}
funcs.forEach(function(func){
    func();    //依次输出 0 ~ 9
});
    
    &循环中的const
        ES6 标准中没有明确指明不允许在循环中使用 const 声明,然而,针对不同类型的循环它会表现出不同的行为。
        对于普通的 for 循环,使用 const 声明计数器,那肯定是会抛出错误的:
var funcs = [];
// 循环一次之后,抛出错误
for(const i=0;i<10;i++){
    //...
}
        
        但是在 for-in 或者 for-of 循环中使用 const 声明的行为与使用 let 是一致的:
var funcs = [];
var obj = {
    a:true,
    b:true,
    c:true
};
// 不会产生错误
for(const key in obj){
    funcs.push(function(){
        console.log(key);
    })
}
funcs.forEach(function(func){
    func();    // 依次输出 a,b,c
})
        之所以可以在 for-infor-of 中使用 const 声明,是因为每次迭代不会修改已有的绑定,而是会创建一个新的绑定
 

#全局作用域绑定

    let 和 const 与 var 的另外一个是它们在全局作用域中的行为;
    当 var 被用于全局作用域时,它会创建一个新的全局变量作为全局对象(浏览器环境下全局对象为window)的属性。这也就意味着 var 可能会在无意中覆盖一个已经存在的全局变量
// 在浏览器中
var RegExp = 'Hello!';
console.log(window.RegExp);    // 输出 Hello!
var ncz = 'Hi!';
console.log(window.ncz);
    
    即使全局对象 RegExp 对象定义在 window上,也不能幸免于被 var 声明覆盖掉;
    如果在全局作用域中使用 let 或 const,会在全局作用域下创建一个新的绑定,但该绑定不会添加为全局对象的属性。换句话说,用 let 或 const 不会覆盖全局变量,而只能遮蔽它;
// 在浏览器中
let RegExp = 'Hello!';
console.log(RegExp);    // 输出 Hello!
console.log(window.RegExp === RegExp);    // false
const ncz = 'Hi!';
console.log(ncz);    // 输出 Hi!
console.log("ncz" in window);    // false
    let 声明的 RegExp 创建了一个绑定并遮蔽了全局的 RegExp 变量,结果就是 window.RegExp 和 RegExp 不相同,但不会破坏全局作用域。
    同样,const 声明的 ncz 创建了一个绑定但没有创建为全局对象的属性。
    如果不想为全局对象创建属性,则使用 let 或 const 要安全得多。
    tips:在浏览器中跨 frame 或者跨 window 访问,依然得使用 var在全局对象下定义变量。
 

#块级绑定最佳实践

    当更多的开发者迁移到ES6后,一种做法日益普及:默认使用 const ,只有确实需要改变变量的值时使用 let。
    因为大部分变量的值在初始化后不应再改变,而预料外的变量值的改变是很多 bug 的源头。
原文地址:https://www.cnblogs.com/startcaft/p/7712504.html