今天看了心哥的博文这可能是掘金讲「原型链」,讲的最好最通俗易懂的了,附练习题!,让我对原型链又有了新的认识,那我现在就把我新的认识,通过解析心哥博文后面的五道练习题的方式,分享给大家吧~
大家在看题解之前可以先去看看心哥的博文~ 不过我也会简单带大家回顾一下的~
什么是原型链呢?其实俗话说就是:
__proto__的路径
就叫原型链
自由变量的查找,就是顺着 原型链 往上查找的!
我先把 Function
和 Object
的原型图画出来(这里省略了constructor
),因为这里我们更多的关注原型链__proto__
,也就是图中画的绿色的线!
-
橙黄色是显式原型
prototype
,在JavaScript中,所有的函数都有显式原型,显式原型是一个对象 -
草绿色是隐式原型
__proto__
,在JavaScript中,所有的对象都有隐式原型,指向其构造函数的显式原型
这里Object
是一个构造函数,所以他有显式原型,Object
的显式原型的隐式原型,指向原型链的尽头——null
Function
是一个构造函数,他有显式原型,显式原型是一个对象,所以他有隐式原型,指向他的构造函数的显式原型,也就是Function.prototype.__proto__ === Object.prototype
练习题1
题目
var F = function() {};
Object.prototype.a = function() {
console.log('a');
};
Function.prototype.b = function() {
console.log('b');
}
var f = new F();
f.a();
f.b();
F.a();
F.b();
图解
我们一行一行的分析
var F = function() {};
这里通过函数表达式的方式创建一个函数F
,这里的函数F
相当于是Function
的实例对象,所以F.__proto__
指向Function
的显式原型对象上
F
是一个函数,他就有显式原型对象,这个显式原型对象是一个对象,所以他的隐式原型指向Object
的显式原型(看下图就清晰明了了!)
Object.prototype.a = function() {
console.log('a');
};
这里是给Object
的显式原型上创建一个a
函数,打印输出a
Function.prototype.b = function() {
console.log('b');
}
这里是给Function
的显式原型上创建一个b
函数,打印输出b
var f = new F();
这里是创建一个F
的实例对象f
,这里的f
是一个对象,所以他有隐式原型,指向其构造函数的显式原型
最后就是考察f
和F
的原型链啦
跟着草绿色的原型链走,就可以了~ 途中用定位标记标记了
可以看到F
的原型链上有Function.prototype
、Object.prototype
、null
而f
的原型链上有F.prototype
、Object.prototype
、null
根据图示,随后的答案就很明了了!
答案
f.a(); // a
f.b(); // TypeError: f.b is not a function
F.a(); // a
F.b(); // b
练习题2
题目
var A = function() {};
A.prototype.n = 1;
var b = new A();
A.prototype = {
n: 2,
m: 3
}
var c = new A();
console.log(b.n);
console.log(b.m);
console.log(c.n);
console.log(c.m);
图解
var A = function() {};
这里创建一个A函数,原型图是下面这样的,我们这次就省略原型链的尽头null
了!
A.prototype.n = 1;
在A的显式原型上添加一个属性n
,值为1
var b = new A();
创建一个A
的实例对象b
A.prototype = {
n: 2,
m: 3
}
这里给A
的显式原型对象重新赋值,指向了一个新的对象
但是原来的对象还保持着b
对他的引用,所以垃圾回收机制不会清除原来这个对象
var c = new A();
这时,又创建了一个A
的实例对象c
,这里c
对象会指向A
的新的显式原型对象
最后就是考察b
和c
的原型链啦,如图所示
答案
console.log(b.n); // 1
console.log(b.m); // undefined
console.log(c.n); // 2
console.log(c.m); // 3
练习题3
题目
var foo = {},
F = function(){};
Object.prototype.a = 'value a';
Function.prototype.b = 'value b';
console.log(foo.a);
console.log(foo.b);
console.log(F.a);
console.log(F.b);
图解
var foo = {},
F = function(){};
这里首先创建一个对象foo
然后创建一个函数F
Object.prototype.a = 'value a';
Function.prototype.b = 'value b';
在Objetct
的显式原型对象上添加一个a
属性
在Function
的显式原型对象上添加一个b
属性
最后就是考察F
和foo
的原型链
答案
console.log(foo.a); // value a
console.log(foo.b); // undefined
console.log(F.a); // value a
console.log(F.b); // value b
练习题4
题目
function A() {}
function B(a) {
this.a = a;
}
function C(a) {
if (a) {
this.a = a;
}
}
A.prototype.a = 1;
B.prototype.a = 1;
C.prototype.a = 1;
console.log(new A().a);
console.log(new B().a);
console.log(new C(2).a);
图解
function A() {}
function B(a) {
this.a = a;
}
function C(a) {
if (a) {
this.a = a;
}
}
创建三个函数
A.prototype.a = 1;
B.prototype.a = 1;
C.prototype.a = 1;
函数的显式原型上添加属性a
new A()
new B()
new C(2)
分别创建三个实例对象,执行构造函数里的代码,this
指向实例对象,相当于给实例对象自身添加属性
答案
console.log(new A().a); // 1
console.log(new B().a); // undefined
console.log(new C(2).a); // 2
练习题5
题目
console.log(123['toString'].length + 123)
图解
这里123
后面调用toString
方法会使JavaScript
产生一个原始包装对象,将123
包装成Number
对象类型的,其原型上有toString
方法,其length
是1
在此感谢 @peng_tianyu 的指正~
这里123
后面调用toString
方法会使JavaScript
产生一个原始包装对象,将123
包装成Number
对象类型的,然后再顺着原型链找toString
方法,是在Object
对象身上,其length
是1
这里错了,实际上
Object.prototype.toString.length === 0
而 Number.prototype.toString.length === 1
length 是函数对象的一个属性值,指该函数期望传入的参数数量,即形参的个数。 形参的数量不包括剩余参数个数,仅包括第一个具有默认值之前的参数个数。 与之对比的是, arguments.length 是函数被调用时实际传参的个数。
答案
console.log(123['toString'].length + 123) // 124
最后
你还有题嘛?
砸过来,我来图解!!!算了,熟了之后是不需要画图的!
你知道记得 函数
和 对象
分别有隐式原型和显式原型就行了,原型链是顺着隐式原型找的,而隐式原型是指向其构造函数的显式原型的!!!