四张图带你搞定原型和原型链
共 5045字,需浏览 11分钟
·
2021-08-31 22:20
在讲原型和原型链之前,先铺垫一些前置知识:
「所有的对象都是通过
new 函数
生成的。」 包括let obj = {}
,这种形式其实是语法糖
,本质上是通过let obj = new Object()
生成的。那么函数又是如何生成的呢?从图中可以清晰的看出函数
本质上是通过new Function
生成的,尽管我们平时不会这么去写,当然也不建议这么去写function Test(){}
//相当于
let Test = new Function();那么
Function函数
又是谁生成的呢?Function函数
也是函数,刚刚我们说函数是通过new Function
生成,但它是一种特殊的情况,不通过任何东西创建,它是JS引擎启动的时候直接添加到内存当中的。「所有的函数也都是对象,既然是对象,那么函数一定会有属性。」 比如:
Array.form
、Array.isArray
等等「对象是一种引用类型。」
🍇 原型 prototype
所有的函数都有一个属性:prototype
,称之为函数原型
。函数创建之初就会自动加上prototype
属性。
那什么是原型呢?
默认情况下,prototype
就是一个普通的object对象。
默认情况下,prototype中有一个属性:constructor
,它也是一个对象,它指向构造函数本身。
这张图很清晰说明了prototype
和constructor
之间的关系,每个函数(add、Object、Array、nothing)都有一个属性prototype
,它指向函数的原型,而函数的原型中也有一个属性constructor
,它也是一个对象,constructor
指向构造函数本身。
那原型有什么用呢?原型本身没什么用,但是配合隐式原型却大有作为
🍄 隐式原型 __proto__
「所有的对象都有一个属性:__proto__
,称之为隐式原型。」 前后两个下划线表示系统私有属性,不要轻易动它。
默认情况下,隐式原型指向创建该对象的函数的原型。这句话特别重要,它将隐式原型跟原型联系起来了,那什么意思呢?
举个栗子🌰:
function Test(){
}
let obj = new Test();
// obj.__proto__ === Test.prototype; 返回true
举个栗子🌰:
function Test(){
return {};// 这里{}是语法糖,本质上是通过new Object()创建的
}
let obj = new Test();//由于Test函数中返回 {},所以new Test()的结果是 let obj = new Object()
// obj.__proto__ === Object.prototype; 返回true
现在我们知道隐式原型指向谁,然后我们将prototype
、constructor
、__proto__
三者关系绘图如下:
在前置知识中,已经说过所有的对象都是通过new 函数
进行创建的,便有了上图关系。细心的小伙伴应该已经发现对象1的__proto__
、对象2的__proto__
以及函数add的prototype
三者指向同一块内存空间,这也就解释了为什么要把函数写在原型上,这是因为将函数写在原型上,只要是通过add构造函数创建的对象都可以访问这个函数。
function Add(name,age){
this.name = name;
this.age = age;
}
Add.prototype.say = function(){
console.log("法医",this.name,this.age)
}
let obj1 = new Add("前端猎手",18);
let obj2 = new Add("仵作",20);
obj1.say(); //法医 前端猎手 18
obj2.say(); //法医 仵作 20
访问对象成员的顺序是:首先会看当前对象中是否存在该属性或者方法,若存在,就直接使用了,否则继续顺着原型链依次查找。
「MDN文档中一段话:」
链接查看
之所以会继承Array.prototype
就是因为隐式原型
的存在,这也是提示我们,将来要把对象需要共享的东西写在原型上,特别是函数,这种行为有个比较有意思的名称,叫猴子补丁
,也许是因为🐒善于模仿,在原型中加入成员,以增强对象的功能,但也是有弊端的,会造成原型污染,所以还是谨慎使用。
🍓 原型链
这张图搞清楚后,自然明白何为原型链,我们一起过一遍
我们先看
白色线条
,白色线条表示原型,在原型部分我们已经说了,所有的函数都有一个属性prototype
,那么Object函数
的原型指向Object原型
,同理,我们自定义函数
的原型必然指向自定义函数原型
,这里有个比较特殊的点,就是Function函数
,没有任何东西创建它,它是由JS引擎启动的时候直接添加到内存里面的,故Function函数
直接指向Function原型
。再看
绿色线条
,绿色线条表示new
,读到这里,想必大家都知道所有的函数都是通过new Function()
创建的,所以Function函数
分别指向Object
和自定义函数
无可厚非,图中有一条线是自定义函数
指向自定义对象
,文章开头已经说了,所有的对象都是通过new 函数
进行创建的,代码表示:function Test(){
}
let obj = new Test();//这里的 obj 可以表示图中的自定义对象最后再看
蓝色线条
,蓝色线条表示隐式原型,我在隐式原型那部分也已经说了,所有的对象都有一条属性__proto__
,那函数是对象吧,那隐式原型指向谁呢?隐式原型指向创建该对象的函数的原型,要理解这句话,我们必须要知道函数是谁创建的?这个谁的原型是什么?所有的函数都是Function函数创建的
,Function函数
的原型指向Function原型
,这是个特殊点,故Object函数
和自定义函数
的隐式原型指向Function原型
,Function函数
也是函数,由于没有谁创建它,是被直接添加到内存的,它的原型是指向Function原型
,这同样是一个特殊点。function Test(){}
//相当于
let Test = new Function();
Test.__proto__ === Function.prototype; // true我们都知道所有的函数都有共同的成员,比如
call
、apply
、bind
等等,我们并没给自定义函数上加上这些成员,那么为什么可以使用呢?这是因为所有的函数的隐式原型指向Function的原型,这些方法都存在于Function的原型
上,所以每个函数都可以使用这些成员,这就是继承的效果。继续来看,自定义函数可以通过new创建自定义对象,自定义对象也是对象,那必然有隐式原型
__proto__
,指向创建该对象的函数原型,所以自定义对象的隐式原型指向自定义函数原型,那么自定义函数原型又指向谁呢?不知道大家是否还记得我在原型那部分说过一句话:默认情况下,prototype是一个普通的Object对象
,所以可以认为prototype
是通过new Object()
创建的,所以prototype是个对象,故自定义函数的prototype的隐式原型指向Object的原型,看代码:function test(){};//自定义函数
test.prototype.__proto__ === Object.prototype;// true📣 特殊点:Object的原型的隐式原型指向
null
,Object.prototype.__proto__ === null
,返回true现在知道什么是原型链了吧,
自定义对象的隐式原型
指向自定义函数的原型
,自定义函数的原型的隐式原型
又指向Object原型
,Object原型
又指向null
,这种链式的关系就是原型链
自测题一道:大家可以试着做一下,然后可以根据最后一张图进行检查
function Fayi() {}
Fayi.prototype.camel = function() {}
var u1 = new Fayi();
var u2 = new Fayi();
console.log(u1.camel === u2.camel);
console.log(Fayi.prototype.constructor);
console.log(Fayi.prototype === Function.prototype);
console.log(Fayi.__proto__ === Function.prototype);
console.log(Fayi.__proto__ === Function.__proto__);
console.log(u1.__proto__ === u2.__proto__);
console.log(u1.__proto__ === Fayi.__proto__);
console.log(Function.__proto__ === Object.__proto__);
console.log(Function.prototype.__proto__ === Object.prototype.__proto__);
console.log(Function.prototype.__proto__ === Object.prototype);
😊 好了, 以上就是我的分享,小伙伴们点个赞再走吧 👍 支持一下哦~ 😘,我会更有动力的 🤞