引言

  在JavaScript中,实现继承的主要方式是通过原型链技术。这一篇文章我们就通过介绍JavaScript中实现继承的几种方式来慢慢领会JavaScript中继承实现的点点滴滴。

原型链介绍

  原型链作为JS实现继承的主要方式,其基本思想是:利用原型让一个引用类型继承另一个引用类型的属性和方法。我们可以简单回顾下构造函数、原型对象和实例对象之间的关系。每一个构造函数都有一个指向原型对象的指针,当然原型对象的构造器属性也指向构造函数对象,而实例对象内部有prototype属性指向原型对象。如果我们让原型对象等于另一个引用类型的实例。那么这时候的原型对象将包含一个指向另一个原型对象的指针。当然,另一个原型对象也包含着指向另一个构造函数的指针。如果另一个原型又是另一个引用类型的实例,那么上述关系依然成立,就这样层层的推进,就构成了实例与原型的链条。这就是原型链的基本原理。

  下面我们来看个实现继承的基本例子:

 /**
* 实现继承的基本方式(原型链基本用法)
**/
function SuperType() {
this.property = true;
} SuperType.prototype.getSuperValue = function () {
return this.property;
} function SubType() {
this.subProperty = false;
} //设置子类型的原型对象是超类的实例
SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function () {
return this.subProperty;
} var instance = new SubType();
alert(instance.getSuperValue()); //true

  在这段代码中,我们看到了最基本的使用原型链来实现继承的方式。在这段代码中最核心的部分就是地17行代码。我们SubType的原型对象设置为SuperType的一个实例。这样存在于SuperType中的所有属性和方法现在也都存在于SubType的原型对象中。我们给SubType的原型中添加了getSubValue方法,实际就是在继承SuperType的属性和方法的基础上又添加了一个方法。

  下面请看详细的关系图

  通过这张图我们看到instance指向的是SubType的原型对象。而SubType原型又指向SuperType原型。通过实现原型链,本质上扩展了前面介绍的原型搜索机制。当以读取模式访问一个属性的时候,首先会在实例中进行搜索,如果搜索不到还会到实例的原型对象中进行搜索。在通过原型链继承的情况下,搜索过程就会沿着原型链继续往上搜索。例如:调用getSuperValue。1、第一步搜索instance实例对象。2、搜索SubType原型对象。3、搜索SuperType原型对象。最后在第三步才找到方法进行调用。

  讲到这里,我们千万别忘记了。所有的引用类型都继承自Object对象。而这个继承也是通过原型链实现的。所有函数的默认原型都是Object的实例。因此默认原型会包含一个指针指向Object.prototype。这也正是所有引用类型都会继承toString()、toValueOf()方法的根本原因。下面这张图很好的表达了这一点。

  

  我们调用instance.toString()方法实际上调用的是Object原型对象中的toString()方法。

  原型链存在的问题

  原型链虽然强大,可以用它来实现继承,但是也存在一些致命的问题。其中,最主要的问题是包含引用类型值的原型。想必大家在上一篇文章中也肯定还记得在原型对象中定义引用类型值的属性会被所有的实例共享。现在使用原型链,原型实际上会变成另一个引用类型的实例。如果我们在这个实例中定义了引用类型的属性(比如:Array)。那么子类型的所有实例都会共享这个实例中的这个属性值。这实际上就回到了我们上一篇遇到的问题。所以,我们首先想到的还是借用构造函数。让实例属性和原型属性分开的方式来进行解决。

  借用构造函数

  在解决原型中包含引用类型值带来的问题的过程中,人们开始使用一种叫借用构造函数的方式来解决此类问题。这种方法很简单。就是在子类构造函数中调用父类构造函数。记住:函数只是在特定环境中环境中执行代码的对象,因此可以使用call()或者apply()函数来在新创建的对象上调用构造函数。请看下面的例子:

 /**
* 借用构造函数
*/
function SuperType() {
this.color = ["red", "blue", "green"];
} function SubType() {
SuperType.call(this);
} var instance1 = new SubType();
instance1.color.push("black");
alert(instance1.color); //"red", "blue", "green","black" var instance2 = new SubType();
alert(instance1.color); //"red", "blue", "green"

  在这段代码中,我们看第9行。SubType构造函数调用了SuperType的构造函数。通过使用call()或者apply()函数,我们在新创建的对象上调用SuperType的构造函数。这样我们会在新创建的对象上执行超类构造函数的初始化代码(this指向的是新创建的对象)。所以每一个新创建的实例都有color的副本了。

  借用构造函数的问题

  如果我们仅仅借用构造函数的方式,那么我们将无法避免构造函数模式带来的问题。1、方法都在构造函数中定义,根本没有办法谈函数复用的问题。2、在超类中定义的方法在子类中根本不可见,结果所有类型都只能使用构造函数模式。为了解决这个问题,我们下面介绍一种方式:组合继承。

  组合继承

  组合继承技术是将原型链和借用构造函数技术组合在一起使用。其背后的思想是:通过使用原型链来实现对原型属性和方法的继承,通过借用构造函数模式实现对实例属性的继承。这样,既通过在原型上定义的方法实现了函数的复用,又能保证每一个实例都有自己的属性。请看下面的例子:

 /**
* 组合继承
**/
function SuperType(name) {
this.name = name;
this.color = ["red", "blue", "black"];
} SuperType.prototype.sayName = function () {
return this.name;
} function SubType(name, age) {
SuperType.call(this, name);
this.age = age;
} SubType.prototype = new SuperType(); SubType.prototype.sayAge = function () {
return this.age;
} var instance1 = new SubType("Nicolas", 29);
instance1.color.push("green");
alert(instance1.color); //"red", "blue", "black","green"
alert(instance1.sayName()); //Nicolas
alert(instance1.sayAge()); // var instance2 = new SubType("Grey", 26);
alert(instance2.color); //"red", "blue", "black"
alert(instance2.sayName()); //Grey
alert(instance2.sayAge()); //

  通过以上的例子我们看到,我们在创建SubType实例的时候,调用SubType构造函数,在构造函数中通过call方法,使每一个实例上都有name、color属性的一份副本。同时设置SubType的原型为SuperType的实例,有可以进一步获取SuperType定义的共享属性和方法。实现了我们前面提到的目标。此处有一点需要注意:color属性其实对于SubType实例来说应该有两份,一份在实例中,还有一份在原型对象中,只是原型对象中的那一份被实例中的那一份覆盖了。还记得对象中属性值的搜索机制吗?

  应该说,组合继承融合了原型链和构造函数方式的优点,成为JavaScript中最常用的一种继承模式。但是对于它的改进还是存在的。请看下面把。

  寄生组合式继承

  虽然组合继承是JavaScript中最常用的一直继承模式,但是它也存在着明显的缺点。例如:1、无论在什么情况下都需要调用两次超类的构造函数。2、子类的实例都会包含超类实例的全部实例属性,但是我们只能在调用超类构造函数的时候重写这些属性,让它覆盖原型对象中的属性值。请看下图:

  

  其实,我们实现继承的关键无非就是一个超类原型对象的副本而已。在组合继承中我们使用超类实例作为原型对象,这样间接的关联上了超类的原型对象。但是我们无法避免子类新的原型对象中那些冗余的实例属性。如果我们定义一个自定义类型里面不存在任何实例属性,将它的原型对象设置为我们需要关联的超类的原型对象。这样不就不必存储那些不必要的实例属性了吗?看下面的代码

 /**
* 组合继承(改进版)
**/
function SuperType(name) {
this.name = name;
this.color = ["red", "blue", "black"];
} SuperType.prototype.sayName = function () {
return this.name;
} function SubType(name, age) {
SuperType.call(this, name);
this.age = age;
} //SubType.prototype = new SuperType();
SubType.prototype = object(SuperType.prototype); SubType.prototype.sayAge = function () {
return this.age;
} var instance1 = new SubType("Nicolas", 29);
instance1.color.push("green");
alert(instance1.color); //"red", "blue", "black","green"
alert(instance1.sayName()); //Nicolas
alert(instance1.sayAge()); // var instance2 = new SubType("Grey", 26);
alert(instance2.color); //"red", "blue", "black"
alert(instance2.sayName()); //Grey
alert(instance2.sayAge()); // function object(obj) {
function F() { }
F.prototype = obj;
return new F();
}

  在代码中我们我们看到,我们定义的object方法的本质就是使用一个自定义的引用类型来复制超类的原型对象。并且这个对象里面很干净,不存在任何实例属性。这初步解决了我们上面提到的属性重复存储的问题。下面咱们看看还有没有进一步的优化空间。

  下面介绍一种更加科学的方法,寄生组合继承的基本模式。并且配合详细的图片给大家分析下。请看代码:

/**
* 寄生组合继承基本模式
**/
function initPrototype(SubType, SuperType) {
var prototype = object(SuperType.prototype);
prototype.constructor = SubType;
SubType.prototype = prototype;
}

  下面请看完整的代码例子:

 /**
* 组合继承(最终版)
**/
function SuperType(name) {
this.name = name;
this.color = ["red", "blue", "black"];
} SuperType.prototype.sayName = function () {
return this.name;
} function SubType(name, age) {
SuperType.call(this, name);
this.age = age;
} //SubType.prototype = new SuperType();
//SubType.prototype = object(SuperType.prototype);
initPrototype(SubType, SuperType); SubType.prototype.sayAge = function () {
return this.age;
} var instance1 = new SubType("Nicolas", 29);
instance1.color.push("green");
alert(instance1.color); //"red", "blue", "black","green"
alert(instance1.sayName()); //Nicolas
alert(instance1.sayAge()); // var instance2 = new SubType("Grey", 26);
alert(instance2.color); //"red", "blue", "black"
alert(instance2.sayName()); //Grey
alert(instance2.sayAge()); // function object(obj) {
function F() { }
F.prototype = obj;
return new F();
} /**
* 寄生组合继承基本模式
**/
function initPrototype(SubType, SuperType) {
var prototype = object(SuperType.prototype);
prototype.constructor = SubType;
SubType.prototype = prototype;
}

浅谈JavaScript中的继承的更多相关文章

  1. 浅谈 JavaScript 中的继承模式

    最近在读一本设计模式的书,书中的开头部分就讲了一下 JavaScript 中的继承,阅读之后写下了这篇博客作为笔记.毕竟好记性不如烂笔头. JavaScript 是一门面向对象的语言,但是 ES6 之 ...

  2. 浅谈javaScript中的继承关系<一>

    // JavaScript Document //创建三个构造函数 function Shape(){ this.name='ahape'; this.toString=function(){retu ...

  3. 浅谈JavaScript中的闭包

    浅谈JavaScript中的闭包 在JavaScript中,闭包是指这样一个函数:它有权访问另一个函数作用域中的变量. 创建一个闭包的常用的方式:在一个函数内部创建另一个函数. 比如: functio ...

  4. 浅谈JavaScript中的null和undefined

    浅谈JavaScript中的null和undefined null null是JavaScript中的关键字,表示一个特殊值,常用来描述"空值". 对null进行typeof类型运 ...

  5. 浅谈JavaScript中的正则表达式(适用初学者观看)

    浅谈JavaScript中的正则表达式 1.什么是正则表达式(RegExp)? 官方定义: 正则表达式是一种特殊的字符串模式,用于匹配一组字符串,就好比用模具做产品,而正则就是这个模具,定义一种规则去 ...

  6. 浅谈Javascript中的原型、原型链、继承

    构造函数,原型,实例三者的关系 构造函数: 构造函数是创建对象的一种常用方式, 其他创建对象的方式还包括工厂模式, 原型模式, 对象字面量等.我们来看一个简单的构造函数: function Produ ...

  7. 浅谈JavaScript中继承的实现

    谈到js中的面向对象编程,都有一个共同点,选择原型属性还是构造函数,两者各有利弊,而就片面的从js的对象创建以及继承的实现两个方面来说,官方所推荐的是两个相结合,各尽其责,各取其长,在前面的例子中,我 ...

  8. 浅谈JavaScript中的内存管理

    一门语言的内存存储方式是我们学习他必须要了解的,接下来让我浅谈一下自己对他的认识. 首先说,JavaScript中的变量包含两种两种类型: 1)值类型或基本类型:undefined.null.numb ...

  9. 【总结】浅谈JavaScript中的接口

    一.什么是接口 接口是面向对象JavaScript程序员的工具箱中最有用的工具之一.在设计模式中提出的可重用的面向对象设计的原则之一就是“针对接口编程而不是实现编程”,即我们所说的面向接口编程,这个概 ...

随机推荐

  1. BZOJ 1853: [Scoi2010]幸运数字

    1853: [Scoi2010]幸运数字 Time Limit: 2 Sec  Memory Limit: 64 MBSubmit: 2117  Solved: 779[Submit][Status] ...

  2. JQuery冲突问题,以及含有jquery的框架与jquery冲突

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  3. Jenkins配置的邮件无法发送的问题

    一.不要使用系统默认的插件,请使用[Extended E-mail Notification] 二.一下的选项要和你发邮件人填写的保持一致

  4. POJ 3662 Telephone Lines(二分+最短路)

    查看题目 最小化第K大值. 让我怀疑人生的一题目,我有这么笨? #include <cstdio> #include <queue> #include <cstring& ...

  5. php常用函数(持续更新)

    每一种编程语言在用的过程中都会发现有时候要一种特定需求的功能函数,结果没有内置这样的函数,这个时候就需要自己根据已有函数编写尽可能简单的函数,下面是我在做php相关工作时积累下的函数,会持续更新,您要 ...

  6. Linux下,使用Git管理 dotfiles(配置文件)

    1.管理你的 dotfiles 作为一个计算机深度使用者,并且长期使用 Linux 作为主要操作系统,折腾各种功能强大的软件是常有的事儿.这些软件有它们各自的配置文件,通常以 . 开头,因此有人管它们 ...

  7. UML 简单介绍

    Unified modeling Language - 统一建模语言

  8. ZOJ 1107FatMouse and Cheese(BFS)

    题目链接 分析: 一个n * n的图,每个点是一个奶酪的体积,从0,0开始每次最多可以走k步,下一步体积必须大于上一步,求最大体积和 #include <iostream> #includ ...

  9. asp.net下调用Matlab生成动态链接库

    对于这次论文项目,最后在写一篇关于工程的博客,那就是在asp.net下调用matlab生成的dll动态链接库.至今关于matlab,c/c++(opencv),c#(asp.net)我总共写了4篇配置 ...

  10. maven学习讲解

    参考链接:http://www.cnblogs.com/bigtall/archive/2011/03/23/1993253.html 1.前言 Maven,发音是[`meivin],"专家 ...