Skip to content

js中的6种继承 #4

Description

@hans000

就拿写的游戏的案例举例

游戏中的基类

function Sprite(posX=0, posY=0, width=10, height=10) {
  this.posX = posX
  this.posY = posY
  this.width = width
  this.height = height
  this.color = ['red', 'blue']
}
Sprite.prototype = {
  render() {
  	console.log('this is a render function')
  },
  update() {
  	console.log('this is a update function')
  }
}
  • 原型链继承
// 功能实现
function Ball(vx, vy) {
  this.vx = vx
  this.vy  = vy
}
Ball.prototype = new Sprite()	// 缺陷2、3

// 使用
let ball = new Ball(0.1, 0.1)	// 缺陷1
ball.posX = 10
ball.posY = 10
ball.width = 50
ball.height = 50
ball.color.push('black') 	// 缺陷3
let ball2 = new Ball(0.2, 0.2)

// 缺陷:
// 1、实例化子类时无法向父类构造函数传参,期望是new Ball(10, 10, 50, 50, 0.1, 0.1)
// 2、单一继承
// 3、子类共享父类实例属性
  • 借用构造函数
// 功能实现
function Ball(posX, posY, width, height, vx, vy) {
  Sprite.call(this, posX, posY, width, height)
  this.vx = vx
  this.vy = vy
}

// 使用
let ball = new Ball(100, 100, 50, 50, 0.1, 0.1)

// 缺陷:
// 1、子类无法继承父类原型上的属性、方法
// 2、无法实现构造函数的复用,每次实例化都会重新调用
// 3、每个实例都有父类构造函数的副本,臃肿
  • 组合继承
// 功能实现
function Ball(posX, posY, width, height, vx, vy) {
  Sprite.call(this, posX, posY, width, height)	// 第一次
  this.vx = vx
  this.vy = vy
}
Ball.prototype = new Sprite()	// 第二次

// 使用
let ball = new Ball(100, 100, 50, 50, 0.1, 0.1)
ball.color.push('black')
et ball2 = new Ball(200, 200, 20, 20, 0.2, 0.2)

// 缺陷:
// 调用了两次构造函数
  • 原型继承
// 功能实现
Ball.prototype = Sprite.prototype
Ball.prototype.canMove = true
function Ball(vx, vy) {
  this.vx = vx
  this.vy = vy
}

// 缺陷:
// 1、实例化子类时无法向父类构造函数传参
// 2、单一继承
// 3、修改子类原型会修改父类原型
// 4、子类无法继承父类构造函数的属性
  • 寄生组合继承,【推荐】
// 功能实现
Ball.prototype = Object.create(Sprite.prototype)
Ball.prototype.constructor = Ball
Ball.prototype.canMove = true
function Ball(posX, posY, width, height, vx, vy) {
  Sprite.call(this, posX, posY, width, height)
  this.vx = vx
  this.vy = vy
}

// 使用
let ball = new Ball(100, 100, 50, 50, 0.1, 0.1)
ball.color.push('black')
let ball2 = new Ball(200, 200, 20, 20, 0.2, 0.2)
  • ES6 extends
// 功能实现
class Sprite {
  constructor(posX, posY, width, height) {
    this.posX = posX
    this.posY = posY
    this.width = width
    this.height = height
  }
  render() {
    console.log('this is a render function')
  }
  update() {
    console.log('this is a update function')
  }
}
class Ball extends Sprite {
  constructor(vx, vy, ...args) {
    super(...args)
    this.vx = vx
    this.vy = vy
  }
}

个人理解

  • 如果不使用原型,就会有不能复用、每个子类实例都有父类的问题

  • 直接给子类原型赋值就会有单一继承的问题

  • 父类原型直接给子类原型就会造成修改子类原型也同时修改了父类原型

  • 只使用父类原型就会有子类不能继承父类构造函数属性的问题

  • 如果不使用call / apply,传参问题就不能解决

  • call / apply不仅可以解决传参问题,还可以解决单一继承问题

  • 原型就是为了解决一些公共属性和方法

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions