JavaScript面向对象编程-继承-12种方案

最近在总结JS继承方面的相关知识,本文是对掌握了一定继承基本知识的拓展,有哪些方法可以实现继承?下面就让我们一起去看下吧~

首先对继承方式进行分类的话,大致可以分为两类:

  • 基于构造器工作的模式
  • 基于对象工作的模式

1、原型链法(仿传统)

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

根据上面原型对象、构造函数和实例三者的关系我们知道:只要实例A的原型对象X是另外一个类的实例B,那么这个实例B又会指向一个新的原型对象Y,以此类推便形成一条原型链。示例代码如下:

// 定义一个 Animal 的构造函数,作为 Dog 的父类
function Animal () {
    this.superType= 'Animal';
}

// 添加原型对象方法
Animal.prototype.superSpeak = function () {
    alert(this.superType);
}

// 顶一个 Dog 构造函数
function Dog (name) {
    this.name = name;
    this.type = 'Dog';
}

// 改变 Dog 的 prototype 指针,指向 Animal 的实例
Dog.prototype = new Animal();

Dog.prototype.speak = function () {
    alert(this.type);
}

var testDog = new Dog('test');
testDog.speak();          // Dog
testDog.superSpeak();     // Animal

2、仅从原型继承法

这个模式在构建继承关系时不需要新建对象实例,效率上会有较好的表现;
原型链上的查询也会比较快,因为不存在原型

Child.prototype = Parent.prototype;
Child.prototype.constructor = Child;
  • 缺点:子对象的修改会影响其父对象。实例代码如下:
// 创建一个空的构造函数
// 尽可能将一些可重用的属性和方法添加到原型中去,如果形成这样一个好习惯
function Animal () {}

Animal.prototype.superName = 'Animal';
Animal.prototype.speak = function () {
   alert(this.superName)
}

// Dog
function Dog () {}

Dog.prototype = Animal.prototype;
Dog.prototype.constructor = Dog;

var testDog = new Dog();
testDog.speak();      // Animal

3、临时构造器法

利用空函数来原型继承,父对象不会受到子对象的影响

// 封装继承方法
function extend (Child, Parent) {
    // 创建一个空函数 F()
    // 并将其原型设置为父级构造器,new F()来创建一些不包含父对象属性的对象
    var F = function () {};  //临时构造器函数
    F.prototype = Parent.prototype;
    Child.prototype = new F();
    Child.prototype.constructor = Child;
    // 通过 uber 属性访问父级原型对象
    Child.uber = Parent.prototype;
}

function Animal () {}
Animal.prototype.superName = 'Animal';
Animal.prototype.speak = function () {
    alert(this.superName);
}

function Dog () {}
extend(Dog, Animal);
Dog.prototype.name = 'Dog';

// 测试
var testDog = new Dog();
testDog.speak();

4、原型属性拷贝法

子对象原型逐一拷贝,而非简单的原型链查询。这种方式仅适用于只包含基本数据类型的对象,所有的对象类型(包括函数与数组)都是不可复制的

示例代码如下:

function extend (Child, Parent) {
    var parent = Parent.prototype;
    var child = Child.prototype;

    // 遍历循环 parent 所有属性
    for (var i in parent) {
        child[i] = parent[i];
    }

    child.uber = parent;
    // 由于这里是对 child 的原型进行扩展,所以不需要 重置 child.constructor = Child
}

Shape = function () {};
Shape.prototype.name = 'shape';
Shape.prototype.toString = function () {
    return this.uber ? this.uber.toString() + ', ' + this.name : this.name;
};

var TwoDShape = function () {};
//也会拷贝属于自己的toString()方法,但这只是一个函数引用,函数本身并没有被再次创建
extend(TwoDShape, Shape);

//测试
var td = new TwoDShape();
console.log(td.__proto__.hasOwnProperty('name'));      // true
console.log(td.__proto__.hasOwnProperty('toString'));  // true
//这两个toString()方法实际上是同一个函数对象
console.log(td.__proto__.toString === Shape.prototype.toString);    // true
console.log(td.toString());    //shape, shape
TwoDShape.prototype.name = '2D shape';    // shape, 2D shape
console.log(td.toString());

5、全属性拷贝法(即浅拷贝法)

浅拷贝大家也比较熟悉,这里就不做赘述。

示例代码如下:

function extendCopy(p) {
    //创建一个没有任何私有属性的“空”对象作为“画板”,然后逐步为其添加属性
    var c = {};
    for (var i in p) {//将现有对象的属性全部拷贝过来
        c[i] = p[i];
    }
    c.uber = p;
    return c;
}

var Shape = {
    name: 'Shape',
    toString: function () {
        return this.name; 
    }
}

var triangle = extendCopy(Shape);
triangle.name = 'Triangle';
triangle.getArea = function () {
    return this.side * this.height / 2;  
}

//测试
triangle.side = 5;
triangle.height = 10;
console.log(triangle.getArea());    // 25
console.log(triangle.toString());    // Triangle

** 经过5个例子的编写,我们也大致知道了大致的 demo 编写,下面的demo我们将仅展示继承相关的代码 **


6、深拷贝法

