关于执行上下文与作用域链
难度:⭐️⭐️
💌 这一部分知识点是面试高频哦
上下文的分类
全局上下文 函数上下文
1️⃣ 全局上下文:最外层的上下文
在浏览器中,全局上下文就是我们常说的 window
对象,因此所有通过 var
定义的全局变量和函数都会成为 window
对象的属性和方法。
上下文在其所有代码都执行完毕后会被销毁,包括定义在它上面的所有变量和函数(全局上下文在应用程序退出前才会被销毁,比如关闭网页或退出浏览器)。
2️⃣ 函数上下文
当代码执行流进入函数时,函数的上下文被推到一个上下文栈上。
在函数执行完之后,上下文栈会弹出该函数上下文,将控制权返还给之前的执行上下文。ECMAScript
程序的执行流就是通过这个上下文栈进行控制的。
作用域链
上下文中的代码在执行的时候,会创建变量对象的一个作用域链(scope chain
)。这个作用域链决定了各级上下文中的代码在访问变量和函数时的顺序。
上下文之间的连接是线性的、有序的。 每个上下文都可以到上一级上下文中去搜索变量和函数,但任何上下文都不能到下一级上下文中去搜索。
变量声明
1.使用 var 的函数作用域声明
- 使用
var
声明变量时,变量会被自动添加到最接近的上下文
function add(num1, num2) {
var sum = num1 + num2;
return sum;
}
let result = add(10, 20); // 30
console.log(sum); // 报错:sum 在这里不是有效变量
- 但是如果变量未经声明就被初始化了,那么它就会自动被添加到全局上下文
function add(num1, num2) {
sum = num1 + num2;
return sum;
}
let result = add(10, 20); // 30
console.log(sum); // 30
附上书中关于 变量提升 的说明:
var
声明会被拿到函数或全局作用域的顶部,位于作用域中所有代码之前。这个现象叫作“提升”(hoisting
)。
👇 一段可用来考察作用域链和变量提升的代码:
console.log(name); // undefined
var name = 'Jake';
function() {
console.log(name); // undefined
var name = 'Jake';
}
2.使用 let 的块级作用域声明
let
和var
有三个区别:
1.let
遵循块级作用域
2.let
在同一作用域内不能声明两次
3.let
也有变量提升,但是存在暂时性死区
- 何谓“块级”作用域:
// 这不是对象字面量,而是一个独立的块,
// JavaScript 解释器会根据其中内容识别出它来
{
let d;
}
console.log(d); // ReferenceError: d 没有定义
let
的行为非常适合在循环中声明迭代变量。使用var
声明的迭代变量会泄漏到循环外部
for (var i = 0; i < 10; ++i) {...}
console.log(i); // 10
for (let j = 0; j < 10; ++j) {...}
console.log(j); // ReferenceError: j 没有定义
3. 使用 const 的常量声明
const
和let
只有一个区别:使用const
声明的变量必须同时初始化为某个值。一经声明,在其生命周期的任何时候都不能再重新赋予新值。
使用const
声明 Object 类型时,const 声明只应用到顶级原语或者对象。换句话说,赋值为对象的 const 变量不能再被重新赋值为其他引用值,但对象的键则不受限制。
如果想让整个对象都不能修改,可以使用 Object.freeze()
,这样再给属性赋值时虽然不会报错, 但会静默失败:
const o3 = Object.freeze({});
o3.name = 'Jake';
console.log(o3.name); // undefined