无处不在的原型链(上)
无处不在的原型链(上)
当我看到JavaScript前端中有原型链这种东西,我就绝知此事并非容易
—— 麦克阿瑟
认识原型
在JavaScript这门语言中,面向对象可以说是JS学习路上的其中一座大山,因为这座大山中的精髓就是原型对象和原型链。看懂了原型和原型链的关系,对于现在我们使用的大部分框架也会了解的更深一个层次。
先看结论
JavaScript常被描述为一种基于原型的语言,了解原型首先就要知道对象与原型之间的关系,以下是它们之间的关系总结(建议结合下方的完整关系图与该结论反复验证):
- 所有函数都是对象,对象中可以包含属性和方法
- 每个函数都有一个特殊的属性叫作原型或者叫作原型对象——
prototype(箭头函数除外,详细可参考:JavaScript高级程序设计第四版P:288 ) - 所有原型对象都有一个
constructor属性 - 所有对象上都有一个
__proto__属性 - 每个对象的
__proto__都是指向它的构造函数的原型对象prototype Function函数是所有函数的祖先函数
对象与原型
这里我们用ES5中常用的构造函数来举例
function Person(myName, myAge){
this.name = myName;
this.age = myAge;
}
Person.prototype.say = function (){
console.log(this.name);
};
这里定义了一个名为Person的构造函数,构造函数中有两个实例属性,name和age,在Person构造函数的原型对象上还添加了一个say方法。
那么现在的问题是:Person与Person.prototype之间存在什么关系呢?
对于Person而言,不管我们叫它构造函数也好还是函数也好,都有一个prototype属性(上方结论),这个属性指向它自身所对应的原型对象。
而这个原型对象上有我们自己挂载的一个say函数,并且还包含一个constructor属性 (上方结论),constructor属性指向当前原型对象对应的那个"构造函数"
对象三角恋关系
现在,我们再在之前的关系上做一个扩展:
function Person(myName, myAge){
this.name = myName;
this.age = myAge;
}
Person.prototype.say = function (){
console.log(this.name);
};
const person1 = new Person('chen', 23) // 新增
我们利用Person作为构造函数创建了一个名为person1的实例化对象,现在他们之间的关系为下图所示:

通过构造函数创建出来的对象我们称之为实例对象,每个对象中都有一个默认的属性叫做__proto__,__proto__指向创建它的那个构造函数的原型对象(上方结论),这样看起来就类似于一个三角恋关系了。
Tips:如果大家看到这里还有疑惑,建议可以敲代码去验证,以免觉得我在无中生有🤔 例:
person1.__proto__ === Person.prototype或person1.__proto__.constructor === Person
Function函数
前面分析了构造函数,对象与原型之间的关系,接下来讲一讲Function函数与它们之间构成的联系。
- JavaScript中的函数是引用类型(对象类型),既然是对象,所以也是通过构造函数创建出来的
所有函数都是通过Function构造函数创建出来的对象(例如普通声明的函数或是构造函数都是Function构造函数的实例对象)- JavaScript中只要是
函数就有prototype属性(上方结论),Function函数的prototype属性同样指向Function原型对象 - JavaScript中只要是
原型对象就有constructor属性(上方结论),Function原型对象的constructor属性同样指向它对应的构造函数 - 所有对象上都有一个
__proto__属性指向它的构造函数的原型对象prototype(上方结论) - 【注意】:
Function的__proto__属性较为特殊,因为它自身就是祖先函数(上方结论),所以Function的__proto__属性同它的prototype属性一样,都是指向它的原型对象
上图:

Object函数与Function函数的关系
Object函数跟大多数构造函数与Function函数之间的关系一样,不过特殊的是,Object构造函数中的__proto__属性是指向null

原型链完整关系图
以下就是完整的原型链关系图,大家可以尝试结合上方的结论去用代码的方式一一验证。
这里除了Object函数与Function函数较为特殊,其他像String构造函数,Number构造函数等等,他们与Funtion函数之间的关系就和Function与Person构造函数之间的关系是一样的。因为Function函数都是他们的祖先函数。
这里也可以自行做验证,例:
String.__proto__ === Function.prototype或Number instanceof Function

原型链查找
如果上面的关系图看懂以后,再来看原型链查找过程便是顺理成章的事。
一句话总结原型链查找过程:原型链的查找就是沿着对象上的__proto__属性一层一层往上查找,直到找到Object原型对象上。
通过从上方的完整关系图中我们可以看到,从obj1实例对象上开始就有一个__proto__属性,再往上走指向的是Person原型对象,顺着Person原型对象上的__proto__属性再往上走就来到了Object原型对象,再往上指向的就是null了。
案例
Object.prototype.say = function() {
console.log('我是Object原型对象上的方法');
}
function Person(myName, myAge){
this.name = myName;
this.age = myAge;
this.say = function(){
console.log('我是实例方法');
};
}
Person.prototype.say = function() {
console.log('我是Person构造函数原型对象的方法');
};
const obj1 = new Person('chen', 18);
obj1.say(); // 我是实例方法
在实例对象obj1调用say方法时首先它会在自身的实例方法中查找,如果没有就顺着__proto__属性往上去到Person构造函数的原型对象上去查找原型中的方法,如果没有就继续顺着__proto__属性往上去到Object构造函数的原型对象上去查找原型中的方法,如果还是没有最终就会报错。
查找关系如下图:

总结
原型对象和原型链就是JavaScript在面向对象的设计中实现继承的方式。在下一章中将会讲到常用的继承方式的关系图,以及我们平时使用的Vue框架是怎样使用原型链的。 JavaScript的几座大山中还有闭包,同步异步等,都是我们日常开发中可能感觉不深,但不知不觉中就已经用到的内容。
以上部分图文内容参考自:李南江(极客江南)
转载自:https://juejin.cn/post/7138602946383577101