深入理解ES6 笔记一: let和const

102 阅读4分钟

var声明及变量提升(Hoisting)机制

变量提升机制——在函数作用域或全局作用域中通过关键字var声明的变量,无论实际上在哪里声明的,都会被当成在当前作用域顶部声明的变量。

function getValue(condition) {
    if(condition) {
        var value = "handsome";
        console.log(value); // handsome
        return value; 
    } else {
        console.log(value); // undefind
        return null
    }
        console.log(value); // undefind
}

块级声明

块级声明用于声明在制定块的作用域之外无法访问的变量(又称词法作用域),一般存在于

  • 函数内部
  • 块中(字符{和}之间的区域)

let和const的特点

  1. let和const的声明只在当前代码块内有效,一旦执行到块外会立即销毁,不会被提升至作用域顶部。
function getValue(condition) {
    if(condition) {
        let value = "handsome";
        const constant = "beautiful"
        console.log(value); // handsome
        console.log(constant); // beautiful
        value = "beautiful"; // beautiful
        constant = "handsome" // 报错,不能更改
        return value; 
    } else {
        console.log(value); // 报错 value不存在
        console.log(constant); // 报错 constant
        return null
    }
        console.log(value); // 报错 value不存在
        console.log(constant); // 报错 constant
}
  1. 禁止重声明
    假设作用域中已经存在某个标识符,如果此时再使用let声明它就会报错,但如果当前作用域内嵌另一个作用域,可以在内嵌的作用域中用let声明同名变量,内部变量的值会遮掩外部变量的值。
var count = 30;
let count = 18; // 报错
if(condition) {
    let count = 18; // 18
}

let和const的区别
let声明的变量声明时可以不赋值,并且可以赋值后可以多次修改,而const声明的是常量,声明时必须进行初始化,且其值一旦被设定后不可更改。

值得注意的时const声明的常量是对象,则const声明不允许修改绑定,但是对象中的值可以修改。

const person = {
    name: '张三';
}

person.name = '李四';  //李四

person = {
    name: '李四';
}   // 报错

临时死区(Temporal Dead Zone)

Javascript引擎在扫描代码发现变量声明时,要么将它们提升至作用域顶部(var声明),要么将声明放到TDZ中(let和const声明)。访问TDZ中的变量会触发运行时错误。只有执行过变量声明语句之后,变量才会从TDZ中移出,方可正常访问。

循环中块作用域绑定

var在循环中的问题

var funcs = [];

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

console.log(i); // 10
funcs.forEach(function(func){
    func(); //  输出10次数字10
});

使用立即调用函数表达式(IIFE)改进后:在循环内部,IIFE表达式为接受的每一个变量i都创建了一个副本并存储为变量value。

var funcs = [];

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

console.log(i); // 10
funcs.forEach(function(func){
    func(); //  依次输出0-10
});

使用let可以直接实现上述IIFE的应用,每次迭代循环都会创建一个新变量,并以之前迭代中同名变量的值将其初始化。(注意:const不用用于此场景——i++修改常量报错)

var funcs = [];

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

console.log(i); // 报错
funcs.forEach(function(func){
    func(); //  依次输出0-10
});

// 相当于
{
    let i = 0
    funcs[i] = function() {
        console.log(i)
    };
}
{
    let i = 1
    funcs[i] = function() {
        console.log(i)
    };
}
{
    let i = 2
    funcs[i] = function() {
        console.log(i)
    };
}

在for-in和for-of循环中let和const的用法一样。const可以在for-in和for-of循环中应用的原因在于每次迭代不会修改已有的绑定,而是会创建一个新绑定。

全局作用域绑定

当var被用于全局作用域时,它会创建一个新的全局变量作为全局对象(浏览器中的window对象)的属性。这意味着用var可能会无意间覆盖一个已将存在的全局属性:

var RegExp = 'hello';
console.log(window.RegExp);

如果你在全局作用域使用let或const,会在全局作用域下创建一个新的绑定,。且改绑定不会添加为全局对象的属性。换句话说,用let和const不能覆盖全局变量,只能遮蔽它。

var RegExp = 'hello';
console.log(RegExp);   // hello
console.log(window.RegExp === RegExp);  // false

最佳实践

在我们开发的时候第一反应可能是默认使用 let 而不是 var,对于需要写保护的变 量要使用const。然而另一种做法日益普及:默认使用const,只有当确实需要改变变量的值的时候才使用let。这是因为大部分的变量的值在初始化后不应再改变,而预料之外的变量之的改变是很多 bug 的源头。