浅拷贝,如果修改了拷贝对象,就等于修改了原对象;深拷贝可以避免这方面问题。

function deepCopy (parent, child) {
    child = child || {};
    for (var i in parent) {
        var prop = parent[i];
        if (parent.hasOwnProperty(i)) {
            if (prop === child) {
                continue;
            }
            if (typeof prop  === 'object') {
                 // 遇到一个对象引用性的属性时,需要再次对其调用深拷贝函数
                 child[i] = Array.isArray(prop ? [] : {};
                 deepCopy(prop, child[i]);
                 // 或者使用Object.create
                 // child[i] = Array.isArray(prop ? [] : Object.create(prop);
            } else {
                  child[i] = parent[i];
            }
        }
    }
}

7、原型继承法

直接在对象之间构造继承关系

function object(o) { //接收父对象
    var n;
    function F() {}
    F.prototype = o; //父对象为自对象原型
    n = new F();
    n.uber = o;
    return n;
}

8、扩展与增强模式(原型继承与属性拷贝)

实际上是7号方法和5号方法的混合应用,通过一个函数一次性完成对象的继承与扩展

示例代码如下:

// 对象o用于继承,另一个对象stuff则用于拷贝方法与属性
function objectPlus (o,stuff) {
    var n;
    function F () {}
    F.prototype = o;   // 将已有对象设置为新对象的原型(原型继承的方式)
    n = new F();

    n.uber = o;
    for (var i in stuff) {    // 将另一个已有对象的所有属性拷贝过来
        n[i] = stuff[i];
    }

    return n;
}

9、多重继承法

所谓的多重继承,通常指的是一个子对象中有不止一个父对象的继承模式。
多重继承的实现很简单,只需要延续属性拷贝法(5号)的继承思路依次扩展对象即可,而对参数中所继承的对象的数量没有限制。

示例代码如下:

function multi () {
    var n = {}, stuff, len = arguments.length;
    for (var i = 0; i < len; i++) {//外层循环用于遍历参数中传递进来的对象
        stuff = arguments[i];
        for (var j in stuff) {//内层循环用于拷贝属性
            if (stuff.hasOwnProperty(j)) {
                //如果传入的两个对象拥有同一个属性,前一个会被后一个覆盖掉
                n[j] = stuff[j];
            }
        }
    }
    return n;
}

10、寄生继承法

在创建对象的函数中直接吸收其他对象的功能,然后对其进行扩展并返回

示例代码如下:

function object (o) {
    var n;
    function F () {}
    F.prototype = o;
    n = new F();
    n.uber = o; 
    return n;
} 

var twoD = {
    name: '2D shape',
    dimensions: 2
};

/*
 *将twoD对象克隆进一个叫做that的对象,这一步可以使用之前的任何一种方法,
 *例如使用object()函数或执行全属性拷贝。
 *扩展that对象,添加更多的属性。返回that对象。
 */
function triangle(s, h) {//只是一个一般函数,不属于构造器
    var that = object(twoD);//克隆一个叫that的对象
    //扩展that对象,添加更多的属性
    that.name = 'Triangle';
    that.getArea = function () {
        return this.side * this.height / 2;
    };
    that.side = s;
    that.height = h;
    return that;
}

//测试
var t = triangle(5, 10);
console.log(t.name);
console.log(t.dimensions);
console.log(t.getArea());

11、构造器借用法

子对象构造器可以通过call()或apply()方法来调用父对象的构造器,因而,它通常被称为构造器盗用法(stealing a constructor),或构造器借用法

function Shape(id) {
    this.id = id;
}
Shape.prototype.name = 'shape';
Shape.prototype.toString = function () {
    return this.name;  
}

function Triangle () {
    Shape.apply(this, arguments);//子对象可以通过call()或apply()方法来调用父对象  
}
Triangle.prototype.name = 'Triangle';

 //测试
 var t = new Triangle(101);
 console.log(t.name);
 console.log(t.id);
 console.log(t.toString());//这里新的triangle对象没有继承父对象原型中的任何东西

12、构造器借用与属性拷贝法

本方法是11号方法与4号方法的结合体。它允许我们在不重复调用父对象构造器的情况下同时继承其自身属性和原型属性)

示例代码如下:

function extend2 (Child, Parent) {
    var p = Parent.prototype;
    var c = Child.prototype;
    for (var i in p) {
        c[i] = p[i];
    }
    c.uber = p;
}
   
//构造一个父类构造器Shape
function Shape (id) {
    this.id = id;
}
Shape.prototype.name = 'Shape';
Shape.prototype.toString = function () {
    return this.name;
}

function Triangle () {
    Shape.apply(this, arguments); 
}
extend2(Triangle, Shape);//对父对象原型属性逐一拷贝
Triangle.prototype.name = 'Triangle';

//测试
var t = new Triangle(101);
console.log(t.toString());    // Triangle
console.log(t.id);    // 101
console.log(typeof t.__proto__.id); // 输出为"undefined",这样双重继承已经不见了
console.log(t.uber.name); // 如有必要,extend2()还可以访问对象的uber属性

看了上面的介绍,大家是不是对继承有了进一步的了解呢~

你可能感兴趣的:(JavaScript面向对象编程-继承-12种方案)