原型、原型链、new做了什么、继承

比我们通过一个构造函数new了一个新对象,构造函数的原型prototype指向一个对象,所有通过该构造函数new的新对象可以共享它所包含的属性和方法。

构造函数的原型prototype是一个对象,那么它也可以有自己的构造函数原型prototype,通过这样,形成一个原型链。原型链最终都可以上溯到Object.prototype。Object.prototype的proto是null。null没有任何属性和方法,也没有自己的原型。因此,原型链的尽头就是null。

来看一个例子:


function Test() {}

var test = new Test();

test.__proto__===Test.prototype

Test.prototype.__proto__===Object.protype

Object.protype.__proto__===null

test.__proto__.__proto__.__proto__ === null

Test.__proto__===Function.prototype

获取对象原型API: Object.getPrototypeOf(a) 即a.proto (tip: 方法在Object上而不是Object.prototype上)

为了更清晰地理解,看以下练习:


var obj= {}

obj.__proto__ === Object.prototype // 为 true

var fn = function(){}

fn.__proto__ === Function.prototype  // 为 true

fn.__proto__.__proto__ === Object.prototype // 为 true

var array = []

array.__proto__ === Array.prototype // 为 true

array.__proto__.__proto__ === Object.prototype // 为 true

Array.__proto__ === Function.prototype // 为 true, Array的本质为一个构造函数

实际上,看a.proto是什么,就看a的本质是什么:

1.new出来的对象,则指向其构造函数的prototype;

2.构造函数,则Function.prototype

new

new的过程:

初始化一个新对象

该对象的proto属性指向构造函数的原型prototype

将构造函数的this指向新对象,并执行函数

将新对象返回

[如果构造函数有返回一个对象(null除外),则将构造函数内的对象返回]

手写new:


function myNew(func, ...params) {

    const obj = {};

    obj.__proto__ = func.prototype

    const temp = func.apply(obj, params)

    if(temp && typeof temp === 'object') {

        return temp

    }else{

        return obj

    }

}

继承

既然通过原型链实例能访问到上层的一些方法和属性,那么,自然而然继承可以由他来实现。

原型链继承

最容易想到的是,让构造函数的原型指向另一个你想要继承的构造函数的实例,如下:


// 原型链继承

    // 缺点1:任何一个实例改变原型的属性,所有实例的原型属性都会变,因为指向的是同一个

    // 缺点2:没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数

    function Father(firstName) {

        this.firstName = firstName

        //属性为引用类型会被共享

        this.ver = ['1.0', '2.0']

    }

    function Son(lastName) {

        this.lastName = lastName

    }

    Son.prototype = new Father('lee')

    const a = new Son('mei')

    const b = new Son('lei')

    a.ver[0] = '3.0'

    console.log(a)

    console.log(b)

<script>

      // 借用构造函数,解决了上面的两个问题

      // 缺点1:方法都在构造函数中定义,函数无法复用

      // 缺点2:在超类型的原型中定义的方法,对子类型而言也是不可见的

      function Father(firstName) {

        this.firstName = firstName

        //属性为引用类型会被共享

        this.ver = ['1.0', '2.0']

      }

      function Son(firstName, lastName) {

        Father.call(this, firstName)

        this.lastName = lastName

      }

      const a = new Son('lee', 'mei')

      const b = new Son('han', 'lei')

      a.ver[0] = '3.0'

      console.log(a)

      console.log(b)

    </script>

    <script>

      // 组合原型链和构造函数,解决了上面的四个问题

      // 缺点1:会执行构造函数两遍,父类的属性会创建两遍

      function Father(firstName) {

        this.firstName = firstName

        //属性为引用类型会被共享

        this.ver = ['1.0', '2.0']

      }

      function Son(firstName, lastName) {

        //第二次调用

        Father.call(this, firstName)

        this.lastName = lastName

      }

      //第一次调用

      Son.prototype = new Father('tan')

      const a = new Son('lee', 'mei')

      const b = new Son('han', 'lei')

      a.ver[0] = '3.0'

      console.log(a)

      console.log(b)

    </script>

    <script>

      // 原型式继承

      // 如果目的是让一个对象与另一个对象保持类似的情况.

      // 在传入一个参数的情况下,Object.create()与 object()方法的行为相同。

      // Object.create(null)创建一个空对象,不继承object的任何方法

      function object(o) {

        function F() {}

        F.prototype = o

        return new F()

      }

      let person = {

        name: "Nicholas",

        friends: ["Shelby", "Court", "Van"]

      }

      const a = object(person)

      console.log(a)

    </script>

    <script>

      // 终极版,利用原型式继承,让子构造函数原型能够调用父构造函数原型的方法

      function Father(firstName) {

        this.firstName = firstName

        this.ver = ['1.0', '2.0']

      }

      function Son(firstName, lastName) {

        Father.call(this, firstName)

        this.lastName = lastName

      }

      Father.prototype.say = function () {

        console.log(this.firstName)

      }

      // 不直接指向父类的原型,因为这样两者指向了同一个对象,实现了继承,但改变任何一个,另一个也会受影响

      Son.prototype = Object.create(Father.prototype)

      const a = new Son('lee', 'mei')

      const b = new Son('han', 'lei')

      a.ver[0] = '3.0'

      console.log(a)

      console.log(b)

    </script>

通过上面的流程也可以很好地去理解ES6中的extends。

es6 extends super


class Father {

      constructor(firstName) {

        this.firstName = firstName

        this.ver = ['1.0', '2.0']

      }

      update(firstName) {

        this.firstName = firstName

      }

    }

    class Son extends Father{

      constructor(firstName, lastName) {

        // 继承父类属性,必须有这句之后才能调用父类方法

        super(firstName)

        this.lastName = lastName

      }

      update(firstName, lastName) {

        // 调用父类方法

        super.update(firstName)

this.lastName = lastName

      }

    }

    // 通过观察xiao的结构可以看出,原理大概是终极版

    let xiao = new Son('lee', 'mei')

    ```

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。