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