一 原型链

1. 代码示例

function SuperType() {
this.superProperty = true;
} SuperType.prototype.getSuperValue = function() {
return this.superProperty;
} function SubType() {
this.subProperty = false;
} SubType.prototype = new SuperType(); //将 SuperType类型的实例 作为 SubType类型的 原型对象, 这样就重写了SubType的原型对象(没有使用SubType默认的原型对象), 实现了继承。 SubType.prototype.getSubValue = function() {
return this.subProperty;
} const subTypeInstance = new SubType();
console.log(subTypeInstance.getSuperValue());//true

详见我的另一篇博客《原型与原型链》 的"二、实现继承的主要范式:原型链

"。

二、 借用构造函数(经典继承)

1.代码示例

function SuperType() {
this.colors = ['red', 'blue', 'green'];
} function SubType() {
SuperType.call(this);//在子类型构造函数内部调用超类型构造函数,继承了SuperType
} var subTypeInstance1 = new SubType();
subTypeInstance1.colors.push('yellow');
console.log(subTypeInstance1);//['red', 'blue', 'green','yellow'] var subTypeInstance2 = new SubType();
console.log(subTypeInstance2.colors);//['red', 'blue', 'green']

基本思想就是在子类型构造函数内部调用超类型构造函数。

2. 优点

(1)子类型每个实例都会继承一份独立的超类型属性副本

通过使用call方法(或apply),实际上是在未来新创建子类型实例时当场调用了超类型的构造函数,也就是在初始化子类型实例时才把超类型的属性添加到子类型实例上。那么子类型的每个实例都会拥有一份独立的超类型属性副本。 这样不同的子类型实例对同一个继承来的属性进行修改(例如对数组属性进行push),也不会互相影响。

(2)可以在子类型构造函数中向超类型构造函数传递参数
function SuperType(name) {
this.name = name;
} function SubType(name) {
SuperType.call(this,name);
} var subTypeInstance1 = new SubType('Bonnie');
console.log(subTypeInstance1.name);//"Bonnie" var subTypeInstance2 = new SubType('Summer');
console.log(subTypeInstance2.name);//"Summer"

3. 缺点

(1)不能做到函数复用:无法避免构造函数模式的问题——使用构造函数模式创建的每个实例都包含着各自独有的同名函数,故函数复用无从谈起。

(2)子类型创建方式限制:而在超类型的原型中定义的方法,对子类而言是不可见的,所以所有子类型都只能通过构造函数模式创建。

三、 组合继承(伪经典继承)

1. 代码示例

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); //第二次调用超类型构造函数SuperType
//自己的属性
this.age = age;
} //继承方法:SubType.prototype也会得到继承的属性,不过会被上述构造函数中call方法继承的属性作为实例属性覆盖掉。
SubType.prototype = new SuperType(); //第一次调用超类型构造函数SuperType
SubType.prototype.constructor = SubType;//重写prototype会割裂子类型原型与子类型构造函数的关系,故要加上这么一句
//自己的方法
SubType.sayAge = function() {
console.log(this.age);
}

将原型链和借用构造函数组合起来:使用原型链实现对超类型原型上的方法和属性的继承,使用借用构造函数实现对超类型实例属性和方法的继承。一般超类型原型上就只有方法,超类型实例上只有属性(即 组合使用构造函数模式和原型模式,参见3.4)。这样一来,该方式就是:用原型链实现对超类型原型上方法的继承,用借用构造函数实现对超类型实例上属性的继承。

2. 优点

组合继承用 原型链实现对 超类型原型上方法的继承,用 借用构造函数实现对 超类型实例上属性的继承。这样可以让子类型的不同实例既分别拥有独立的属性(尤其是引用类型属性,如colors数组),又可以共享相同的方法。

组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,是 JavaScript中最常用的继承模式

3. 缺点

无论在什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型时,一次是在子类型构造函数内部。这样的话,虽然子类型会包含超类型的全部属性,但是对于超类型的实例属性而言,调用子类型的构造函数时会重写一遍这些实例属性。

四、 原型式继承

1. 代码示例

function object(o) {
function F(){};//先构造一个临时性的构造函数
F.prototype = o;//将传入的对象作为该构造函数的原型
return new F();//返回临时类型的新实例
} //使用
var person = {
name:'Bonnie',
friends: ['Summer', 'Spring']
} var person1 = object(person);
person1.name = 'Huiyun';
person1.friends.push('Tony'); var person2 = object(person);
person2.name = 'Huiyun';
person2.friends.push('Joy'); console.log(person1.friends);//["Summer", "Spring", "Tony", "Joy"]
console.log(person2.friends);//["Summer", "Spring", "Tony", "Joy"]
console.log(person.friends);//["Summer", "Spring", "Tony", "Joy"]

