无处不在的原型链(上)
无处不在的原型链(上)
当我看到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