第一章 块级绑定

82 阅读5分钟

使用var声明变量带来的问题

  1. 允许在同一个作用域下有多个重复的变量声明:导致数据被覆盖
  2. 变量提升:怪异的数据访问问题
  3. 全局变量挂载到全局对象:全局对象成员污染问题

使用let声明变量

  1. 全局作用域下使用let声明的变量不会挂载到全局对象

  2. let声明的变量,不允许在同一作用域内出现重复声明

    这里所谓的“重复声明”不仅仅是指使用let重复声明,var和const以及同名形参都是不允许的

    function test(a){
        let a = 10;		// Identifier 'a' has already been declared
    }
    
    test(1);
    
  3. 在块级作用域中用let声明的变量,在块级作用域外访问不到

  4. let声明的变量不会有变量提升,因此不能在let声明变量之前访问它

    在初始化完成之前也不行

    let a = (function(){
        console.log(a);			// Cannot access 'a' before initialization
        return 1;
    })();
    

注意:在全局作用域中出现的全局let变量,若window对象中也存在同名属性,则直接访问时访问的是使用let声明的

let a = 10;

window.a = 20;

console.log(a, window.a);		// 10 20

块级作用域

ES6除了引入了新的变量声明的方式,还引入了块级作用域的概念

块级作用域即“{}”中的作用域

这里的“{}”可以是判断语句中的“{}”,也可以是循环语句中的“{}”,甚至可以是一个直接的“{}”

let a = 1;

{
    let a = 10;
    console.log(a);
}

注意:

  • 这样的“{}”不代表一个对象字面量,而是一个代码块
  • 函数语句中的“{}”不代表块级作用域,它仍代表着函数作用域

当执行到“{”时,JS就会创建一个块级作用域,当执行到对应的“}”时,JS就会销毁该块级作用域

细节

  1. 使用let声明的变量实际上还是会有提升的,只是提升后会将其放入到“暂时性死区”中,暂时性死区中的变量是不允许访问的,只有当代码执行到let声明变量的位置时,才会将其从“暂时性死区”中取出,之后才能允许你正常访问它

    因此,在let声明变量之前使用该变量,报的错是Cannot access 'XXX' before initialization,而不是XXX is not defined

    console.log(a);
    let a = 10;
    

    image.png

  2. JS会对循环进行特殊处理

    每执行到一轮循环的循环体时,JS都会创建一个全新的块级作用域,当本次循环的循环体执行结束后,块级作用域也将会被立即销毁

    for(let i = 1; i <= 3; i++){	// 将产生3个不同的块级作用域
        // 循环体
    }
    

    使用let声明的循环变量,JS都会自动会帮你将其绑定到每轮循环的循环体对应的块级作用域中

    并且每轮循环绑定的循环变量还不会与之前循环绑定的循环变量发生冲突(可以理解为JS在每轮循环中都使用了一个let声明的临时变量记录着当前循环变量的值,且循环体中使用的循环变量都转变为了使用该临时变量)

    for(let i = 1; i <= 3; i++){	// 将产生3个不同的块级作用域
        setTimeout(function(){		// 块级作用域的闭包
            console.log(i);			// 1 2 3
        }, 1000);
    }
    

    当循环整体结束时,该循环变量也将不再能够访问到(不能理解为循环变量是在循环外部声明的)

    for(let i = 1; i <= 3; i++){	// 将产生3个块级作用域
        console.log(i);				// 1 2 3
    }
    console.log(i);					// i is not defined
    
  3. 在let声明变量前对其进行typeof类型检测,将会导致异常,而不会返回"undefined"

    console.log(typeof a);		// Uncaught ReferenceError: Cannot access 'a' before initialization
    let a = 10;
    
  4. 不管是let还是const,其声明的变量一定会附着在执行上下文中,因为只有这样才能够对其进行访问

使用const声明常量

const声明的变量和let声明的变量效果基本一致,两者区别在于const声明的变量必须在声明时就赋值,并且之后不允许重新赋值,因此const声明的变量也称为常量

细节

  1. const只是不允许你对变量进行重新赋值,不是不允许你修改该变量所引用的对象中的内容

    const obj = {};
    
    obj = {};	// Assignment to constant variable.
    
    const obj = {};
    
    obj.name = "张三";	// 正常
    
  2. for in循环每进行一轮循环,都会创建一个新的循环变量,因此for in循环中可以使用const声明循环变量,但在普通的for循环中则不行

    普通的for循环并不会每轮循环都创建一个新的循环变量,它的循环变量只会在循环开始之前创建,之后改变的就是此时创建的循环变量

    每轮循环JS都会将const声明的循环变量绑定到循环体对应的块级作用域中,循环整体结束后也将不再能够使用

    var obj = {
        a: 1,
        b: 2
    };
    
    for (const prop in obj) {
        console.log(obj[prop]);
    }
    
    console.log(prop);		// prop is not defined
    

    因为for in循环中每轮循环都是一个新的循环变量,因此循环之间的循环变量也不会发生冲突

    var obj = {
    	name: 1,
    	age: 2,
    	sex: 3
    };
    
    for(const prop in obj){
    	setTimeout(function(){
    		console.log(obj[prop]);
    	}, 1000);
    }