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. MVC4的过滤器

    过滤器 提供的四种基本类型过滤器接口,IAuthorizationFilter.IActionFilter.IResultFilter和IExceptionFilter,可通过继承对应的接口和Filt ...

  2. wcf系列5天速成——第一天 binding的使用(1)

    原文:wcf系列5天速成--第一天 binding的使用(1) 作为WCF速成系列,只介绍些项目开发中常用到的实战知识. 学习wcf,还是对其中的几个术语要了解一下.wcf中有一个ABC的概念,就是 ...

  3. Backup and Recovery Strategies1

    2.1.Data Recovery Strategy Determines Backup Strategy 在设计备份策略.如若数据恢复需求和数据恢复战略启动.每种类型的数据恢复需要你采取相应的备份类 ...

  4. 使用SQL Server Driver for PHP解决PHP连接MSSQL乱码的问题

    原文 使用SQL Server Driver for PHP解决PHP连接MSSQL乱码的问题 最近帮客户写了一个.net商城网站的发布接口,大家都知道.net一般都使用MSSQL数据库,但鱼丸不会. ...

  5. JDK动态代理机制

    JDK Proxy OverView jdk的动态代理是基于接口的.必须实现了某一个或多个随意接口才干够被代理.并且仅仅有这些接口中的方法会被代理. 看了一下jdk带的动态代理api,发现没有样例实在 ...

  6. hdu 4858 项目管理(STL集装箱)

    项目管理 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submi ...

  7. c#分部类型详解

    一.先看代码来理解 代码一 class ClassA { void A(){;} void B(){;} } 代码二 partial class ClassA { void A(){;} } part ...

  8. OCP-1Z0-051-题目解析-第5题

    5. Which SQL statements would display the value 1890.55 as $1,890.55? (Choose three .) A. SELECT TO_ ...

  9. JQUERY简写案例

    源代码: <script ttype="text/javascript"> $(function(){ $(".btn").eq(0).click( ...

  10. SQL Server 2008 允许远程链接,适用于广域网和局域网

    用户在使用SQL Server 2008远程链接时,可能会弹出如下对话框: 在链接SQL服务器时发生网络链接错误或特定实例错误.SQL服务器不存在或者链接不成功.请验证用户名是否正确或SQL服务器是否 ...