這是一篇閱讀筆記,原文在此
1. 作用域內部原理
var a = 2;
- 編譯
- 分詞tokenizing
- 詞法單元流數組
[ "var": "keyword", "a": "identifier", "=": "assignment", "2": "integer", ";": "eos" // (end of statement) ]
- 解析parsing
- 抽象語法樹
AST
VariableDeclaration Identifier AssignmentExpression Numericliteral
{ operation: "=", left: { keyword: "var", right: "a" }, right: "2" }
- 抽象語法樹
- 代碼生成
- 將抽象語法樹轉換成可執行代碼(機器指令)
- 分詞tokenizing
- 執行
- 查詢
LHS
和RHS
- 嵌套
- 作用域鏈
- 查詢
- 異常
- RHS查詢失敗,拋出ReferenceError/TypeError
- LHS查詢,無法找到變量,全局作用域會創建一個具有該名稱的變量
2. 詞法作用域和動態作用域
- 詞法作用域只由函數被聲明時所處的位置決定(快手面試)
- 作用域鏈&遮蔽
- 動態作用域與調用棧有關,JavaScript不採用
// example1(快手面試題)
var a = 10;
function f(){
console.log(a);
}
// 詞法作用域綁定:f向上級查找到全局作用域
function fun(){
var a = 20;
f(); // 作用域鏈與調用棧無關
}
fun(); // 10
// example2
var a = 10;
function f(){
var a = 20;
return function (){
return a;
}
}
var fun = f();
fun(); // 20
3. 變量提升hoisting
- 定義聲明在編譯階段由編譯器進行;賦值操作會被留在原地等待引擎在執行階段進行。
- 函數聲明提升,但是函數表達式不會提升
foo();
var foo = function(){
console.log(1);
}
/*
函數表達式中的變量聲明會被提升,所以foo(),不會拋出ReferenceError
foo是undefined,被當做函數調用,會拋出TypeError
*/
- 具名函數表達式也不會被提升,同時具名函數只能在函數內部使用函數名,在外部會報錯
var foo = function bar(n){
if(n == 1) return 1;
return n * bar(n-1);
}
bar(); // ReferenceError
foo(3); // 6
// 注意差異
var bar;
var foo = function bar(n){
if(n == 1) return 1;
return n * bar(n-1);
}
bar(); // TypeError
- 函數優先:函數聲明和變量聲明都會被提升,但是函數聲明會覆蓋變量聲明(快手面試)
- 重複聲明:變量重複聲明無用;函數重複聲明會覆蓋;變量賦值會覆蓋函數
var foo;
function foo(){ return 1;}
foo = 2;
/*
1. var是變量聲明,function是函數聲明
2. 函數聲明優於變量聲明
3. 變量賦值會覆蓋函數聲明
*/
4. 塊作用域
var
的作用域污染
for(var i = 0; i < 10; i++){
console.log(i);
}
console.log(i); // 10
{let}
塊級作用域;不提升- 塊級作用域可以用
IIFE
替代
{
let i = 0;
}
console.log(i); // ReferenceError
// IIFE
(function (){
var a = 0;
})()
console.log(a);
IIFE
立即執行函數表達式
for(var i = 0; i < 10; i++){
setTimeout(function timer(){
console.log(i);
}, i * 1000);
}
// 10 10 ... 10 10
for(var i = 0; i < 10; i++){
(function (j){
setTimeout(function timer(){
console.log(j);
}, j * 1000);
})(i);
}
// 1 2 3 ... 9 10
- 重複聲明:
let
不允許在相同作用域內有重複聲明
{
let a = 0;
var a = 1; // SyntaxError: Unexpected Identifier
}
{
let a = 0;
let a = 1; // SyntaxError: Unexpected Identifier
}
const
塊級作用域
{
const a = 2;
a = 3; // TypeError: Assignment to constant variable
}
console.log(a); // ReferenceError
try-catch
創建塊級作用域
try{
throw 2;
}catch(a){
console.log(a); // 2
}
console.log(a); // ReferenceError
5. 執行環境和作用域(execution context & scope)
- 自由變量: 可以在作用域鏈中找到,但並非在當前作用域中聲明的變量
- 如果標識符identifier在全局作用域中沒有找到:RHS和嚴格模式的LHS會拋出ReferenceError;非嚴格模式下LHS會在全局變量中聲明該變量。(滴滴面試)
- 詞法作用域是函數定義時確定的;執行環境是函數調用時確定的。
- 作用域鏈的盡頭是全局對象
window
,RHS
會拋錯(滴滴面試)- 原型鏈的盡頭是
Object.prototype.__proto__ === null
(京東面試)