思想: 借助原型可以基于已有的对象(而非类型)创建新对象(而非创建自定义类型)。其实,object对传入其中的对象执行了一次 浅复制

2. 优点

可以基于已有的对象创建新对象,而且还不必创建新类型。适于基于已有对象加以修改得到另一个对象。

在只想让一个对象与另一个对象保持类似,又不想兴师动众创建构造函数的情况下,原型式继承完全可以胜任。

延伸:ES6的Object.create()

该方法规范化了原型式继承,在只传入一个参数的情况下,和上述object达到的效果相同。该方法的第二个参数为新对象额外属性组成的对象,也就是简化了上述object的后续用法。

语法:
Object.create(protoObj, [newPropertiesObj])

参数:

  • protoObj: 新创建对象的原型对象
  • newPropertiesObj:可选。 要添加(或重写)到新对象上的可枚举的实例属性的属性描述符及其名称组成的对象(与Object.defineProperties()的第二个参数相同)。

newPropertyiesObj语法:

{
prop1Name:{
value: valueConent,
writable: false(default)/true
enumerable: false(default)/true
configuragle: false(default)/true
},
prop2Name: {
...
},
...
}

详见https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties

返回值: 一个带有指定的原型对象属性和自己新添加的实例属性的对象

Eg:
var person = {
name: 'Bonnie',
friends: ['Summer','Spring']
} var person1 = Object.create(person, {
name:{
value:'Huiyun'
}
}); console.log(person1);//{name: "Huiyun"}
console.log(person1.friends);// ["Summer", "Spring"]

3.缺点

引用类型属性共享: 该方法基于已有对象创建新对象,但是对新对象修改引用类型属性,已有的基础对象也会受到修改,基于基础对象的其他对象也会受到修改。

五、 寄生式继承

1. 代码示例


function createAnother(original) {
var clone = object(original);//此处运用了4.4中的objec函数,也可以使用Object.create(original)
clone.sayHi = function() {
console.log('Hi');
}
return clone;
}

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

2. 优点

在主要考虑基于某个对象而非考虑自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式。该模式可以基于已有对象创建添加了函数的新对象。

3. 缺点

(1)不能做到函数复用:不能做到对新添加函数进行函数复用,这样会降低效率。该缺点与 借用构造函数继承类似。

(2)引用类型属性共享:该方法也是基于已有对象创建新对象,但是对新对象修改引用类型属性,已有的基础对象也会受到修改,基于基础对象的其他对象也会受到修改。该缺点 与原型式继承 一样。

六、 寄生组合式继承

1. 代码示例


//其实是寄生式继承的一种应用:以SuperType.prototype为基础对象,创建SubType.prototype对象。这个SubType.prototype对象是SuperType.prototype的浅复制,同时SubType.prototype对象上又增添了额外的属性constructor指向SubType。
function inheritPrototype(SubType, SuperType) {
var prototype = Object.create(SuperType.prototype);
prototype.constructor = SubType;
Subtype.prototype = prototype;
} //应用
function SuperType(name) {
this.name = name;
this.colors = ['red', 'blue'];
}
SuperType.prototype.sayName = function() {
console.log(this.name);
}; //子类型实例通过借用构造函数继承来获取超类型实例上的属性:
function Subtype(name, age) {
SuperType.call(this,name);
this.age = age;
}
//子类型的原型通过寄生式继承来获取超类型原型上的方法:
inheritPrototype(SubType,SuperType);
//给子类型原型添加自己的方法
SubType.prototype.sayAge = function() {
console.log(this.age);
}

思想: 子类型的实例通过借用构造函数来获取超类型实例上的属性,子类型的原型通过寄生式继承来获取超类型原型上的方法(此处寄生式继承是指子类型原型对超类型原型进行浅复制)。也就是说子类型通过借用构造函数继承属性,通过寄生式继承来继承方法。 和组合式继承相比,该寄生组合式继承不必为了指定子类型的原型而调用超类型的构造函数,只是使用了超类型原型的一个副本;而只有在指定子类型的实例属性时调用了超类型的构造函数(借用构造函数继承);这样该方法就只调用了一次超类型的构造函数。

2. 优点

(1) 属性独立、方法共享:拥有组合式继承的所有优点:分别拥有独立的属性(尤其是引用类型属性,如colors数组),又可以共享相同的方法。

(2) 高效率:避免了组合式继承调用两次超类型构造函数的缺点,只调用一次超类型构造函数,具有高效率。

(3) 原型链不变:能够正常使用instanceof和isPrototypeOf()

该寄生组合式继承是最理想的继承范式。

七、Es6的Class对继承的实现*

参考资料

