JavaScript实现继承的几种重要范式
一 原型链
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: {
...
},
...
}
返回值: 一个带有指定的原型对象属性和自己新添加的实例属性的对象
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实现继承的几种重要范式的更多相关文章
- 实现JavaScript中继承的三种方式
在JavaScript中,继承可以通过三种手法实现原型链继承 使用apply.call方法 对象实例间的继承. 一.原型链继承 在原型链继承方面,JavaScript与java.c#等语言类似 ...
- javascript实现继承的几种方式
原型链方式实现继承 function SuperType(){ this.property = true; this.colors = ['red','blue','green']; } SuperT ...
- javascript实现继承的三种方式
一.原型链继承 function Parent(){} function Child(){} Child.prototype = new Parent(); 通过对象child的prototype属 ...
- JavaScript——实现继承的几种方式
实现继承的6中方法: 借用构造函数 组合继承 原型式继承 寄生式继承 寄生组合式继承 拷贝继承 1. 借用构造函数 在子类型构造函数的内部调用超类构造函数.通过使用apply()和call()方法在新 ...
- javascript实现继承的一种方式
function extend(Child, Parent) { var F = function(){}; F.prototype = Parent.prototype; Child.prototy ...
- javascript实现继承的6种方式
/*1.原型链继承*/ function SuperType() { this.property = true; } SuperType.prototype.getSuperValue = funct ...
- javascript实现继承的4种方法,以及它们的优缺点
1. 原型链继承(有缺陷): 缺陷1:切断了Zi.prototype.constructor与Zi的关系 缺陷2:原型链上的引用类型的数据会被所有实例共享 2. 构造函数继承(有缺陷): 缺陷1:Fu ...
- 玩转JavaScript OOP[4]——实现继承的12种套路
概述 在之前的文章中,我们借助构造函数实现了"类",然后结合原型对象实现了"继承",并了解了JavaScript中原型链的概念. 理解这些内容,有助于我们更深入 ...
- javascript面向对象系列第三篇——实现继承的3种形式
× 目录 [1]原型继承 [2]伪类继承 [3]组合继承 前面的话 学习如何创建对象是理解面向对象编程的第一步,第二步是理解继承.本文是javascript面向对象系列第三篇——实现继承的3种形式 [ ...
随机推荐
- 【TopCoder】SRM152 DIV2总结
为什么平常刷的时候感觉还不错,比赛的时候只能做出来一道题=.= 250分题:大水题,根据题目规则把一个字符串翻译成数字,直接代码:GitHub 我是通过遍历一个个数出来的,看到大神的解法是把字符用‘- ...
- poj 1905 Expanding Rods(木杆的膨胀)【数学计算+二分枚举】
...
- AJAX跨域资源共享 CORS 详解
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing). 它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从 ...
- c# 判断字符串中是否含有汉字,数字
正则表达式使用时需要引用 using System.Text.RegularExpressions; private void buttonX1_Click(object sender, EventA ...
- java 开发面试题小整理(一)
本篇文档将持续更新,有基础滴,也有深层次的,谢谢! 1.看下面的程序是否有问题,如果有问题,请指出并说明理由. * byte b1 = 3; * byte b2 = 4; * byte b3 = b1 ...
- HDU 3966 & POJ 3237 & HYSBZ 2243 & HRBUST 2064 树链剖分
树链剖分是一个很固定的套路 一般用来解决树上两点之间的路径更改与查询 思想是将一棵树分成不想交的几条链 并且由于dfs的顺序性 给每条链上的点或边标的号必定是连着的 那么每两个点之间的路径都可以拆成几 ...
- Spring boot学习整理
目录: Springboot结合hbase Springboot结合elasticsearch Springboot结合RestTemplate处理Http请求 Springboot的maven相关 ...
- 03_01_基本操作_增(insert)
1. 1.1.创建表 create table employee( id number primary key, name varchar2(64) not null, email varchar2( ...
- 设置浏览器地址栏URL前面显示的图标
其实很简单,你只做个ico图标,命名为favicon.ico,把它传到你的页面下面. 并在相应的页面里加上代码 在页面<heah></heah>之间加, <link r ...
- DOM初体验(绑定事件,监听事件)
JavaScript的组成: ECMAScript(js的基本语法).DOM(文档对象模型).BOM(浏览器对象模型) DOM的作用: 1. 找到页面上的元素 2. 增添.删除.修改页面上的元素 3. ...