继承在JavaScript中的表现

JavaScript的继承非常有研究价值,我最近也是在这上面花了很多时间,对我理解OOP起到了很大的作用。

基于原型链

很自然的,既然对象可以指向一个原型类,并且当对象查找属性时会顺着原型链而上,那么原型完全可以作为JavaScript解决继承问题的方法。
这也是JavaScript继承方式最大的特色。

function Person() {
    this.name = "Jaycee";
    this.age = 19;
}

Person.prototype.sayName = function () {
    return console.log(this.name);
};

function Student() {
    this.number = 31801319;
}

Student.prototype = new Person();

Student.prototype.sayNumber = function () {
    return console.log(this.number);
};

var student1 = new Student();
student1.sayName();
student1.sayNumber();

上面的代码用Student去继承Person,我们仅仅把构造函数和原型混成模式改了一下就变成了原型继承,是的,可以说两者简直一摸一样。
如果你还不清楚这些设计模式,可以参考我前几天写的:
使用设计模式创建JavaScript对象
那么既然如此,那么这种创建方式的弊端也很明显了。

  • 不能使用对象字面量快速地创建原型,因为这样做会重写原型链。
  • 当一个对象被多个子类继承的时候,子类共享了它。
  • 创建子类型的时候,不能向超类的构造函数传递参数。

还有一种基于原型的继承写法,由道格拉斯·克洛克福德提出的原型式继承。
这个方法不需要我们自己创建构造函数,听起来可能有点神奇。
ES5还为这个方法添加了新的方法Object.create(),克洛克福德的原方案是直接使用object()

var Person = {
    name: "Jaycee",
    age: 19
};


var student1 = Object.create(Person, {
    number: {
        value: 31801319
    }
});

student1.sayName = function () {
    console.log(this.name);
};
student1.sayNumber = function () {
    console.log(this.number);
};

student1.sayName();
student1.sayNumber();

Person对象传入object,这样子,object就会以Person作为原型,自动创建一个新的对象。
这个方法和上一个方法的缺点是一样的。

借用构造函数

这种想法非常自然,也是其他很多语言的做法。
也就是,在子类型构造函数的内部调用超类型的构造函数。
别忘了,apply()call()函数。

function Marker() {
    this.colors = ["red", "blue", "green"];
}

function Canvas() {
    Marker.call(this);
}

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

var canvas2 = new Canvas();
console.log(canvas2.colors); //[ 'red', 'blue', 'green' ]

canvas1canvas2之间不再共享属性了,这很好。
但这个方法,产生了很多可以被重用的代码,所以我们不妨和原型链结合来做继承。

组合继承

function Marker(brand) {
    this.brand = brand;
    this.colors = ["red", "blue", "green"];
}

Marker.prototype.getBrand = function () {
    return this.brand;
};

function Canvas(brand, owner) {
    Marker.call(this, brand);
    this.owner = owner;
}

Canvas.prototype = new Marker();
Canvas.prototype.getOwner = function () {
    return this.owner;
};

var canvas1 = new Canvas("Muji", "Jaycee");
console.log(canvas1.getBrand()); //Muji
console.log(canvas1.getOwner()); //Jaycee

组合继承有一个问题,它调用了两次超类的构造函数。
第一次在例子的第14行,第二次在例子的第10行,怎么回事?
第一次调用是创建一个对象来作为Canvas的原型,这可以理解。
那么第二次在干什么?
第二次事实上是重写了这个对象的一些方法,注意,借用构造函数它把Marker创建的属性和方法添加到了新的Canvas对象中,而不是原型,这样就屏蔽了原型中的属性和方法。

寄生式继承

这种方式的思路与寄生构造函数与工厂模式类似,也就是把构造函数仅仅作为用于封装过程的函数,在函数内部增强对象,也就是添加方法和属性。

function createAnother(original) {
    var clone = object(original);
    clone.sayHi = function () {
        console.log("hi");
    };
    return clone;
}

但这样的一个问题就是无法实现共用。
没关系,我们还可以组合使用其他继承方法来达到这个目的。
接下来要介绍的寄生组合式继承解决了前面在组合继承遇到的一些问题。
我们改进一下前面组合继承的代码。

function Marker(brand) {
    this.brand = brand;
    this.colors = ["red", "blue", "green"];
}

Marker.prototype.getBrand = function () {
    return this.brand;
};

function Canvas(brand, owner) {
    Marker.call(this, brand);
    this.owner = owner;
}

function inheritPrototype(subType, superType){
    var prototype = Object(superType.prototype);
    prototype.constructor = subType;
    subType.prototype = prototype;
}

inheritPrototype(Canvas, Marker);

Canvas.prototype.getOwner = function(){
    return this.owner;
};

var canvas1 = new Canvas("Muji", "Jaycee");
console.log(canvas1.getBrand()); //Muji
console.log(canvas1.getOwner()); //Jaycee

这个方法的巧妙在于,我们把Marker的原型作为了Canvas的原型,然后在借用构造函数的时候调用Marker的构造函数。这样我们还是初始化了两次,但把第一次初始化变成了初始化Marker的原型。
但是要注意,为了弥补重写原型造成constructor缺失,我们需要重新设置一下constructor

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  •   面向对象(Object-Oriented,OO)的语言有一个标志,那就是它们都有类的概念,而通过类可以创建任意...
    霜天晓阅读 2,158评论 0 6
  • 第3章 基本概念 3.1 语法 3.2 关键字和保留字 3.3 变量 3.4 数据类型 5种简单数据类型:Unde...
    RickCole阅读 5,212评论 0 21
  • 函数和对象 1、函数 1.1 函数概述 函数对于任何一门语言来说都是核心的概念。通过函数可以封装任意多条语句,而且...
    道无虚阅读 4,679评论 0 5
  • 博客内容:什么是面向对象为什么要面向对象面向对象编程的特性和原则理解对象属性创建对象继承 什么是面向对象 面向对象...
    _Dot912阅读 1,456评论 3 12
  • 我们在对象创建模式中讨论过,对象创建的模式就是定义对象模板的方式。有了模板以后,我们就可以轻松地创建多个结构相同的...
    csRyan阅读 916评论 0 7