关于ES5中继承的思考和总结

继承无疑是面对对象的核心

JS中的继承通过原型链实现的,对原型不理解的推荐阅读:
https://blog.csdn.net/qq_45668041/article/details/115495381

本文结论:

寄生式组合继承是最佳的继承方式

参考资料:
JavaScript高级程序设计 (第四版)

重温原型链


> 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,发生继承关系后:
在这里插入图片描述
可见,寄生式组合继承方式只调用一次父构造函数以实现实例属性的继承,且不用创建父类实例对象作为子类原型,而是通过新对象直接引用父类原型。
这是实现继承的最佳方式

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值