• 【JS】479- 又见原型和原型链


    作者:觉非

    https://juejin.im/post/5dcc3840e51d4510912421fd

    前言

    在前端这块领域,原型与原型链是每一个前端er必须掌握的概念。我们多次在面试或者一些技术博客里面看见这个概念。由此可见,这个玩意对于前端来说有多重要。其实它本身理解起来不难,但是很多刚入行前端的同学,看到prototype__proto__理解起来还是有点吃力,然后脑子里面就乱成一锅粥,就像我一样。但是这是很正常的事情,没什么大不了的,就像我们想要学会跑步,那么我们就必须先学会走路。任何事情都是有个过程的。所以现在就跟我一起来攻克这个难点吧。通过这篇文章你将掌握以下知识点:

    • 理解 __proto_;

    • 理解 prototype;

    • 理解javascript对象的概念;

    • 理解原型和原型链;

    • 理解javascript的概念;

    • 理解new的实现;

    • 理解instanceof的实现;

    • 理解javascript的继承;

    • 加深对javascript这门语言的理解。

    这也是本篇文章的写作思路。


    对象

    那么我们就从对象这一概念开始说起,其实对象这一概念相信大家并不陌生。有一种说法是“javasrcript中万物皆是对象”,其实这个说法是错误的,一个很简单的例子,javasript中简单基本类型(string、boolean、number、null、undefined、symbol)本身就不是对象。其实javasript中对象主要分为函数对象普通对象。其中:

    • String

    • Number

    • Boolean

    • Object

    • Function

    • Array

    • Date

    • RegExp

    • Error

    这些都是函数对象,他们同时也被称为内置对象函数对象本身其实就是一个纯函数,javascript用他们来模拟。普通对象就很简单了,就是我们常见的对象:

    const obj = {
        name: 'juefei',
        desc: 'cool'
    }
    

    可能说到这,你还是无法理解到底啥是函数对象,啥是普通对象,那我们就一起来看看下面的代码:

    const obj1 = {};
    const obj2 = new Object();
    function func1() {
    
    
    }
    const obj3 = new func1();
    const func2 = new function() {
    
    
    }
    const func3 = new Function()
    

    接着我们来分别打印一下他们:

    console.log(obj1);  // object
    console.log(obj2);  // object
    console.log(obj3);  // object
    console.log(func1);  // function
    console.log(func2);  // function
    console.log(func3);  // function
    

    所以可以看见,obj1obj2、,obj3是普通对象,他们都是Object的实例,而func1func2func3则都是Function的实例,称为函数对象。我们再看看:

    console.log(typeof Object);  //f unction
    console.log(typeof Function); // function
    

    你是不是惊呆了,原来ObjectFunction都是 Function的实例。所以我们得出一个结论就是:

    • 只要是Function的实例,那就是函数对象,其余则为普通对象

    同样我们也可以看出,不仅 Object函数对象,就连  Function 本身也是函数对象,因为我们通过 console.log(typeof Function); 得知 FunctionFunction 的实例。是不是又开始有点绕了?没事,到这一步你就记住我们刚刚的结论就算完成目标:

    • 只要是Function的实例,那就是函数对象,其余则为普通对象

    那么说到对象,我们从上面可以看出,一个对象是通过构造函数 new 出来的,这其实跟原型原型链有很大的关系,那么原型原型链到底是用来干嘛的呢?


    原型

    涉及到这两个概念,我们就必须先来介绍两个东西: __proto__prototype ,这两个变量可以说,在 javascript 这门语言里面随处可见,我们不管他三七二十一,我们先来看一张表:

    对象类型__proto__prototype
    普通对象
    函数对象

    所以,请你先记住以下结论:

    • 只有函数对象prototype 属性,普通对象 没有这个属性。

    • 函数对象普通对象 都有 __proto__这个属性。

    • prototype 和  __proto__都是在创建一个函数或者对象会自动生成的属性。

    接着我们来验证一下:

    function func (){  //func称为构造函数
    
    
    }
    console.log( typeof func.prototype); // object
    console.log(typeof func.__proto__);  // function
    
    复制代码
    
    const obj = {}
    console.log(typeof obj.__proto__) //object
    console.log(typeof obj.prototype) //undefined (看见了吧,普通对象真的没有 prototype 属性)
    
    所以就验证了我们刚刚的结论:
    
    • 只有函数对象prototype 属性,普通对象 没有这个属性

    • 函数对象普通对象 都有 __proto__这个属性。

    • prototype 和  __proto__都是在创建一个函数或者对象会自动生成的属性。

    你看我又重复写了一遍,我不是为了凑字数,是为了你加深记忆,这对于我们接下来的篇幅很重要。接着我们来看看下面的代码:

    console.log(obj.__proto__ === Object.prototype); // true
    console.log(func.__proto__ === Function.prototype); // true
    

    所以我们又得出如下结论:

    • 实例的 __proto__属性主动指向构造的 prototype;

    • prototype 属性被 __proto__ 属性 所指向。

    这就是prototype 属性和 __proto__ 属性的区别与联系。这可能又有点绕了,来多看几遍这一节,多背一下我们的结论。我们继续。
    那么问题来了,既然
    func是一个函数对象,函数对象是有 prototype 属性的,那么func.prototype.__proto__等于啥呢?
    为了解决这个问题,我们来思考一下:
    首先,我们看看
    func.prototype 是啥:

     console.log(typeof func.prototype); //object
    

    好,我们知道了,func.prototype  是一个对象,那既然是对象,那 func.prototype 那不就是 Object的实例吗?那也就是说,func.prototype.__proto__属性肯定是指向 Object.prototype 咯!好,我们来验证一下:

    console.log(func.prototype.__proto__ === Object.prototype); //true
    

    看见没有,就是这样的。那看到这里,我们应该也知道当我们这创建一个构造函数的时候,javascript是如何帮我们自动生成__proto__prototype属性的。哈哈没错就是这样:

    //我们手动创建func函数
    function func() {}
    //javascript悄悄咪咪执行以下代码:
    func._proto = Function.prototype; //实例的 __proto__ 属性主动指向构造的 prototype
    func.prototype = {
        constructor: func,
        __proto: Object.prototype //我们刚刚才在上面验证的,你别又忘记了
    }
    

    我还专门为你画了个图(够贴心不老铁):

    所以prototype又被称为显式原型对象,而__proto__又被称为隐式原型对象。

    hi,看到这里,你是不是有种脑子开了光的感觉。哈哈,所以到现在你应该已经理解原型的概念了,如果你还不理解,那就把上述章节再看一遍。最好拿个纸笔出来跟着画一画,顺便拿出电脑把示例代码敲一敲。好,整理一下头脑,接下来我们来看看什么又是原型链

    原型链

    再介绍这个概念之前,我们先来看如下代码:

    function Person = function(name,desc){
        this.name = name;
        this.desc = desc;
    } //***1****//
    Person.prototype.getName = function(){
        return this.name;
    }//***2****//
    Person.prototype.getDesc = function(){
        return this.desc;
    }//***3****//
    
    
    const obj = new Person('juefei','cool');//***4****//
    console.log(obj);//***5****//
    console.log(obj.getName);//***6****//
    

    接下来我们来逐步解析一下:

    1. 创建了一个构造函数 Person,此时,Person.portotype自动创建,其中包含了 constructor__proto__两个属性;

    2. 给对象 Person.prototype 新增了一个方法 getName;

    3. 给对象 Person.prototype 新增了一个方法 getDesc;

    4. 根据构造函数 Person 新建一个实例: obj(在创建实例的时候,构造函数会自动执行);

    5. 打印实例 obj :

    {
        name: 'juefei',
        desc: 'cool'
    }
    

    根据上面一节的结论,我们得出:

    obj.__proto__ = Person.prototype;
    
    1. 执行到第6步时,由于在实例 obj 上面找不到  getName()这个方法,所以它就会自动去通过自身的 __proto__ 继续向上查找,结果找到了 Person.prototype ,接着它发现,刚好 Person.prototype 上面有getName()方法,于是找到了这个方法,它就停止了寻找。怎么样,是不是有一种环环相扣的感觉?他们形成一个链了,没错,这就是原型链

    我们得出如下结论:
    在访问一个对象(假设这个对象叫obj)的属性/方法时,若在当前的对象上面找不到,则会尝试通过
    obj.__proto__去寻找,而 obj.__proto__ 又指向其构造函数(假设叫objCreated)的 prototype,所以它又自动去 objCreated.prototype 的属性/方法上面去找,结果还是没找到,那么就访问 objCreated.prototype.__proto__继续往上面寻找,直到找到,则停止对原型链对寻找,若最终还是没能找到,则返回 undefined 。一直沿着原型链寻找下去,直到找到 Object.prototype.__proto__,指向 null,于是返回 undefined了。

    是不是自然而然就理解了。我又给你画了个图(请对照着上面????那个图看):

    接下来我们再来增加一些概念:

    1. 任何内置函数对象本身的 __proto__属性都指向 Function的原型对象,即:Function.prototype;

    2. 除了 Object.prototype.__proto__指向 null ,所有的内置函数对象的原型对象的 __proto__属性 ( 内置函数对象.prototype.__proto__),都指向Object

    我们得出如下终极原型链的图:

    针对这个图,我最终给出我们经常看见那个原型链的图:

    好好对比一下,拿出纸和笔画一画,根据上面章节的讲解,相信你很容易就能明白。

    javascript中的

    刚刚我们终于明白什么是 原型原型链。下面我们根据上面的概念来讲解一下javascript中的。我们知道,在面向对象的语言中,类可以被实例化多次,这个实例化是指我们可以根据构造函数去独立复制多个独立的实例,这些实例之间是独立的。但是实际上在 javascript 却不是这样的,因为它不是这种复制机制。我们不能创建一个类的多个实例,我们只能创建这个类的多个对象,因为他们都是通过原型原型链关联到同一个对象。所以在 javascript 中 ,都是通过原型原型链来实现的,它其实是一种委托方式


    new的实现

    了解了上面javascript中的的概念,那我们应该很容易就理解new的过程,其核心无非就是执行原型链的链接:

    function myNew(Cons,...args){
        let obj = {};
        obj.__proto__ = Cons.prototype; //执行原型链接
        let res = Cons.call(obj,args);
        return typeof res === 'object' ? res : obj;
    }
    复制
    

    instanceof的实现

    那么学习了原型原型链instanceof的实现肯定也很简单了,它也是通过原型原型链来实现的:

      function myInstanceof(left,right){
          let rightProto = right.prototype;
          let leftValue = left.__proto__;
          while(true){
              if(leftValue === null){
                  return false;
              }
              if(leftValue === rightProto){
                  return true;
              }
              leftValue = leftValue.__proto__;
          }
      }
    

    我就不讲解过程了,因为我知道你肯定能看懂,哈哈。


    javascript的继承

    我们都知道继承也是通过原型原型链来实现的,那我在这里介绍两种常见的继承方式:

    1. 组合继承:

    //组合式继承
    //通过call继承Parent的属性,并传入参数
    //将Child的原型对象指向Parent的实例,从而继承Parent的函数
     function Parent(value){
         this.val = value;
     }
     Parent.prototype.getValue = function(){
         console.log(this.val);
     }
     function Child(value){
         Parent.call(this,value);//继承Parentd的属性
     }
     Child.prototype = new Parent();
    
    1. 寄生组合式继承:

    //寄生组合式继承
    //通过call继承Parent的属性,并传入参数
    //通过Object.create()继承Parent的函数
      function Parent(value){
          this.val = value;
      }
      Parent.prototype.getValue = function(){
          console.log(this.val);
      }
      function Child(value){
          //继承Parentd的属性
          Parent.call(this,value);
      }
      Child.prototype = Object.create(Parent.prototype,{
          constructor:{
              value:Child,
              writable:true,
              configurable:true,
              enumerable:false
          }
      })
    

    总结

    1. 若 A 通过 new 创建了 B,则 B.__proto__ = A.prototype

    2. 执行B.a,若在B中找不到a,则会在B.__proto__中,也就是A.prototype中查找,若A.prototype中仍然没有,则会继续向上查找,最终,一定会找到Object.prototype,倘若还找不到,因为Object.prototype.__proto__指向null,因此会返回undefined

    3. 原型链的顶端,一定有 Object.prototype.__proto__ ——> null。

    由此可见,原型原型链是如此的强大,希望看完这篇文章,你不再会对他们感到恐惧。写完这篇已经近凌晨两点,如果你觉得这篇文章对你有些许收获,请点赞支持!!

    ▼原创系列推荐▼1. JavaScript 重温系列(22篇全)
    2. ECMAScript 重温系列(10篇全)
    3. JavaScript设计模式 重温系列(9篇全)4. 正则 / 框架 / 算法等 重温系列(16篇全)5. Webpack4 入门(上)|| Webpack4 入门(下)6. MobX 入门(上) ||  MobX 入门(下)7. 59篇原创系列汇总回复“加群”与大佬们一起交流学习~点这,与大家一起分享本文吧~
    
    个人博客:http://www.pingan8787.com 微信公众号【前端自习课】和千万网友一起,每日清晨,享受一篇前端优秀文章。 目前已连续推送文章 600+ 天,愿每个人的初心都能一直坚持下去!
  • 相关阅读:
    C# 深浅复制 MemberwiseClone
    负载均衡算法,轮询方式
    大话设计模式之工厂模式 C#
    大话设计模式:代理模式 C#
    C# 单元测试
    【前端安全】JavaScript防http劫持与XSS
    神秘的 shadow-dom 浅析
    【CSS进阶】伪元素的妙用2
    【CSS进阶】CSS 颜色体系详解
    【CSS进阶】box-shadow 与 filter:drop-shadow 详解及奇技淫巧
  • 原文地址:https://www.cnblogs.com/pingan8787/p/13069520.html
Copyright © 2020-2023  润新知