Skip to content

类与实例

类的声明

javascript
// 类的声明(es5)
var Animal = function () {
  this.name = 'Animal'
}

// 类的声明(es6)
class Animal2 {
  constructor() {
    this.name = 'Animal2'
  }
}

生成实例

javascript
// 类的声明(es5)
var Animal = function () {
  this.name = 'Animal'
}

// 类的声明(es6)
class Animal2 {
  constructor() {
    this.name = 'Animal2'
  }
}

// 实例化
console.log(new Animal()) // Animal { name: 'Animal' }
console.log(new Animal2()) // Animal2 { name: 'Animal2' }

继承实现

本质就是原型链

概念

说到继承的概念,首先要说一个经典的例子。 先定义一个类(Class)叫汽车,汽车的属性包括颜色、轮胎、品牌、速度、排气量等,由汽车这个类可以派生出“轿车”和“货车”两个类,那么可以在汽车的基础属性上,为轿车添加一个后备厢、给货车添加一个大货箱。这样轿车和货车就是不一样的,但是二者都属于汽车这个类,这样从这个例子中就能详细说明汽车、轿车以及卡车之间的继承关系。 继承可以使得子类别具有父类的各种方法和属性,比如上面的例子中“轿车” 和 “货车” 分别继承了汽车的属性,而不需要再次在“轿车”中定义汽车已经有的属性。在“轿车”继承“汽车”的同时,也可以重新定义汽车的某些属性,并重写或覆盖某些属性和方法,使其获得与“汽车”这个父类不同的属性和方法。 继承的基本概念就初步介绍这些,下面我们就来看看 JavaScript 中都有哪些实现继承的方法。

继承的实现方式

构造函数继承

特点: 子类实例共享父类引用属性的问题 创建子类实例时,可以向父类传递参数 可以实现多继承(call多个父类对象)

缺点: 实例并不是父类的实例,只是子类的实例 只能继承父类的实例属性和方法,不能继承原型属性/方法 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

javascript
function Parent1() {
  this.name = 'parent1'
}
Parent1.prototype.say = function () {}

function Child1() {
  Parent1.call(this)
  this.type = 'child1'
}
console.log(new Child1(), new Child1().say())

原型链继承

特点: 解决子类实例共享父类引用属性的问题 创建子类实例时,可以向父类传递参数 可以实现多继承(call多个父类对象)(不完美,没有父类方法)

缺点: 实例并不是父类的实例,只是子类的实例 只能继承父类的实例属性和方法,不能继承原型属性/方法 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

javascript
function Parent2() {
  this.name = 'parent2'
  this.play = [1, 2, 3]
}

function Child2() {
  this.type = 'child2'
}
Child2.prototype = new Parent2()

var s1 = new Child2()
var s2 = new Child2()
console.log(s1.play, s2.play) // [ 1, 2, 3 ] [ 1, 2, 3 ]
s1.play.push(4)
// TODO: 原型链中的原型对象共用
console.log(s1.play, s2.play) // [ 1, 2, 3, 4 ] [ 1, 2, 3, 4 ]
console.log(s1.__proto__ === s2.__proto__) // true

组合方式继承

特点: 解决原型链实现继承的缺点

缺点: 父级构造函数执行了两次,影响性能

javascript
function Parent3() {
  this.name = 'parent3'
  this.play = [1, 2, 3]
}

function Child3() {
  Parent3.call(this)
  this.type = 'child3'
}
Child3.prototype = new Parent3()
var s3 = new Child3()
var s4 = new Child3()
s3.play.push(4)
console.log(s3.play, s4.play) // [ 1, 2, 3, 4 ] [ 1, 2, 3 ]

组合继承 - 优化1

特点: 父类执行一次(子类实例化执行),Child4.prototype = Parent4.prototype解释(因为都是对象,都是引用类型,不会执行父级构造函数)

缺点: 如何区分一个对象是由它的子类实例化的,还是父类实例化的(s5是Child4还是Parent4直接实例化的)

javascript
function Parent4() {
  this.name = 'parent4'
  this.play = [1, 2, 3]
}

function Child4() {
  Parent4.call(this)
  this.type = 'child4'
}
Child4.prototype = Parent4.prototype
var s5 = new Child4()
var s6 = new Child4()
console.log(s5, s6) // Parent4 { name: 'parent4', play: [ 1, 2, 3 ], type: 'child4' } Parent4 { name: 'parent4', play: [ 1, 2, 3 ], type: 'child4' }

console.log(s5 instanceof Child4, s5 instanceof Parent4) // true true
console.log(s5.constructor) // [λ: Parent4]

组合继承 - 优化2

Child5.prototype = Object.create(Parent5.prototype)解释:Object.create来创建一个中间对象,把两个原型对象区分开,这个中间对象还具备一个特性,它的原型对象是父类的原型对象,这样就可以连起来,通过给Child5的原型对象的constructor做修改,就可以正常区分开父类和子类的构造函数

javascript
function Parent5() {
  this.name = 'parent5'
  this.play = [1, 2, 3]
}

function Child5() {
  Parent5.call(this)
  this.type = 'child5'
}
Child5.prototype = Object.create(Parent5.prototype)
Child5.prototype.constructor = Child5

var s7 = new Child5()
console.log(s7 instanceof Child5, s7 instanceof Parent5) // true true
console.log(s7.constructor) // [λ: Child5]