原型链基础及多种使用方式

628 阅读4分钟

在程序开发过程中,我们经常使用原型链,但是呢,我们对它的概念又不太熟,最近重新又看了一遍,算是增强记忆,顺便记录下,希望能帮到大家!

一、原型链的基础

基本思想:原型链是指利用原型让一个引用类型继承另一个引用类型的属性和方法。

顺带解释下 构造函数、原型 、实例的关系

每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,实例都包含一个指向原型对象的内部指针。

实现原型链的基本模式


  SuperType.prototype.getSuperValue = function () {
    return this.property
  }

  function SubType() {
    this.subproperty = false
  }

  SubType.prototype = new SuperType()
  // 继承了SuperType
  SubType.prototype.getSubValue = function () {
    return this.subproperty
  }

  var instance = new SubType()

console.log(instance.getSuperValue()) // true

他们的继承方式是用SubType.prototype = new SuperType(),创建实例,把实例赋值给prototype

这里说两个东西,

  1. 第一个是要所有函数的默认原型都是object的实例
  2. 第二个是确定原型和实例的关系可以用instanceof来判断
  3. 在通过原型链实现继承时,不能使用对象字面量创建原型方法

原型链的问题

    function SuperType() {
        this.color = ['red', 'blue', 'green']
    }

    function SubType() {}

    // 继承了SuperType
    SubType.prototype = new SuperType()

    var instance1 = new SubType()
    instance1.color.push('black')
    console.log(instance1.color) // ["red", "blue", "green", "black"]

    var instance2 = new SubType()
    console.log(instance2.color) // ["red", "blue", "green", "black"]

很神奇,上边代码只执行了instance1.color.push('black'),但在新创建的实例instance2中,输出也一样。 主要是因为包含引用类型值的原型属性会被所有实例共享。 于是就有了下面几种方式来解决这些问题

二、借用构造函数(伪造对象或经典继承)

这种技术的基本思想是在子类构造函数的内部调用超类构造函数。 通过使用apply或者call方法在新创建的对象上执行构造函数。

代码如下:

    function SuperType() {
        this.colors = ['red', 'blue', 'green']
    }

    function SubType() {
        // 继承了SuperType
        SuperType.call(this)
    }

    var instance1 = new SubType()
    instance1.colors.push('black')
    console.log(instance1.colors) // ["red", "blue", "green", "black"]

    var instance2 = new SubType()
    console.log(instance2.colors) // ["red", "blue", "green"]

主要通过使用call,使得在新创建的SubType实例环境下调用了SuperType构造函数。

1.传递参数

可以在子类构造函数中向超类型构造函数传递参数。

    function SuperType(name) {
        this.name = name
    }

    function SubType() {
        // 继承了SuperType,同时还传递了参数
        SuperType.call(this, 'neil')
            // 实例属性
        this.age = 25
    }

    var instance = new SubType()
    console.log(instance) // SubType {name: "neil", age: 25}

由于方法都是构造函数中定义,因此函数的复用会有用问题,因此就有了组合继承

三、组合继承

组合继承也叫做伪经典继承,指的是将原型链和借用构造函数的技术结合到一起,从而发挥二者之长的继承模式。 背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承

    function SuperType(name) {
        this.name = name
        this.colors = ['red', 'blue', 'green']
    }

    SuperType.prototype.sayName = function() {
        console.log(this.name)
    }

    function SubType(name, age) {
        // 继承属性
        SuperType.call(this, name)
        this.age = age
    }

    // 继承方法
    SubType.prototype = new SuperType()
    SubType.prototype.constructor = SubType
    SubType.prototype.sayAge = function() {
        console.log(this.age)
    }

    let instance1 = new SubType('neil', 25)
    instance1.colors.push('black')
    console.log(JSON.stringify(instance1.colors)) // ["red","blue","green","black"]
    instance1.sayName() // neil
    instance1.sayAge() // 25

    let instance2 = new SubType('neil2', 23)
    console.log(JSON.stringify(instance2.colors)) // ["red","blue","green"] 
    instance2.sayName() // neil2
    instance2.sayAge() // 23

最主要的三个步骤,

  1. SuperType.call(this, name),
  2. SubType.prototype = new SuperType(),
  3. SubType.prototype.constructor = SubType。

四、原型式继承

道格拉斯·克罗克福德在2006年写了一篇名为Prototypal Inheritance in JavaScript。在这篇文章中他介绍了这种实现继承的方式。这种方式没有使用严格意义上的构造函数。他的想法是借助原型可以基于已有的对象创建对象,同时还可以不必因此创建自定义类型。

    function object(o) {
        function F() {}
        F.prototype = o
        return new F()
    }

    let person = {
        name: 'neil',
        friends: ['neil1', 'neil2', 'neil3']
    }

    let anotherPerson = Object(person)
    anotherPerson.name = 'neil4'
    anotherPerson.friends.push('neil5')

    let yetAotherPerson = object(person)
    yetAotherPerson.name = 'neil6'
    yetAotherPerson.friends.push('neil7')
    console.log(person.friends) // ["neil1", "neil2", "neil3", "neil5", "neil7"]

ECMAScript5通过新增Object.create()方法,规范了原型式继承。这个方法接收两个参数:一个用作新对象原型的对象和一个为新对象定义额外属性的对象

    function object(o) {
        function F() {}
        F.prototype = o
        return new F()
    }

    let person = {
        name: 'neil',
        friends: ['neil1', 'neil2', 'neil3']
    }
    let anotherPerson = Object.create(person)
    anotherPerson.name = 'neil4'
    anotherPerson.friends.push('neil5')

    let yetAotherPerson = Object.create(person)
    yetAotherPerson.name = 'neil6'
    yetAotherPerson.friends.push('neil7')

    console.log(person.friends) // ["neil1", "neil2", "neil3", "neil5", "neil7"]

两个的不同点在于,一个类似于工厂函数,返回的是一个实例,另外一个通过object.create来实现。

五、寄生式继承

寄生式继承的思路与寄生构造函数和工厂模式类似,创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再返回对象。

    function object(o) {
        function F() {}
        F.prototype = o
        return new F()
    }

    let person = {
        name: 'neil',
        friends: ['neil1', 'neil2', 'neil3']
    }
    function createAnother(originnal) {
        let clone = Object(originnal)
        clone.sayHi = function() {
            console.log('hi')
        }
        return clone
    }

    let anotherPerson = createAnother(person)
    anotherPerson.sayHi() // hi

新对象不仅拥有了person的所有属性和方法,还有自己的sayhi()方法。

相关文章:

工厂模式,构造器模式和原型模式