一 原型链

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. Wi-Fi基带芯片和Wi-Fi无线网卡设计方案

    转:http://wenku.baidu.com/link?url=Q0ImC 0IIG7qrbB8DpGrrU3aOYvxNYCyHsxukspH8XMCDYMjYMPSJq_TCISC5amsNY ...

  2. Django框架之HTTP本质

    1.Http请求本质 浏览器(socket客户端): socket.connect(ip,端口) socket.send("http://www.xiaohuar.com/index.htm ...

  3. S005SELinux(SEAndroid)的实际文件组成无标题文章

    SEAndroid 是将SELinux 移植到Android 上的产物,可以看成SELinux 辅以一套适用于Android 的策略. 那么在android系统中那些文件是与SELinux(SEAnd ...

  4. Redux API之bindActionCreators

    bindActionCreators(actionCreators,dispatch) 把 action creators 转成拥有同名 keys 的对象,但使用 dispatch 把每个 actio ...

  5. Ubuntu 使用国内apt源

    编辑/etc/apt/source-list deb http://cn.archive.ubuntu.com/ubuntu/ trusty main restricted universe mult ...

  6. poj 1905 Expanding Rods(木杆的膨胀)【数学计算+二分枚举】

                                                                                                         ...

  7. 如何判断Linux服务器是否被入侵?

    被入侵服务器的症状 当服务器被没有经验攻击者或者自动攻击程序入侵了的话,他们往往会消耗 100% 的资源.他们可能消耗 CPU 资源来进行数字货币的采矿或者发送垃圾邮件,也可能消耗带宽来发动 DoS ...

  8. centos7下安装tomcat7

    1 安装说明安装环境:CentOS-7.0.1611安装方式:源码安装软件:apache-tomcat-7.0.75.tar.gz 下载地址:http://tomcat.apache.org/down ...

  9. maven 内置属性有哪些?该如何使用?

    maven 共有6类内置属性: 内置属性(maven预定义,用户可以直接使用的) ${basedir}表示项目的根目录,既包含pom.xml文件的目录: ${version}表示项目版本: ${pro ...

  10. webview 详解

    [原文] 1. 打开网页时不调用系统浏览器, 而是在本WebView中显示: mWebView.setWebViewClient(new WebViewClient(){ @Override publ ...