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属性

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353

推荐阅读更多精彩内容

  •   面向对象(Object-Oriented,OO)的语言有一个标志,那就是它们都有类的概念,而通过类可以创建任意...
    霜天晓阅读 2,107评论 0 6
  • 第3章 基本概念 3.1 语法 3.2 关键字和保留字 3.3 变量 3.4 数据类型 5种简单数据类型:Unde...
    RickCole阅读 5,124评论 0 21
  • 继承是 OO 语言中的一个最为人津津乐道的概念。许多 OO 语言都支持两种继承方式:接口继承 和 实现继承。接口继...
    threetowns阅读 445评论 0 0
  • 博客内容:什么是面向对象为什么要面向对象面向对象编程的特性和原则理解对象属性创建对象继承 什么是面向对象 面向对象...
    _Dot912阅读 1,421评论 3 12
  • 到了某个年纪, 我们开始不会笑, 只是简单的嘴角上扬。 到了某个年纪, 我们开始不会哭, 而是在伤心的时候仍然保持...
    提起裙摆做女王i阅读 327评论 2 2