文章

var、let、const的详解与区别

ECMAScript
JavaScript

ES6 中 var、let、const 的详解与区别

var、let、const的详解与区别

体验了一下 let 和 const,真香

为啥香啊?在我看来,写代码过程中,当用 var 定义了多个变量时,有时难免出现变量名重复而你根本没发现,然后抓头发,头发抓着抓着就没了 🤣 如果没有某些方面的改善,let 和 const 也许就不会出现了。


先来说说 let 和 const 两兄弟

这俩的出现绝非偶然,就像你人生中遇到的人一样。

let 用来声明变量,而 const 用来声明常量(即不变的值,源自单词 constant)。显然地,let 代替了 var 用来声明变量,而const 则是新出现的专门用来声明常量的,与 var 没有任何关系!(有些练习题就喜欢这样下套!)。虽然一个用来代替,一个是新出现的,但两个的用法与 var 是一样的。


列一下变量与常量的区别吧

变量:可对其重新赋值。

常量:一旦初始化,就不能对其重新赋值。


let 没啥好说的,说说const

新出现的关键字,其注意事项有:

1.使用const声明常量时,一旦声明,就必须立即赋值初始化,不能留到以后赋值。

const a;
a = 2;
console.log(a);

如果你这样写,恭喜你,浏览器控制台给你留了一句话:“Missing initializer in const declaration”。因此,使用const进行声明常量时,千万别忘了给它赋值。

2.使用const声明的常量,允许在不重新赋值的情况下修改它的值。

啊?这是什么意思?

其实有两层意思,重点是”不重新赋值”。1.当 const 声明一个基本数据类型时,如:const sex = ‘male’; 你如何在不重新为 sex 常量赋值的情况下改变 sex 常量的值?有啥办法呢?答案是:没有任何办法。那你说的这种情况是指啥?说的就是:2.当 const 声明一个引用数据类型时,如(对象):

const person = {
       name: 'Alex';
};
// 以下哪种修改是对的?
const person = {}; -> 重新赋值了,且空对象在转为Boolean类型时会隐式转换为true
person.name = 'ZhangSan';

上面两句谁修改的是对的?当然是第二句啰。第一句明显就是给常量对象 person 赋值了一个空对象,犯了大忌。我们不能给常量对象赋值,难道还不能修改其属性的值啊?显然是可以的。


什么时候用 let,什么时候用 const?

首先,当清楚自己的需求或者你一眼就能看出是用 let 还是 const(比如循环);当你不清楚需求的情况下,推荐先用 const 声明,因为在以后需要改变常量的值时,你能看得到错误,不至于用 let 可能发现不了。


重头戏来啦,开始讲故事了

我觉得这绝壁是八股文中的一个:let、const、var 的区别。区别是什么?有多少个区别?

有 5 个区别:

  1. 重复声明
  2. 变量提升
  3. 暂时性死区
  4. window 对象的属性和方法
  5. 块级作用域

啥叫重复声明啊?

重复声明是指:已经存在的变量或常量,又声明了一遍。var 存在重复声明,而 let 和 const 不存在。

var i = 1;
......
var i = 2;
console.log(i);

上面的输出 i 为 2,再来看 let 和 const。

let a = 1;
......
let a = 2;
console.log(a);

用 let 声明时,会出现报错,显示 a 已经声明过了。

那再来看看这个例子:

function func(a) {
  let a = 1;
}
func();

调用函数 func 时,会出现啥结果呢?仍然是报错,显示 a 已经声明过了。为啥啊?

因为含义中说的”已经存在”是指<以任何形式存在的变量或常量>,那么函数的形参也算是变量啊,因此会报错。所以在理解重复声明时,要特别注意这个”已经存在”指的是以任何形式存在的变量或常量


啥叫变量提升呢?

其含义是:var 会将变量的声明提升到当前作用域的顶部。因此var 存在变量提升,而 let 和 const 不存在,上栗子:

console.log(a);
var a = 1;

这个会报错吗?还是会输出 a 的值呀?都不是,这段代码相当于下面的代码

var a;
console.log(a);
a = 1;

看完这两个栗子再加上含义理解理解。咱来说说:

正如下面代码所示,其背后的形式长这样。在上面的代码中,程序开始按顺序执行,console 语句执行时,发现程序要它去输出 a 的值,可 console 愁呀!前面都没看到 a 的影子啊,更别提上哪儿去找 a 的值了。诶,且慢,这时,在它下面的 var 看到了 console 的愁容,说:哥们儿,我有超能力啊,我可以让 a 去你的前面,但这超能力有限,不能让 a 把它的值也带上。console 无语了,想了想这也是个办法,总比让浏览器大哥报错的好啊。于是,var 就照办了,谁来都不好使。因此,console 只能输出一个未带值的 a,于是浏览器无情地告诉咱们:undefined。那咱们就明白了呗,var 会将声明的变量提升到当前作用域的顶部,那这个栗子中的作用域是啥?当然是全局作用域啊,难道这里还有啥作用域吗?

