ES6基础之块级作用域

461 阅读4分钟

众所周知,ES6之前,javascript只有函数作用域。今天我们来聊聊ES6的新特性:块级作用域

var的迷思

ES6之前,声明变量的关键字var,在用var的时候是不是经常出现一些特别的特性,我们来回顾一下

  1. 声明提升
function print() {
    console.log(value);   // undefined
    var value = "1";
    console.log(value);   // 1
}

如上例,变量的定义是会提升的,在变量赋值之前使用,不会报错,输出的是undefined。所以前端规范经常会有一条,先赋值后使用,变量的声明一定要在使用之前

  1. 无块级作用域,只有函数级作用域
function print(condition) {
    if (condition) {
        var value = "1";
        console.log(value);     // 1
    }
    else {
        console.log(value);     // undefined
    }
}
  1. 重复声明和定义
function print() {
    var value = 1;
    console.log(value);
    // doSometing
    var value = "hello";
    console.log(value);
}
  1. 无法定义常量
var MAX_NUMBER = 1000;

ES6之前定义常量都是使用全部大写的方式标识常量信息,但是这个常量是可以重新赋值的,前端规范中常量使用大写在一方面就是提醒大家在使用的时候注意,大写的是常量不要赋值。

块级作用域

为了解决上述的问题ES6祭出了两个关键词let和const,let替代变量定义,const补充了常量定义

问题1:声明提升

解决方案:TDZ(Temporal Dead Zone),临时死区

  1. 概念:简言之,在变量或者常量声明语句之前不能使用该常量和变量

  2. let和const共有特性

function print() {
    console.log(typeof value);  // 调用的时候会报错
    let value = "1";
}

如下图所示,红色框所标识的区域是变量“value”的临时死区

临时死区

问题2:无块级作用域

解决方案:let和const都增加了块级作用域

function print(condition) {
    if (condition) {
        let value = "1";
        console.log(value);
    }
    else {
        console.log(value);  // error:value is not defined
    }
}

问题3: 重复声明和定义

解决方案:let和const都不可以在一个块级中进行重复的声明和定义

let a = "1";
let a = "2";   // error: Identifier 'a' has already been declared

const b = "1";
const b = "2";  // error: Identifier 'b' has already been declared

// 下面的例子是在两个代码块中定义a,所以互相不会影响,不会报错

{
  let a = "1";
}
{
   let a = "2";
}

问题4:无法定义常量

解决方案:关键词const定义的就是常量,在同一块级作用域不能重复赋值

  1. const定义的常量,不可以再进行赋值操作
const A = "1";
A = "2"; //error: Assignment to constant variable.
  1. const定义的常量必须在声明的同时进行赋值操作
const A; // error:Missing initializer in const declaration

注意:常量定义对于值类型和引用类型的数据的处理方式是不一致的 常量定义如果值为对象或者数组,实际上是指针不变,内容可变

const ARR = [1,2];
ARR.push(3);
console.log(ARR);	

循环的迷思

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
});

当我们写上面的代码的时候,我们想输出0-9,但是实际上输出的全是10;因为var是没有块级作用域的,实际上我们在输出的时候访问的是同一个i,而i在执行完循环后变成了10,所以都输出的10

在ES6之前我们怎么实现输出0-9,改写上面的代码,在push的时候增加一个函数作用域,使i在指定的函数作用域中有效

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和const是有块级作用域的,所以使用let和const的时候就不会有这个迷思了

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循环是不支持const的,只能使用let。为什么?

普通的for循环实际上可以改写成下面的样子

var funcs  = [];
{ 
    let k;
    for (k = 0; k < 10; k++) {
   		let i = k; //注意这里,每次循环都会创建一个新的i变量
  		funcs.push(function() {
			console.log(i);
		});
	}
}
funcs.forEach(function(func) {
    func(); // outputs 0, then 1, then 2, up to 9
});

从上面的代码可以看出,每一次都会对k进行++操作,如果是使用const定义k,那么k++违反了const的不变性,导致错误发生,所以普通循环中不能使用const定义变量

但是对于 for-in,for-of 这样循环就可以使用let和const两种定义方式了,因为不存在++,--这样的操作,不会改变key的值。

var  funcs = [],
object = {
    a: true,
    b: true,
    c: true
};
// doesn't cause an error
for (const key in object) {
    funcs.push(function() {
        console.log(key);
    });
}
funcs.forEach(function(func) {
    func(); // outputs "a", then "b", then "c"
});

全局块绑定

let,const和var还有一点是不一样的。var如果是在全局作用域中定义的时候,实际上是在全局对象上加了一个属性,在浏览器端就是在window中加了一个属性,这样相当于可以覆盖window中的默认属性。而使用let和const定义的时候不会给全局对象中加属性,也不会覆盖全局对象中的默认属性。

// in a browser
var RegExp = "Hello!";
console.log(window.RegExp); // "Hello!"

var ncz = "Hi!";
console.log(window.ncz); // "Hi!"


// in a browser
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