开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情
岁月如梭天天天,
寒来暑往年年年,
指点江山侃侃侃,
掘金文章发发发!
1.词法作用域和动态作用域
1.1 什么是作用域
- 作⽤域是指程序源代码中定义变量的区域。
- 作⽤域规定了如何查找变量,也就是确定当前执⾏代码对变量的访问权限。
- JavaScript 采⽤词法作⽤域 (lexical scoping ),也就是静态作⽤域。
1.2 静态作⽤域和动态作⽤域
- Javascript采用的是词法作用域(静态作用域),所以函数所在的作用域在定义时就决定了。
- 而动态作用域是在函数调用时决定的。bash语言就是动态作用域。
2. 变量对象
2.1 什么是变量对象
- 变量对象是执行上下文相关的数据作用域,存储了在上下文中定义的变量和函数声明。
- 全局上下文和函数上下文的变量对象是不同的。
2.2 全局上下文
变量对象 - Variable object(VO)
- 全局对象是预定义的对象,作为 JavaScript 的全局函数和全局属性的占位符。通过使⽤全局对象, 可以访问所有其他所有预定义的对象、函数和属性。
- 在顶层 JavaScript 代码中,可以⽤关键字 this 引⽤全局对象。因为全局对象是作⽤域链的头,这意 味着所有⾮限定性的变量和函数名都会作为该对象的属性来查询。
全局对象Window就是全局上下文的变量对象。用 var 声明的全局变量会成为全局对象上的属性。
2.3 函数上下文
活动对象 - Activation object (AO)
活动对象和变量对象其实是⼀个东⻄,只是变量对象是规范上的或者说是引擎实现上的,不可在 JavaScript 环境中访问,只有到当进⼊⼀个执⾏上下⽂中,这个执⾏上下⽂的变量对象才会被激活,所 以才叫 activation object,⽽只有被激活的变量对象,也就是活动对象上的各种属性才能被访问。 活动对象是在进⼊函数上下⽂时刻被创建的,它通过函数的 arguments 属性初始化。arguments 属性值 是 Arguments 对象。
2.4 执行过程
执行上下文的代码分成两个阶段进行处理:分析和执行,可以理解为:
- 进入执行上下文
- 代码执行
2.4.1 进入执行上下文
变量对象包括:
- 函数的所有形式参数(如果是函数上下文)
- 由名称和对应值组成的一个变量对象的属性被创建;
- 没有实参,属性值设为 undefined;
- 函数声明
- 由名称和对应值(函数对象(function-object)组成一个变量对象的属性被创建;
- 如果变量对象已经存在相同名称的属性,则完全替换这个属性;
- 变量声明
- 由名称和对应值(undefined)组成一个变量对象的属性被创建;
- 如果变量名跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性;
例子:
function foo(a) {
var b = 2;
function c() {}
var d = function() {};
b = 3;
}
foo(1);
进入执行上下文后,AO是:
AO = {
arguments: {
0: 1,
length: 1
},
a: 1,
b: undefined,
c: reference to function c(){},
d: undefined
}
2.4.2 代码执行
在执行阶段会按顺序执行,根据代码,修改变量对象的值。
上述例子执行之后的 AO :
AO = {
arguments: {
0: 1,
length: 1
},
a: 1,
b: 3,
c: reference to function c(){},
d: reference to FunctionExpression "d"
}
变量创建过程总结:
- 全局上下⽂的变量对象初始化是全局对象;
- 函数上下⽂的变量对象初始化只包括 Arguments 对象;
- 在进⼊执⾏上下⽂时会给变量对象添加形参、函数声明、变量声明等初始的属性值;
- 在代码执⾏阶段,会再次修改变量对象的属性值;
3. This
3.1 Reference
reference 类型只存在于规范里的抽象类型,是为了更好地描述语言的底层行为逻辑的,并不存在与实际的js代码中。
Reference 的构成,由三个部分组成:
- base value;
- referenced name;
- strict reference;
例子:
var foo = 1;
// 对应的Reference
var fooReference = {
base:EnvirnomentRecord;
name:'foo',
strict:false
}
var foo = {
bar:function(){
return this
}
}
foo.bar()
// bar 对应的Reference
var BarReference = {
base:foo,
propertyName:'bar',
strict:false
}
规范内部提供了获取Reference组成部分的方法,GetValue和IsPropertyReference
- GetBase
- 返回 base 的值。
- IsPropertyReference
- 如果 base 的值是个对象,就返回 true。
3.2 确定this指向的判断逻辑
- 计算 MemberExpression 的结果赋值给 ref;
- 判断 ref 是不是一个 Reference 类型;
- 如果 ref 是 Reference,并且 IsPropertyReference(ref) 是true,那么 this 的值为 GetBase(ref);
- 如果 ref 是 Reference,并且base value 值是Environment Record,那么 this 的值为 ImplicitThisValue(ref);
- 如果 ref 不是 Reference,那么 this 的值为 undefined;
- 如果 ref 是 Reference,并且 IsPropertyReference(ref) 是true,那么 this 的值为 GetBase(ref);
*注意:如果MemberExpression的结果中调用过了 GetValue 方法,那么返回的值就不是 Reference 类型。
var value = 1
var foo = {
value:2,
bar:function(){
return this.value
}
}
console.log(foo.bar()) // 2
console.log((foo.bar)()) // 2
// 这里因为由赋值运算符调用了GetValue(),所以this执行undefined,非严格模式下隐式转为window,所以值为1
console.log((foo.bar = foo.bar)()) // 1
console.log((false || foo.bar)()) // 1
console.log((foo.bar,foo.bar)()) // 1
4. 闭包
MDN 对闭包的定义为:
闭包是指那些能够访问自由变量的函数。
自由变量:
自由变量是指在函数中使用的,但既不是函数参数也不是函数局部变量的变量。
由此,闭包共有两部分组成:
闭包 = 函数 + 函数能够访问的自由变量。
闭包的两种说法
- 从理论⻆度:所有的函数。因为它们都在创建的时候就将上层上下⽂的数据保存起来了。哪怕是简 单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问⾃由变量,这个时候使⽤最外 层的作⽤域;
- 从实践⻆度:以下函数才算是闭包: a. 即使创建它的上下⽂已经销毁,它仍然存在(⽐如,内部函数从⽗函数中返回); b. 在代码中引⽤了⾃由变量;