理解 JavaScript 继承的核心(基于原型)

  • 前言:如有不懂可以查看原型/原型链等知识。自es6出来后就有class关键字了,实战不用这么麻烦,但class只是语法糖,核心还是基于原型,理解很有必要

原型链继承

  • 简单理解,串联起各个原型实现继承

  • 通过原型链实现继承时,不能使用对象字面量创建原型方法,否则原型链被重写(原型间的关系断了)

function F1 () {
	this.a = true
}
F1.prototype.method1 = function () {
	return this.a
}

function F2 () {
	this.x = false
}
// 创建F1的实例,使得F2的原型指向了F1的,且F2的原型取得了F1原型中的属性和方法
F2.prototype = new F1() // 继承了F1
F2.prototype.method2 = function () { // F2新增一个方法
	return this.x
}

let instance = new F2()
// instance指向F2的原型,F2的~指向F1的~
console.log(instance.x)        // false
console.log(instance.method1)  // true
  • 弊端(所以实践中很少单独使用该方法)
    • 当牵涉到引用类型值时,所有实例会共享该值,在其中一个实例上改变该值则其他实例调用时也被改变了(因为 ins1 和 ins2 都是引用的 F1 中的a属性)
    • 创建子类型的实例时,不能向超类型的构造函数中传递参数

借用构造函数

  • 在子类构造函数的内部调用超(父)类型构造函数实现继承
function F1 (name) { // 设置参数name
	this.name = name
	this.al = ["a", "b", "c"]
}

function F2 () {
	// 创建实例时,既调用F2的构造函数,又调用F1的,则实例兼具它两的属性、方法
	F1.call(this, "Eric") // 继承,同时子类(F2)向父类(F1)传参
	this.age = 20 // 为了防止父类重写子类属性,子类定义的属性应在继承语句后
}

let ins1 = new F2()
ins1.al.push("d") // 向al数组中添加一个 “d”,仅针对该实例而言
let ins2 = new F2()

console.log()
console.log(ins1.name + ins1.age) // Eric20
console.log(ins1.al) // ["a", "b", "c", "d]
console.log(ins2.al) // ["a", "b", "c"],ins1并未影响ins2
// 因为每个实例创建时都是重新执行F1的构造函数,取得的属性(此处是al)是副本
  • 弊端(虽然该方法解决了原型链继承的两个弊端)
    • 方法都在构造函数中定义,函数无法复用
    • 父类原型定义的方法子类不能用(显然,没有串联起原型)

组合继承

  • 将上述两种方法结合:原型链实现原型上的继承;构造函数实现实例上的继承
  • 实现了函数复用,保证每个实例都有自己的属性
// 基本上就是把之前的代码拼接起来
function F1 (name) {
	this.name = name
	this.al = ["a", "b", "c"]
}
F1.prototype.method1 = function () {
	console.log(this.name)
}
function F2 (name, age) { //继承属性
	F1.call(this, name)
	this.age = age
} //继承方法
F2.prototype = new F1()
// 此处F2的prototype被F1的覆盖,导致F2失去默认的constructor,需要修正回来
// constructor:返回创建实例对象的构造函数的引用。
F2.prototype.constructor = F2
F2.prototype.method2 = function () {
	console.log(this.age)
}
  • 弊端:无论什么情况下,都会调用两次超类型构造函数

原型式继承

  • 借助原型基于已有的对象创建新对象,同时不用创建自定义类型
// es5新增Object.create()方法取代了这个
function Obj (o) {
	function F () {} // 创建一个临时性的构造函数
	F.prototype = o  // o对象作为构造函数的原型
	return new F()   // 返回该临时类型的一个新实例
}

let person = {
	name: "eric",
	arr: ["a", "b"]
}

let x = Obj(person) // es5调用:Object.create(person)
x.name = "mike"
x.arr.push("c")

let y = Obj(person)
y.name = "amy"
y.arr.push("d")

console.log(x.arr) // ["a", "b", "c", "d"]
  • 弊端:用原型继承的通病,引用类型的值共享,任何一个实例的该值改变,其他实例的也会变

寄生式继承

  • 创建一个仅用于封装继承过程的函数,函数内部实现继承,最后返回对象
function F1 (o) {
	let x = Obj(o)      // 上文中的Obj函数
	x.say = function () {  // 给o加方法
		console.log("hi")
	}
	return x               // 返回一个新对象(携带了say方法)
}

let person = {
	name: "eric"
}

let y = F1(person)
y.say() // "hi"
  • 弊端:显然,代码难以复用。想要生成不同的对象就得改写 F1 函数

寄生组合式继承

  • 通过借用构造函数来继承属性,通过原型链的混合形式继承方法
// 寄生组合式继承函数
// 解决组合继承调用两次F3的弊端,同时避免在父类的原型上创建多余的属性
function F1 (F2, F3) { // F2是子类,F3是父类
	// Obj()为前文对应的函数
	let p = Obj(F3.prototype) // 创建F3原型的副本p
	// 给p加上由于重写原型而失去的“默认”的constructor属性
	p.constructor = F2 
	F2.prototype = p // 将p赋值给F2的原型
}

function F3 (name) {
	this.name = name
	this.x = ["a", "b"]
}
F3.prototype.say1 = function () {
	console.log(this.name)
}

function F2 (name, age) {
	F3.call(this, name) // 仅调用一次F3的构造函数
	this.age = age
}

F1(F2, F3) // 实现继承
F2.prototype.say2 = function () {
	console.log(this.age)
}

// 检测
let f1 = new F3("mike")
let f2 = new F2("eric", "12")
console.log(f1.name + " " + f2.name) // eric mike
console.log(f2.age + " " + f2.say2()) // 12 12
console.log(f1.say2) // undefined,说明f1的原型上没有新增say2()函数
  • 终于分析完了!既然你都看到这里了,觉得有用的话不妨给作者点个赞吧~
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值