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

人生不止有技术

共 5045字,需浏览 11分钟

 ·

2021-08-31 22:20


前端猎手
 链接每一位开发者,让编程更有趣儿!
关注


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

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

    function Test(){}
    //相当于
    let Test = new Function();

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

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

  3. 「对象是一种引用类型。」

🍇 原型 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 可以表示图中的自定义对象
  3. 最后再看蓝色线条,蓝色线条表示隐式原型,我在隐式原型那部分也已经说了,所有的对象都有一条属性__proto__,那函数是对象吧,那隐式原型指向谁呢?隐式原型指向创建该对象的函数的原型,要理解这句话,我们必须要知道函数是谁创建的?这个谁的原型是什么?所有的函数都是Function函数创建的Function函数的原型指向Function原型,这是个特殊点,故Object函数自定义函数的隐式原型指向Function原型Function函数也是函数,由于没有谁创建它,是被直接添加到内存的,它的原型是指向Function原型,这同样是一个特殊点。

       function Test(){}
       //相当于
       let Test = new Function();
       
       Test.__proto__ === Function.prototype; // true

    我们都知道所有的函数都有共同的成员,比如callapplybind等等,我们并没给自定义函数上加上这些成员,那么为什么可以使用呢?这是因为所有的函数的隐式原型指向Function的原型,这些方法都存在于Function的原型上,所以每个函数都可以使用这些成员,这就是继承的效果。

    继续来看,自定义函数可以通过new创建自定义对象,自定义对象也是对象,那必然有隐式原型__proto__,指向创建该对象的函数原型,所以自定义对象的隐式原型指向自定义函数原型,那么自定义函数原型又指向谁呢?不知道大家是否还记得我在原型那部分说过一句话:默认情况下,prototype是一个普通的Object对象,所以可以认为prototype是通过new Object()创建的,所以prototype是个对象,故自定义函数的prototype的隐式原型指向Object的原型,看代码:

        function test(){};//自定义函数
        
        test.prototype.__proto__ === Object.prototype;// true    

    📣 特殊点:Object的原型的隐式原型指向nullObject.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); 

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


浏览 5
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报