本文已参与「新人创作礼」活动,一起开启掘金创作之路
Javascript中的继承
总览五种继承方式
// 1、原型链继承
function Animal() {
this.colors = ['black', 'red']
}
function Dog() {}
Dog.prototype = new Animal() // 原型链继承的特点
// 2、借用构造函数继承
function Animal(colors) {
this.colors = colors
this.getColors = function () {
return this.colors
}
}
function Dog(colors) {
Animal.call(this, colors) // 借用构造函数继承的特点
}
// 3、组合继承
function Animal(name) {
this.name = name
this.colors = ["black", "white"]
}
Animal.prototype.getName = function () {
return this.name
}
function Dog(name, age) {
Animal.call(this, name) // 借用构造函数继承的特点
this.age = age
}
Dog.prototype = new Animal() // 原型链继承的特点
Dog.prototype.constructor = Dog
// 4、寄生式组合继承
function Animal(name) {
this.name = name
this.colors = ["black", "white"]
}
Animal.prototype.getName = function () {
return this.name
}
function Dog(name, age) {
Animal.call(this, name)
this.age = age
}
Dog.prototype = Object.create(Animal.prototype)
Dog.prototype.constructor = Dog
// 5、ES6实现继承
class Animal {
constructor(name) {
this.name = name
}
// 方法是在构造器外面的
getName() {
return this.name
}
}
// extends关键字实现继承
class Dog extends Animal {
constructor(name, age) {
super(name)
this.age = age
}
}
1、原型链继承
function Animal() {
this.colors = ['black', 'red']
}
function Dog() {}
Dog.prototype = new Animal() // 原型链继承的特点
上下区别不大,下面只是表明在Animal原型上面定义的方法,也是同样可以被继承的,可以只看上面的
function Animal() {
this.colors = ['black', 'red']
}
// 这部分和下面的分析关系不大,但是原型链继承时,该方法也是能够被继承的
Animal.prototype.getColor = function () {
return this.colors
}
function Dog() {}
Dog.prototype = new Animal() // 原型链继承的特点
原型链继承的两个缺点
缺点1:子类在实例化的时候不能给父类构造函数传参数,因为子类构造函数里面没有调用父类构造函数
function Animal() {
this.colors = ['black', 'red']
}
Animal.prototype.getColor = function () {
return this.colors
}
function Dog() {
// 缺点1:
// 子类在实例化的时候不能给父类构造函数传参数
// 因为子类构造函数里面没有调用父类构造函数
}
Dog.prototype = new Animal()
缺点2:原型中包含的属性将被所有实例共享,注意是原型中包含的属性,并不新建实例上的属性,而是新建实例的原型上的属性
新建Dog的实例dog1,并且打印dog1.colors
let dog1 = new Dog()
console.log(dog1.colors) // ['black', 'red']
dog1的colors属性,明明没有这个属性,为什么能打印出来呢? 因为在dog1上找不到就去dog1的原型上面找这个属性
let dog1 = new Dog()
console.log(dog1.colors) // ['black', 'red']
console.log(dog1.__proto__.colors) // ['black', 'red']
往dog1.colors中添加一个值,其实这里也是给dog1原型上的colors添加一个值
dog1.colors.push('blue')
// 等价于
dog1.__proto__.colors.push('blue')
再次打印dog1.colors
console.log(dog1.colors) // ['black', 'red', 'blue']
console.log(dog1.__proto__.colors) // ['black', 'red', 'blue']
新建Dog的实例dog2,并且打印dog2.colors
let dog2 = new Dog()
console.log(dog2.colors) //['black', 'red','blue']
console.log(dog2.__proto__.colors) //['black', 'red','blue']
为什么和dog1刚开始打印dog1.colors时显示的不一样呢,而是和往dog1.colors里面push数据后的一样?因为dog1和dog2共用一个原型,当dog2上找不到colors这个属性的时候,就去原型上面找,因为共用原型,所以打印的结果是原型上面的colors。
2、借用构造函数继承
function Animal(colors) {
this.colors = colors
this.getColors = function () {
return this.colors
}
}
function Dog(colors) {
Animal.call(this, colors) // 借用构造函数继承的特点
}
// 无法实现构造函数的复用,每个子类都有父类实例函数的副本,影响性能,代码会臃肿
function DogCopy(colors) {
// 父类实例函数的副本
Animal.call(this, colors)
}
let dog1 = new Dog(["blue", "red"])
优点:解决了原型链继承的两个缺点
借用构造函数继承的两个缺点
缺点一,这种方法其实就没有用到父类原型,没有涉及到Animal.prototype的代码,和第一种继承法(原型链继承)形成鲜明的对比,只是使用了call()或者apply()方法,调用父类的构造函数,所以不能继承父类原型属性和方法,只能继承父类的实例属性和方法(即父类构造函数中的东西)。
缺点二,无法实现构造函数的复用,每个子类都有父类实例函数的副本,影响性能,代码会臃肿。
// 新建实例dog1
let dog1 = new Dog(["blue", "red"])
console.log(dog1.colors) // ['blue', 'red']
console.log(dog1.__proto__.colors) //undefined
// dog1.colors中push一个值
dog1.colors.push("black")
console.log(dog1.colors) // ['blue', 'red', 'black']
console.log(dog1.__proto__.colors) //undefined
// 新建实例dog2
let dog2 = new Dog(["yellow", "green"])
console.log(dog2.colors) // ['yellow', 'green']
console.log(dog2.__proto__.colors) //undefined
3、组合继承
组合继承就是结合原型链继承和借用构造函数继承的继承方式,将两者的优点集中。
function Animal(name) {
this.name = name
this.colors = ["black", "white"]
}
Animal.prototype.getName = function () {
return this.name
}
function Dog(name, age) {
Animal.call(this, name) // 借用构造函数继承的特点
this.age = age
}
Dog.prototype = new Animal() // 原型链继承的特点
Dog.prototype.constructor = Dog // 这一步有什么作用呢???
实现思路
- 使用原型链继承,继承父类原型上的属性和方法
- 通过借用构造函数继承,继承父类实例上的属性和方法
这样既可以把方法定义在原型上实现重用,又可以让每个实例都有自己的属性
let dog1 = new Dog("小花", 2)
dog1.colors.push("brown")
console.log(dog1) // {name: '小花', colors:['black', 'white', 'brown'], age: 2}
let dog2 = new Dog("小白", 1)
console.log(dog2) // {name: '小白', colors:['black', 'white'], age: 1}
组合继承的缺点:调用了两次父类构造函数
- 第一次是在
new Animal() - 第二次是在
Animal.call()
function Animal(name) {
this.name = name
this.colors = ["black", "white"]
}
Animal.prototype.getName = function () {
return this.name
}
function Dog(name, age) {
Animal.call(this, name) // 第二次
this.age = age
}
Dog.prototype = new Animal() // 第一次
Dog.prototype.constructor = Dog
思考:这一步有什么作用呢?
Dog.prototype.constructor = Dog之前的原型链继承和借用构造函数继承都没有出现这一步,为什么这边会出现这么一步?
目的就是在原型链中加上标红的这一步,使得原型链更加完整。
4、寄生式组合继承(最成熟的)
function Animal(name) {
this.name = name
this.colors = ["black", "white"]
}
Animal.prototype.getName = function () {
return this.name
}
function Dog(name, age) {
Animal.call(this, name)
this.age = age
}
// Dog.prototype = new Animal();
// Dog.prototype.constructor = Dog;
// 将这两行注释了换成下面的
function F() {}
F.prototype = Animal.prototype
let f = new F()
f.constructor = Dog
Dog.prototype = f
这三步,返回了一个由父类构造函数实例化的一个对象f
function F() {}
F.prototype = Animal.prototype
let f = new F()
这两步,将父类构造函数实例化的对象f与Dog的原型相互绑定,从而实现Dog继承Animal
f.constructor = Dog
Dog.prototype = f
简单封装
function Animal(name) {
this.name = name
this.colors = ["black", "white"]
}
Animal.prototype.getName = function () {
return this.name
}
function Dog(name, age) {
Animal.call(this, name)
this.age = age
}
function object(o) {
function F() {}
F.prototype = o
let f = new F()
return f
}
function inheritPrototype(son, father) {
let prototype = object(father.prototype)
prototype.constructor = son
son.prototype = prototype
}
inheritPrototype(Dog, Animal)
基于组合继承改写
基于组合继承的代码改成最简单的寄生式组合继承
function Animal(name) {
this.name = name
this.colors = ["black", "white"]
}
Animal.prototype.getName = function () {
return this.name
}
function Dog(name, age) {
Animal.call(this, name)
this.age = age
}
Dog.prototype = Object.create(Animal.prototype)
Dog.prototype.constructor = Dog
5、ES6实现继承
ES中关键字extends实现继承
class Animal {
constructor(name) {
this.name = name
}
// 方法是在构造器外面的
getName() {
return this.name
}
}
// extends关键字实现继承
class Dog extends Animal {
constructor(name, age) {
super(name)
this.age = age
}
}