闲谈js中执行期上下文,作用域,作用域链,闭包(并不详细)
一.预编译期间产生执行期上下文
-
代码执行之前的准备工作(预编译),确定当前环境下标识符(变量、常量、数组、函数、对象等)的取值,this的指向
-
当函数每次执行时,都会创建一个新执行期上下文,当函数执行完毕,它所产的执行上下文被销毁
-
执行期上下文分为: 全局执行上下文(全局预编译产生)和函数执行上下文(函数预编译产生)
- 函数预编译有四步骤,而全局预编译有只有三步骤
1.函数预编译四部曲
1. 创建AO(activation object)对象 (函数执行期上下文)
2. 找形参和变量声明,将变量声明和形参作为AO对象的属性名,值为undefined
3. 将实参和形参统一
4. 在函数体里面找函数声明,值赋予函数体 (函数声明定义的函数名与形参和变量声明一样的话,
覆盖掉他们的值)
注意:定义的形参和变量声明以及函数名如果相同,也只能作为AO对象的一个属性名(对象属性的唯一性)
2.全局预编译三部曲
1. 创建GO(Gloabl object)对象(全局执行期上下文)
2. 找变量声明,将变量声明作为GO对象的属性名,值为undefined
3. 在函数体里面找函数声明,值赋予函数体
注意:定义的变量声明以及函数名如果相同,也只能作为GO对象的一个属性名(对象属性的唯一性)
- 举个例子,来详解全局预编译三部曲
var a = 10
function a() {}
console.log(a)
// 第一步 创建GO对象
GO {
}
// 第二步 找变量声明,将变量声明作为GO对象的属性名,值为undefined
GO {
a: undefined
//...还有其他内容,原型链会讲
}
// 第三步 在函数体里面找函数声明,值赋予函数体
GO {
a: function a() {}
//...还有其他内容,原型链会讲
}
// 到此预编译完成,开始从上到下依次执行代码
var a = 10 => a: function a() {} => a: 10
function a() {} => 经过上一步,a已经指向10,所以这个函数变成无效代码,无法调用
console.log(a) => 10
二.作用域[[scope]] (暂时不涉及es6)
- 作用域[[scope]]: [[scope]]指的就是我们所说的作用域,其中存储了运行期上下文的集合。即作用域决定了代码区块中变量和其他资源的可见性。
- 作用域分全局作用域和局部(函数)作用域
// 全局作用域
function a() {
var b = 10
}
var glod = 100
console.log(b) // 报错 b is not defined(b没有定义) 原因是[[scope]]中并没有属性b
三.作用域链
- 作用域链: [[scope]]中所储存的执行期上下文对象的集合,这个集合呈链式链接,我们把这种链式链接叫作用域链
- 查找变量: 作用域链的顶部依次向下查找
function a() {
function b() {
var b = 234;
}
var a = 123;
b();
}
var glod = 100
a()
四.闭包
-
闭包的概念:当内部函数被保存到外部,将生成闭包. 本质上,闭包是将函数内部和函数外部连接起来的桥梁。
-
注意:每个函数执行完毕就销毁作用域链,闭包会导致原有作用域链不释放造成内存泄露.(内存泄露指的是内容被占用,内容变小)
function a() {
var b = 100
function c() {
console.log(b)
}
return c
}
console.log(a()()) // 打印 100
这时我们在a函数外部依然读取到a函数中定义的变量b,这个就是闭包
这个函数中闭包导致的内存泄露: 原本a函数指向完就销毁他的执行期上下文,
但是现在a的执行期上下文中的b属性仍然被c函数使用,所以无法销毁,导致多
占用了内存,这个就是内存泄露.
3.闭包的应用场景:
- 变量私有化
- 柯里化
- 函数防抖
- 函数节流
- …