在解释 inherits 函数之前先来再次熟悉一下js6种继承方式。熟悉后再来看 inherits 函数中的继承实现。
这种继承的最大特点是:子类的原型继承父类的实例对象
例子
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
};
function SubType() {
this.subproperty = false;
}
SubType.prototype = new SuperType(); // 子类原型继承父类实例对象(这是原型链继承的特点)
SubType.prototype.getSubValue = function() {
return this.subproperty;
}
let instance1 = new SubType();
console.log(instance1.getSuperValue()); // 输出:true
优点:
缺点:
如何理解共享父类的引用类型的实例属性会导致数据混乱问题?
function SuperZoo() {
this.zoo = ['dog', 'pig'];
}
SuperZoo.prototype.pushAnimal = function(animal) {
return this.zoo.push(animal);
};
SuperZoo.prototype.getAnimal = function() {
return this.zoo;
};
function SubZoo() {
this.isDoorClose = false; // 这里随意定义了一个属性值,可以根据自己需求改动
}
SubZoo.prototype = new SuperZoo(); // 子类原型继承父类实例对象(这是原型链继承的特点)
SubZoo.prototype.isDoorClose = function() {
return this.isDoorClose;
}
let instance1 = new SubZoo();
instance1.pushAnimal('duck'); // 第一个实例进行了修改
console.log(instance1.getAnimal()); // 输出:['dog', 'pig', 'duck']
let instance2 = new SubZoo();
instance1.pushAnimal('swallow'); // 第二个实例进行了修改
console.log(instance1.getAnimal()); // 理应该输出 ['dog', 'pig', 'swallow'], 但实际上输出 ['dog', 'pig', 'duck', 'swallow']
从上述代码中可以看出,两个实例(instance1、instance2)操作了同一个父类实例属性,这里只是向里面推入数据,如果有多个实例对该实例共享属性做增删改查,那么最后得出的结果会和预期不符。
为了解决‘原型链继承’中引用类型实例属性带来的数据混乱问题,进而出现借用构造函数继承(盗用构造函数/对象伪装/经典继承)
这种继承的最大特点是:在子类构造函数中调用父类构造函数,可以传递参数
示例
function SuperType(name) {
this.name = name;
}
function SubType(name) {
SuperType.call(this, name);
this.age = 29;
}
let instance1 = new SubType('kh-demo');
console.log(instance1); // 输出:{ name: 'kh-demo', age: 29 }
优点
缺点
由于借用构造函数继承的缺点导致了它在处理继承问题时不能单独使用。因此引出了“组合继承”(下文会有介绍)
如何理解没有继承父类型的原型,所以原型上的属性和方法无法使用?
function SuperType(name) {
this.name = name;
}
SuperType.prototype.getName = function() {
return this.name;
}
SuperType.prototype.kh = 'demo';
function SubType(name) {
SuperType.call(this, name);
this.age = 29;
}
let instance1 = new SubType('kh-demo');
console.log(instance1.getName(), instance1.kh); // 访问 getName 函数时会报错,访问 kh 变量出现 undefined
上面示例中因为只是继承了父类的实例属性和方法,所有父类原型链上的方法和属性时访问不到的。
组合继承(伪经典继承)综合了原型链和借用构造函数,将两者的优点集中起来。它的思路是使用原型链继承原型上的属性和方法,使用借用构造函数继承实例属性。这样既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性。
这种继承的最大特点是:在子类构造函数中调用父类构造函数以及子类原型继承父类的实例对象
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function() {
return this.name;
};
function SubType(name, age){
// 继承属性
SuperType.call(this, name); // 调用父类构造函数
this.age = age;
}
// 继承方法
SubType.prototype = new SuperType(); // 继承父实例对象
SubType.prototype.sayAge = function() {
this.age;
};
let instance1 = new SubType('kh-demo');
console.log('instance1', instance1);
console.log('instance1.sayName()', instance1.sayName());
优点
缺点
1.缺点也很明显就是调用两次父级构造函数:一次是在创建子类原型的时候,另一次是在子类构造函数内部。
注意:因为SuperType函数在两个地方分别调用,导致了它的实例属性在两个地方都存在。如下图所示
代码示例
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function() {
return this.name;
};
function SubType(name, age){
// 继承属性
SuperType.call(this, name); // 调用父类构造函数
this.age = age;
}
// 继承方法
SubType.prototype = new SuperType(); // 继承父实例对象
SubType.prototype.sayAge = function() {
this.age;
};
let instance1 = new SubType('kh-demo');
delete instance1.colors; // 删除实例上的属性
console.log('instance1', instance1.colors); // 在原型链上查找,并输出 ["red", "blue", "green"]
这种继承的最大特点是:原型式继承的object方法本质上是对参数对象的一个浅复制
如何理解对参数对象的一个浅复制呐?
function object(o) {
function F() {}
F.prototype = o; // 这里是浅复制(和 extend 函数中的复制一样) 函数 F 的原型指向了参数
return new F();
}
let person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
let anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
let yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
console.log(person.friends); // 输出:"Shelby,Court,Van,Rob,Barbie"
es5 中也可以使用Object.create() 方法来创建
优点
缺点
这种继承的最大特点是:二次封装原型式继承(在原型式继承基础上拓展属性和方法)
function object(o) {
function F() {}
F.prototype = o; // 这里是浅复制,和 extend 函数中的复制一样
return new F();
}
function createAnother(original){
let clone = object(original); // 通过调用函数创建一个新对象
clone.sayHi = function() { // 以某种方式增强这个对象
return "hi";
};
clone.newPo = 'kh-demo';
return clone; // 返回这个对象
}
let person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
let instance1 = createAnother(person);
console.log(instance1);// 输出:sayHi 函数和 newPo 属性
缺点
这种继承的最大特点是:它是寄生式继承+借用构造函数的组合
function object(o) {
function F() {}
F.prototype = o; // 这里是浅复制,和 extend 函数中的复制一样
return new F();
}
function inheritPrototype(subType, superType){
const prototype = object(superType.prototype); // 使用原型链继承,继承父类prototype上面的属性和方法
prototype.constructor = subType;
subType.prototype = prototype;
}
inheritPrototype()函数实现了寄生式组合继承的核心逻辑。这个函数接收两个参数:子类构造函数和父类构造函数。在这个函数内部,第一步是创建父类原型的一个副本。然后,给返回的prototype 对象设置 constructor 属性,解决由于重写原型导致默认 constructor 丢失的问题。最后将新创建的对象赋值给子类型的原型。
通过调用 inheritPrototype() 函数就可以实现子类的原型赋值,如下图
寄生组合继承完整代码如下
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
function inheritPrototype(subType, superType){
const prototype = object(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function() {
return this.name;
};
function SubType(name, age) {
SuperType.call(this, name); // 借用构造函数
this.age = age;
}
SubType.prototype.sayAge = function() {
return this.age;
};
inheritPrototype(SubType, SuperType); // 寄生式继承
优点
重温了js常见的6种继承后,看一下 inherits 函数源码。
/**
* 构建类之间的继承关系
*
* @param {Function} subClass 子类函数
* @param {Function} superClass 父类函数
*/
function inherits(subClass, superClass) {
/* jshint -W054 */
var subClassProto = subClass.prototype;
var F = new Function(); // 1
F.prototype = superClass.prototype; // 2
subClass.prototype = new F(); // 2
subClass.prototype.constructor = subClass;
extend(subClass.prototype, subClassProto);
/* jshint +W054 */
}
从 inherits 函数源码可以看出,它使用的继承和寄生组合继承核心逻辑一致。只不过这里使用的代码风格不一样罢了(其实是另一种实现)。
比如
最后通过 extend() 函数把 subClassProto 上的属性和方法复制到 subClass.prototype 上
function SuperType() {
this.name = 'super-demo';
}
SuperType.prototype.sayName = function() {
return 'SuperType-sayName';
};
function SubType() {
this.age = 'sub-age';
}
SubType.prototype.sayAge = function() {
return this.age;
};
inherits(SubType, SuperType);
let subInstance = new SubType();
console.log(subInstance.sayName()); // 输出: super-demo
本篇博客和个人博客https://www.kinghiee.cn/2023/05/07/san-util-inherits/同步更新,更多优质文章请查看个人博客