原型链
function的原型是prototype
实例对象的原型是__proto__
person.constructor === Person.prototype.constructor
//因为对象本身没有,所以会去原型链上去寻找
//每一个对象都会从原型上继承(委托)属性
访问对象属性时,就近查找, 找不到去链上寻找.
作用域
定义变量的区域, 支持定义变量的存址与操作
var value = 1
function foo() {
console.log(value)
}
function bar() {
let value = 2
foo()
}
静态作用域(词法作用域)
函数的作用域在函数定义的时候决定,
在调用foo时,现在内部AO中寻找, 找不到去沿着作用域链寻找
foo:AO
{
arguments: {
0: 0,
length: 0
}
}
foo.[[scope]] = [
globalContext.VO
]
这些是在定义时确定好的, 因此找value不回去bar中,而是去全局对象中
因此上述代码会输出1
动态作用域
在函数执行调用时决定
上述代码会输出2
执行上下文
可执行代码: executable code: 全局代码, 函数代码, eval代码
js中会创建一个上下文的栈 , execution context stack ECS
作用域与执行上下文没有关系
执行上下文的属性
- 变量对象
- 作用域链
- this
变量对象 VO variable object
全局环境中定义的对象为全局对象
函数:函数中的对象叫 AO(activation object), 需要进入与离开上下文, 因此为活动对象
argumnets: 类数组, 创建一个函数时, 这是默认加入的值
AO
- 函数的形参, 没有的话默认undefined
- 声明的变量
- 当前代码段中默认的属性
function foo(a) {
var b = 2;
function c() {}
var d = function() {};
b = 3;
}
foo(1)
//初始
AO = {
//固有的
arguments: {
0: 1,
length: 1
},
a: 1,
b: undefined,
//注意此处, 此处是导致同名function会覆盖的原因, 因为进入之后,会依据代码更新AO, 因此后面的函数会覆盖掉前面的同名函数
c: reference to function c(){},
d: undefined
}
作用域链
有一个ECStack , 栈
在当前AO找不到属性, 则会想VO中去找, 知道找到全局的VO
在执行代码过程中,与原型链不同
function foo() {
function bar() {
...
}
}
//注意递归可能会出现爆栈的问题, 因为栈的容量有限
foo.[[scope]] = [
globalContext.VO
];
bar.[[scope]] = [
fooContext.AO,
globalContext.VO
];
闭包
MDN:闭包是能够访问自由变量的函数
自由变量: 自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量。
因此闭包 = 函数 + 函数能够访问的自由变量
《JavaScript权威指南》中就讲到:从技术的角度讲,所有的JavaScript函数都是闭包。
ECMAScript中,闭包指的是:
- 从理论角度:所有的函数。因为它们都在创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量,这个时候使用最外层的作用域;
- 从实践角度:以下函数才算是闭包:
- 即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回);
- 在代码中引用了自由变量;
this指向
ECMAScript 的类型分为语言类型和规范类型。
ECMAScript 语言类型是开发者直接使用 ECMAScript 可以操作的。其实就是我们常说的Undefined, Null, Boolean, String, Number, 和 Object。
而规范类型相当于 meta-values,是用来用算法描述 ECMAScript 语言结构和 ECMAScript 语言类型的。规范类型包括:Reference, List, Completion, Property Descriptor, Property Identifier, Lexical Environment, 和 Environment Record。
Reference
Reference 是一个 Specification Type,也就是 “只存在于规范里的抽象类型”。它们是为了更好地描述语言的底层行为逻辑才存在的,但并不存在于实际的 js 代码中。
Reference 的构成,由三个组成部分,分别是:
- base value;
- referenced name;
- strict reference;
我们简单的理解的话:
base value 就是属性所在的对象或者就是 EnvironmentRecord,它的值只可能是 undefined, an Object, a Boolean, a String, a Number, or an environment record 其中的一种。
referenced name 就是属性的名称
strict reference就是是否为严格模式
var foo = 1;
// 对应的Reference是:
var fooReference = {
base: EnvironmentRecord,
name: 'foo',
strict: false
};
var foo = {
bar: function () {
return this;
}
};
foo.bar(); // foo
// bar对应的Reference是:
var BarReference = {
base: foo,
propertyName: 'bar',
strict: false
};
GetBase()
返回 reference 的 base value。
IsPropertyReference
如果 base value 是一个对象,就返回true。
如何判断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;
1:找MemberExpression
function foo() {
console.log(this)
}
foo(); // MemberExpression 是 foo
function foo() {
return function() {
console.log(this)
}
}
foo()(); // MemberExpression 是 foo()
var foo = {
bar: function () {
return this;
}
}
foo.bar(); // MemberExpression 是 foo.bar
简单讲, memberExpression是()左边的东西
2:判断reference
var value = 1;
var foo = {
value: 2,
bar: function () {
return this.value;
}
}
//示例1
//此时MemberExpression为foo.bar, foo.bar的 var Reference = { base: foo, name: 'bar', strict: false };
//根据上面的流程, 此时的this指向base即foo
console.log(foo.bar()); // 2
//示例2
//与示例一相同
console.log((foo.bar)()); // 2
//示例3
//有赋值操作符,使用了GetValue, 所以返回的不是Reference
//()左侧是一个运算 ,因此this指向undefined, 非严格模式undefined转为全局, 因此为1
console.log((foo.bar = foo.bar)()); // 1
//示例4
//与3类似
console.log((false || foo.bar)()); // 1
//示例5
//使用了GetValue
console.log((foo.bar, foo.bar)()); // 1
简单来说:始终指向调用它的对象
重点搞懂!!!静态作用域, 执行上下文,闭包, chain, this指向