变量提升,咱就讲完了。


啥叫暂时性死区啊?

别急,咱先说说其含义:只要作用域中存在 let、const,它们所声明的变量或常量就会自动”绑定”这个区域,不会再受到外部作用域的影响。因此,let 和 const 存在暂时性死区,而 var 不存在。

啊?这是啥? 那咱看看下面的栗子呗:

let a = 1;
function func() {
  console.log(a);
  let a = 2;
}
func();

别急别急,咱先来捋一捋程序咋样执行的:**值得注意的是:只有当函数被调用时,才会生成函数作用域,函数调用结束后,其生成的函数作用域也随之而消亡。**有了这个前提,咱们再来说说:程序执行时,会先略过声明,而是直接执行函数调用(这又是重要的一点:程序执行时会先略过声明!),func 函数被调用了,那么它生成了一个函数作用域,在这个函数中,接着执行 console 语句,诶,你说巧不巧,程序又让 console 去找 a 的值,那它咋办嘛,只能是现在当前的函数作用域中找有没有 a 呀,很可惜,它没找到。这里可能又有人说了:啊?下面不是有 a 吗?记住:查找是从内部作用域——>外部作用域——>直到全局作用域。因此 console 没找到 a,console 苦啊,内部的函数作用域中,它没找到 a,在家里竟然都没找到。于是它又只能爬山涉水地往外部作用域走了,但此时!它发现它走不出去了!家里门被锁了!为什么呀?因为它看不到它的家里其实还有一个 let 声明的变量 a!这个 let 很坏,当它出现在别人家里并声明了一个变量时,它就会把别人家的门给锁了!console 麻了呀,到死都出不去,又只能向浏览器报告说自己根本找不到 a,浏览器直呼离谱,于是直接向我们报错:a 未初始化。也就是说,当一个作用域中出现了 let、const,且它们也声明了一个变量,那么这个变量就会自动”绑定“当前作用域,使其不受外部作用域的影响

我们能否先将视线看向变量提升和暂时性死区的代码?能发现什么吗?这些代码的顺序是不是不太正常啊?没错,反观这些栗子以及变量提升和暂时性死区的含义,我们不难得出:这两个名词的出现,不就是提醒我们要养成良好的编程习惯吗?


咱们轻松地聊聊关于 window 对象的属性和方法

这个完全是对于 var 来说的。

其含义为:在全局作用域中,var 声明的变量,通过 function 关键字声明的函数,会自动地变成 window 对象的属性和方法

var age = 18;
function add() {}
console.log(window.age);
console.log(window.add === add);

会输出 18 和 true。

而当 let 和 const 声明时

let age = 18;
const add = function () {};
console.log(window.age);
console.log(window.add === add);

会输出 undefined 和 false。

这个没啥好讲的,记住就行。


啥叫块级作用域?怎么又有一个块级作用域啊?

var 不存在块级作用域,而 let 和 const 存在块级作用域。

先来个栗子看看:

for(var i = 0; i < 3; i ++){
   ......
}
 console.log(i);

哈?我就猜到你会说这是个块级作用域。其实,这里没有啥块级作用域,只存在一个全局作用域。那么此时 var 声明的就是一个全局变量啦,最终输出为 3。

再来看一个栗子:

for(let i = 0; i < 3; i++){
  ......
}
console.log(i);

诶?那这次输出的会不会是 3 呢?很遗憾,也不是。此时let 与 for 循环组成了一个块级作用域。那么,在循环之外的 console 语句想输出 i 的值,却找不到,就告诉浏览器没找到,于是浏览器告诉我们:i 没有被定义。

谈谈作用域链

由于自己写然后再画作用域时直线不直太丑了,就找了张图。

正如图中所看到的,for 循环与 let 形成了块级作用域,函数被调用之后形成了函数作用域,而全局作用域本身就存在。其实,这顺序在上边中也提到过,查找变量时,是沿着作用域链往外寻找的,也即顺序为:内部作用域——>外部作用域——>…——>直到全局作用域。沿着这个顺序查找就行。


那有哪些块级作用域呢?

大部分{ } 、if(){ }、while(){ }、do{ }while()、for( ){}、switch(){ },这些花括号+let/const = 块级作用域。

尽管 function(){ }也有花括号,但它属于函数作用域。

尽管对象也有{},但它不构成任何作用域!

对了,作用域有三个:全局作用域、函数作用域、块级作用域。