js实现继承
许多oo语言都支持两种继承方式:接口继承和实现继承,接口继承只继承方法签名,而实现继承则继承实际的方法,如前所述,由于函数没有签名,在ECMAScript中无法实现接口继承,只支持实现继承(JavaScript高级程序设计)
结合网上大家的理解,总结了七种方法
1、通过原型链继承(子类的prototype为父类的实例)
写的时候一直不明白原型链继承的第二个问题,为什么不能传递参数,后来百度,结合两位大哥的解释,明白多了,直接上图
直接上代码有注释
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>手写继承-原型链继承</title>
</head>
<body>
<script>
// 1、原型链继承
function Father(sex){
this.name="quan"
this.do=["打游戏","刷抖音","等消息"]
this.sex=sex
}
Father.prototype.sayHi=function(){
console.log('Im '+this.name)
}
function Son(sex){
this.age=21
// this.sex=sex // 但是,这个值必定会影响所有子类的对象实例,相当于子类构造函数 擅作主张 给所有对象实例的属性提前 赋了值。这样写是不太符合面向对象编程的规则的。
}
// 子类的prototype为父类的实例(核心思想)
// Son.prototype=new Father(this.sex) //这里可以传一个参数上去,但是必须在Son加this.sex=sex,但是这里加了Son的实例就已经有了这个属性,Father里面在加好像也没什么意义(证明了出现的问题二),影响了其他对象实例(自我理解,有什么错误,欢迎交流)
Son.prototype=new Father()
// 这里给原型添加的方法,一定要在替换原型的后面(如果是给实例添加属性和方法,可以放在构造函数里面),这里是替换原型,如果加前面就加到一开始的默认的原型上面去了
Son.prototype.sayAge=function(){
console.log('Im '+this.age)
}
let instance1=new Son()
instance1.sayHi() //Im quan
instance1.sayAge() //Im 21
let instance2=new Son()
instance2.do.push('看直播')
// 1、这里反映了原型链继承的第一个问题-----------来自原型对象的所有属性被所有实例共享
console.log(instance1.do) //["打游戏","刷抖音","等消息","看直播"]
console.log(instance2.do) //["打游戏","刷抖音","等消息","看直播"]
instance1.name="ddd" //这里是在实例上添加属性,以后访问的时候,直接就是访问对象的,直接屏蔽原型的同名属性
instance1.__proto__.name="fff" //这里就是访问原型的属性,这样instance2的原型中name也为fff,证明了出现的问题一,不只是包含引用类型值的原型属性会被所有实例共享
console.log(instance1.name) //ddd
console.log(instance2.name) //fff
// 2、这里反映了原型链继承的第二个问题-----------不能向超类型的构造函数值传递参数,实际上,应该说没有办法在不影响所有对象实例的情况下向超类型的构造函数值传递参数
let instance3=new Son('男')
console.log(instance3.sex) //undefined
</script>
</body>
</html>
2、通过借用构造函数继承(在子类型构造函数的内部,调用超类型构造函数)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>手写继承-借用构造函数继承</title>
</head>
<body>
<script>
// 2、借用构造函数继承
function Father(sex){
this.name="quan"
this.do=["打游戏","刷抖音","等消息"]
this.sex=sex
// 1、借用构造函数继承的问题------------方法只能在构造函数中定义,函数复用无从谈起
this.sayHi=function(){
console.log('Im '+this.name)
}
this.saySex=function(){
console.log('Im '+this.sex)
}
}
// 这里可以去掉,根本没有用到原型链,访问不到
// Father.prototype.sayHi=function(){
// console.log('Im '+this.name)
// }
function Son(sex){
Father.call(this,sex)//在子类型构造函数的内部,调用超类型构造函数(核心思想)这里还可以传递参数了
this.age=21 //写下后面好一些,不然如果父类有同样的,会被覆盖
this.sayAge=function(){
console.log('Im '+this.age)
}
}
// Son.prototype.sayAge=function(){
// console.log('Im '+this.age)
// }
let instance1=new Son('男')//可以传递参数到超类型构造函数
console.log(instance1.age) // 21
console.log(instance1.name) // quan
instance1.sayHi() // Im quan
instance1.sayAge() // Im 21
instance1.saySex() // Im 男
let instance2=new Son()
instance2.do.push("看直播")
console.log(instance1.do) // ["打游戏","刷抖音","等消息"]
console.log(instance2.do) // ["打游戏","刷抖音","等消息","看直播"]
</script>
</body>
</html>
3、组合继承(前两种结合)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>手写继承-组合继承</title>
</head>
<body>
<script>
// 3、组合继承
// 结合了原型链继承和借用构造函数继承的优点
// 1、组合继承的问题
// 第一次调用Father会在Son.prototype得到三个属性name,do,sex(里面应该为undefined)
// 第二次调用Father在新对象创建了实例属性name,do,sex(都有值)通过实例属性屏蔽了原型中的同名属性
function Father(sex){
this.name="quan"
this.do=["打游戏","刷抖音","等消息"]
this.sex=sex
}
Father.prototype.sayHi=function(){
console.log('Im '+this.name)
}
function Son(age,sex){
// 在子类型构造函数的内部,调用超类型构造函数(核心思想)这里还可以传递参数了
Father.call(this,sex) // 第二次调用Father
this.age=age // 写下面好一些,不然如果父类有同样的,会被覆盖
}
console.log(Son.prototype.constructor) //还没替换原型之前指向Son
// 子类的prototype为父类的实例(核心思想)
Son.prototype=new Father() // 第一次调用Father
console.log(Son.prototype.constructor) //替换原型之后指向Father
Son.prototype.constructor=Son //替换回来
Son.prototype.sayAge=function(){
console.log('Im '+this.age)
}
let instance1=new Son(21,'男') //可以传递参数到超类型构造函数
console.log(instance1.age) // 21
console.log(instance1.name) // quan
console.log(instance1.sex) // 男
instance1.sayHi() // Im quan
instance1.sayAge() // Im 21
let instance2=new Son(18,'女')
instance2.sayAge() // Im 18
console.log(instance2.sex) // 女
instance2.do.push("看直播")
console.log(instance1.do) // ["打游戏","刷抖音","等消息"]
console.log(instance2.do) // ["打游戏","刷抖音","等消息","看直播"]
</script>
</body>
</html>
4、原型式继承(拷贝继承)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>手写继承-原型式继承</title>
</head>
<body>
<script>
// 4、原型式继承(拷贝继承)
// 问题
// 来自原型对象的所有属性被所有实例共享(和原型链继承一样)
let Father={
name:"quan",
do:["打游戏","刷抖音","等消息"]
}
// 实现拷贝的函数
function object(o){
function F() {}
F.prototype=o
return new F()//返回新实例
}
let Son1=object(Father)
let Son2=object(Father)
// 这里可以不用上面的拷贝函数 用ES5的Object.create也差不多
// Son1=Object.create(Father)
// Son2=Object.create(Father)
console.log(Son1) //F {}
Son1.name="dd" //这里是在实例上添加属性,以后访问的时候,直接就是访问对象的,直接屏蔽原型的同名属性
console.log(Son1) //F {name: "dd"}
Son2.do.push('看直播')
console.log(Father.do) //["打游戏", "刷抖音", "等消息", "看直播"]
Son1.__proto__.name="DD" //通过Son1修改name的值
console.log(Son2.name) //通过Son2输出DD
</script>
</body>
</html>
5、寄生式继承(可以理解为在原型式继承的基础上新增一些函数或属性)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>手写继承-寄生式继承</title>
</head>
<body>
<script>
// 5、寄生式继承(可以理解为在原型式继承的基础上新增一些函数或属性)
// 问题
// 1、不能做到函数复用(和借用构造函数继承一样)
// 2、来自原型对象的所有属性被所有实例共享(和原型链继承一样)
let Father={
name:"quan",
do:["打游戏","刷抖音","等消息"]
}
// 实现拷贝的函数
function object(o){
function F() {}
F.prototype=o
return new F()//返回新实例
}
// 增强对象的函数
function other(original){
let clone=object(original)
clone.sayHi=function(){
console.log('Im '+clone.name)
}
return clone
}
let Son1=other(Father)
let Son2=other(Father)
console.log(Son1) //F {sayHi: ƒ}
Son1.name="dd" //这里是在实例上添加属性,以后访问的时候,直接就是访问对象的,直接屏蔽原型的同名属性
console.log(Son1) //F {name: "dd", sayHi: ƒ}
Son1.sayHi() //Im dd(直接用了实例属性)
Son2.sayHi() //Im quan
console.log(Son1.sayHi===Son2.sayHi) //false 证明问题一
Son2.do.push('看直播')
console.log(Father.do) //["打游戏", "刷抖音", "等消息", "看直播"]
Son1.__proto__.name="DD" //通过Son1修改name的值
console.log(Son2.name) //通过Son2输出DD
</script>
</body>
</html>
6、寄生组合式继承(通过借用构造函数继承属性,原型链的混成形式来继承方法)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>手写继承-寄生组合式继承</title>
</head>
<body>
<script>
// 6、寄生组合式继承
// 通过借用构造函数继承属性,原型链的混成形式来继承方法
function Father(sex){
this.name="quan"
this.do=["打游戏","刷抖音","等消息"]
this.sex=sex
}
Father.prototype.sayHi=function(){
console.log('Im '+this.name)
}
function Son(age,sex){
// 在子类型构造函数的内部,调用超类型构造函数(核心思想)这里还可以传递参数了
Father.call(this,sex)
this.age=age // 写下面好一些,不然如果父类有同样的,会被覆盖
}
// 不必为了指定子类型的原型而调用超类型的构造函数,我们做需要的无非就是超类型原型的一个副本(核心思想)
// 实现继承方法的函数
function inheritPrototype(Son,Father){
// 方法一
let quan=object(Father.prototype) //quan是Father.prototype的实例,是Son的原型
// 方法二(ES5)
// let quan=Object.create(Father)
console.log(quan.constructor) //改变之后指向Father
quan.constructor=Son //手动改为Son
console.log(quan.constructor) //手动改正之后指向Son
Son.prototype=quan //quan是Father.prototype的实例就有Father.prototype所有属性和方法,然后等于Son.prototype,相当于实现了一个原型的替换
}
// 实现拷贝的函数
function object(o){
function F() {}
F.prototype=o
return new F()//返回新实例
}
inheritPrototype(Son,Father)
Son.prototype.sayAge=function(){
console.log('Im '+this.age)
}
let instance1=new Son(21,'男') //可以传递参数到超类型构造函数
console.log(instance1.age) // 21
console.log(instance1.name) // quan
console.log(instance1.sex) // 男
instance1.sayHi() // Im quan
instance1.sayAge() // Im 21
let instance2=new Son(18,'女')
instance2.sayAge() // Im 18
console.log(instance2.sex) // 女
instance2.do.push("看直播")
console.log(instance1.do) // ["打游戏","刷抖音","等消息"]
console.log(instance2.do) // ["打游戏","刷抖音","等消息","看直播"]
</script>
</body>
</html>
7、ES6继承(实际也只是一个语法糖)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>手写继承-ES6继承</title>
</head>
<body>
<script>
// 7、ES6继承
// 实际也只是一个语法糖
class Father{
constructor(sex){
this.name="quan"
this.do=["打游戏","刷抖音","等消息"]
this.sex=sex
}
sayHi(){
console.log('Im '+this.name)
}
saySex(){
console.log('Im '+this.sex)
}
}
class Son extends Father{
constructor(age,sex){
// 利用super 调用父类的构造函数
// super 必须在子类this之前调用
super(sex)
this.age=age
}
sayAge(){
console.log('Im '+this.age)
}
}
let instance1=new Son(21,'男') //可以传递参数到超类型构造函数
console.log(instance1.age) // 21
console.log(instance1.name) // quan
console.log(instance1.sex) // 男
instance1.sayHi() // Im quan
instance1.sayAge() // Im 21
let instance2=new Son(18,'女')
instance2.sayAge() // Im 18
console.log(instance2.sex) // 女
instance2.do.push("看直播")
console.log(instance1.do) // ["打游戏","刷抖音","等消息"]
console.log(instance2.do) // ["打游戏","刷抖音","等消息","看直播"]\
</script>
</body>
</html>
参考文档
https://www.cnblogs.com/humin/p/4556820.html