- 前言:如有不懂可以查看原型/原型链等知识。自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()函数
- 终于分析完了!既然你都看到这里了,觉得有用的话不妨给作者点个赞吧~