最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 原型、原型链、继承之随记随查至通透

    正文概述 掘金(SandySY)   2021-01-26   424

    原型、原型链、继承

    这是一个炒冷饭的话题,既然是很老旧的知识点,那还有这么多人去炒,肯定是说明这其中有事儿:

    • 有难以揣摩的、
    • 不好记忆的、
    • 容易混淆的;

    那这此就一起给收拾干净!

    开局代码

    function Person(name) {
      this.name = name;
    }
    Person.prototype.age = 18;
    var person = new Person();
    

    上面的代码咱们来看两个打印:

    console.dir(person);
    console.log(typeof person, typeof Person, typeof Person.prototype);
    

    原型、原型链、继承之随记随查至通透

    原型、原型链、继承之随记随查至通透

    原型


    在JavaScript中每个对象(除null外)创建的时候,就会关联另一个对象,这个关联对象就是原型对象,与大多数资料一致,后续简称原型(本质也是对象),每一个对象都会从原型中继承属性。

    1) prototype

    从这里可以看到,每个函数都有一个prototype属性,这个属性指向的是函数的原型对象。函数的原型可以通过.prototype属性获取到,可以读取或设置其原型对象的属性。

    2) __ proto__

    console.log(person.__proto__ === Person.prototype);   // true
    

    每个对象(除null外)都会有一个__proto__属性,这个属性可以获取到该对象的原型

    3) constructor

    console.log(Person === Person.prototype.constructor);  //true
    

    每个原型都有constructor属性,指向关联它的构造函数。

    4) instanceof

    有了以上的基础,我们继续探讨,怎么知道Person是不是person的原型上的构造函数呢? instanceof就是处理这个事情,如果A沿着原型链能找到B.prototype,那么A instanceof B为true,此时B却不一定是A直接new出来的,切记,看几个特例;

    console.log(Object instanceof Object);//true 
    console.log(Function instanceof Function);//true 
    console.log(Function instanceof Object);//true 
    console.log(Object instanceof Function);//true 
    
    console.log(Number instanceof Number);//false 
    console.log(String instanceof String);//false 
    console.log(Person instanceof Function);//true 
    console.log(Person instanceof Person);//false
    

    原型链


    1) 套娃

    原型是一个对象,原型的原型是什么?
    我们可以反复去打印获悉:

    console.log(person.__proto__.__proto__)   //Object
    console.log(person.__proto__.__proto__.__proto__);    //null
    

    于是,用一张图展示出person的原型链,那应该是这样: 原型、原型链、继承之随记随查至通透

    2) 澄清概念

    person套娃链结束,然而真正的难点却才刚刚开始,如果认真看到这里的话:

    • 上面的instanceof特例是怎么回事?
    • Object和Function为何搞特殊待遇?

    不急,我们先来澄清一些概念,至少到目前为止可以帮助总结:

    那么,我们归纳和精简一下:

    谈到这里,不得不上一张永恒的图,Js灵魂图:(对与老前端来讲,那是曾经夕阳下的奔跑...磕磕~) 原型、原型链、继承之随记随查至通透

    3) 特例单独记忆(易混淆)

    Object.__proto__ === Function.prototype;
    Function.prototype.__proto__ === Object.prototype;
    Object.prototype.__proto__ === null;
    

    这里推荐一个有趣的故事记忆法则,偶然发现的,觉得有意思它就有意思,觉得无聊就跳过此处: 原型、原型链、继承之随记随查至通透

    继承

    现在扩充一下父类Person,使其具有以下属性和方法:

    function Person(name) {
      this.name = name || "noName";
      this.sleep = function () {
        console.log(this.name + "好想睡觉");
      };
    }
    Person.prototype.eat = function (food) {
      console.log(this.name + "想吃" + food);
    };
    

    1) 原型链继承

    // 原型链继承
    function Man(){}
    Man.prototype = new Person();
    let man = new Man();
    
    • 优点:
      • 这样将父类的示例作为子类的原型,容易实现。
      • 父类后续新增的属性或方法,子类中都可以访问。
      • 操作子类即可修改原型中的属性。
    • 缺点:
      • 要新增原型中属性或方法,必须要先new一个实例。
      • 无法多继承
      • 创建子类实例时,无法向父类构造函数传参

    2) 构造函数继承

    // 构造函数继承
    function Man(name) {
      Person.call(this, name);
      this.name = name || "No Name";
    }
    let man = new Man("jack");
    
    • 优点:
      • 解决了原型链继承中不可传递参数的缺点。
      • 子类可以继承多个父类,使用多个call或apply之类的
    • 缺点:
      • 这里的man是Man的实例,但不是Person的实例
      • 父类Person原型中的属性和方法(eat)无法被继承。
      • 子类实例man虽然可以继承父类Person的属性和方法,但是父类Person中的函数无法被复用,如上代码中的sleep()

    3) 实例继承

    // 实例继承
    function Woman(name) {
      let instance = new Person();
      instance.name = name || "no name";
      return instance;
    }
    var women = new Woman();
    
    • 优点:
      • 不限制调用方式,无论是new Woman()还是Woman()都可以实现继承
      • 子类可以继承多个父类,使用多个call或apply之类的
    • 缺点:
      • woman是Person的实例,而不是Woman的实例
      • 不支持多继承

    4) 组合继承

    //组合继承
    function Man(name) {
      Person.call(this, name);
      this.name = name || "no name";
    }
    // 将整个原型链规整
    Man.prototype = new Person();
    Man.prototype.constructor = Man;
    let man = new Man();
    
    • 优点:
      • man实例既能获取Man的属性和方法,也能获取到Person的原型对象的属性和方法(eat)
      • man即是子类Man的实例,也是父类Person的实例
      • 弥补了单纯的构造继承的缺点,可以向父类Person传值
      • 可以复用所有对象的函数
    • 缺点:
      • 调用了两次父类构造函数,生成了两份实例,多了一些些内存消耗。

    5) 寄生组合继承

    //寄生组合继承
    function Woman(name) {
      Person.call(this, name);
      this.name = name || "No Name";
    }
    (function () {
      // Super无实例方法,避免了上一种继承方式的创建两次实例
      var Super = function () {};
      Super.prototype = Person.prototype;
      Woman.prototype = new Super();
    });
    Woman.prototype.constructor = Woman;
    
    let woman = new Woman();
    
    • 优点:
      • 在组合继承方式优点上,解决了创建两次实例的问题
    • 缺点:
      • 实现复杂

    6) ES6 Class继承

    Class 作为构造函数的语法糖,同时有prototype属性和__proto__属性,因此同时存在两条继承链。

    // Class 继承
    class A {}
    class B extends A {}
    
    B.__proto__ === A; // true
    B.prototype.__proto__ === A.prototype; // true
    

    再看:

    //继承
    class A extends Object {}
    A.__proto__ === Object; // true
    A.prototype.__proto__ === Object.prototype; // true
    
    //非继承
    class A {}
    A.__proto__ === Function.prototype; // true
    A.prototype.__proto__ === Object.prototype; // true
    A.prototype.__proto__ === Function.prototype; // false
    

    ES5继承与ES6继承的区别

    ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。
    ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。
    class 的职责是充当创建 object 的模板, 通常来说,data 数据是由 instance 承载,而 methods 行为/方法则在 class 里。

    // 特殊 如果子类没有定义constructor方法,这个方法会被默认添加,
    class ColorPoint extends Point {
    }
    
    // 等同于
    class ColorPoint extends Point {
      constructor(...args) {
        super(...args);
      }
    }
    

    new 浅析

    1) 做了什么?拆解:

    2) 实现new方法

    • 调用需求:createInstance(Person, {name: 'Tom', age:20});
    • 实现思路:
      • 创建一个空对象
      • 从参数中删除第一个元素并返回,第一个参数(就是构造函数),剩下就是参数
      • 链接到原型
      • 调用构造函数,把this绑定到新对象上
      • 返回构造函数调用的结果,或者新对象
    • 代码实现一:
    function createInstance() {
        let obj = {}
        let constructor = [].shift.call(arguments)
        obj.__proto__ = constructor.prototype
        let result = constructor.apply(obj, arguments)
        return typeof result === 'object' ? result : obj
    }
    
    • 代码实现二:
    const createInstance = (Constructor, ...args) => {
      let instance = Object.create(Constructor.prototype);
      Constructor.call(instance, ...args);
      return instance;
    };
    function User(firstname, lastname) {
      this.firstname = firstname;
      this.lastname = lastname;
    }
    

    小结

    一开始的结构序幕拉得太大了,导致这篇慢慢倒腾,居然篇幅这么长,后面有时间看看是否需要调整一下内容板块,拆两篇还是重新整合,暂时就这样了,虽然长一些,但结构还算清晰!

    文中如有错误,欢迎在评论区指正,如果有所帮助,欢迎点赞和关注!


    起源地下载网 » 原型、原型链、继承之随记随查至通透

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元