了解一下JavaScript继承的方法

660 阅读3分钟

在这里需要注意的是,在JavaScript中并没有类的概念,我们的写法只是为了去模仿类,在JavaScript中类的实现是基于原型链、原型委托实现面向对象的。

1、构造函数使用call、apply

function P1() {
    this.name = "p1"
}
P1.prototype.say = function() {
    console.log('say')
}
function C1() {
    P1.call(this) // 关键
    this.age = 1
}

这是最简单的一种方式,但是这种方式存在一种明显的缺陷,即只能继承构造函数内的属性,不能继承原型链上的属性和方法

2、原型链

function P2() {
  this.name = 'p2'
  this.arr = [1,2,3]
}
P2.prototype.say = function() {
    console.log('say')
}
function C2() {
    this.age = 2
}
C2.prototype = new P2()
let s1 = new C2()
let s2 = new C2()

这是通过原型链的方式实现继承,我们通过将子类(C2)的prototype属性挂载到父类(P2)的实例对象(new P2())上,当访问访问子类实例没有的方法的时候,会访问子类的prototype属性,如果子类的prototype上也没有该方法,则会访问prototype__proto__属性。形成的继承关系: s1.prototype === new P2()new P2().__proto__ === P2.prototype

这种方式的实现较第一种有明显的改进,但是也存在一些问题:由于所有实例共用prototype属性,当我们通过某个实例修改了原型链上的某个属性值的时候,其他的实例也会受到影响。这是违背面向对象的。

3、组合方式

组合方式是将第一种方式和第二种方式结合起来,

function P3() {
    this.name = "p3"
    this.arr = [1,2,3]
}
P3.prototype.say = function() {
    console.log('say')
}
function C3() {
    P3.call(this)
    this.age = 3
}
C3.prototype = P3.prototype

我们把父类的中的属性通过call写到子类中,然后通过子类实例化的每个实例对象中都会有这个属性值,当改变其中一个实例中的属性的时候,其他的实例对象不会受到影响;然后将子类的prototype属性挂载到父类的prototype属性上,就可以访问父类原型上的方法。但是这种方法也存在一些问题,当我们访问C3.prototype的constructor属性的时候会发现是P3,这可能会引起一些误解。这是因为我们直接使C3.prototype = P3.prototype,当我们访问C3.prototype的时候其实是访问的是,P3.prototype。这里我们很容易想到重写C3.prototypeconstructor属性。但是我们必须引入一个中间的变量来表示C3.prototype然后将中间变量的__proto__指向父类。如果不引入中间变量当我们修改的C3.prototypeconstructor,因为C3.prototype、P3.prototype指向同一个引用,P3.prototypeconstructor属性也会被修改。

4、组合方式优化

function P4() {
    this.name = "p4"
    this.arr = [1,2,3]
}
P4.prototype.say = function() {
    console.log('say')
}
function C4() {
    P4.call(this)
    this.age = 4
}
C4.prototype = Obiect.create(P4.prototype)
C4.prototype.constructor = C4

这是组合方式的优化方式,通过C4.prototype = Obiect.create(P4.prototype)这段代码,将C4.prototype__proto__属性指向P4.prototype,当我们修改了C4.prototype上的constructor属性·的时候,P4.prototypeconstructor属性并不会受到影响。

这里C4.prototype = Obiect.create(P4.prototype) 相当于下面这段代码:

    function F() {}
     F.prototype = P4.prototype
     C4.prototype = new F()

5、圣杯模式

圣杯模式,yahoo 贡献的高端写法:YUI3库有个inherit,现在不用了,了解一下:

let inherit = (function() {
    let F = function(){}
    return function(Child, Father) {
        F.prototype = Father.prototype
        Child.prototype = new F()
        Child.prototype.constructor = Child
        Child.prototype.uber = Father.prototype
    }
})()

这个方法只能继承父类的原型上的方法,不能继承构造函数内部的方法。