继承在高级程序语言中是非常常用的,此处讨论js继承的几种情况
- 构造函数继承,使用call或apply
let Father = function(){
this.type = "person"
}
let Son = function(){
this.age = 10
Father.call(this);
}
let oneSon = new Son()
console.log(oneSon.type) //person
console.log(oneSon.age) //10
子实例内使用父类函数的call()使得子实例可以获取父类中的自有属性
-
原型继承
先定义父类函数和子类函数
let Father = function(){
this.type = "person"
}
Father.prototype = {
sex:"man",
walk(){
console.log("walk")
}
}
Object.defineProperty(Father.prototype,"constructor",{
value:Father,
enumerable:false
}) //由于将一个对象直接量赋值给父原型,故将其constructor属性定义回来并设置为不可枚举
let Son = function(){
this.age = 10
}
一下为原型继承的各种情况
2.1 将父原型赋值给子原型,则改变子原型,父原型会跟着变,不符合需求
Son.prototype = Father.prototype
let oneSon = new Son()
console.log(oneSon.type) //undefined,此时没有继承自父构造函数
console.log(oneSon.age) //10
console.log(oneSon.sex) //man
Son.prototype.sex = "women";
console.log(Father.prototype.sex) //woman 改变子原型,父原型会跟着变
console.log(oneSon.sex) //woman
oneSon.walk() //walk
2.2 将父实例赋值给子原型,此时改变子原型则父原型不变,但是所有父内容都在子原型上
Son.prototype = new Father()
let oneSon = new Son()
console.log(oneSon.type) //person
console.log(oneSon.age) //10
console.log(oneSon.sex) //man
Son.prototype.sex = "women";
console.log(Father.prototype.sex) //man 改变子原型,父原型不变
oneSon.walk() //walk
console.log(Object.keys(oneSon)) //打印子实例中可枚举的自有属性,结果为 ["age"]
//尝试打印所有可枚举的自有属性以及继承属性,用for/in
let arr = [];
for(let i in oneSon){
arr.push(i)
}
console.log(arr) //["age","type","sex","walk"]
2.3 寄生式继承
2.3.1 赋实例
let TempFunc = function(){};
TempFunc.prototype = new Father();
Son.prototype = new TempFunc()
Object.defineProperty(Son.prototype,"constructor",{
value:Son,
enumerable:false
}) //将子原型构造器矫正
let oneSon = new Son()
console.log(oneSon.type) //person
console.log(oneSon.age) //10
console.log(oneSon.sex) //man
oneSon.walk() //walk
console.log(Object.keys(oneSon)) //打印子实例中可枚举的自有属性,结果为 ["age"]
//尝试打印所有可枚举的自有属性以及继承属性,用for/in
let arr = [];
for(let i in oneSon){
arr.push(i)
}
console.log(arr) //["age","type","sex","walk"]
此方式与父实例给子原型相似,且比前者多继承了一层,属实没有必要。
2.3.2 赋父原型
let TempFunc = function(){};
TempFunc.prototype = Father.prototype //注意,此处是将父原型赋值给临时类的原型,
//修改临时类的原型时父原型也会变,但我们此处没必要也不会这么做
Son.prototype = new TempFunc()
Object.defineProperty(Son.prototype,"constructor",{
value:Son,
enumerable:false
}) //将子原型构造器矫正
let oneSon = new Son()
console.log(oneSon.type) //undefined ,此时只继承了父原型,没有继承父构造函数
console.log(oneSon.age) //10
console.log(oneSon.sex) //man
oneSon.walk() //walk
console.log(Object.keys(oneSon)) //打印子实例中可枚举的自有属性,结果为 ["age"]
//尝试打印所有可枚举的自有属性以及继承属性,用for/in
let arr = [];
for(let i in oneSon){
arr.push(i)
}
console.log(arr) //["age","sex","walk"]
3.寄生组合式继承
结合1和2.3.2即可,下面是整合代码
let inherit = function(Father,Son){
if(Father&&typeof Father==="function"&&Son&&typeof Son==="function"){
//先寄生式继承原型
let TempFunc = function(){};
TempFunc.prototype = Father.prototype
Son.prototype = new TempFunc();
Object.defineProperty(Son.prototype,"constructor",{
value:Son,
enumerable:false
}) //将子原型构造器矫正
//再继承父构造函数,另一方面也是先改原型后创建实例,不然出
//问题(原型对象赋值新对象时在它之前定义的实例不会及时变更)
let newSon = new Son();
Father.call(newSon);
//返回处理后的子实例
return newSon
}else{
throw new Error("请传入两个构造函数")
}
}
//使用inherit函数
let exp = inherit(Father,Son);
console.log(exp)
//输出:
//Son {age: 10, type: "person"}
// age: 10
// type: "person"
// __proto__: Father
// constructor: ƒ ()
// __proto__:
// sex: "man"
// walk: ƒ walk()
// constructor: ƒ ()
// __proto__: Object
总结:几种不同的继承方法的本质要搞明白,几种有明显缺点的继承方法就不要再用了,实际上关于js继承我们可以尽量用寄生组合式继承。