前言
今天跟各位朋友分享一下如何一步步推到出最优秀的继承方案,在阅读本文前,最好先弄懂原型原型链的关系,后续我也会写相关的文章,那么废话少说,我们开始
原型链继承方法
可以通过原型链,修改子类构造函数的原型对象为父级构造函数的一个实例,从而实现继承
javascript
// 定义父级构造函数
function Animal() {
this.name = '小动物'
this.age = '2岁了'
}
// 往父级原型上添加方法
Animal.prototype.running = function() {
console.log(this.name + '在快乐地跑步');
}
// 定义子级构造函数
function Rabbit() {
this.color = '白色'
}
// 关键点------------ 将子构造函数的prototype属性指向Animal的一个实例
Rabbit.prototype = new Animal()
// 在子级的原型上添加方法
Rabbit.prototype.eating = function() {
console.log(this.name + '在开心地吃东西');
}
const rabbit = new Rabbit()
rabbit.eating() //小动物在开心地吃东西
rabbit.running() //小动物在快乐地跑步
缺点
一.原型中的引用值会在所有的实例中共享,一旦被某一个实例改变了之后,下一个实例拿到的的引用值就不再是原来的数值,而是上一个实例改变后的值
javascript
function Animal() {
this.name = "小动物"
this.friends = ["tiger", "dog"]
}
Animal.prototype.running = function() {
console.log(this.name + "在快乐地跑步~")
}
function Rabbit() {
this.color = '白色'
}
Rabbit.prototype = new Animal()
Rabbit.prototype.eating = function() {
console.log(this.name + "在快乐地吃东西")
}
var rabbit1 = new Rabbit()
var rabbit2 = new Rabbit()
rabbit1.friends.push("cat")
console.log(rabbit2.friends) // [ 'tiger', 'dog', 'cat' ]
盗用构造函数
解决了包含引用值导致的继承问题
做法:通过apply() call()方法在新创建的对象上执行构造函数
javascript
function Animal(name,friend) {
this.name = name
this.friends = friend
}
Animal.prototype.running = function() {
console.log(this.name + "在快乐地跑步~")
}
function Rabbit(name,friend,color) {
Animal.call(this,name,friend)
this.color = color
}
Rabbit.prototype.eating = function() {
console.log(this.name + "在快乐地吃东西")
}
var rabbit1 = new Rabbit('兔子一号',['kobe'])
var rabbit2 = new Rabbit('兔子二号',['james'])
rabbit1.friends.push("curry")
console.log(rabbit2.friends) // [ 'james' ]
缺点:
组合继承
思路:使用原型链继承原型上的属性和方法,而盗用构造函数继承实例的属性
javascript
function Animal(name,friend) {
this.name = name
this.friends = friend
}
Animal.prototype.running = function() {
console.log(this.name + "在快乐地跑步~")
}
function Rabbit(name,friend,color) {
Animal.call(this,name,friend) // 第二次调用父类函数
this.color = color
}
Rabbit.prototype = new Animal() // 第一次调用父类函数
Rabbit.prototype.eating = function() {
console.log(this.name + "在快乐地吃东西")
}
var rabbit1 = new Rabbit('兔子一号',['kobe'])
var rabbit2 = new Rabbit('兔子二号',['james'])
rabbit1.friends.push("curry")
console.log(rabbit2.friends) // [ 'james' ]
组合继承是JavaScript中使用最多的继承模式之一,虽然有一些小瑕疵,但基本不影响使用
那么组合继承有哪些小瑕疵呢?
1.无论在什么情况下都会调用两次父类函数,第一次是在创建子类原型的时候,第二次是在子类构造函数内部
2.会储存着两份父类属性,第一份在rabbit本身,第二份在他的__proto__里面
接下来我们进行一系列优化
原型式继承函数
我们回顾一下实现继承的目的:重复利用另外一个对象的属性和方法
javascript
function object(obj) {
function fn() {}
fn.prototype = obj
return new fn()
}
const Animal = {
name: "lebi",
age: 18
}
const rabbit = obj(Animal)
上面代码进行了达到了什么目的呢?
把子类rabbit的对象原型指向了Animal对象
事实上,ES6之后还实现了一个Object.create()方法,同样也能实现相同的效果
javascript
const Animal = {
name: "lebi",
age: 18
}
const rabbit = Object.create(Animal)
console.log(rabbit.__proto__) // { name: 'lebi', age: 18 }
寄生式继承
思路:寄生式继承的思路是结合原型类继承和工厂模式的一种方式
即创建一个封装继承过程的函数, 该函数在内部以某种方式来增强对象,最后再将这个对象返回;
javascript
function object(obj) {
function fn() {}
fn.prototype = obj
return new fn()
}
function createAnimal(obj) {
const newObj = object(obj)
newObj.running = function() {
console.log(this.name + "running")
}
return newObj
}
const Animal = {
name: "lebi",
age: 18
}
const rabbit = createAnimal(Animal)
rabbit.running()
缺点:
一.引用类型数据还是会共享
二.寄生式继承还存在函数不能复用的问题,每一次调用createAnimal,都会创建出一个新的函数出来
寄生式组合继承
在前面提到的组合继承是存在以下两个问题
1.多次调用父类函数
2.父类的属性会有两份,一份在原型中,一份在子类实例里
而实际上,我们是可以使用寄生继承的办法来解决以上两个问题:
首先我们要明白,组合继承已经使用call方法,将父级的属性复制一份给了子类的实例,所以我们是不需要在将其再次拷贝进原型里面,接着我们可以使用刚才的寄生思想来对原型的属性方法进行继承
寄生组合的核心代码如下
javascript
function object(obj) {
function fn() {}
fn.prototype = obj
return new fn()
}
// 继承方法
function inhreitPrototype(son,dad) {
object(dad.prototype)
Object.defineProperty(son.prototype,'constructor',{
configurable:true,
enumerable:false,
writable:true,
value:son
})
}
完整的代码如下
javascript
function object(Object) {
function fn() {}
fn.prototype = Object
return new fn()
}
function inhreitPrototype(son,dad) {
son.prototype = object(dad.prototype)
Object.defineProperty(son.prototype,'constructor',{
configurable:true,
enumerable:false,
writable:true,
value:son
})
}
function Person(name,age,height) {
this.name = name
this.age = age
this.height = height
}
Person.prototype.eating = function() {
console.log(this.name + '在吃东西~');
}
Person.prototype.running = function() {
console.log(this.name + '在跑步~');
}
function Student(name,age,height,grade,sno) {
// 继承属性
Person.call(this,name,age,height)
this.grade = grade
this.sno = sno
}
// 继承方法
/*
把子类的prototype改成一个原型对象是父类原型对象的对象
*/
inhreitPrototype(Student,Person)
const stu1 = new Student('孙沛涵',18,1.88,'大二',3122001621)
stu1.eating()
stu1.running()
console.log(stu1);
console.log(stu1.name,stu1.age,stu1.height,stu1.grade,stu1.sno);
以上是我借鉴自《JavaScript高级程序设计》继承一篇,结合自己理解整理出来的。希望对大家有帮助