【javascript】javascript继承的多种方式
1, 原型链继承
function Super() { };
function Sub() { };
Sub.prototype = new Super()
会出现的问题:
a,原型对象的属性,如果是一个引用类型,那么所有实例都会共享这一个引用类型[比如是数组],某一个实例修改了该数组,那么其他实例获取该属性也会是改变之后的结果。
b,第二个问题,没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。
2,构造函数继承,在子类型的构造函数里面里面调用父类型的的构造函数,利用call 或者apply
function Person(name, age) {
this.name = name, this.age = age
}
function Worker(name, age, salary, phone) {
this.salary = salary
this.phone = phone
Person.call(this, name, age)
}
会出现的问题:
a, 子类型没有办法公用父类型的方法。属性其实只是表层上的引用。
3, 组合继承
方法采用原型继承【方法可以公用】,属性采用构造器的继承方式。
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "yellow"]
}
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.sayAge = function() {
console.log(this.age)
}
var in1 = new SubType("k1", 18)
in1.colors.push('black')
in1.sayAge()
in1.sayName()
in1.colors // what is result ?
var in2 = new SubType("k2", 28)
in2.sayAge()
in2.sayName()
in2.colors // what is result ?
4,原型式继承【不是原型链继承】
function object(o) {
function F() {}
F.prototype = o
return new F()
}
ES5 提供了Object.create()
方法来规范原型链继承。这个方法和上面的object方法实现同样的功能,但是他提供第二个参数,第二个参数和Object.defineProperty(obj, 'name', { })
的第三个参数,描述符是一样的。
例子:
const obj = {
name: 'kev',
colors: ['yellow', 'green', 'blue']
}
const copyPaste = Object.create(obj,
{ name: {
value: 'kevin',
enumerable: true
}
})
5, 寄生式继承【以第4种原型式继承为基础】
寄生式继承和原型式继承紧密相关的一种思路,也是会调用Object.create方法,但是会以某种方式增强返回的对象。
function createAnother(original) {
// 这个object方法来自第4步
const clone = object(original)
clone.sayHi = function() {
console.log('hi')
}
return clone
}
第六种,寄生组合式继承
其实到现在我认为第3种是最自然的,结合了原型和构造函数。但是问题来了:我们调用了两次超类SuperType的构造函数
看完前面5种,我自己都认为常用的应该是第3种继承方式最好,结合原型和构造函数。但是存在问题:我们调用了两次超类SuperType的构造函数。
第一次是指定SubType.prototype = new SuperType()
,这时候SubType的原型对象上面会有两个属性,name,colors。
第二次是实例化对象in1的时候,又调用了一次SuperType的构造函数,这时候对象in1又创建了实例属性name和colors。
实际上我们只需要继承原型对象上面的方法,属性我们可以不在原型对象上面创建。
概念来了,寄生组合式继承是指的是通过构造函数来继承属性,通过原型链的方式来继承方法。
我们不用为了指定子类型SubType的原型(对象)而去调用父类或者超类的构造函数。避免子类的原型对象有多余的属性,我们可以利用Object.create()
或者手写的object()
方法来返回一个对象tempObj,这个对象tempObj的原型tempObj.__proto__
或者Object.getPrototypeOf(temoIbj)
都是等于SuperType.prototype的。
关键总结:我们都是为了将SubType的原型对象设置为SuperType的实例,但是组合继承调用了构造函数,SubType的原型对象是有多余的属性的, 而且如果SuperType构造函数有一些副作用 比如写日志,修改状态,注册到其他对象,给this添加数据属性等,就会影响SubType的"后代"。 所以我们设置SubType的原型对象为另外一个特殊的对象,这个特殊的对象是通过Object.create(SuperType.prototype)来返回的,这个特殊对象没有多余的属性,但是.__proto__为SuperType.prototype, 继承了prototype.sayName方法。
但是因为我们重写了prototype,失去了默认的constructor属性,这一步之后代码长下面这样:
// 超类
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "yellow"]
}
SuperType.prototype.sayName = function() {
console.log(this.name)
}
// 子类
function SubType(name, age) {
SuperType.call(this, name);
this.age = age
}
// A 设置prototype
SubType.prototype = Object.create(SuperType.prototype)
// B 恢复默认的Constructor
SubType.prototype.constructor = SubType
SubType.prototype.sayAge = function() {
console.log(this.age)
}
我们将A和B下面的两句代码抽成一个方法:
function inheritPrototype(subType, superType) {
// or object(superType.prototype)
const proto = Object.create(superType.prototype)
proto.constructor = subType
subType.prototype = proto
}
所以最后的结果是这样:
// 超类
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "yellow"]
}
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)
}
等等—还没有完。。。
ES6 新增了一个方法Object.setPrototypeOf(obj, prototype), 我们可以直接设置原型对象【但是注意兼容性】,这样我们修改一下这个inheritPrototype
方法:
function inheritPrototype(subType, superType) {
Object.setPrototypeOf(subType.prototype, superType.prototype)
subType.constructor = subType
subType.prototype.constructor = subType
}
more:
之前关于prototype的一点记录
来源《js高级程序设计》第6章第三节
《你不知道的js》原型章节