js实现继承的方式

本文深入探讨JavaScript中的六种继承实现:原型链、构造函数、组合继承、原型式继承、寄生式继承和寄生组合继承。每种方式的优缺点、工作原理及应用场景均有详细阐述,帮助理解JavaScript中对象间的继承机制。

原型链

构造函数、原型和实例之间的关系:
每个构造函数都有一个原型对象(prototype),原型也有一个属性(constructor)指向构造函数,而实例有一个内部指针(__proto__)指向原型。

如果一个构造函数的原型是另一个类型的实例,则这个原型本身有一个内部指针指向另一个原型,相应地,另一个原型也有一个指针指向另一个构造函数。这样就在实例和原型之间构造了一条原型链

function Father() {
	this.property = true;
}
Father.prototype.getFatherProperty = function() {
	return this.property;
}
function Son() {
	this.sonProperty = false;
}
Son.prototype = new Father();
Son.prototype.getSonProperty = function() {
	return this.sonProperty;
}
let instance = new Son();
console.log(instance.getFatherProperty());//true
//搜索getFatherProperty方法经历了3步,instance、Son.prototype和Father.prototype。
//搜索不到会一直持续到原型链的末端,通常是Object.prototype

原型与实例的关系

  1. 使用instanceof操作符,如果一个实例的原型链中出现过相应的构造函数,则instanceof返回true
console.log(instance instanceof Object);//true
console.log(instance instanceof Father);//true
console.log(instance instanceof Son);//true
  1. 使用isPrototypeOf()方法,只要原型链中包含这个原型,就返回true
console.log(Object.prototype.isPrototypeOf(instance));//true
console.log(Father.prototype.isPrototypeOf(instance));//true
console.log(Son.prototype.isPrototypeOf(instance));//true

原型链的问题
如果Father构造函数定义了一个引用类型的属性,则Son通过原型继承Father后,Son.prototype是Father的一个实例,因而也有这个引用类型的属性。所以现在所有的Son的实例都会共享这个属性,即其中一个实例修改了这个属性值也会反映到别的实例上。

盗用构造函数(经典继承)

在子类构造函数中调用父类构造函数,使用apply()或call()方法以子类新创建的对象为上下文this来执行父类构造函数。

function Father() {
	this.hobbies = ['music','football','swimming'];
	this.sayHi = function() {
		console.log('Hi!');
	}
}
function Son() {
	Father.call(this);
}
let instance1 = new Son();
instance1.hobbies.push('chess');
console.log(instance1.hobbies);//["music", "football", "swimming", "chess"]
instance1.sayHi();//Hi!

let instance2 = new Son();
console.log(instance2.hobbies);//["music", "football", "swimming"]
instance2.sayHi();//Hi!

盗用构造函数的问题
在构造函数中定义的方法,不能重用,每次创建新实例,都会给该实例添加一遍方法属性,造成资源浪费。而且使用这种方式继承,子类不能访问父类原型上的属性和方法,只能使用构造函数模式。

组合继承(伪经典继承)

综合了原型链和盗用构造函数,使用原型链继承原型上的属性和方法,通过构造函数继承实例属性。这样可以把方法定义在原型上得以重用,每个实例有属于自己的属性。

function Father() {
	this.hobbies = ['music','football','swimming'];
}
Father.prototype.sayHi = function () {
	console.log('Hi!');
}
function Son() {
	Father.call(this);
}
Son.prototype = new Father();

let instance1 = new Son();
instance1.hobbies.push('chess');
console.log(instance1.hobbies);//["music", "football", "swimming", "chess"]
instance1.sayHi();//Hi!

let instance2 = new Son();
console.log(instance2.hobbies);//["music", "football", "swimming"]
instance2.sayHi();//Hi!

组合继承应该是使用最多的继承方式,但两次调用父类构造函数,让子类原型中存在多余的属性(子类实例中已经存在的属性),导致效率低下。

原型式继承

有这么一个函数,先创建一个临时构造函数,将传入的对象赋值给这个构造函数的原型,然后返回这个临时构造函数的一个实例。本质上,是对传入的对象执行了一次浅复制。

function object(o) {
	function F() {}
	F.prototype = o;
	return new F();
}

Object.create()接收两个参数,一个是作为新对象原型的对象,另一个是给新对象定义额外属性的对象(可选)。

在只有一个参数时,效果与object()方法相同。

let house = {
	furniture: ['fan','refrigerator','table']
}
let house1 = Object.create(house);
house1.furniture.push('chair');

let house2 = Object.create(house);
house2.furniture.push('cabinet');

console.log(house.furniture);//["fan", "refrigerator", "table", "chair", "cabinet"]

Object.create()的第二个参数与Object.defineProperties()的第二个参数一样,通过各自的描述符来描述,这样添加的属性会遮蔽原型对象上的同名属性。

let house = {
	color: 'yellow',
	furniture: ['fan','refrigerator','table']
}
let anotherHouse = Object.create(house, {
	color: {
		value: 'green'
	}
})
console.log(anotherHouse.color);//green

原型式继承适合不需要创建构造函数,但需要在对象间共享信息的场合,但属性中包含引用值的会在相关对象间共享。

寄生式继承

创建一个实现继承的函数(比如前面用到的object函数),以某种方式增强对象,然后返回这个对象。

function createAnother(original){
	let clone = object(original);
	clone.getColor = function() {
		console.log(this.color);
	};
	return clone;
}
let house = {
	color: 'yellow',
	furniture: ['fan','refrigerator','table']
}
let anotherHouse = createAnother(house);
anotherHouse.getColor();//yellow

object()函数不是一定的,任何返回新对象的函数都可以使用
这里添加的函数是难以重用的,与构造函数类似

寄生式组合继承

寄生式组合继承通过盗用构造函数继承属性,使用混合式原型链继承方法。

这里不通过调用父类构造函数给子类原型赋值,而是使用寄生式继承来继承父类原型,然后将返回的新对象赋值给子类原型。

function inheritPrototype(subType, superType) {
	function F() {};
	F.prototype = superType.prototype;
	let prototype = new F();
	prototype.constructor = subType;
	subType.prototype = prototype;
}
function Father() {
	this.hobbies = ['music','football','swimming'];
}
Father.prototype.sayHi = function () {
	console.log('Hi!');
}
function Son() {
	Father.call(this);
}
inheritPrototype(Son, Father);
let instance1 = new Son();
instance1.hobbies.push('chess');
console.log(instance1.hobbies);//["music", "football", "swimming", "chess"]
instance1.sayHi();//Hi!

let instance2 = new Son();
console.log(instance2.hobbies);//["music", "football", "swimming"]

这里只调用了一次父类构造函数,避免了组合继承中子类的原型上不必要的属性,因此这个例子的效率最高,可以算是引用类型继承的最佳模式。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值