体验了一下 let 和 const,真香
为啥香啊?在我看来,写代码过程中,当用 var 定义了多个变量时,有时难免出现变量名重复而你根本没发现,然后抓头发,头发抓着抓着就没了 🤣 如果没有某些方面的改善,let 和 const 也许就不会出现了。
先来说说 let 和 const 两兄弟
这俩的出现绝非偶然,就像你人生中遇到的人一样。
let 用来声明变量,而 const 用来声明常量(即不变的值,源自单词 constant)。显然地,let 代替了 var 用来声明变量,而const 则是新出现的专门用来声明常量的,与 var 没有任何关系!(有些练习题就喜欢这样下套!)。虽然一个用来代替,一个是新出现的,但两个的用法与 var 是一样的。
列一下变量与常量的区别吧
变量:可对其重新赋值。
常量:一旦初始化,就不能对其重新赋值。
let 没啥好说的,说说const
新出现的关键字,其注意事项有:
1.使用const
声明常量时,一旦声明,就必须立即赋值初始化,不能留到以后赋值。
如果你这样写,恭喜你,浏览器控制台给你留了一句话:“Missing initializer in const
declaration”。因此,使用const
进行声明常量时,千万别忘了给它赋值。
2.使用const
声明的常量,允许在不重新赋值的情况下修改它的值。
啊?这是什么意思?
其实有两层意思,重点是”不重新赋值”。1.当 const 声明一个基本数据类型时,如:const
sex = ‘male’; 你如何在不重新为 sex 常量赋值的情况下改变 sex 常量的值?有啥办法呢?答案是:没有任何办法。那你说的这种情况是指啥?说的就是:2.当 const 声明一个引用数据类型时,如(对象):
上面两句谁修改的是对的?当然是第二句啰。第一句明显就是给常量对象 person 赋值了一个空对象,犯了大忌。我们不能给常量对象赋值,难道还不能修改其属性的值啊?显然是可以的。
什么时候用 let,什么时候用 const?
首先,当清楚自己的需求或者你一眼就能看出是用 let 还是 const(比如循环);当你不清楚需求的情况下,推荐先用 const 声明,因为在以后需要改变常量的值时,你能看得到错误,不至于用 let 可能发现不了。
重头戏来啦,开始讲故事了
我觉得这绝壁是八股文中的一个:let、const、var 的区别。区别是什么?有多少个区别?
有 5 个区别:
- 重复声明
- 变量提升
- 暂时性死区
- window 对象的属性和方法
- 块级作用域
啥叫重复声明啊?
重复声明是指:已经存在的变量或常量,又声明了一遍。var 存在重复声明,而 let 和 const 不存在。
上面的输出 i 为 2,再来看 let 和 const。
用 let 声明时,会出现报错,显示 a 已经声明过了。
那再来看看这个例子:
调用函数 func 时,会出现啥结果呢?仍然是报错,显示 a 已经声明过了。为啥啊?
因为含义中说的”已经存在”是指<以任何形式存在的变量或常量>,那么函数的形参也算是变量啊,因此会报错。所以在理解重复声明时,要特别注意这个”已经存在”指的是以任何形式存在的变量或常量。
啥叫变量提升呢?
其含义是:var 会将变量的声明提升到当前作用域的顶部。因此var 存在变量提升,而 let 和 const 不存在,上栗子:
这个会报错吗?还是会输出 a 的值呀?都不是,这段代码相当于下面的代码
看完这两个栗子再加上含义理解理解。咱来说说:
正如下面代码所示,其背后的形式长这样。在上面的代码中,程序开始按顺序执行,console 语句执行时,发现程序要它去输出 a 的值,可 console 愁呀!前面都没看到 a 的影子啊,更别提上哪儿去找 a 的值了。诶,且慢,这时,在它下面的 var 看到了 console 的愁容,说:哥们儿,我有超能力啊,我可以让 a 去你的前面,但这超能力有限,不能让 a 把它的值也带上。console 无语了,想了想这也是个办法,总比让浏览器大哥报错的好啊。于是,var 就照办了,谁来都不好使。因此,console 只能输出一个未带值的 a,于是浏览器无情地告诉咱们:undefined。那咱们就明白了呗,var 会将声明的变量提升到当前作用域的顶部,那这个栗子中的作用域是啥?当然是全局作用域啊,难道这里还有啥作用域吗?
变量提升,咱就讲完了。
啥叫暂时性死区啊?
别急,咱先说说其含义:只要作用域中存在 let、const,它们所声明的变量或常量就会自动”绑定”这个区域,不会再受到外部作用域的影响。因此,let 和 const 存在暂时性死区,而 var 不存在。
啊?这是啥? 那咱看看下面的栗子呗:
别急别急,咱先来捋一捋程序咋样执行的:**值得注意的是:只有当函数被调用时,才会生成函数作用域,函数调用结束后,其生成的函数作用域也随之而消亡。**有了这个前提,咱们再来说说:程序执行时,会先略过声明,而是直接执行函数调用(这又是重要的一点:程序执行时会先略过声明!),func 函数被调用了,那么它生成了一个函数作用域,在这个函数中,接着执行 console 语句,诶,你说巧不巧,程序又让 console 去找 a 的值,那它咋办嘛,只能是现在当前的函数作用域中找有没有 a 呀,很可惜,它没找到。这里可能又有人说了:啊?下面不是有 a 吗?记住:查找是从内部作用域——>外部作用域——>直到全局作用域。因此 console 没找到 a,console 苦啊,内部的函数作用域中,它没找到 a,在家里竟然都没找到。于是它又只能爬山涉水地往外部作用域走了,但此时!它发现它走不出去了!家里门被锁了!为什么呀?因为它看不到它的家里其实还有一个 let 声明的变量 a!这个 let 很坏,当它出现在别人家里并声明了一个变量时,它就会把别人家的门给锁了!console 麻了呀,到死都出不去,又只能向浏览器报告说自己根本找不到 a,浏览器直呼离谱,于是直接向我们报错:a 未初始化。也就是说,当一个作用域中出现了 let、const,且它们也声明了一个变量,那么这个变量就会自动”绑定“当前作用域,使其不受外部作用域的影响。
我们能否先将视线看向变量提升和暂时性死区的代码?能发现什么吗?这些代码的顺序是不是不太正常啊?没错,反观这些栗子以及变量提升和暂时性死区的含义,我们不难得出:这两个名词的出现,不就是提醒我们要养成良好的编程习惯吗?
咱们轻松地聊聊关于 window 对象的属性和方法
这个完全是对于 var 来说的。
其含义为:在全局作用域中,var 声明的变量,通过 function 关键字声明的函数,会自动地变成 window 对象的属性和方法。
会输出 18 和 true。
而当 let 和 const 声明时
会输出 undefined 和 false。
这个没啥好讲的,记住就行。
啥叫块级作用域?怎么又有一个块级作用域啊?
var 不存在块级作用域,而 let 和 const 存在块级作用域。
先来个栗子看看:
哈?我就猜到你会说这是个块级作用域。其实,这里没有啥块级作用域,只存在一个全局作用域。那么此时 var 声明的就是一个全局变量啦,最终输出为 3。
再来看一个栗子:
诶?那这次输出的会不会是 3 呢?很遗憾,也不是。此时let 与 for 循环组成了一个块级作用域。那么,在循环之外的 console 语句想输出 i 的值,却找不到,就告诉浏览器没找到,于是浏览器告诉我们:i 没有被定义。
谈谈作用域链:
由于自己写然后再画作用域时直线不直太丑了,就找了张图。
正如图中所看到的,for 循环与 let 形成了块级作用域,函数被调用之后形成了函数作用域,而全局作用域本身就存在。其实,这顺序在上边中也提到过,查找变量时,是沿着作用域链往外寻找的,也即顺序为:内部作用域——>外部作用域——>…——>直到全局作用域。沿着这个顺序查找就行。
那有哪些块级作用域呢?
大部分{ } 、if(){ }、while(){ }、do{ }while()、for( ){}、switch(){ },这些花括号+let/const = 块级作用域。
尽管 function(){ }也有花括号,但它属于函数作用域。
尽管对象也有{},但它不构成任何作用域!
对了,作用域有三个:全局作用域、函数作用域、块级作用域。