JS继承的6种方法详解以及优缺点对比
1. 原型链继承
- 方法
我们知道构造函数、原型对象和实例的关系:每个构造函数都有一个原型对象,原型对象(.prototype
)的constructor
属性指向构造函数,而实例的__proto__
指向构造函数的原型对象(可以参考文章 原型链)。
那么,如果让原型对象等于另一个对象的实例的话,这时原型对象的constructor
属性指向另一个对象,原型对象的__proto__
指向另一个对象的原型对象。 - 举例
function SuperType(name) { this.name = name; } SuperType.prototype.getName = function() { return this.name; }; function SubType(age) { this.age = age; } //原型链继承SuperType SubType.prototype = new SuperType('super');//继承了SuperType的所有属性和方法SubType.prototype.getAge = function (){ return this.age; };//为SubType添加新方法var sub1 = new SubType(10); //实例化SubTypeconsole.log(sub1.getName());//superconsole.log(sub1.getAge());//10
其中,前几行代码可以看作通过组合使用原型模式和构造函数模式创建对象的过程,只不过这个对象是已经存在的 SubType.prototype
,也就是重写了子类型的原型对象。创建对象的方法进 JS创建对象
function SuperType() { this.name ='super'; } SuperType.prototype.getName = function() { return this.name; }; SubType.prototype = new SuperType();
所以,原型链继承的本质就是将SubType
的原型重写为SuperType
的实例,这时,SubType
的原型不仅具有作为一个 SuperType
的实例所拥有的全部属性和方法, 而且其内部的__proto__
指向了 SuperType
的原型。最终结果就是:sub1
指向 SubType
的原型,SubType
的原型又指向 SuperType
的原型。
console.log(sub1.constructor = SubType.prototype.constructor === SuperType);//true,注意这一点console.log(SubType.prototype.__proto__ === SuperType.prototype);//true
注意,此时instance.SuperType
为SuperType
,因为SubType.prototype
被重写为了
SuperType
的实例化对象,而这个对象的constructor
为SuperType
。如下图所示
- 优点
容易实现,父类新增原型属性和方法,子类都能访问到 - 缺点
(1) 原型属性在所有实例中是共享的
function SuperType() {this.name='Super';this.colors = ["red", "blue", "green"];
}
function SubType(){ }
//继承了 SuperType
SubType.prototype = new SuperType();
var sub1 = new SubType();
var sub2 = new SubType();
sub1.name = 'sub1';
sub1.colors.push("black");console.log(sub1.name); //sub1
console.log(sub2.name); //Super
console.log(sub1.colors); //"red,blue,green,black"
console.log(sub2.colors); //"red,blue,green,black"
通常情况下,我们不希望改变sub1
的colors
时,会影响到sub2
的colors
属性。
(2) 在创建子类型的实例sub1
时,不能向超类型的构造函数SuperType
中传递参数。实际上, 应该说是没有办法在不影响所有对象实例的情况下,给SuperType
传递参数。
因此,实践中很少会单独使用原型链继承这种方式。
2. 借助构造函数
- 方法
在子类型构造函数的内部通过使用apply()
和call()
等方法调用超类型构造函数。 - 举例
function SuperType(name) {this.colors = ["red", "blue", "green"];this.name = name;this.getName = function() {return this.name;}
}
function SubType(name){ //借助 SuperType构造函数继承了 SuperType SuperType.call(this,name);
}
var sub1 = new SubType('sub1');
var sub2 = new SubType();
sub1.colors.push("black");console.log(sub1.getName()); //sub1
console.log(sub1.colors); //"red,blue,green,black"
console.log(sub2.colors); //"red,blue,green"
这样每个SubType
的实例在创建的时候都会调用 SuperType
构造函数,SubType
的每个实例就都会具有自己的 colors
和name
属性的副本了。
- 优点
(1) 每个实例不会共享属性,sub1
的colors
改变不会影响sub2
的colors
属性值;
(2) 相比原型链的方法,sub1
可以向SuperType
构造函数传递参数。 - 缺点
(1) 方法都在构造函数SuperType
中定义,因此函数复用就无从谈起了;
(2)SuperType
的原型方法对子类型和实例不可见。
SuperType.prototype.getColors = function() {console.log(this.colors)
}
sub1.getColors();//sub1.getColors is not a function
因此,实践中也很少会单独使用构造函数继承这种方式。
3. 组合继承
方法
顾名思义,组合继承就是将原型链和借用构造函数组合到一块。其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。原型属性和实例属性举例
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;}
//原型链方式继承 sayName 方法
SubType.prototype = new SuperType(); //再次调用 SuperType 构造函数
SubType.prototype.constructor = SubType; //将 constructor指回 SubType
SubType.prototype.sayAge = function() {console.log(this.age);};
var sub1 = new SubType("Nicholas", 29);
var sub2 = new SubType("Greg", 27);
sub1.colors.push("black");console.log(sub1.colors); //"red,blue,green,black"
sub1.sayName(); //"Nicholas";
sub1.sayAge(); //29
console.log(sub2.colors); //"red,blue,green"
sub2.sayName(); //"Greg";
sub2.sayAge(); //27
这样一来,就可以让两个不同的 SubType
实例sub1
和sub2
既分别拥有自己的属性(通过借助构造函数)—包括 colors
属性,又可以使用相同的方法了(原型链方式创建的原型方法sayName
)。
- 优点
组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点。 - 缺点
SuperType
的构造函数会被调用两次
它是 JavaScript中常用的继承模式。
4. 原型式继承
- 方法
这种方法并没有使用严格意义上的构造函数,而是借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型,如下函数:
function object(o) {function F(){} F.prototype = o; return new F();}
在 object()
函数内部,先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的 原型,后返回了这个临时类型的一个新实例。从本质上讲,object()
对传入其中的对象执行了一次浅复制。
- 举例
ECMAScript 5通过新增Object.create()
方法规范化了原型式继承。这个方法接收两个参数:一个用作新对象原型的对象(object()
中的参数o
)和(可选的)一个为新对象定义额外属性的对象。在传入一个参数的情况下,Object.create()
与object()
方法的行为相同。
var person = {name: "Nicholas",friends: ["Shelby", "Court", "Van"],sayname: function() {console.log(this.name);}
};
var yetAnotherPerson = Object.create(person);
var anotherPerson = Object.create(person, { name: { value: "Greg" }
});
//相当于:
//var anotherPerson = Object.create(person);
//anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie"); console.log(person.friends); //"Shelby,Court,Van,Rob,Barbie"
anotherPerson.sayname(); //"Greg"
- 优点
不用再执行和建立person
的实例 - 缺点
从例子中可以看出,同原型链继承一样,引用类型值的属性friends
属性是被共享的,这不是我们期待的
5. 寄生式继承
- 方法
创建一个函数将继承过程封装起来,函数如下:
function createAnother(o) {var clone = Object(o); //通过基类型Object()创建一个新对象clone.sayName = function() {//以某种方式来增强这个对象console.log(this.name);}; return clone; //返回这个对象}
同原型式继承类似,createAnother()
函数接收了一个参数o
,然后,把这个对象o传递给 object()
创建了一个新对象,这相当于浅复制了对象o
,再为这个新对象添加一个新方法 sayName()
,后返回这个新对象。
- 举例
var person = {name: "Nicholas", friends: ["Shel", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.name='another';
anotherPerson.friends.push('Barbie');console.log(person.name); //another
console.log(person.friends); // ["Shel", "Court", "Van", "Barbie"]
console.log(anotherPerson.name); //another
console.log(anotherPerson.friends); // ["Shel", "Court", "Van", "Barbie"]
anotherPerson.sayName(); //another
基于 person
返回了一个新对象——anotherPerson
。新对象不仅具有 person
的所有属性和方法,而且还有自己的 sayName()
方法。
- 缺点
(1) 在createAnother()
中为对象添加函数,不能做到函数复用
(2) 由于是浅复制person
对象,anotherPerson
的改变会影响person
对象
6. 寄生组合式继承
- 方法
与组合继承思路类似,也是通过借用构造函数来继承属性,通过“ 原型链 ”来继承方法,不同的是,这里的“ 原型链 ”本质上是使用寄生式继承来继承超类型的原型,寄生组合式继承的基本模式如下函数:
function inheritPrototype(subType, superType) {var prototype = Object(superType.prototype); //创建新对象prototype.constructor = subType; //增强对象subType.prototype = prototype; //指定对象 }
在函数内部,首先通过浅复制创建超类型原型的一个副本。第二步将副本的constructor
属性重新指向subType
(在原型链继承中,constructor
指向superType
)。 最后,将新创建的对象(即副本)赋值给subType
的原型。这样,就可以用调用 inheritPrototype()
函数代替前面例子中为子类型原型赋值的语句了
- 举例
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); //借助构造函数继承,调用一次超类型构造函数this.age = age; } inheritPrototype(SubType, SuperType); //调用函数代替原型链继承SubType.prototype.sayAge = function(){ console.log(this.age); };
var sub1 = new SubType("Nicholas", 29);
var sub2 = new SubType("Greg", 27);
sub1.colors.push("black");console.log(sub1.colors); //"red,blue,green,black"
sub1.sayName(); //"Nicholas";
sub1.sayAge(); //29
console.log(sub2.colors); //"red,blue,green"
sub2.sayName(); //"Greg";
sub2.sayAge(); //27
SubType.prototype.__proto__
指向Object.prototype
,因为在inheritPrototype()
中,副本是由基类型Object()
创建的。
console.log(sub1.constructor === SubType.prototype.constructor === SubType);//true,重新指回了SubType
console.log(SubType.prototype.__proto__ === Object.prototype);//true
- 优点
(1) 既能具有组合继承的优点,又可以不必两次调用超类型的构造函数
(2) 避免了在SubType.prototype
上面创建不必要的、多余的属性(在原型链继承时,SubType.prototype
被重写为SuperType
的实例,因此具有了他的实例属性)
针对(2)进行一点补充说明:
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;}
SuperType
和SubType
如上,分别使用组合继承和寄生组合式继承两种方式比较一下SubType.prototype
//组合继承
SubType.prototype = new SuperType(); //原型链继承
SubType.prototype.constructor = SubType; //将constructor指回SubType
SubType.prototype.sayAge = function() {console.log(this.age);};
var sub = new SubType();
console.log(SubType.prototype);
//寄生组合式继承
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function() {console.log(this.age);};
var sub = new SubType();
console.log(SubType.prototype);//true
可以看到,使用寄生组合式继承SubType.prototype
上没有创建name
和colors
属性。
因此,寄生组合式继承是引用类型理想的继承范式。
你有什么想法呢?欢迎交流~~
JS继承的6种方法详解以及优缺点对比相关推荐
- JS创建对象模式7种方法详解
创建对象的几种模式 虽然Object构造函数或者字面量,都可以用来创建对象,但这些方式有明显的缺点:使用同一个接口创建很多对象,会产生大量的代码, 于是,工厂模式诞生了 1 工厂模式 工厂模式是广为人 ...
- matlab合并有序数组,《数组合并》JS合并两个数组的3种方法详解
这篇文章主要介绍了JS合并两个数组的3种方法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一个包解决你所有的JS问题,点击获取 需要将两个数组 ...
- ios 获取html的高度,iOS Webview自适应实际内容高度的4种方法详解
//第一种方法 - (void)webViewDidFinishLoad:(UIWebView *)webView { CGFloat webViewHeight=[webView.scrollVie ...
- python process 函数_Python Process创建进程的2种方法详解
前面介绍了使用 os.fork() 函数实现多进程编程,该方法最明显的缺陷就是不适用于 Windows 系统.本节将介绍一种支持 Python 在 Windows 平台上创建新进程的方法. Pytho ...
- java构造和解析json_Java构造和解析Json数据的两种方法详解一
在www.json.org上公布了很多JAVA下的json构造和解析工具,其中org.json和json-lib比较简单,两者使用上差不多但还是有些区别.下面首先介绍用json-lib构造和解析Jso ...
- java json解析 代码_Java构造和解析Json数据的两种方法详解一
在www.json.org上公布了很多JAVA下的json构造和解析工具,其中org.json和json-lib比较简单,两者使用上差不多但还是有些区别.下面首先介绍用json-lib构造和解析Jso ...
- python 命令-python解析命令行参数的三种方法详解
这篇文章主要介绍了python解析命令行参数的三种方法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 python解析命令行参数主要有三种方法: ...
- Java构造和解析Json数据的两种方法详解一
在www.json.org上公布了很多JAVA下的json构造和解析工具,其中org.json和json-lib比较简单,两者使用上差不多但还是有些区别.下面首先介绍用json-lib构造和解析Jso ...
- php 去重_php求两数组交集的四种方法详解
题目:给定两个数组,编写一个函数来计算它们的交集. 示例 1: 输入: nums1 = [1,2,2,1],nums2 = [2,2] 输出: [2] 示例 2: 输入: nums1 = [4,9,5 ...
最新文章
- JavaScript是如何工作的:与WebAssembly比较及其使用场景
- 3dContactPointAnnotationTool开发日志(三三)
- android intent 跳转卡顿_Intent七大属性
- Online Learning算法理论与实践
- linux chcon命令详解
- 小米商城html_北京市发放新一批 170 万个消费券:京东、小米商城等平台可领
- shell学习之跳出循环
- java 类 引用数组对象_Java 方法重载与引用数组类型
- 51单片机4位抢答器_倒计时可调仿真设计
- 阿里云跨云迁移工具案例实践:腾讯云迁移到阿里云
- gentoo mysql_gentoo
- 《Python代码审计》(1)一款超好用的代码扫描工具
- js和html:周岁年龄计算器
- android与ios ui切图关系,iOS、Android 开发单位换算及 UI 切图要求
- BZOJ1778: [Usaco2010 Hol]Dotp 驱逐猪猡
- jupter 使用
- 【c++】factory的使用:create和destory,以cicadaplayer的render实现为例
- 华为主题 主题兑换券活动高端操作
- word方案基本结构格式记录
- android音乐播放器的历史,基于Android音乐播放器的研究