“睡服”面试官系列第一篇之let和const命令(建议收藏学习)
目錄
1let命令
1.1基本用法
1.2for循環(huán)小案例
1.3不存在變量提升
1.4暫時性死區(qū)
1.5不允許重復聲明
2塊級作用域
2.1為什么需要塊級作用域?
2.2ES6 的塊級作用域
2.3塊級作用域和函數(shù)聲明
3const
3.1本質(zhì)
4頂層對象的屬性
5global對象
6總結
1let命令
1.1基本用法
ES6 新增了 let 命令,用來聲明變量。它的用法類似于 var ,但是所聲明的變量,只在 let 命令所在的代碼塊內(nèi)有效。
<!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>test</title> </head><body><script>{let a = 10;var b = 1}console.log(a) // ReferenceError: a is not defined.console.log(b) //1</script> </body></html>上面代碼在代碼塊之中,分別用 let 和 var 聲明了兩個變量。然后在代碼塊之外調(diào)用這兩個變量,結果 let 聲明的變量報錯, var 聲明的變量返回了正確
的值。這表明, let 聲明的變量只在它所在的代碼塊有效
1.2for循環(huán)小案例
for 循環(huán)的計數(shù)器,就很合適使用 let 命令
<!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>test</title> </head><body><script>for (let i = 0; i < 10; i++) {// ...}console.log(i);// ReferenceError: i is not defined</script> </body></html>上面代碼中,計數(shù)器 i 只在 for 循環(huán)體內(nèi)有效,在循環(huán)體外引用就會報錯。
下面的代碼如果使用 var ,最后輸出的是 10 。
<!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>test</title> </head><body><script>var a = [];for (var i = 0; i < 10; i++) {a[i] = function() {console.log(i);};}a[6](); // 10</script> </body></html>上面代碼中,變量 i 是 var 命令聲明的,在全局范圍內(nèi)都有效,所以全局只有一個變量 i 。每一次循環(huán),變量 i 的值都會發(fā)生改變,而循環(huán)內(nèi)被賦給數(shù)組 a
的函數(shù)內(nèi)部的 console.log(i) ,里面的 i 指向的就是全局的 i 。也就是說,所有數(shù)組 a 的成員里面的 i ,指向的都是同一個 i ,導致運行時輸出的是最后
一輪的 i 的值,也就是 10。
如果使用 let ,聲明的變量僅在塊級作用域內(nèi)有效,最后輸出的是 6
上面代碼中,變量 i 是 let 聲明的,當前的 i 只在本輪循環(huán)有效,所以每一次循環(huán)的 i 其實都是一個新的變量,所以最后輸出的是 6 。你可能會問,如果每
一輪循環(huán)的變量 i 都是重新聲明的,那它怎么知道上一輪循環(huán)的值,從而計算出本輪循環(huán)的值?這是因為 JavaScript 引擎內(nèi)部會記住上一輪循環(huán)的值,初
始化本輪的變量 i 時,就在上一輪循環(huán)的基礎上進行計算。
另外, for 循環(huán)還有一個特別之處,就是設置循環(huán)變量的那部分是一個父作用域,而循環(huán)體內(nèi)部是一個單獨的子作用域。
上面代碼正確運行,輸出了 3 次 abc 。這表明函數(shù)內(nèi)部的變量 i 與循環(huán)變量 i 不在同一個作用域,有各自單獨的作用域。
1.3不存在變量提升
var 命令會發(fā)生”變量提升“現(xiàn)象,即變量可以在聲明之前使用,值為 undefined 。這種現(xiàn)象多多少少是有些奇怪的,按照一般的邏輯,變量應該在聲明語
句之后才可以使用。
為了糾正這種現(xiàn)象, let 命令改變了語法行為,它所聲明的變量一定要在聲明后使用,否則報錯
上面代碼中,變量 foo 用 var 命令聲明,會發(fā)生變量提升,即腳本開始運行時,變量 foo 已經(jīng)存在了,但是沒有值,所以會輸出 undefined 。變量 bar 用
let 命令聲明,不會發(fā)生變量提升。這表示在聲明它之前,變量 bar 是不存在的,這時如果用到它,就會拋出一個錯誤。
1.4暫時性死區(qū)
只要塊級作用域內(nèi)存在 let 命令,它所聲明的變量就“綁定”(binding)這個區(qū)域,不再受外部的影響。
<!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>test</title> </head><body><script>var tmp = 123;if (true) {tmp = 'abc'; // ReferenceErrorlet tmp;}</script> </body></html>上面代碼中,存在全局變量 tmp ,但是塊級作用域內(nèi) let 又聲明了一個局部變量 tmp ,導致后者綁定這個塊級作用域,所以在 let 聲明變量前,對 tmp 賦
值會報錯。
ES6 明確規(guī)定,如果區(qū)塊中存在 let 和 const 命令,這個區(qū)塊對這些命令聲明的變量,從一開始就形成了封閉作用域。凡是在聲明之前就使用這些變量,
就會報錯。
總之,在代碼塊內(nèi),使用 let 命令聲明變量之前,該變量都是不可用的。這在語法上,稱為“暫時性死區(qū)”(temporal dead zone,簡稱 TDZ)。
上面代碼中,在 let 命令聲明變量 tmp 之前,都屬于變量 tmp 的“死區(qū)”。
“暫時性死區(qū)”也意味著 typeof 不再是一個百分之百安全的操作
上面代碼中,變量 x 使用 let 命令聲明,所以在聲明之前,都屬于 x 的“死區(qū)”,只要用到該變量就會報錯。因此, typeof 運行時就會拋出一個
ReferenceError 。
作為比較,如果一個變量根本沒有被聲明,使用 typeof 反而不會報錯
上面代碼中, undeclared_variable 是一個不存在的變量名,結果返回“undefined”。所以,在沒有 let 之前, typeof 運算符是百分之百安全的,永遠不
會報錯。現(xiàn)在這一點不成立了。這樣的設計是為了讓大家養(yǎng)成良好的編程習慣,變量一定要在聲明之后使用,否則就報錯。
有些“死區(qū)”比較隱蔽,不太容易發(fā)現(xiàn)
上面代碼中,調(diào)用 bar 函數(shù)之所以報錯(某些實現(xiàn)可能不報錯),是因為參數(shù) x 默認值等于另一個參數(shù) y ,而此時 y 還沒有聲明,屬于”死區(qū)“。如果 y 的默
認值是 x ,就不會報錯,因為此時 x 已經(jīng)聲明了。
另外,下面的代碼也會報錯,與 var 的行為不同
<!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>test</title> </head><body><script>// 不報錯var x = x;// 報錯let x = x;// ReferenceError: x is not defined</script> </body></html>上面代碼報錯,也是因為暫時性死區(qū)。使用 let 聲明變量時,只要變量在還沒有聲明完成前使用,就會報錯。上面這行就屬于這個情況,在變量 x 的聲明
語句還沒有執(zhí)行完成前,就去取 x 的值,導致報錯”x 未定義“。
ES6 規(guī)定暫時性死區(qū)和 let 、 const 語句不出現(xiàn)變量提升,主要是為了減少運行時錯誤,防止在變量聲明前就使用這個變量,從而導致意料之外的行為。
這樣的錯誤在 ES5 是很常見的,現(xiàn)在有了這種規(guī)定,避免此類錯誤就很容易了。
總之,暫時性死區(qū)的本質(zhì)就是,只要一進入當前作用域,所要使用的變量就已經(jīng)存在了,但是不可獲取,只有等到聲明變量的那一行代碼出現(xiàn),才可以獲
取和使用該變量。
1.5不允許重復聲明
let 不允許在相同作用域內(nèi),重復聲明同一個變量
<!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>test</title> </head><body><script>// 報錯function func() {let a = 10;var a = 1;}// 報錯function func() {let a = 10;let a = 1;}</script> </body></html>因此,不能在函數(shù)內(nèi)部重新聲明參數(shù)
<!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>test</title> </head><body><script>function func(arg) {let arg; // 報錯}function func(arg) {{let arg; // 不報錯}}</script> </body></html>2塊級作用域
2.1為什么需要塊級作用域?
ES5 只有全局作用域和函數(shù)作用域,沒有塊級作用域,這帶來很多不合理的場景。
第一種場景,內(nèi)層變量可能會覆蓋外層變量。
上面代碼的原意是, if 代碼塊的外部使用外層的 tmp 變量,內(nèi)部使用內(nèi)層的 tmp 變量。但是,函數(shù) f 執(zhí)行后,輸出結果為 undefined ,原因在于變量提
升,導致內(nèi)層的 tmp 變量覆蓋了外層的 tmp 變量。
第二種場景,用來計數(shù)的循環(huán)變量泄露為全局變量
上面代碼中,變量 i 只用來控制循環(huán),但是循環(huán)結束后,它并沒有消失,泄露成了全局變量。
2.2ES6 的塊級作用域
let 實際上為 JavaScript 新增了塊級作用域。
<!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>test</title> </head><body><script>function f1() {let n = 5;if (true) {let n = 10;}console.log(n); // 5}</script> </body></html>上面的函數(shù)有兩個代碼塊,都聲明了變量 n ,運行后輸出 5。這表示外層代碼塊不受內(nèi)層代碼塊的影響。如果兩次都使用 var 定義變量 n ,最后輸出的值
才是 10。
ES6 允許塊級作用域的任意嵌套。
上面代碼使用了一個五層的塊級作用域。外層作用域無法讀取內(nèi)層作用域的變量。
<!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>test</title> </head><body><script>{{{{{let insane = 'Hello World'}console.log(insane); // 報錯}}}};</script> </body></html>內(nèi)層作用域可以定義外層作用域的同名變量。
<!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>test</title> </head><body><script>{{{{let insane = 'Hello World'; {let insane = 'Hello World'}}}}};</script> </body></html>塊級作用域的出現(xiàn),實際上使得獲得廣泛應用的立即執(zhí)行函數(shù)表達式(IIFE)不再必要了。
// IIFE 寫法(function() {var tmp = ...;...}());// 塊級作用域?qū)懛▄let tmp = ...;...}2.3塊級作用域和函數(shù)聲明
函數(shù)能不能在塊級作用域之中聲明?這是一個相當令人混淆的問題。
ES5 規(guī)定,函數(shù)只能在頂層作用域和函數(shù)作用域之中聲明,不能在塊級作用域聲明。
上面兩種函數(shù)聲明,根據(jù) ES5 的規(guī)定都是非法的。
但是,瀏覽器沒有遵守這個規(guī)定,為了兼容以前的舊代碼,還是支持在塊級作用域之中聲明函數(shù),因此上面兩種情況實際都能運行,不會報錯。ES6 引入了塊級作用域,明確允許在塊級作用域之中聲明函數(shù)。ES6 規(guī)定,塊級作用域之中,函數(shù)聲明語句的行為類似于 let ,在塊級作用域之外不可引
用
上面代碼在 ES5 中運行,會得到“I am inside!”,因為在 if 內(nèi)聲明的函數(shù) f 會被提升到函數(shù)頭部,實際運行的代碼如下。
ES6 就完全不一樣了,理論上會得到“I am outside!”。因為塊級作用域內(nèi)聲明的函數(shù)類似于 let ,對作用域之外沒有影響。但是,如果你真的在 ES6 瀏
覽器中運行一下上面的代碼,是會報錯的,這是為什么呢?
原來,如果改變了塊級作用域內(nèi)聲明的函數(shù)的處理規(guī)則,顯然會對老代碼產(chǎn)生很大影響。為了減輕因此產(chǎn)生的不兼容問題,ES6 在附錄 B里面規(guī)定,瀏覽
器的實現(xiàn)可以不遵守上面的規(guī)定,有自己的行為方式。
允許在塊級作用域內(nèi)聲明函數(shù)。
函數(shù)聲明類似于 var ,即會提升到全局作用域或函數(shù)作用域的頭部。
同時,函數(shù)聲明還會提升到所在的塊級作用域的頭部。
注意,上面三條規(guī)則只對 ES6 的瀏覽器實現(xiàn)有效,其他環(huán)境的實現(xiàn)不用遵守,還是將塊級作用域的函數(shù)聲明當作 let 處理。
根據(jù)這三條規(guī)則,在瀏覽器的 ES6 環(huán)境中,塊級作用域內(nèi)聲明的函數(shù),行為類似于 var 聲明的變量
上面的代碼在符合 ES6 的瀏覽器中,都會報錯,因為實際運行的是下面的代碼。
<!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>test</title> </head><body><script>// 瀏覽器的 ES6 環(huán)境function f() {console.log('I am outside!');}(function() {var f = undefined;if (false) {function f() {console.log('I am inside!');}}f();}());// Uncaught TypeError: f is not a function</script> </body></html>考慮到環(huán)境導致的行為差異太大,應該避免在塊級作用域內(nèi)聲明函數(shù)。如果確實需要,也應該寫成函數(shù)表達式,而不是函數(shù)聲明語句。
<!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>test</title> </head><body><script>// 函數(shù)聲明語句{let a = 'secret';function f() {return a;}}// 函數(shù)表達式{let a = 'secret';let f = function() {return a;};}</script> </body></html>另外,還有一個需要注意的地方。ES6 的塊級作用域允許聲明函數(shù)的規(guī)則,只在使用大括號的情況下成立,如果沒有使用大括號,就會報錯
<!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>test</title> </head><body><script>// 不報錯'use strict';if (true) {function f() {}}// 報錯'use strict';if (true)function f() {</script> </body></html>3const
const 聲明一個只讀的常量。一旦聲明,常量的值就不能改變
<!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>test</title> </head><body><script>const PI = 3.1415;PI // 3.1415PI = 3;// TypeError: Assignment to constant variable</script> </body></html>上面代碼表明改變常量的值會報錯。
const 聲明的變量不得改變值,這意味著, const 一旦聲明變量,就必須立即初始化,不能留到以后賦值。
上面代碼表示,對于 const 來說,只聲明不賦值,就會報錯。
const 的作用域與 let 命令相同:只在聲明所在的塊級作用域內(nèi)有效。
const 命令聲明的常量也是不提升,同樣存在暫時性死區(qū),只能在聲明的位置后面使用
<!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>test</title> </head><body><script>if (true) {console.log(MAX); // ReferenceErrorconst MAX = 5;}</script> </body></html>上面代碼在常量 MAX 聲明之前就調(diào)用,結果報錯。
const 聲明的常量,也與 let 一樣不可重復聲明。
3.1本質(zhì)
const 實際上保證的,并不是變量的值不得改動,而是變量指向的那個內(nèi)存地址不得改動。對于簡單類型的數(shù)據(jù)(數(shù)值、字符串、布爾值),值就保存在
變量指向的那個內(nèi)存地址,因此等同于常量。但對于復合類型的數(shù)據(jù)(主要是對象和數(shù)組),變量指向的內(nèi)存地址,保存的只是一個指針, const 只能保
證這個指針是固定的,至于它指向的數(shù)據(jù)結構是不是可變的,就完全不能控制了。因此,將一個對象聲明為常量必須非常小心
上面代碼中,常量 foo 儲存的是一個地址,這個地址指向一個對象。不可變的只是這個地址,即不能把 foo 指向另一個地址,但對象本身是可變的,所以
依然可以為其添加新屬性
4頂層對象的屬性
頂層對象,在瀏覽器環(huán)境指的是 window 對象,在 Node 指的是 global 對象。ES5 之中,頂層對象的屬性與全局變量是等價的
<!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>test</title> </head><body><script>window.a = 1;a // 1a = 2;window.a // 2</script> </body></html>?上面代碼中,頂層對象的屬性賦值與全局變量的賦值,是同一件事。
頂層對象的屬性與全局變量掛鉤,被認為是 JavaScript 語言最大的設計敗筆之一。這樣的設計帶來了幾個很大的問題,首先是沒法在編譯時就報出變量未
聲明的錯誤,只有運行時才能知道(因為全局變量可能是頂層對象的屬性創(chuàng)造的,而屬性的創(chuàng)造是動態(tài)的);其次,程序員很容易不知不覺地就創(chuàng)建了全
局變量(比如打字出錯);最后,頂層對象的屬性是到處可以讀寫的,這非常不利于模塊化編程。另一方面, window 對象有實體含義,指的是瀏覽器的窗
口對象,頂層對象是一個有實體含義的對象,也是不合適的。
5global對象
ES5 的頂層對象,本身也是一個問題,因為它在各種實現(xiàn)里面是不統(tǒng)一的。
瀏覽器里面,頂層對象是 window ,但 Node 和 Web Worker 沒有 window 。
瀏覽器和 Web Worker 里面, self 也指向頂層對象,但是 Node 沒有 self 。
Node 里面,頂層對象是 global ,但其他環(huán)境都不支持。
同一段代碼為了能夠在各種環(huán)境,都能取到頂層對象,現(xiàn)在一般是使用 this 變量,但是有局限性。
全局環(huán)境中, this 會返回頂層對象。但是,Node 模塊和 ES6 模塊中, this 返回的是當前模塊。
函數(shù)里面的 this ,如果函數(shù)不是作為對象的方法運行,而是單純作為函數(shù)運行, this 會指向頂層對象。但是,嚴格模式下,這時 this 會返回
undefined 。
不管是嚴格模式,還是普通模式, new Function('return this')() ,總是會返回全局對象。但是,如果瀏覽器用了 CSP(Content Security
Policy,內(nèi)容安全政策),那么 eval 、 new Function 這些方法都可能無法使用。
綜上所述,很難找到一種方法,可以在所有情況下,都取到頂層對象
6總結
本博客源于本人閱讀相關書籍和視頻總結,創(chuàng)作不易,謝謝點贊支持。學到就是賺到。我是歌謠,勵志成為一名優(yōu)秀的技術革新人員。
歡迎私信交流,一起學習,一起成長。
推薦鏈接 其他文件目錄參照
“睡服“面試官系列之各系列目錄匯總(建議學習收藏)
總結
以上是生活随笔為你收集整理的“睡服”面试官系列第一篇之let和const命令(建议收藏学习)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 车流量计数、不同车型统计算法
- 下一篇: 怎么看java源代码