【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 (京东面试)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章