转 各种实现js继承的方法总结 _
昨天主要介绍了原型,在js中,原型,原型链和继承是三个很重要的概念,而这几个概念也是面试中经常会被问到的问题,今天,就把昨天还没总结的原型链和继承继续做一个整理,希望大家一起学习,一起进步呀O(∩_∩)O
一、原型链
学过java的同学应该都知道,继承是java的重要特点之一,许多面向对象的语言都支持两种继承方式:接口继承和实现继承,接口继承只继承方法签名,而实现继承则继承实际的方法,在js中,由于函数没有签名,因此支持实现继承,而实现继承主要是依靠原型链来实现的,那么,什么是原型链呢?
首先,我们先来回顾一下构造函数,原型和实例之间的关系
当我们创建一个构造函数时,构造函数会获得一个prototype属性,该属性是一个指针,指向一个原型对象,原型对象包含一个constructor属性,该属性也是一个指针,指向构造函数,而当我们创建构造函数的实例时,该实例其实会获得一个[[Prototype]]属性,指向原型对象
function SuperType() {}
function SubType() {}
SubType.prototype = new SuperType();
var instance = new SubType();
上面的代码中,我们先是让SubType继承了SuperType,接着创建出SubType的实例instance,因此,instance可以访问SubType和SuperType原型上的属性和方法,也就是实现了继承,继承关系我们可以用下面的图说明
最后,要提醒大家的是,所有引用类型默认都继承了Object,这个继承也是通过原型链实现的,因此,其实原型链的顶层就是Object的原型对象啦
二、继承
上面我们弄清了原型链,接下来主要就介绍一些经常会用到的继承方法,具体要用哪一种,还是需要依情况而定的
1、原型链继承
最常见的继承方法就是使用原型链实现继承啦,也就是我们上面所介绍的,接下来,还是看一个实际的例子把
function SuperType() {
this.colors = ["red","blue","green"]
}
function SubType() {
SuperType.call(this); // 实现继承
}
var instance1 = new SubType();
var instance2 = new SubType();
instance2.colors.push("black");
console.log(instance1.colors"); // red,blue,green
console.log(instance2.colors"); // red,blue,green,black
在上面的例子中,如果我们使用原型链继承,那么instance1和instance2将会共享colors属性,因为colors属性存在于SubType.prototype中,而上面我们使用了借用构造函数继承,通过使用call()方法,我们实际上是在新创建的SubType实例的环境下调用了SuperType的构造函数,因此,colors属性是分别存在instance1和instance2实例中的,修改其中一个不会影响另一个
使用这个方法,我们还可以在子类型构造函数中向超类型构造函数传递参数
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;
}
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
console.log(this.age);
}
var instance1 = new SubType("Nicholas", 29);
var instance2 =new SubType("Greg", 27);
instance1.colors.push("black");
console.log(instance1.colors); // red,blue,green,black
console.log(instance2.colors); // red,blue,green
instance1.sayName(); // Nicholas
instance2.sayName(); // 29
instance1.sayAge(); // Greg
instance2.sayAge(); // 27
组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,现在已经成为js中最常用的继承方法
缺点:无论什么情况下,都会调用两次超类型构造函数,一次是在创建子类型的时候,另一次是在子类型构造函数内部,子类型最终会包含超类型对象的全部实例属性,但是需要在调用子类型构造函数时重写这些属性
4、原型式继承
原型式继承主要的借助原型可以基于已有的对象创建新的对象,基本思想就是创建一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回这个临时类型的一个新实例
var person = {
name: "Nicholas",
friends: ["Shelby","Court","Van"]
}
var anotherPerson = Object.create(person, {
name: {
value: "Greg"
}
});
console.log(anotherPerson.name); // Greg
上面的例子中,我们让anotherPerson继承了person,其中,friends作为引用类型,将会被所有继承该对象的对象所共享,而通过传入第二个参数,我们可以定义额外的属性,修改person中的原有信息
缺点:原型式继承中包含引用类型的属性始终都会共享相应的值
5、寄生式继承
寄生式继承其实和我们前面说的创建对象方法中的寄生构造函数和工程模式很像,创建一个仅用于封装继承过程的函数,该函数在内部以某种方法来增强对象,最后再返回该对象
var person = {
name: "Nicholas",
friends:["Shelby","Court","Van"]
}
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); // hi
从上面的代码中我们可以看出,原来person是没有包含任何方法的,而通过将person传进去createAnother方法中进行加工,返回的新对象就包含了一个新的方法
缺点:不能实现函数的复用
6、寄生组合式继承
组合继承是js中最经常用到的一种继承方法,而我们前面也已经说了组合继承的缺点,组合继承需要调用两次超类型构造函数,一次是在创建子类型原型的时候,另一次是在子类型构造函数内部,子类型最终会包含超类型对象的全部实例属性,但是我们不得不在调用子类型构造函数时重写这些属性
function inheritPrototype(subType,SuperType) {
var prototype = Object(SuperType); // 创建对象
prototype.constructor = subType; // 增强对象
subType.prototype = prototype; // 指定对象
}
在上面的例子中,第一步创建了超类型原型的一个副本,第二步为创建的副本添加constructor属性,从而弥补因重写原型而失去的默认的constructor属性,最后一步将副本也就是新对象赋值给子类型的原型,因此,我们可以用这个函数去替换前面说到为子类型原型赋值的语句
class Point {
}
class ColorPoint extends Point {
}
上面这个例子中可以实现ColorPoint类继承Point类,这种简洁的语法确实比我们上面介绍的那些方法要简洁的好多呀
但是呢,使用extends实现继承的时候,还是有几点需要注意的问题,子类在继承父类的时候,子类必须在constructor方法中调用super方法,否则新建实例时会报错,这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法,如果不调用super方法,子类就得不到this对象
let cp = new ColorPoint(25, 8, 'green'); cp instanceof ColorPoint // true cp instanceof Point // true
对于es6的继承,如果大家想继续了解,可以进行更进一步的学习
今天就介绍到这里啦,对于js的继承方法,不知道大家是不是更了解了呢
__EOF__
- 本文作者: Yellow-ice
- 本文链接:
- 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
- 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角【
Yellow_ice
关注 - 2
粉丝 - 57 我在关注他 上一篇:
下一篇: