写点什么

四张图带你搞定原型和原型链

用户头像
法医
关注
发布于: 23 小时前
四张图带你搞定原型和原型链

在讲原型和原型链之前,先铺垫一些前置知识:



  1. 所有的对象都是通过new 函数生成的。 包括let obj = {},这种形式其实是语法糖,本质上是通过let obj = new Object()生成的。那么函数又是如何生成的呢?从图中可以清晰的看出函数本质上是通过new Function生成的,尽管我们平时不会这么去写,当然也不建议这么去写


    function Test(){}    //相当于    let Test = new Function();
复制代码


那么`Function函数`又是谁生成的呢?`Function函数`也是函数,刚刚我们说函数是通过`new Function`生成,但它是一种特殊的情况,不通过任何东西创建,它是JS引擎启动的时候直接添加到内存当中的。
复制代码


  1. 所有的函数也都是对象,既然是对象,那么函数一定会有属性。 比如:Array.formArray.isArray等等

  2. 对象是一种引用类型。

🍇 原型 prototype

所有的函数都有一个属性:prototype,称之为函数原型。函数创建之初就会自动加上prototype属性。


那什么是原型呢?


默认情况下,prototype就是一个普通的 object 对象。



默认情况下,prototype 中有一个属性:constructor,它也是一个对象,它指向构造函数本身。



这张图很清晰说明了prototypeconstructor之间的关系,每个函数(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
复制代码


现在我们知道隐式原型指向谁,然后我们将prototypeconstructor__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就是因为隐式原型的存在,这也是提示我们,将来要把对象需要共享的东西写在原型上,特别是函数,这种行为有个比较有意思的名称,叫猴子补丁,也许是因为🐒善于模仿,在原型中加入成员,以增强对象的功能,但也是有弊端的,会造成原型污染,所以还是谨慎使用。

🍓 原型链

这张图搞清楚后,自然明白何为原型链,我们一起过一遍



  1. 我们先看白色线条,白色线条表示原型,在原型部分我们已经说了,所有的函数都有一个属性prototype,那么Object函数的原型指向Object原型,同理,我们自定义函数的原型必然指向自定义函数原型,这里有个比较特殊的点,就是Function函数,没有任何东西创建它,它是由 JS 引擎启动的时候直接添加到内存里面的,故Function函数直接指向Function原型

  2. 再看绿色线条,绿色线条表示new,读到这里,想必大家都知道所有的函数都是通过new Function()创建的,所以Function函数分别指向Object自定义函数无可厚非,图中有一条线是自定义函数指向自定义对象,文章开头已经说了,所有的对象都是通过new 函数进行创建的,代码表示:


        function Test(){                }        let obj = new Test();//这里的 obj 可以表示图中的自定义对象
复制代码


  1. 最后再看蓝色线条,蓝色线条表示隐式原型,我在隐式原型那部分也已经说了,所有的对象都有一条属性__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    
复制代码


![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4ab876b825bd4cc59af5f75e04d64a7f~tplv-k3u1fbpfcp-watermark.image)
> 📣 特殊点: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);
复制代码


😊 好了, 以上就是我的分享,小伙伴们点个赞再走吧 👍 支持一下哦~ 😘,我会更有动力的 🤞

发布于: 23 小时前阅读数: 7
用户头像

法医

关注

公众号@前端猎手 2020.07.17 加入

我是法医,一只治疗系前端码猿🐒,与代码对话,倾听它们心底的呼声,期待着大家的点赞👍与关注➕。 [微信:wKavin]

评论

发布
暂无评论
四张图带你搞定原型和原型链