JS是门灵活的语言,实现一种功能往往有多种做法,ECMAScript没有明确的继承机制,而是通过模仿实现的,根据js语言的本身的特性,js实现继承有以下通用的几种方式
// 定义一个父类
function Person(name) {
// 属性
this.name = name || 'Person';
// 实例方法
this.eat = function() {
console.log(this.name + '正在吃!');
}
}
// 给Person原型添加方法
Person.prototype.eat = function(food) {
console.log(this.name + '正在吃:' + food);
}
1、原型式继承
实现原理: 砍掉父类的实例属性,在调用两次父类的构造函数的时候,就不会初始化两次实例方法和属性
function Child(name) {
this.name = name
}
Child.prototype = Person.prototype
var child = new Child()
优点:
- 可以继承父类构造函数的原型对象上的成员
缺点:
- 只能继承父类构造函数的原型对象上的成员,不能继承父类构造函数的实例对象的成员
2、原型链继承
实现原理: 将父类的实例作为子类的原型(使子类原型对象指向父类的实例以实现继承,即重写类的原型)
function Child(name) {
this.name = name
}
Child.prototype = new Person()
Childat.prototype.name = 'Lisi'
var child = new Child()
优点:
- 非常纯粹的继承关系,实例是子类的实例,也是父类的实例
- 父类新增原型方法/原型属性,子类都能访问到
缺点:
- 无法实现多继承
- 创建子类实例时,无法向父类构造函数传参
- 要想为子类新增属性和方法,必须要在 new Person() 这样的语句之后执行,不能放到构造器中
- 来自原型对象的所有属性被所有实例共享
3、借用构造函数(改变函数上下文实现继承)
实现原理: 使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)
function Child(name) {
Person.call(this,name)
// Person.apply(this,[name])
}
var child = new Child()
优点:
- 可以实现多继承
- 没有属性共享的问题
- 可以向父类构造函数传递参数
缺点:
- 只能继承父类构造函数里面的成员,不能继承父类原型链上的成员
- 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
4、拷贝继承
var person = new Person()
var child = {}
for(var i in person) {
child[i] = person[i]
}
优点:
- 可以实现多继承
缺点:
- 效率较低,内存占用高
- 如果继承过来的成员是引用类型的话,那么这个引用类型的成员在父对象和子对象之间是共享的,也就是说修改了之后, 父子对象都会受到影响
5、借用构造函数 + 深拷贝继承
function deepCopy(obj1, obj2) {
for (var key in obj2) {
// 判断是否是obj2上的实例成员
if (obj2.hasOwnProperty(key)) {
// 判断是否是引用类型的成员变量
if (typeof obj2[key] == 'object') {
obj1[key] = Array.isArray(obj2[key]) ? [] : {};
deepCopy(obj1[key], obj2[key]);
} else {
obj1[key] = obj2[key];
}
}
}
}
function Child(name) {
Person.call(this,name)
}
deepCopy(Child.prototype,Person.prototype)
Child.prototype.constructor = Child
优点:
- 没有属性共享的问题
6、借用构造函数 + 原型链继承
function Child(name) {
Parent.call(this)
}
Child.prototype.constructor = Child
Child.prototype = new Parent()
优点:
- 函数可复用
- 没有属性共享的问题
- 不仅可以继承父类的实例的成员,还可以继承父类的原型对象的成员
缺点:
- 父类构造函数执行了两次
7、借用构造函数 + 原型式继承
function Child(name) {
Parent.call(this)
}
Child.prototype.constructor = Child
Child.prototype = Person.prototype
优点:
- 可以向父类构造函数传递参数
- 不仅能继承父类构造函数内的成员,还可以继承父类原型对象上的成员
缺点:
- 不能继承父类构造函数的实例对象的成员