一、原型链

  JavaScript 中原型链是实现继承的主要方法。其主要的思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。实现原型链有一种基本模式,其代码如下。

function SuperType() {
    this.property = true;
}

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

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

SubType.prototype.getSubValue = function() {
    return this.subproperty;
};

var instance = new SubType();
console.log(instance.getSuperValue());     // true;

这种继承的本质是重写原型对象,代之以一个新类型的实例。上面这段代码对应的对象关系图如下:

原型链实现的继承的主要问题是:a:当原型包含引用类型的值时,此属性会被所有实例共享; b:在创建子类型的实例时,不能向超类型的构造函数中传递参数。

二、借用构造函数

  借用构造函数也叫伪造对象或经典继承。其思想是在子类型的构造函数的内部调用超类型构造函数(这里的调用是指以普通函数的调用方式调用,而不是使用new操作符)。

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

function SubType() {
    // 继承了SuperType, 同时传递了参数
    SuperType.call(this, "Haha");

    // 实例属性
    this.age = 34;
}

var instance = new SubType();
console.log(instance.name);   // "Haha"

这样以后,SubType的每个实例都会有自己的实例属性的副本了。同时也可以在子类型构造函数中向超类型构造函数传递参数。同时,带来的问题是:方法都在构造函数中定义,函数复用无从谈起。而且在超类型的原型中定义的方法,对子类型而言是不可见的。

三、组合继承

  组合继承也叫做伪经典继承,其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。

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);
};

var instance1 = new SubType("Haha", 23);
var instance2 = new SubType("Hehe", 45);        

两个不同的SubType实例即分别拥有了自己的属性,又可以使用相同的方法。这里的缺点是:实例和实例的原型对象中,都存有父类的实例属性,不是特别完美。这个问题的解决方案是寄生组合式继承。

四、原型式继承

  基本思想是借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。

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

var person = {
    name: "Haha",
    friends: ["Shelby", "Court", "Van"]
};

var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");

这种原型式继承,要求你必须有一个对象可以作为另一个对象的基础。ECMAScript 5中通过新增Object.create()方法规范化了原型式继承。这个方法接收两个参数:一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。这种方式定义的属性会覆盖原型对象上的同名属性。

五、寄生式继承

  寄生式继承的思路与计生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。

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

var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
};

var anotherPerson = createAnother(person);

这种方式使用了原型式继承,并在自己的实例对象上,又添加了自己的方法。在主要考虑对象而不是自定义类型和构造函数的情况下,继承式继承是一种有用的模式。示例代码中的object()函数不是必需的;任何能够返回新对象的函数都适用此模式。

六、寄生组合式继承

  前面的第三种组合式继承模式最大的问题是:无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。解决办法是使用寄生组合式继承:通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。背后的思路是:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。所以,我们可以使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。

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

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

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

inheritPrototype(SubType, SuperType);

SubType.prototype.sayAge = function() {
    console.log(this.age);
};

这个例子的高效率体现在它只调用了一次SuperType构造函数,并且避免了在SubType.prototype上创建不必要的、多余的属性。同时,保持了原型链不变。能正常使用instanceof和isPrototypeOf()。这就是关于JavaScript中对象继承的完美的解决方案。

七、总结

  当我们仔细去分析前面的六种方法的时候,其中的某几个模式之间的差异是非常小的。其实所有的这些模式无非就是利用了JavaScirpt的那些底层特性:构造函数、原型链、不同对象中属性的访问性的差异。

