Character.prototype.health = 100;
Character.prototype.getHealth = function() {
return this.health;
}
// Inherited classes
function Player() {
this.health = 200;
}
Player.prototype = new Character;
function Monster() {}
Monster.prototype = new Character;
var player1 = new Player();
var monster1 = new Monster();
player1.getHealth(); // 200- assigned in constructor
monster1.getHealth(); // 100- inherited from the prototype object
为一个子类分配一个父类需要调用 new 并将结果分配给子类的 prototype 属性,如 清单 3 所示。因此,明智的做法是保持构造函数尽可能的简洁和无副作用,除非您想要传递类定义中的默认值。
如果您已经开始尝试在 JavaScript 中定义类和继承,那么您可能已经意识到该语言与经典 OOP 语言的一个重要区别:如果已经覆盖这些方法,那么没有 super 或 parent 属性可用来访问父对象的方法。对此有一个简单的解决方案,但该解决方案违背了 “不要重复自己 (DRY)” 原则,而且很有可能是如今有很多库试图模仿经典继承的最重要的原因。
清单 3. 从子类调用父方法
function ParentClass() {
this.color = 'red';
this.shape = 'square';
}
function ChildClass() {
ParentClass.call(this); // use 'call' or 'apply' and pass in the child
// class's context
this.shape = 'circle';
}
ChildClass.prototype = new ParentClass(); // ChildClass inherits from ParentClass
ChildClass.prototype.getColor = function() {
return this.color; // returns "red" from the inherited property
};
在 清单 3 中, color 和 shape 属性值都不在原型中,它们在 ParentClass 构造函数中赋值。ChildClass 的新实例将会为其形状属性赋值两次:一次作为 ParentClass 构造函数中的 "squre",一次作为 ChildClass 构造函数中的 "circle"。将类似这些赋值的逻辑移动到原型将会减少副作用,让代码变得更容易维护。
在原型继承模型中,可以使用 JavaScript 的 call 或 apply 方法来运行具有不同上下文的函数。虽然这种做法十分有效,可以替代其他语言的 super 或 parent,但它带来了新的问题。如果需要通过更改某个类的名称、它的父类或父类的名称来重构这个类,那么现在您的文本文件中的很多地方都有了这个 ParentClass 。随着您的类越来越复杂,这类问题也会不断增长。更好的一个解决方案是让您的类扩展一个基类,使代码减少重复,尤其在重新创建经典继承时。
经典继承
虽然原型继承对于 OOP 是完全可行的,但它无法满足优秀编程的某些目标。比如如下这些问题:
constructor: function() {
// this is called every time you use "new"
},
attack: function(player) {
player.hit(this.damage); // "this" is your enemy!
}
});
// create a robot class that uses Enemy as its parent
//
var RobotEnemy = Enemy.extend({
health: 100,
damage: 10,
// because a constructor isn't listed here,
// Base.js automatically uses the Enemy constructor for us
attack: function(player) {
// you can call methods from the parent class using this.base
// by not having to refer to the parent class
// or use call / apply, refactoring is easier
// in this example, the player will be hit
this.base(player);
// even though you used the parent class's "attack"
// method, you can still have logic specific to your robot class
this.health += 10;
}
});
游戏设计中的 OOP 模式
基本的游戏引擎不可避免地依赖于两个函数:update 和 render。render 方法通常会根据 setInterval 或 polyfill 进行 requestAnimationFrame,比如 Paul Irish 使用的这个(请参阅 参考资料)。使用 requestAnimationFrame 的好处是仅在需要的时候调用它。它按照客户监视器的刷新频率运行(对于台式机,通常是一秒 60 次),此外,在大多数浏览器中,通常根本不会运行它,除非游戏所在的选项卡是活动的。它的优势包括:
var Engine = Base.extend({
stateMachine: null, // state machine that handles state transitions
viewStack: null, // array collection of view layers,
// perhaps including sub-view classes
entities: null, // array collection of active entities within the system
// characters,
constructor: function() {
this.viewStack = []; // don't forget that arrays shouldn't be prototype
// properties as they're copied by reference
this.entities = [];
// set up your state machine here, along with the current state
// this will be expanded upon in the next section
// start rendering your views
this.render();
// start updating any entities that may exist
setInterval(this.update.bind(this), Engine.UPDATE_INTERVAL);
},
render: function() {
requestAnimFrame(this.render.bind(this));
for (var i = 0, len = this.viewStack.length; i < len; i++) {
// delegate rendering logic to each view layer
(this.viewStack[i]).render();
}
},
update: function() {
for (var i = 0, len = this.entities.length; i < len; i++) {
// delegate update logic to each entity
(this.entities[i]).update();
}
}
},
// Syntax for Class "Static" properties in Base.js. Pass in as an optional
// second argument to.extend()
{
UPDATE_INTERVAL: 1000 / 16
});
如果您对 JavaScript 中 this 的上下文不是很熟悉,请注意 .bind(this) 被使用了两次:一次是在 setInterval 调用中的匿名函数上,另一次是在 requestAnimFrame 调用中的 this.render.bind() 上。setInterval 和 requestAnimFrame 都是函数,而非方法;它们属于这个全局窗口对象,不属于某个类或身份。因此,为了让此引擎的呈现和更新方法的 this 引用我们的 Engine 类的实例,调用 .bind(object) 会迫使此函数中的 this 与正常情况表现不同。如果您支持的是 Internet Explorer 8 或其更早版本,则需要添加一个 polyfill,将它用于绑定。
12下一页
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。TEL:177 7030 7066 E-MAIL:11247931@qq.com