【JavaScript】關於作用域

這是一篇閱讀筆記,原文在此

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"
      }
      
    • 代碼生成
      • 將抽象語法樹轉換成可執行代碼(機器指令)
  • 執行
    • 查詢
      • LHSRHS
    • 嵌套
      • 作用域鏈
  • 異常
    • 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會在全局變量中聲明該變量。(滴滴面試)
  • 詞法作用域是函數定義時確定的;執行環境是函數調用時確定的。
  1. 作用域鏈的盡頭是全局對象windowRHS會拋錯(滴滴面試)
  2. 原型鏈的盡頭是Object.prototype.__proto__ === null (京東面試)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章