Canvas 射击小游戏详解系列(二)

接下来会介绍关于游戏项目中关于对象的设计,但在这之前想先对JavaScript对象做一些总结,然后再介绍项目的对象设计。这里会分三篇介绍。

JavaScript创建对象的三种模式

js创建对象的三种模式有工厂模式,构造函数模式及原型模式。

其实正常来说,创建一个对象是可以直接声明一个Object类型,但是如果需要创建多个对象的时候,这个方法显然不利于开发。而且很多时候需要创建的对象都有一些相似点,比如同一类型,同样的属性跟方法。如果能有一种方式可以用相同的代码处理创建对象的话,就可以提高代码的性能了。上面提及的三种模式就是解决方案。

工厂模式

工厂模式就是通过传参给一个函数方法,在函数中进行对象的创建,赋值,然后返回。

代码1 项目中飞机对象根据工厂模式的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function createPlane (option, context) {
var plane = new Object();
plane.opts = option;
plane.context = context;
plane.drawing = function () {
// 画布上渲染图片
}
plane.move = function () {
// 飞机每帧移动
}
// 其他方法省略
return plane;
}

var plane1 = createPlane(opts, context);

这种方式创建对象比较简单,只需要知道创建对象的函数跟传入需要的参数即可。其不足在于两点:工厂模式是通过函数方法来创建对象,而不是通过new方法来创建,这样的创建方式不够直观,还是会有一些冗余的代码;工厂模式所返回的对象,在后面的程序中,无法识别对象类型,只能识别是Object类型。

所以就有了构造函数模式。

构造函数模式

这种模式是先声明一个构造函数,然后通过new来创建对象。

代码2 项目中飞机对象根据工厂模式的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
function Plane (option, context) {
this.opts = option;
this.context = context;
this.drawing = function () {
// 画布上渲染图片
}
this.move = function () {
// 飞机每帧移动
}
// 其他方法省略
}

var plane2 = new Plane(opts, context);

代码2跟代码1的对比中可以发现,工厂模式是在函数内部通过new一个对象再返回,但是构造函数模式中,new是在构造函数外面,而且函数内部是通过this来进行赋值的。

在构造函数模式中,程序是先执行了new关键词,即创建了一个对象。然后把构造函数的作用域赋值给了这个对象,也就是构造函数的this指针指向这个对象。然后执行函数,这期间会把属性跟方法全部赋值给这个对象。最后再把对象返回出来。

构造函数的优点也比较明显,它避免了工厂模式的两个不足。通过构造函数创建的对象,其实就是这个构造函数的一个实例。我们就可以通过instanceof方法判断一个对象的构造函数。

代码3 instanceof判断对象是否是某个构造函数的实例

1
console.log(plane2 instanceof Plane); // true

不过,构造函数模式也有它的一些不足。构造函数所创建出来的每个实例对象都会包含一些属性跟方法,而方法其实在所有实例对象中的功能都是一样的。而这些方法实际上也是Function的实例对象,这样就出现了不必要的冗余,这些方法应该实现复用。

下面的原型模式就能解决这个不足。

原型模式

原型模式就是利用到JavaScript中对象的原型prototype来实现的。

每个构造函数上都会有一个属性prototype。prototype就是原型,也是一个对象,而且这个对象prototype是所有通过同一构造函数创建(new)的实例对象所共享的(实际上在浏览器console窗口打印每个实例对象,显示的是属性__proto__指向prototype,实际开发中只要用prototype就好)。prototype下的constructor指向实例对象的构造函数。

代码4 判断实例的prototype

1
Object.prototype.isPrototypeOf(object1) // 判断object1挂载的prototype是不是Object的

原型模式根据JavaScript的原型原理,在构造函数的基础上,把方法放到了prototype对象下。创建对象的方法跟构造函数模式一样,内部创建对象的流程也差不多,也是先执行new关键词,创建一个对象,然后把函数的this指向这个对象,在执行属性赋值跟方法声明时,把方法都放在this.prototype原型对象中。

代码5 项目中飞机对象根据工厂模式的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Plane (option, context) {
this.opts = option;
this.context = context;
}

Plane.prototype.drawing = function () {
// 画布上渲染图片
}

Plane.prototype.move = function () {
// 飞机每帧移动
}
// 其他方法省略

var plane3 = new Plane(opts, context);

这样的创建对象模式,可以使构造函数创建的实例对象,共享原型中的方法。但是也会有问题存在,这个问题是共享本身的问题。当所有的实例对象共享原型时,就可能出现数据污染的情况。

数据污染的原因在于JavaScript的数据存储方式。JavaScript的数据分为值类型跟引用类型,值类型就是一个变量储存一个值,而引用类型就是一个变量指向内存的一个地址,这个地址储存一个值。值类型变量的赋值是深赋值,赋值完成后两个变量的操作互不影响,但是引用类型的变量之间的赋值其实是复制了指针,这就导致一个变量对指针指向的值进行修改时,另一个变量的值也会跟着改变,就会出现数据污染。

所以,在使用原型模式时,一般把函数方法放在原型prototype中。

下一篇介绍继承。