此文算是翻译(非逐行翻译),主要参考了下面两篇英文文章:
要说 var 、 let 、 const 三者的区别,必须要提到 ES2015(ES6);因为在这之前,是没有 let 和 const 的,在 ES6 中,添加了 let 和 const 的声明方式。
首先我们来说说 var
Var
作用域(Scope of var)
这里不详细解释作用域的定义。简单来说,范围(Scope)就是指的:可以使用这些变量的位置,var 的声明是全局范围或者局部范围的。
- 当 var 在函数外被声明,他就是全局的范围,作用域是全局的。这意味着:在函数外用 var 声明的任何变量都可以在整个窗口(window)中使用。
- 当 var 在函数内被声明,他就是函数内范围,作用域是函数内,这意味着,这个声明只有在函数内可用并且被访问。
下面是例子:
var greeter = "hey hi";
function newFunction() {
var hello = "hello";
}
上述例子, 因为在函数外部声明,所以greeter 是全局作用域;hello 则是函数范围(局部作用域)。所以,我门是不能在函数外访问到 hello 的,如果我们做下面的尝试:
var tester = "hey hi";
function newFunction() {
var hello = "hello";
}
console.log(hello); // error: hello is not defined
由于 hello 无法在函数外部使用,所以我们得到了一个报错。
作用域提升 (Hoisting of var)
这里其实 hoisting 这个词,比作用域提升更好理解。hoisting 这个单词的解释是:「raise (something) by means of ropes and pulleys.」,直白翻译就是用滑轮和绳索把东西升起来,我理解为:把声明,升上去。
「提升」就是 JS 的一种机制,作用是,把变量和函数的声明,移动到他们作用域的顶部。
所以下述代码:
console.log (greeter);
var greeter = "say hello"
执行的时候,等于下面:
var greeter;
console.log(greeter); // greeter is undefined
greeter = "say hello"
var 一个变量提升到了作用域的顶部,初始化了一个值:undefined ,所以 console.log 的时候,返回的是 undefined,然后 greeter 被赋值 say hello 。
更新和重新声明(updated & re-declared)
var 定义的变量是可以更新和重新声明的,这就带来了 var 的一个问题:
var greeter = "hey hi";
var times = 4;
if (times > 3) {
var greeter = "say Hello instead";
}
console.log(greeter) // "say Hello instead"
在上述代码中:由于 times > 3 返回 true ,所以 gretter 被重新定义为"say Hello instead",如果你知道这个知识点当然没什么问题,但是如果你没有意识到,greeter 已经被重新定义,而不是之前的定义内容,这就会导致问题。
如果在其他部分的代码,你使用了 greeter,你可能会得到一个你意料之外的返回结果。这回导致代码上的很多bug。
简单来说,如果你忽略了这个重定义的问题。在多次定义的情况下,你会弄不清(至少让人迷惑) greeter 到底返回什么?以上述例子为例:如果你认为,greeter 会返回 "hey hi" ,由此使用的其他部分代码,会导致未知的 bug ~
这就是 let 和 const 的诞生的原因和必要性了。
Let
我个人认为,let 的出现是为了解决:在全局和局部作用域上,var 声明中由于提升和全局性导致的变量重定义的问题的。
Let 是一个块作用域声明(let is block scoped)
块就是是在{} (大括号)内写的代码,块就是大括号。任何在大括号内容中的内容,就是块。所以,let 声明的内容,只能被使用在块中。
下面是例子:
let greeting = "say Hi";
let times = 4;
if (times > 3) {
let hello = "say Hello instead";
console.log(hello);// "say Hello instead"
}
console.log(hello) // hello is not defined
我们可以看到,hello 在块的外部,由于没有被定义,返回了错误。由此可以证明,let 的作用范围是块作用域。
Let 可以被更新,但是不能被重定义(let can be updated but not re-declared)
在作用域范围内,let 是可以和 var 一样被更新的,但是 let 与 var 不同的地方在于,不能被重定义。所以下述代码是 ok 的:
let greeting = "say Hi";
greeting = "say Hello instead";
但是如果我们这样写,则会返回错误:
let greeting = "say Hi";
let greeting = "say Hello instead"; // error: Identifier 'greeting' has already been declared
一些文章会简单粗暴的解释:let 每次迭代的时候,都会变量重新创建绑定。思考一下,这里的理由是什么?因为 let 不允许被重定义,所以他每次创建的都是副本,去更新它的值。如果用 var ,由于重定义的存在,多次迭代后,变量就重定义为最后一个定义值了。
对于 let ,如果在不同的块区间定义不同的内容,那么就不会出现 error 。
let greeting = "say Hi";
if (true) {
let greeting = "say Hello instead";
console.log(greeting); // "say Hello instead"
}
console.log(greeting); // "say Hi"
这里就很好理解了,因为存在于不同的块作用域,两个 greeting 是独立存在的实例作为不同的变量,不存在重定义的问题。
由此带来的 let 的特性,就可以很好的解决 var 中问题:
- let 是被束缚在他自己的作用域中,解决重复命名可能出现的问题
- 一个作用域中,let 不允许被多次声明
Let 的作用域提升 (Hoisting of let)
let 也可以被作用域提升(hoisting),let 和 var 一样提升声明到作用域顶部,但是和 var 不同的是:
- var 会初始化, 值为:undefined
- let 则是报错 ReferenceError
Const
那么 const 是为了解决什么?我们先来看看 const 的规则和表现。
Const 的声明也是块作用域(const declarations are block scoped)
和 let 一样,const 也是块作用域,这里就不重复赘述了。
Const 的特性
const 存在下面的特性:
- 不能被更新和重定义
- 每个 const 声明都必须在声明的时候被初始化
比如:
const a = 1;
a = 2;// error: Assignment to constant variable.
这里就表现为,当 a 被赋值 1 后,无法赋值 2 给 a 了。
由于不能被重定义所以会出现下述的情况:
const a = 1;
const a = 2;// error: Identifier 'greeting' has already been declared
所以,当 const 一个变量 a ,但是无法更新这个变量 a 的值时,就表现为:定义了一个常量 a 。所以有一些文章,直接把 const 认为,就是用来定义常量的。但是这个是错误的。
const 只是不能被更新,当声明一个数组、对象的时候,固然他不能被更新,但是可以给数组或者对象的值进行更新(While a const object cannot be updated, the properties of this objects can be updated.)。这里说的比较绕,英文可能会好理解一些。
举个例子:
const greeting = {
message: "say Hi",
times: 4
}
对于上述 的 greeting ,我们可以更新下面的已声明变量的数据结构的值(properties):
greeting.message = "say Hello instead";
但是我们不能进行对于数据结构进行更新:
greeting = {
words: "Hello",
number: "five"
} // error: Assignment to constant variable.
Const 的作用域提升 (Hoisting of const)
和 let 一样,const 会将声明提升到作用域顶部,但是不会初始化。
所以,const 在我看来,是为了解决,固定数据结构下的数据处理的,为了避免 var 或者 let 导致的数据结构破坏。
总结?
这里,我不总结~
留给看文章的你吧~