这是一篇阅读笔记,原文在此
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
(京东面试)