之前,我容易将作用域链和原型链两者混淆,其实这两个完全不同。
引擎怎么保存我声明的变量?
直接使用:
console.log(a); // ReferenceError: a is not defined
这是未声明的错误,表示在作用域中找不到标识符a
。
var a;
console.log(a); // undefined
那我们给他一个声明。这里undefined
是基本类型之一,表示“没有被赋值”。
我们调换下顺序,让a
在为声明前被计算。
console.log(a); // undefined
var a;
依然输出undefined
,而不是ReferenceError: a is not defined
,说明实际上a
已声明,表现为变量提升。
变量提升表示为:
console.log(a); // undefined
var a = 123;
console.log(a); // 123
// --等价于--
var a;
console.log(a);
a = 123;
console.log(a);
换个角度想,引擎在函数真正调用时就已经知道了a
的存在,说明作用域在函数执行前就确定了。
函数入栈前会进行准备工作,我们管它叫做执行上下文的创建。
准备工作包括AST(抽象语法树)的创建(代码转换AST:https://astexplorer.net):

这样引擎就能收集标识符,从而确定作用域,顺便还进行了语法检查。
奇怪的函数作用域
我们刚才讨论的作用域,是跟着函数走的,不妨把它叫做“函数作用域”。
但函数作用域会有很多让人困惑的地方,比如:
for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i);
}, 0);
}
console.log("window.i is " + i);
// window.i is 5
// 5
// 5
// 5
// 5
// 5
我们定义的那些匿名函数,在下一个EventLoop的定时器阶段被调用,这时,引擎需要计算标识符i
对应的值,沿着作用域,计算得到原始值5。
我们可以这么写来保存循环变量i
:
for (var i = 0; i < 5; i++) {
(function () {
var _i = i;
setTimeout(function () {
console.log(_i);
}, 0);
})();
}
console.log()
// window.i is 5
// 0
// 1
// 2
// 3
// 4
又或者使用ES6新加的let
来声明循环变量(https://es6.ruanyifeng.com/#docs/let)。
特殊的标识符this
与作用域编译时确立不同,this
是在运行时绑定,指向一个对象,可以认为是函数调用时传入的一个实参,比如:
a = "outside";
var obj = {
a: "inside",
fun: function () {
console.log(this.a);
},
};
obj.fun(); // inside
var fun = obj.fun;
fun(); // outside
关于绑定的规则,网上的文章很多,例如:https://juejin.cn/post/6844903805587619854#heading-1
this
引出的原型链
prototype
表示原型对象,只有函数对象具有这个属性;而__proto__
指向函数的原型对象,每个对象都有这个属性。例如:{}.__proto__ => Object.prototype
、new Function().__proto__ => Function.prototype
。
同时,不同对象间的原型对象是相互独立的,函数的prototype
起到了“公共模板”的作用,如:
修正:同一函数构造的对象的原型对象是相同的。
function Obj() {
this.a = "inside";
}
function DerivedObj() {
this.b = "derived inside";
}
DerivedObj.prototype = new Obj();
var obj1 = new DerivedObj();
var obj2 = new DerivedObj();
console.log(obj1.a); // inside
console.log(obj2.a); // inside
obj1.a = "edit inside";
console.log(obj1.a); // edit inside
console.log(obj2.a); //inside
上面这段代码,对obj1.a
进行了赋值操作,在=
左边的obj1.a
并不是__proto__.a
这是obj1
和obj2
:


可以看到obj1
原型链上有两个a
,一般情况下,如果只想保留一个a
,并且这个a
只属于实例对象而不是原型对象,可以使用寄生组合式继承。
原型链的尽头
函数也是对象,那么对于构造“函数”的函数,它的原型链又是怎样的?是这样的:Function.__proto__ => Function.prototype
。这就让人有点摸不着头脑,咋们再往下看看:
Function.prototype.__proto__ => Object.prototype
Object.prototype.__proto__ => null
,原型链到这里终止
由此可以得知,Object构造函数的prototype
是一个特殊的对象,不由任何函数构造。
才疏学浅,不吝赐教。