开局图
从图中我们可以看到涉及到的属性有三个:prototype,
__proto__,constructor,而他们就是原型与原型链的核心,现在就跟我一起了解他们的关系吧。
构造函数
现在先让我们定义一个构造函数Parent()
function Parent() {
this.name = '仙米丫',
this.age = 18
}
然后创建一个实例son()
const son = new Parent()
既然上述提到的三个核心是属性,那我们不妨把Parent()和son()的属性打印出来
Parent()属性:
son()的属性:
通过观察Parent()和son()的是属性,你会发现这样几个问题:
1.并没有__proto__属性
其实这里的 [[prototype]] 就是__proto__属性
2.他们都有__proto__属性,但是只有构造函数有prototype属性
没错从这里你就得到了一个结论
对象都有proto属性,但只有函数拥有prototype属性。
3.Parent()同时有proto和prototype属性,并且prototype属性下还有proto属性
这个问题相信你看了下面的内容就会有答案
原型prototype
上文多次提到的prototype属性还有一个名字——原型。
其实就是一个对象。
原型的作用就是在同一个构造函数,不同的实例之间共享信息。
我们在Parent()的原型上添加一个属性
Parent.prototype.sex = '女'
新建两个实例
const son1 = new Parent()
const son2 = new Parent()
son1.color = 'blue'
用实例访问
console.log(son1.sex) // 女
console.log(son2.sex) // 女
console.log(son1.color) // blue
console.log(son2.color) // undefined
可以看到在Parent()原型上的属性sex,son1和son2都可以访问到,但是属于自身的属性(son1的color)只有自己(son1)能访问到,其它实例(son2)不能访问。
所以原型存在的意义就是将公共的一些属性和方法放在同一个空间中,减少了内存消耗。
访问器属性__proto__
我们知道构造函数Parent()有一个孩子son(),但是口说无凭,我们怎么证明son()是Parent()的孩子呢?
答案就是__proto__。
__proto__只有一个作用,那就是指向创建自身的构造函数的原型。
得到如下结果:
console.log(son.__proto__ === Parent.prototype) // true
我们上文不是还遗留了一个问题吗,为什么Parent()同时有__proto__和prototype属性,并且prototype属性下还有__proto__属性?
先说前半句,Parent()同时有__proto__和prototype属性,Parent()有prototype属性就不用多说了吧,他是函数,而函数就有__prototype__属性。
而Parent()有__proto__属性是因为Parent()也有创建自己的构造函数,那就是Function。
console.log(Parent.__proto__ === Function.prototype) // true
至于后半句,prototype属性下还有__proto__属性,就好理解了,那是因为prototype是一个对象,而他也有创建自己的构造函数———Object
console.log(Parent.prototype.__proto__ === Object.prototype) // true
原型链
假设我们现在要访问一个属性,首先会查看自身有没有这个属性,没有就通过__proto__访问自己构造函数的prototype
console.log(son.__proto__) // 这里访问的是Parent的prototype
还是没有就接着访问
console.log(son.__proto__.__proto__) // 这里访问的是Object的prototype
直到到达终点
console.log(son.__proto__.__proto__.__proto__) // 这里访问的是null, null不是对象,没有__proto__属性,无法往上了
这个过程就是原型链,原型链的顶端是null
也就是图中这条红色的线
constructor
我们知道原型prototype上还有一个属性constructor。
他的作用很简单,就是从构造函数的原型指向构造函数自身,即确认构造函数与其原型之间的对应关系。
如下图所示:
注意点
相信到这里你已经能理解开局图里的大部分内容了,但是还有几点我必须要提一下。
1.Object的__proto__指向Function的原型
console.log(Object.__proto__ === Function.prototype) // true
虽然看起来像是先有的Object,再有的Function,但是事实确实如此。Object是构造函数,而构造函数产生于Function,所以有了这样的关系。
2.Function的__proto__指向Function的原型
console.log(Function.__proto__ === Function.prototype) // true
相信看到这里你会感到诧异,为什么有我生了我这样的关系,js就是这么神奇,个人觉得这个关系记住就好了,如果不这样的话就没有尽头了,因为总想找到一个更高一级的存在。
3.Function.prototype.__proto__指向Object的原型
console.log(Function.prototype.__proto__ === Object.prototype) // true
Function的原型是一个对象,那他的__proto__自然是指向Object.prototype
3.Parent.__proto__和Parent.prototype.__proto__的指向关系弄清楚,这一点我经常弄混
console.log(Parent.__proto__ === Function.prototype) // true
console.log(Parent.prototype.__proto__ === Object.prototype) // true
Parent.__proto__指向的是创建Parent()的构造函数的原型也就是Function.prototype。
Parent.prototype.__proto__指向的是创建Parent原型的构造函数的原型,因为原型是一个对象,所以指向的是Object.prototype
弄清这一点的意义在于清楚原型链是如何访问的。
题目
现在来做几个题验收一下成果吧
题目一
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 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();
题目三
function Person(name) {
this.name = name
}
let p = new Person('Tom');
//问题1:1. p.__proto__等于什么?
//问题2:Person.__proto__等于什么?
题目四
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);
答案
题目一
b.n -> 1
b.m -> undefined;
c.n -> 2;
c.m -> 3;
题目二
f.a() -> a
f.b() -> f.b is not a function
F.a() -> a
F.b() -> b
题目三
答案1:Person.prototype
答案2:Function.prototype
题目四
foo.a => value a
foo.b => undefined
F.a => value a
F.b => value b
最后
关于题目的分析我在文中也有对应的解答,这里就不一一解答了,如果还是有不明白的地方,欢迎留言。
小女子才疏学浅,学艺不精,如有问题,还望批评指正。