这篇文章是为了做什么?
这篇文章可以来捋清实现继承的专业名词hha
组成继承是什么?
组合继承(也叫伪经典继承)综合了原型链和盗用构造函数。
基本的思路是使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性。这样既可以把方 法定义在原型上以实现重用,又可以让每个实例都有自己的属性。
原型链大家都知道,盗用构造函数是什么鬼?
- 盗用构造函数”(constructor stealing)(也称作“对象伪装”或“经典继承”)。
基本思路很简单:在子类构造函数中调用父类构造函数。
- 它的出现是为了解决:原型包含引用值导致的继承问题。
举个栗子
function Father(name){
this.name = name;
}
function Son() {
// 继承 Father 并传参
Father.call(this, "Nicholas");
// 实例属性
this.age = 29;
}
Son 里面的 Father.call(this, “Nicholas”); 就是盗用构造函数
它的优缺点
- 相对于原型链,盗用构造函数可以在子构造函数中给父构造函数传参
- 子类也不能访问父类原型上定义的方法,因此所有类型只能使用构造函数模式。
- 必须在构造函数中定义方法,因此函数不能重用
如何实现组合继承?
代码描述:
- 创建父子构造函数、父子构造函数的原型方法。
- 子构造函数中盗用父构造函数,同时绑定父构造函数的实例到子的原型上
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.sayAge = function() {
console.log(this.age);
};
let instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
console.log(instance1.colors); // "red,blue,green,black"
instance1.sayName(); // "Nicholas"; instance1.sayAge(); // 29
let instance2 = new SubType("Greg", 27);
console.log(instance2.colors); // "red,blue,green"
instance2.sayName(); // "Greg";
instance2.sayAge(); // 27
SubType
的原型链上会存在一个SuperType
的实例,从而可以继承SuperType
的属性和方法。但是,这种方式也会导致一些问题,因为SubType.prototype
直接引用了一个SuperType
的实例,所以对于SubType.prototype
的修改可能会影响到其他使用SuperType
的子类。- 组合继承其实也存在效率问题。最主要的效率问题就是父类构造函数始终会被调用两次:一次在是 创建子类原型时调用,另一次是在子类构造函数中调用。
ok接下来实现组合继承的 new
/**
*
* @param {*} 这种方式为组合继承,解决了原型继承的问题:原型构造函数中的所有属性变为原型属性,并且被所有实例共享
* @param {*} 同时解决了盗用构造函数(对象伪装、经典继承)的问题: 子类构造函数可以向父类构造函数传递参数,,并且每个
* @param {*} 实例都会拥有单独的父类构造函数的属性,不再全部共享父类构造函数的属性,但是同时因为改变this指向,子类也无法访问父类的原型
*/
function myNew(fn, ...args) {
// 创建一个空对象,该对象的 __proto__ 属性指向构造函数的 prototype对象
// const obj = Object.create(fn.prototype);
const obj = {}
obj.__proto__ = fn.prototype // 原型继承
// 将构造函数中的 this 绑定到新创建的对象上
const result = fn.apply(obj, args); // 盗用构造函数
// const result = fn.call(obj, ...args);
/**
* @why 为什么要判断 object
* 判断构造函数的返回值是否为对象,如果是则直接返回该对象,否则返回新对象
* 防止构造函数 Person 中返回对象把 new 创建的对象覆盖
*/
if (result && typeof (result) === 'object') {
return result;
}
return obj;
}
function Person(name, age) {
this.name = name;
this.age = age;
/**
* @why 为什么要判断 object 的情况
*/
// return {aaa:'aaa'}
}
const p = myNew(Person, 'Tom', 16);
console.log(p); // Person { name: 'Tom', age: 16 }
寄生式组合继承是什么?
寄生式组合继承上原型式继承和寄生式继承的组合
原型式继承是啥?
是即使不自定义 类型也可以通过原型实现对象之间的信息共享。它的实现是以下函数:
function object(o) {
function F() { }
F.prototype = o;
return new F();
}
- 这个 object()函数会创建一个临时构造函数,将传入的对象赋值给这个构造函数的原型,然后返回这个临时类型的一个实例。本质上,object()是对传入的对象执行了一次浅复制。
let person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
let a_person = object(person);
a_person.name = "Greg";
a_person.friends.push("Rob");
let b_person = object(person);
b_person.name = "Linda";
b_person.friends.push("Barbie");
console.log(person.friends); // "Shelby,Court,Van,Rob,Barbie"
console.log(person.name); // Nicholas
console.log(a_person); // "Shelby,Court,Van,Rob,Barbie"
console.log(b_person); // "Shelby,Court,Van,Rob,Barbie"
- 适用于这种情况:你有一个对象,想在它的基础上再创建一个新对象。 8 你需要把这个对象先传给 object(),然后再对返回的对象进行适当修改。
- 在这个例子中,person 对象定义了另一个对象也应该共享的信息,把它传给 object()之后会返回一个新对象。这个新对象的原型 是 person,意味着它的原型上既有原始值属性又有引用值属性。这也意味着 person.friends 不仅是 person 的属性,也会跟 a_person 和 b_person 共享。
寄生式继承是啥?
寄生式继承背后的思路类似于寄生构造函数和工厂模式:创建一个实现继承的函数,以某种 方式增强对象,然后返回这个对象。
function createAnother(original){
let clone = object(original); // 通过调用函数创建一个新对象
clone.sayHi = function() { // 以某种方式增强这个对象
console.log("hi");
};
return clone; // 返回这个对象 }
}
- 寄生式继承同样适合主要关注对象,而不在乎类型和构造函数的场景。object()函数不是寄生式 继承所必需的,任何返回新对象的函数都可以在这里使用
- 通过寄生式继承给对象添加函数会导致函数难以重用,与构造函数模式类似。
如何实现寄生式组合继承?
- 它的基本模式如下:
// 寄生式组合继承:
function inheritPropertype(Sub, SuperType) {
let prototype = object(SuperType); // 创建对象:原型式继承
prototype.constructor = Sub; // 增强对象:修改父类构造函数的实例,属于寄生式继承,指定constructor是为了解决重写子类原型导致的constructor丢失
Sub.prototype = prototype; // 赋值对象:赋值修改prototype
}
相对于组合继承,这里只调用了一次 SuperType 构造函数,避免了 SubType.prototype 上不必要也用不到的属性,因此可以说这个例子的效率更高。
而且,原型链仍然保持不变,因此 instanceof 操作符和isPrototypeOf()方法正常有效。寄生式组合继承可以算是引用类型继承的最佳模式
接下来实现寄生式组合继承的 new
/**
* @param {*} 寄生式组合继承:对组合继承的扩展
* @why 使用这个函数来拷贝 SuperType.prototype 来防止赋值是地址的引用
*/
function object(obj) {
// 原型式继承 Object.create()是使用的这种方式,原型式继承的规范化,第一个参数为原型对象,第二个参数是新增属性
function F() {} // 一个临时的构造函数
F.prototype = obj.prototype; // 修改临时构造函数的原型指向
return new F(); // 返回临时构造函数的实例,实际上是对 obj 的一次浅复制
// 若在return 之前,对F的实例进行修改属性,则为寄生式继承
}
// 寄生式组合继承:
function inheritPropertype(Sub, SuperType) {
let prototype = object(SuperType); // 原型式继承
prototype.constructor = Sub; // 修改父类构造函数的实例,属于寄生式继承,指定constructor是为了解决重写子类原型导致的constructor丢失
Sub.prototype = prototype; // 赋值修改prototype
}
// 定义父构造函数
function SuperType(type) {
this.type = type;
}
// 定义一个父上下文的原型方法
SuperType.prototype.sayType = function (){
console.log(`SuperType's prototype function :${this.type}`);
}
// 定义子构造函数
function Sub(type, age) {
SuperType.call(this, type);
this.age = age;
}
/**
* @usePlace 继承
*/
inheritPropertype(Sub, SuperType);
// 修改原型后定义一个Sub上的原型方法
Sub.prototype.sayAge = function () {
console.log(`Sub's prototype function :${this.age}`);
}
const SubT = new Sub('a', 12);
console.log(SubT); // Sub { type: 'a', age: 12 }
SubT.sayAge(); // Sub's prototype function :12
SubT.sayType(); // SuperType's prototype function :a