关于JavaScript中对象的继承实现的学习总结的更多相关文章

  1. javascript中对象函数继承的概念

    什么是函数对象?这个对象既是通常意义上的对象,又可以加上括号直接执行的函数. 产生函数对象的方式有两种:1.通过function关键字产生:var fn = function(){};2.实例化Fun ...

  2. 理解JavaScript中的原型继承(2)

    两年前在我学习JavaScript的时候我就写过两篇关于原型继承的博客: 理解JavaScript中原型继承 JavaScript中的原型继承 这两篇博客讲的都是原型的使用,其中一篇还有我学习时的错误 ...

  3. javascript中对象字面量的理解

    javascript中对象字面量与数组字面量 第一部分 我们知道JavaScript中的数据类型有基本数据类型和引用类型,其中Object类型就是非常常用的类型.那么如果创建一个Object类型的实例 ...

  4. javascript中的原型继承

    在Javascript面向对象编程中,原型继承不仅是一个重点也是一个不容易掌握的点.在本文中,我们将对Javascript中的原型继承进行一些探索. 基本形式 我们先来看下面一段代码: <cod ...

  5. JavaScript中一个对象如何继承另外一个对象

    如题,JavaScript中一个对象a如何继承另外一个对象b.即将b中的属性和方法复制到a中去. 面试中遇到了这个问题,当时脑子里的想法是: 1.除了循环遍历复制,还能怎样 2.javascript中 ...

  6. JavaScript中对象的属性

    在JavaScript中,属性决定了一个对象的状态,本文详细的研究了它们是如何工作的. 属性类型 JavaScript中有三种不同类型的属性:命名数据属性(named data properties) ...

  7. JavaScript中对象转换为原始值的规则

    JavaScript中对象转换为原始值遵循哪些原则? P52 对象到布尔值对象到布尔值的转换非常简单:所有的对象(包括数字和函数)都转换为true.对于包装对象亦是如此:new Boolean(fal ...

  8. 【你不知道的javaScript 上卷 笔记7】javaScript中对象的[[Prototype]]机制

    [[Prototype]]机制 [[Prototype]]是对象内部的隐试属性,指向一个内部的链接,这个链接的作用是:如果在对象上没有找到需要的属性或者方法引用,引擎就 会继续在 [[Prototyp ...

  9. javascript中对象的深度克隆

    记录一个常见的面试题,javascript中对象的深度克隆,转载自:http://www.2cto.com/kf/201409/332955.html 今天就聊一下一个常见的笔试.面试题,js中对象的 ...

随机推荐

  1. WPF:父窗口与子窗口的层次关系

    关于子窗体的层级关系总结一下哈,希望能对大家有些帮助 假设有这样两个窗体:RootWindow,SubWindow,在RootWindow中引发某事件而显示SubWindow 1,如果弹出窗体(比如S ...

  2. 转:遗传算法解决TSP问题

    1.编码 这篇文章中遗传算法对TSP问题的解空间编码是十进制编码.如果有十个城市,编码可以如下: 0 1 2 3 4 5 6 7 8 9 这条编码代表着一条路径,先经过0,再经过1,依次下去. 2.选 ...

  3. Java递归算法——二分查找

    import java.lang.reflect.Array; import java.nio.Buffer; import java.util.Arrays; import java.util.Ra ...

  4. C++常用的#include头文件总结

    C++常用的#include头文件总结 这篇文章主要介绍了C++常用的#include头文件,对初学者理解C++程序设计大有好处的相关资料   本文详细罗列了C++所包含的头文件的名称及作用说明,比较 ...

  5. CodeForces 689B Mike and Shortcuts (BFS or 最短路)

    题目链接:http://codeforces.com/problemset/problem/689/B 题目大意: 留坑 明天中秋~

  6. cmake 编译 c++ dll 的一个例子(更新2:增加 python 调用方法)

    CMakeLists.txt project(xxx) add_library(xxx SHARED xxx.cpp) add_executable(yyy yyy.cpp) target_link_ ...

  7. jQM基本代码

    <div data-role="page"> <div data-role="header" data-position="fixe ...

  8. aufomaper Queryable Extensions ProjectTo

    When using an ORM such as NHibernate or Entity Framework with AutoMapper's standard Mapper.Map funct ...

  9. spring 容器技术入门

    官方文档 翻译 https://waylau.gitbooks.io/spring-framework-4-reference/content/III.%20Core%20Technologies/C ...

  10. Python之闭包

    Python之闭包 我们知道,在装饰器中,可以在函数体内创建另外一个函数,例如: def makebold(fn): def wrapped(): return "<b>&quo ...