《JavaScirpt高级程序设计》6.3

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties

JavaScript实现继承的几种重要范式的更多相关文章

  1. 实现JavaScript中继承的三种方式

    在JavaScript中,继承可以通过三种手法实现原型链继承 使用apply.call方法 对象实例间的继承.     一.原型链继承 在原型链继承方面,JavaScript与java.c#等语言类似 ...

  2. javascript实现继承的几种方式

    原型链方式实现继承 function SuperType(){ this.property = true; this.colors = ['red','blue','green']; } SuperT ...

  3. javascript实现继承的三种方式

    一.原型链继承  function Parent(){} function Child(){} Child.prototype = new Parent(); 通过对象child的prototype属 ...

  4. JavaScript——实现继承的几种方式

    实现继承的6中方法: 借用构造函数 组合继承 原型式继承 寄生式继承 寄生组合式继承 拷贝继承 1. 借用构造函数 在子类型构造函数的内部调用超类构造函数.通过使用apply()和call()方法在新 ...

  5. javascript实现继承的一种方式

    function extend(Child, Parent) { var F = function(){}; F.prototype = Parent.prototype; Child.prototy ...

  6. javascript实现继承的6种方式

    /*1.原型链继承*/ function SuperType() { this.property = true; } SuperType.prototype.getSuperValue = funct ...

  7. javascript实现继承的4种方法,以及它们的优缺点

    1. 原型链继承(有缺陷): 缺陷1:切断了Zi.prototype.constructor与Zi的关系 缺陷2:原型链上的引用类型的数据会被所有实例共享 2. 构造函数继承(有缺陷): 缺陷1:Fu ...

  8. 玩转JavaScript OOP[4]——实现继承的12种套路

    概述 在之前的文章中,我们借助构造函数实现了"类",然后结合原型对象实现了"继承",并了解了JavaScript中原型链的概念. 理解这些内容,有助于我们更深入 ...

  9. javascript面向对象系列第三篇——实现继承的3种形式

    × 目录 [1]原型继承 [2]伪类继承 [3]组合继承 前面的话 学习如何创建对象是理解面向对象编程的第一步,第二步是理解继承.本文是javascript面向对象系列第三篇——实现继承的3种形式 [ ...

随机推荐

  1. Ubuntu启动自动登录并启动程序

    最近在研究Ubuntu,需要在系统启动之后自动登录,并且启动某个程序. 手上拿到的系统只有一个空桌面,其他嘛也没有,鼠标右键也不管用.于是借助自己的虚拟机研究发现,自动启动程序配置文件在: /home ...

  2. linux下的cacti安装(centos7)

    1 cacti运行环境准备 cacti需要php+apache+mysql+snmp+RRDTool,以及cacti本身.cacti本体是用php开发的网站,通过snmp对远端设备信息进行采集.apa ...

  3. Go HelloWorld 网络版和并发版

    网络版 package main import ( "net/http" "fmt" ) func main() { http.HandleFunc(" ...

  4. CCNA 课程 四

    Vlan基础: Vlan的作用:把物理上分割的用户,让他们逻辑上在一起. Vlan 范围: 0- 4095 0  4095 是保留的 不可以使用 1 cisco 本证vlan 标准vlan 1 -10 ...

  5. Lucene简单介绍

    [2016.6.11]以前写的笔记,拿出来放到博客里面~ 相关软件: Solr, IK Analyzer, Luke, Nutch;Tomcat; 1.是什么: Lucene是apache软件基金会j ...

  6. sql 数据库中只靠一个数据,查询到所在表和列名

    有时候我们想通过一个值知道这个值来自数据库的哪个表以及哪个字段,在网上搜了一下,找到一个比较好的方法,通过一个存储过程实现的.只需要传入一个想要查找的值,即可查询出这个值所在的表和字段名. 前提是要将 ...

  7. eclipse修改端口启动多个tomcat

    参考:https://blog.csdn.net/zl544434558/article/details/47857343 在一个eclipse启动多个tomcat,修改tomcat的端口是不可以的, ...

  8. EntityFramework 学习 一 Colored Entity in Entity Framework 5.0

    You can change the color of an entity in the designer so that it would be easy to see related groups ...

  9. PowerDesigner生成数据库表和逆向生成表结构(MySQL数据库)

    一.Download Connector/ODBC下载ODBC驱动,地址:https://dev.mysql.com/downloads/connector/odbc/, 需要注意:PowerDesi ...

  10. 数据链路层--PPP协议

    数据链路层使用的信道主要有两种类型:点对点信道和广播信道. 点对点 路由器在转发分组时只使用了下面的三层. 链路是从一个结点到相邻结点的一段物理线路,中间没有其他交换结点. 必须有一些必要的通信协议来 ...