JS 編譯原理#
var name = "rose";
上面這行程式碼在 JS 中會這樣呈現:
var name; // 編譯階段處理
name = "rose"; // 執行階段處理
JS 編譯主要分為兩個階段:編譯階段和執行階段。
編譯階段#
此階段主角為編譯器。
- JS 找遍作用域,看是否存在 name 的變數
- 如果已經存在,則什麼都不做,直接忽略
var name
這個宣告,繼續編譯下去; - 如果沒有,則在當前作用域中新增一個
name
變數
- 如果已經存在,則什麼都不做,直接忽略
- 編譯器會為引擎生成運行時所需的程式碼,程式就進入了執行階段。
執行階段#
此階段主角為JS 引擎。
- JS 引擎在執行時,會先找遍當前作用域,看是否有一個叫
name
的變數。- 如果有,直接賦值
- 如果沒有,則為當前作用域沒有。則去父級作用域看是否有,如果無,則去上一級作用域中查找。
- 如果最終沒有找到,則拋異常。
作用域套作用域,即作用域鏈。
作用域#
變數最基本的能力就是能夠存儲變數中的值,並且允許我們對此變數進行訪問和修改,而對於變數存儲、訪問的規則是作用域。
全局作用域#
在任何函數外或程式碼塊外的頂層作用域就是全局作用域,裡面的變數就是全局變數。
var name = "rose"; //全局作用域
function showName() {
//函數作用域
console.log(name);
}
{
name = "test"; //區塊級作用域
}
showName(); //test
可以看到,全局變數在全局作用域、函數作用域、區塊級作用域中都可以正常訪問。
函數作用域#
在函數中的作用域就是函數作用域。
function showName() {
var name = "jack"; //函數作用域
}
showName(); //方法調用
{
console.log(name); //區塊級作用域,Uncaught ReferenceError: name is not defined
}
console.log(name); //全局作用域,Uncaught ReferenceError: name is not defined
可以看到,函數內部變數,在全局作用域及區塊級作用域中,都無法訪問,只有在函數內部,才能訪問到,所以函數內部的變數也稱為局部變數。
區塊級作用域#
ES6
中新出的let
和const
關鍵字 自帶作用域。
區塊級作用域相當於是只在這塊程式碼塊中生效,如果它被大括號{}
包圍,則大括號就是一段程式碼塊,程式碼塊中使用const
宣告的變數也被稱為局部變數。
{
let name='rose';
}
console.log(name); //Uncaught ReferenceError: name is not defined
function showName{
console.log(name);
}
showName(); //Uncaught ReferenceError: name is not defined
可以看到,區塊級作用域中的變數,在程式碼塊外面就訪問不到了。
作用域鏈#
作用域和作用域的嵌套,就產生了作用域鏈。作用域鏈的查找,向外不向內。
變數提升#
name = "rose";
console.log(name); //rose
var name;
可以發現,這段程式碼可以正常執行,並且不會報錯。
在 JS 眼中程式實際上是這樣的:
var name;
name = "rose";
console.log(name); // rose
let
和const
程式碼:
name = "rose";
console.log(name); //Uncaught ReferenceError: Cannot access 'name' before initialization
let name;
let 、const ** 禁用變數提升。**const 宣告後必賦值。
let、const、var 的區別#
- 區塊級作用域:區塊級作用域由
{}
包括,let
和const
具有區塊級作用域,var
不存在區塊級作用域。
區塊級作用域解決了ES5
中的兩個問題:
- 內層變數可能覆蓋外層變數
- 用來計數的迴圈變數洩漏為全局變數
- 變數提升:var 存在變數提升,let 和 const 不存在變數提升,即變數只能在宣告後使用,否則會報錯。
- 給全局添加屬性:瀏覽器的全局物件是 window,Node 的全局變數是 global。var 宣告的變數為全局變數,並且會將該變數添加為全局物件的屬性,但是 let 和 const 不會。
- 重複宣告:var 宣告變數時,可以重複宣告變數,後宣告的變數會覆蓋之前宣告的變數。let 和 const 不允許在同一作用域下重複宣告變數。
- 暫時性死區:在使用 let、const 關鍵字宣告變數時,該變數是不可用的,這在語法上,成為暫時性死區。
使用 var 宣告的變數不存在暫時性死區。
- 初始值設定:在變數宣告時,var 和 let 可以不用設定初始值。而 const 宣告變數必須設定初始值。
- 指標指向:let 和 const 都是 ES6 新增的用於創建變數的語法。let 創建的變數可以更改指標指向(可以重新賦值)。但 const 宣告的變數不允許改變指標的指向(不允許重新賦值)。
暫時性死區#
var name = "rose";
{
name = "bob";
let name; //Uncaught ReferenceError: Cannot access 'name' before initialization
}
如果區塊中存在 let 和 const,這個區塊對於這些關鍵字宣告的變數,從一開始就形成了封閉作用域。
因為 JS 清楚地感知到了 name 是用 let 宣告在當前這個程式碼塊內的,所以會給這個變數 name 加上了暫時性死區的限制,它就不往外探出頭了。
因此,如果我們把上面的let name;
去掉,程式也能正常執行,name 的值也能被成功修改為 blob,就是正常地按照作用域鏈的規則,向外探出頭去了。