继承无疑是面对对象的核心
JS中的继承通过原型链实现的,对原型不理解的推荐阅读:
https://blog.csdn.net/qq_45668041/article/details/115495381
本文结论:
参考资料:
重温原型链
> function SuperType() {
... this.property = true;
... }
undefined
> SuperType.prototype.getSuperValue = function() { // SuperType原型方法
... return this.property;
... }
[Function]
> function SubType() {
... this.subproperty = false;
... }
undefined
> SubType.prototype = new SuperType(); // SuperType实例对象作为原型对象
SuperType { property: true }
> SubType.prototype.getSubValue = function() {
... return this.subproperty;
... };
[Function]![在这里插入图片描述](https://img-blog.csdnimg.cn/20210408224849393.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1NjY4MDQx,size_16,color_FFFFFF,t_70)
> let instance = new SubType();
undefined
> console.log(instance.getSuperValue());
true
原型链如下如红色路径:
继承离不开原型链
通过原型继承多个引用类型的属性和方法,以重用共享。
判断原型和继承的关系
以下两种方法都适用于下面所提到的多种继承方式。
1,instanceof 关键字 :判断实例和构造函数的关系
下图可见,instance 是原型链上所有构造函数的实例。
> instance instanceof SubType;
true
> instance instanceof SuperType;
true
> instance instanceof Object;
true
2,isPrototypeOf()实例方法 :判断原型和实例的关系
下图可见,该方法可以判断指定原型是否再instance的原型链上
> SubType.prototype.isPrototypeOf(instance);
true
> SuperType.prototype.isPrototypeOf(instance);
true
> Object.prototype.isPrototypeOf(instance);
true
原型链存在的问题
原型的继承可以实现数据的共享,但是当前类型的原型实际上变成了另一个类型的实例对象。
1,意味着这样另一个构造函数的实例属性变成了当前类型的原型属性。
// 承接上面的代码
> let sub1 = new SubType(); // 生成两SubType的实例对象
undefined
> let sub2 = new SubType();
undefined
> console.log(sub1.property, sub2.property);// 找到原型属性(本身并没有)
true true
undefined
> sub1.hasOwnProperty("property"); // 验证不是自身属性
false
> sub2.hasOwnProperty("property");
false
// 结果可见,SuperType构造函数中定义的实例属性变成了SubType的原型属性
// 因为SuperType的实例对象作为了SubType的原型。
2,只能给子类型构造函数传参,却无法给父类型的构造函数传参。
因为子类型的构造函数只是引用父类型的对象其中的属性和方法,只能对其覆盖,却不能修改。
所以,基本不会单独使用原型链实现继承。
借用构造函数的思想
核心:
通过 手动调用其他构造函数 (call apply)来给当前类型的对象添加实例属性和方法(本例不涉及原型的继承)。
> function SuperType(name) { // 父类构造
... this.name = name;
... this.colors = ["red", "blue", "green"];
... }
undefined
> function SubType(name, age) { // 子类构造
... SuperType.call(this, name); // 手动调用父类构造函数,可以传递参数
... this.age = age;
... }
undefined
// 注意:父类和子类并未用原型连接起来,所以还没有继承关系,只是通过父类组合子类
> let instance1 = new SubType("ES"); // 创建两个子类实例对象
undefined
> let instance2 = new SubType("JS");
undefined
> instance1.colors.push("black");
4
> console.log(instance1.colors, instance2.colors);
[ 'red', 'blue', 'green', 'black' ] [ 'red', 'blue', 'green' ]
// 可见,两个对象中保存的结果并不一样
// 因为,构造函数中手动调用了其他构造函数,使得创建的实例对象创建了同样的属性和方法
关于手动调用的 call apply bind 方法:
方法 描述 func.call(指针, 参数列表); 指针赋给func函数的this,值逗号分隔,对应传给func形参 func.call(指针, [参数列表]; 同理,不同的是参数列表用数组传参 func.bind(指针, 参数); 和call一样,只是返回一个函数需要,追加()表示调用函数
优点:
可以通过调用其他构造函数来创建自己的实例属性,以实现重用。
缺点:
通过其他构造函数创建的是实例方法,而不同对象中该方法功能相同(方法应该放在当前构造函数的原型,实现重用)。
结论:
实例属性可以手动调用其他构造函数来创建;
实例方法应该放在构造函数的原型上实现重用;
组合继承
组合继承正是采取了 借用构造函数 + 原型链 的思想。
核心:
借用构造函数组合出实例属性,func.call()方法
使用原型链继承原型属性和方法,Sub.prototype = new Sup();
// 修改上面的代码
function SubType(name, age) {
SuperType.call(this, name); // 手动调用父类构造函数
this.age = age;
}
SubType.prototype = new SuperType(); // 父类实例对象作为原型对象
// 给子类原型添加constructor实例属性(可枚举),使子类对象能找到子类构造函数
SubTyep.prototype.constructor = SubType;
// 可见,原型将父类和子类联系了起来,有继承关系了,同时也利用父类组合出子类
分析:
由于父类对象作为子类的原型,所以父类对象中的实例属性和方法就是子类的原型属性和原型方法。
由于父类组合出子类,所以子类对象中保留了一份父类实例属性的副本,作为实例属性。
可见,两者是一样的,都来自于父类构造函数,实际上,子类对象的实例方法会屏蔽原型中的同名属性和方法
结论:
需要共享的属性可以放在原型链上;
实例属性可放在其他构造函数中;
组合继承存在的不足
虽然可以实现方法和属性的重用,但 调用了两次父类构造函数:
第一次在创建子类原型对象时调用,
第二次在子类构造函数中调用。
原型式继承
核心原理:
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
可见,这样可以把已有对象作为原型来创建新对象,而不需要另外定义构造函数。
应用场景:
非常适合不需要单独创建构造函数,但仍然需要在对象间共享信息的场合。
这也是ES5推出的 Object.create() 方法的实现原理。
Object.create(作为原型的对象,{ 新对象的自身属性描述符 }); 返回新对象
寄生式继承
就是在原型式继承的继承上增强对象而已(添加实例属性或方法)。
所以,和原型式继承相似,创建的对象没有对应的构造函数。
// 承接上面的代码
function createAnother(original) {
let clone = object(original); // 得到新对象,原型为original
clone.sayHi = function() { // 增强对象
console.log("hi");
};
return clone;
}
寄生式组合继承
思想:
不采用父类实例化对象作为子类原型:Sub.prototype = new Sup();
而采用父类原型创建的新对象来作为子类的原型:Sub.prototype=Object.create();
借用构造函数来继承属性,sup.call()方法
寄生式继承来继承父类原型,返回的新对象作为子类原型
// 核心代码:
// 参数:子类构造函数,父类构造函数
function inheritPrototype(SubType, SuperType) {
let prototype = object(SuperType.prototype);// 返回原型为父类原型的新对象(前面原型式继承有具体实现)
prototype.constructor = SubType;// 添加constructor属性,增强对象
SubType.prototype = prototype; // 子类原型指向新对象
}
案例:
// 父类构造
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;
}
// 创建父类原型的实例对象
let prototype = Object.create(SuperType.prototype);//等效于前面的object()方法
// 该实例对象作为子类原型,并记录子类构造函数
prototype.constructor = SubType; // 原型对象自身属性
SubType.prototype = prototype;
// 给子类原型添加原型方法
SubType.prototype.sayAge = function() {
console.log(this.age);
};
1,起初的子类和父类状态:之间还没有继承关系!
2,发生继承关系后:
可见,寄生式组合继承方式只调用一次父构造函数以实现实例属性的继承,且不用创建父类实例对象作为子类原型,而是通过新对象直接引用父类原型。
这是实现继承的最佳方式