Open kangkai124 opened 5 years ago
function Person () {}
var person = new Person()
person.__proto__ === Person.prototype
Object.getPrototypeOf(person) === Person.prototype
Person === Person.prototype.constructor
Person.prototype.__proto__ === Object.prototype
Object.getPrototypeOf(Person.prototype) === Object.prototype
Object.prototype.__proto__ === null
JavaScript 采用词法作用域(也就是静态作用域),意味着作用域是由书写代码时函数声明的位置来决定的。
来看个栗子:
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f();
}
checkscope();
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
checkscope()();
上面两端代码执行结果都是 local scope
。
JavaScript 函数的执行用到了作用域链,这个作用域链是在函数定义的时候创建的。嵌套的函数 f() 定义在这个作用域链里,其中的变量 scope 一定是局部变量,不管何时何地执行函数 f(),这种绑定在执行 f() 时依然有效。
上面两段代码执行的结果一样,但是两段代码究竟有哪些不同呢?
答案就是执行上下文栈的变化不一样。
让我们模拟第一段代码:
ECStack.push(<checkscope> functionContext);
ECStack.push(<f> functionContext);
ECStack.pop();
ECStack.pop();
让我们模拟第二段代码:
ECStack.push(<checkscope> functionContext);
ECStack.pop();
ECStack.push(<f> functionContext);
ECStack.pop();
当 JavaScript 执行一段可执行代码时,会创建对应的执行上下文(执行环境),执行上下文可以抽象的理解为一个 object,由以下几个属性构成:
每一个执行上下文都会分配一个 变量对象(variable object) ,变量对象的属性由 变量(variable) 和 函数声明(function declaration) 构成,变量对象保存了当前作用域的所有函数和变量。
在函数上下文中,我们用活动对象(activation object, AO)来表示变量对象。
当函数被激活,那么一个活动对象就会被创建并且分配给执行上下文。活动对象由特殊对象 arguments 初始化而成。随后,他被当做变量对象用于变量初始化。
全局上下文的变量对象初始化是全局对象
函数上下文的变量对象初始化只包括 Arguments 对象
在进入执行上下文时会给变量对象添加形参、函数声明、变量声明等初始的属性值
在代码执行阶段,会再次修改变量对象的属性值
function a(name, age){
var gender = "male";
function b(){}
}
a(“k”,10);
a
被调用时,在 a
的执行上下文会创建一个活动对象 AO,并且被初始化为 AO = [arguments],随后AO 又被当做变量对象 VO 进行变量初始化,此时 VO = [arguments].concat([name,age,gender,b])。
当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。
以下面的例子为例,结合着之前讲的变量对象和执行上下文栈,我们来总结一下函数执行上下文中作用域链和变量对象的创建过程:
var scope = "global scope";
function checkscope(){
var scope2 = 'local scope';
return scope2;
}
checkscope();
执行过程如下:
1.checkscope 函数被创建,保存作用域链到 内部属性[[scope]]
checkscope.[[scope]] = [
globalContext.VO
];
2.执行 checkscope 函数,创建 checkscope 函数执行上下文,checkscope 函数执行上下文被压入执行上下文栈
ECStack = [
checkscopeContext,
globalContext
];
3.checkscope 函数并不立刻执行,开始做准备工作,第一步:复制函数[[scope]]属性创建作用域链
checkscopeContext = {
Scope: checkscope.[[scope]],
}
4.第二步:用 arguments 创建活动对象,随后初始化活动对象,加入形参、函数声明、变量声明
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: undefined
},
Scope: checkscope.[[scope]],
}
5.第三步:将活动对象压入 checkscope 作用域链顶端
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: undefined
},
Scope: [AO, [[Scope]]]
}
6.准备工作做完,开始执行函数,随着函数的执行,修改 AO 的属性值
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: 'local scope'
},
Scope: [AO, [[Scope]]]
}
7.查找到 scope2 的值,返回后函数执行完毕,函数上下文从执行上下文栈中弹出
ECStack = [
globalContext
];
原型链,继承,作用域,上下文,this,闭包,传参,call,apply,bind,new。。
主要参考:https://github.com/mqyqingfeng/Blog/issues/17 和 《你不知道的JavaScript》