Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ES6中的let和const #2

Open
chenqf opened this issue Jun 4, 2019 · 0 comments
Open

ES6中的let和const #2

chenqf opened this issue Jun 4, 2019 · 0 comments

Comments

@chenqf
Copy link
Owner

chenqf commented Jun 4, 2019

前言

let 和 const 是 ES6 中新增的命令,是用于解决 ES5 中使用 var 命令声明变量的一些问题而出现的,
在了解 let 和 const 之前,我们先来简单回看一下 var 命令的一些不合理的问题,然后再来 了解 let 和 const 的特性。

var 的问题

var 主要有如下几个不合理的地方:

  • 没有块级作用域
  • 重复声明
  • 绑定全局作用域
  • 变量提升

问题:没有块级作用域

ES5 只有全局作用域和函数作用域,没有块级作用域。

for(var i = 0; i<10; i++){
    //...    
}
console.log(i); // 输出 10

对于有块级作用域的语言来说,for 语句初始化变量的表达式所定义的变量,只会存在于循环的环境中。
而对于 ES5 中的 var 命令来说,由 for 语句创建的变量 i 即使在 for 循环结束执行后,也依旧存在于循环外部的执行环境中。

问题:重复声明

使用 var 语句重复声明变量是合法且无害的。

var a = 1;
console.log(a); //print : 1
var a = 2;
console.log(a); //print : 2
var a = 3;
console.log(a); //print : 3

在大多数语言中,在同一个作用域多次声明同一个变量是非法的,但是在 ES5 中使用 var 命令重复声明的没有任何问题的。

问题:绑定全局作用域

在全局作用域下通过 var 命令声明变量,会创建一个新的全局变量作为全局对象的属性。

var a = 1;
console.log(window.a); // print: 1

我们仅仅只是在全局作用域中通过 var 声明了一个变量,这个变量居然就挂载到了全局对象上,
这完全超出了我们的预期,是不是很不合理呢!

问题:变量提升

JS引擎在对代码进行编译解释的时候,会查找所有通过 var 命令声明的变量,会将创建变量的时机提升到当前作用域

第一个例子:

if( flg ){
    var value = 1;
}
console.log(value);

初学者可能会觉得只有 flg 为 true 的时候,才会创建 value,如果 flg 为 false,结果应该是报错,然而因为变量提升的原因,代码相当于:

var value;
if (flg) {
    value = 1;
}
console.log(value); // flg 为true 的时候,输出 1;flg 为false 的时候,输出 undefined

第二个例子:

console.log(tmp); // print : undefined
var tmp = 1;
console.log(tmp); // print : 1

初学者可能认为我们第一次打印 tmp 的时候应该抛出异常,因为此时 tmp 还没有声明。
其实不然,因为 ES5 中的 var 命令存在变量提升,会将变量创建和初始化阶段提升至作用域顶部,
将变量的赋值阶段保留在当前位置。

let 和 const

为了解决 var 所产生的种种不合理的情况,ES6 新增了 let 和 const 命令,用于创建安全稳定易理解的变量。

改进:块级作用域

ES6 中新增了块级作用域,并且块级作用域可以任意嵌套

{
    let a = 1;
    console.log(a); // print: 1
}
console.log(a); // print error:ReferenceError: a is not defined

这个例子中,通过 let 命令声明的变量 a 仅在块级作用域内生效,所以在块级作用域外引用变量 a 会提示未定义。

改进:不绑定全局作用域

let a = 1;
const b = 1;
console.log(window.a); // print: undefined
console.log(window.b); // print: undefined

let 和 const 命令在全局作用域下创建变量,并不会绑定至全局对象中。

改进:不存在变量提升

为了纠正 var 命令会发生变量提升的现象,let 和 const 命令改变了语法行为,
let 和 const 所声明的变量一定要在声明后使用,否则报错。

// var 的情况
console.log(foo); // 输出undefined
var foo = 2;

// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;

上面代码中,变量 foo 用 var 命令声明,会发生变量提升,即脚本开始运行时,变量 foo 已经存在了,但是没有值,所以会输出 undefined。
变量 bar 用 let 命令声明,不会发生变量提升。这表示在声明它之前,变量 bar 是不存在的,这时如果用到它,就会抛出一个错误。

改进:不可重复声明

let 和 const 不允许在相同作用域内,重复声明同一个变量。

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

在函数体中重新声明形参,同样报错

function fn(params){
    let params = 1;
} // print error: Identifier 'params' has already been declared

改进:暂时性死区

暂时性死区(Temporal Dead Zone),简写为 TDZ。

let 和 const 声明的变量不会被提升到作用域顶部,如果在声明之前访问这些变量,会导致报错。

console.log(x); // print error: ReferenceError: value is not defined
let x = 1;

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

简单的来说,只要当前作用域内使用 let 和 const 命令创建了变量,那么在当前作用域的开始直到变量声明的一刻,
这个阶段就叫做暂时性死区,也就是说从当前作用域的开始到变量创建的一刻,只要使用了这个变量就会报错。

无法使用和当前作用域内同名的外部变量

let name = 'tom';
function fn(){
    console.log(name); // print error : ReferenceError: name is not defined
    let name = 'jack'
}
fn();

typeof不再是一个百分之百安全的操作

typeof x; // ReferenceError
let x;

变量 x 使用 let 命令声明,所以在声明前,都属于 x 的死区,只要用到变量 x 就会报错。

let 和 const 的区别

const 声明的是一个只读常量,一旦声明,常量的值就不能改变。

const x = 1; 
x = 2; // print error: TypeError: Assignment to constant variable.

const 声明的时候必须立即初始化,不能等到以后赋值,只声明不赋值,就会报错。

const y; // print error:SyntaxError: Missing initializer in const declaration

const 实际上保证的并不是变量的值不可改动,而是变量指向的内存地址所保存的数据不可变更。

对于简单类型,值就保存在变量指向的那个内存地址,因此等同于常量。

但对于引用类型的数据,变量指向的内存地址,保存的只是一个指向实际数据的指针,
const 只能保证这个指针是固定的(即总是指向另一个固定的地址),
至于它指向的数据结构是不是可变的,就完全不能控制了

const foo = {};
foo.prop = 123;
foo.prop // 123

foo = {}; // print error: TypeError: Assignment to constant variable. 

上面代码中,常量foo储存的是一个地址,这个地址指向一个对象。
不可变的只是这个地址,即不能把foo指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。

写在最后

  • 文中如有错误,欢迎在评论区指正,如果这篇文章帮到了你,欢迎点赞关注
  • 本文同步首发与github,可在github中找到更多精品文章,欢迎Watch & Star ★
  • 后续文章参见:计划

欢迎关注微信公众号【前端小黑屋】,每周1-3篇精品优质文章推送,助你走上进阶之旅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant