JavaScript原型与继承

原型

在JavaScript中,每个函数都有一个prototype属性,这个属性是一个指针,指向该函数的原型对象。这个原型对象为所有该实例所共享。在默认情况下,原型对象包含一个属性叫做constructor,它指向prototype属性所在的函数指针。

图片和例子来自《JavaScript高级程序设计(第三版)》。

1
2
3
4
5
6
7
8
9
10
11
function Person () {}
 
Person.prototype.name = 'Nicholas';
Person.prototype.age = 29;
Person.prototype.job = 'Software Enginner';
Person.prototype.sayName = function () {
    alert(this.name);
};
 
var person1 = new Person(),
    person2 = new Person();

但是改变原型时,可能会改变constructor,比如:

1
Dog.prototype = new Animal();

此时Dog.prototype.constructor指向构造函数Animal,如果有需要,可以重写constructor,比如

1
Dog.prototype.constructor = Dog;

基于原型链的继承

通过让子类型的原型指向父类型的实例来实现基于原型链的继承。其本质是原型搜索机制:当访问一个实例的数据或方法时,首先在实例中寻找,实例中找不到后自然会沿着原型链寻找。这样继承之后,子类型的实例可以共享父类型的数据和方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function SuperType (name) {
    this.name = name;
    this.age = 24;
    this.foo = ['bar1', 'bar2'];
}
SuperType.prototype.say = function () {
    console.log(this.name, this.age, this.foo);
};
function SubType () {}
 
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
 
var sub1 = new SubType('zdy1');
var sub2 = new SubType('zdy2');
 
sub1.age = 23;
sub1.foo.push('bar3');
 
sub1.say(); // undefined 23 ["bar1", "bar2", "bar3"]
sub2.say(); // undefined 24 ["bar1", "bar2", "bar3"]

在上面代码中,SubType通过原型继承了SuperTpe,但是同时也暴露了两个问题:

  1. 子类型无法为构造函数传入参数,所以SubType的name属性为undefined。
  2. 所有子类型共享父类型中的引用类型的数据或方法,也就是说,他们的引用类型属性都是同一个地址,修改引用类型的属性会影响所有子类型。

其中有一句需要说明一下:

1
SubType.prototype.constructor = SubType;

在“SubType.prototype = new SuperType();”之后,“SubType.prototype.constructor”是指向SuperType的,需要把它更正回来。否则每一个SubType的实例的constructor都指向了SuperType,这显然是不科学的。

基于原型链的继承方式很少单独使用。

借用构造函数

在子类型构造函数中调用父类型的构造函数,叫做“借用构造函数”,也可以实现继承。它的思想是在子类型中重新调用一遍父类型的构造函数来初始化数据和方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function SuperType (name) {
    this.name = name;
    this.age = 24;
    this.foo = ['bar1', 'bar2'];
}
SuperType.prototype.say = function () {
    alert(this.name);
};
function SubType () {
    // 继承了SuperType
    SuperType.apply(this, arguments);
}
var sub = new SubType('zdy');
console.log(sub) //SubType {name: "zdy", age: 24, foo: Array[2]}
sub.say(); // Uncaught TypeError: Object #<SubType> has no method 'say'

借用构造函数的模式可以在构造函数中传入参数,但子类型不能共享父类型在原型上的数据和方法。所以,它也很少单独使用。

组合继承

组合继承可谓整合了上面两种方法的特点:

  1. 子类型在构造函数中借用父类型的构造函数,在初始化时可以传递参数。 ——继承属性
  2. 子类型在原型上共享父类型数据和方法。 ——继承方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function SuperType (name) {
    this.name = name;
    this.age = 24;
    this.foo = ['bar1', 'bar2'];
    this.sex = 'other';
}
SuperType.prototype.say = function () {
    console.log(this.name, this.age, this.foo);
};
function SubType (name, sex) {
    SuperType.apply(this, arguments);
    this.sex = sex;
}
 
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
 
var sub1 = new SubType('zdy1', 'male');
var sub2 = new SubType('zdy2', 'female');
 
sub1.age = 23;
sub1.foo.push('bar3');
 
sub1.say(); // zdy1 23 ["bar1", "bar2", "bar3"]
sub2.say(); // zdy2 24 ["bar1", "bar2"]
console.log(sub1.sex, sub2.sex); // male female

组合式继承也有不足之处,就是它实际调用了两次父类型的构造函数(第10行和第14行),并且在子类型的构造函数中重写(覆盖)了原型中的属性。所以改进的思路是,如何在不添加多余的、被覆盖的属性的同时,获得父类型的原型?请看最后一种继承方法。

寄生组合式继承

《JavaScript高级程序设计(第3版)》对这种继承方式给予了肯定:

开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。

主要是它只调用了一次父类型的构造函数,所以避免了子类型在prototype上创建不必要的、多余的属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function inheritPrototype (subType, superType) {
    function F () {}
    F.prototype = superType.prototype;
    var proto = new F();
    proto.constructor = subType;
    subType.prototype = proto;
}
 
function SuperType (name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
}
SuperType.prototype.sayName = function () {
    alert(this.name);
};
function SubType (name, age) {
    SuperType.call(this, name);
    this.age = age;
}
inheritPrototype(SubType, SuperType);
var instance = new SubType('Nicholas', 29);
 
// SubType {name: "Nicholas", colors: Array[3],
// age: 29, constructor: function, sayName: function}
console.log(instance);
1
  

以上代码改自《JavaScript高级程序设计(第3版)》,说说我个人对这种方法思路的理解。

在inheritPrototype函数中,创建一个临时构造函数F()并实例化,来获得父类型原型对象的一个副本。因为它只复制了父类型的原型对象,从而并没有包括父类型构造函数中的属性与方法。相当于在继承过程中,绕了一个弯来躲避再一次初始化父类型的构造函数,所以不会在子类型的原型中存在多余的父类型属性。

 
 
 
标签: JS

JavaScript原型与继承的更多相关文章

  1. 深入理解:JavaScript原型与继承

    深入理解:JavaScript原型与继承 看过不少书籍,不少文章,对于原型与继承的说明基本上让人不明觉厉,特别是对于习惯了面向对象编程的人来说更难理解,这里我就给大家说说我的理解. 首先JavaScr ...

  2. JavaScript原型与继承的秘密

    在GitHub上看到的关于JavaScript原型与继承的讲解,感觉很有用,为方便以后阅读,copy到自己的随笔中. 原文地址:https://github.com/dreamapplehappy/b ...

  3. JavaScript 原型与继承

    JavaScript 原型与继承 JavaScript 中函数原型是实现继承的基础.prototype.construct.原型链以及基于原型链的继承是面向对象的重要内容 prototype 原型即 ...

  4. javascript原型链继承

    一.关于javascript原型的基本概念: prototype属性:每个函数都一个prototype属性,这个属性指向函数的原型对象.原型对象主要用于共享实例中所包含的的属性和方法. constru ...

  5. JavaScript 原型与继承机制详解

    引言 初识 JavaScript 对象的时候,我以为 JS 是没有继承这种说法的,虽说 JS 是一门面向对象语言,可是面向对象的一些特性在 JS 中并不存在(比如多态,不过严格来说也没有继承).这就困 ...

  6. 8条规则图解JavaScript原型链继承原理

    原形链是JS难点之一,而且很多书都喜欢用一大堆的文字解释给你听什么什么是原型链,就算有图配上讲解,有的图也是点到为止,很难让人不产生疑惑. 我们先来看一段程序,友情提示sublimeText看更爽: ...

  7. 【Javascript】Javascript原型与继承

    一切都是对象! 以下的四种(undefined, number, string, boolean)属于简单的值类型,不是对象.剩下的几种情况——函数.数组.对象.null.new Number(10) ...

  8. 【前端知识体系-JS相关】深入理解JavaScript原型(继承)和原型链

    1. Javascript继承 1.1 原型链继承 function Parent() { this.name = 'zhangsan'; this.children = ['A', 'B', 'C' ...

  9. JavaScript原型及继承

    一.浅谈原型 首先我们要知道创建对象的方法有两种: 1.通过字面量的方式直接创建 var obj = { name:'baimao', age:21 } 2.通过构造函数创建对象 function P ...

随机推荐

  1. ASP.NET 5简介

    ASP.NET 5简介 解读ASP.NET 5 & MVC6系列(1):ASP.NET 5简介 2015-05-13 09:14 by 汤姆大叔, 3379 阅读, 39 评论, 收藏, 编辑 ...

  2. iOS开发- &quot;duplicate symbol for architecture i386&quot; 解决的方法

    今天整合项目的时候, 遇到了这样一个问题. duplicate symbol _flag in: /Users/apple/Library/Developer/Xcode/DerivedData/bl ...

  3. 【地图API】为何您的坐标不准?如何纠偏?

    原文:[地图API]为何您的坐标不准?如何纠偏? 摘要:各种坐标体系之间如何转换?到底有哪些坐标体系?什么是火星坐标?为什么我的坐标,在地图上显示会有偏移?本文详细解答以上问题.最后给出坐标拾取工具. ...

  4. 【百度地图API】——国内首款团购网站的地图插件

    原文:[百度地图API]--国内首款团购网站的地图插件 摘要: 本文介绍了一款应用在团购网站上的地图插件,适用于目前非常流行的团购网站.使用这款地图插件,无需任何编程技术,你就把商家的位置轻松地标注在 ...

  5. (转)迎接 Entity Framework 7

    对实体框架的下一版本的开发正在顺利进行中.我在 2014 年度北美 TechEd 上第一次了解 EF 团队的工作内容,当时项目经理 Rowan Miller 讨论了 Entity Framework ...

  6. AngularJs应用页面

    AngularJs应用页面切换优化方案   葡萄城的一款尚在研发中的产品,对外名称暂定为X项目.其中使用了已经上市的wijmo中SpreadJS产品,另外,在研发过程中整理了一些研发总结分享给大家.如 ...

  7. HDU 3094 A tree game 树删边游戏

    叶节点SG值至0 非叶节点SG值至于它的所有子节点SG值添加1 XOR和后 #include <cstdio> #include <cstring> #include < ...

  8. mysql utf8mb4与emoji表情

    一 什么是Emoji emoji就是表情符号:词义来自日语(えもじ,e-moji,moji在日语中的含义是字符) 表情符号现已普遍应用于手机短信和网络聊天软件. emoji表情符号,在外国的手机短信里 ...

  9. sudo找不到npm的解决办法及及使用cnpm加速[已解决]

    sudo ln -s /usr/local/bin/node /usr/bin/node sudo ln -s /usr/local/lib/node /usr/lib/node sudo ln -s ...

  10. Java开发工具IntelliJ IDEA使用教程:创建新的Andriod项目

    IntelliJ IDEA社区版作为一个轻量级的Java开发IDE,本身是一个开箱即用的Android开发工具. 注意:在本次的教程中我们将以Android平台2.2为例进行IntelliJ IDEA ...