[Effective JavaScript 笔记]第38条:在子类的构造函数中调用父类的构造函数
示例
场景类
场景图(scene)是在可视化的过程中(如游戏或图形仿真场景)描述一个场景的对象集合。一个简单的场景包含了在该场景中的所有对象(称角色),以及所有角色的预加载图像数据集,还包含一个底层图形显示的引用(通常被称为context)。
function Scene(context,width,height,images){
this.context=context;
this.width=width;
this.height=height;
this.images=images;
this.actors=[];
}
Scene.prototype.register=function(actor){
this.actors.push(actor);
};
Scene.prototype.unregister=function(actor){
var i=this.actors.indexOf(actor);
if(i>=0){
this.actors.splice(i,1);
}
};
Scene.prototype.draw=function(){
this.context.clearRect(0,0,this.width,this.height);
for(var a=this.actors,i=0,n=a.length;i < n;i++){
a[i].draw();
}
};
角色基类
场景中所有的角色都是继承自基类Actor。基类Actor抽象出了一些通用的方法。每个角色都存储了其自身场景的引用以及坐标位置,然后将自身添加到场景的角色注册表中。
function Actor(scene,x,y){
this.scece=scene;
this.x=0;
this.y=0;
scene.register(this);
}
为了能改变角色在场景中的位置,我们提供了一个moveTo方法。该方法改变角色坐标,然后重绘场景。
Actor.prototype.moveTo=function(x,y){
this.x=x;
this.y=y;
this.scene.draw();
};
当一个角色离开了场景,我们从场景图的注册表中删除它,并重新绘制场景。
Actor.prototype.exit=function(){
this.scene.unregister(this);
this.scene.draw();
};
想要绘制一个角色,我们需要查找它在场景图图像表中的图像。我们假设每个actor有一个type字段,可以用来查找它在图像表中的图像。一旦我们有了这个图像数据,就可以使用底层图形库将其绘制到图形上下文中。
Actor.prototype.draw=function(){
var image=this.scene.images[this.type];
this.scene.context.drawImage(image,this.x,this.y);
};
同样,我们可以通过角色的图像数据确定尺寸。
Actor.prototype.width=function(){
return this.scene.images[this.type].width;
};
Actor.prototype.height=function(){
return this.scene.images[this.type].height;
}
角色子类
我们将角色的特定类型实现为Actor的子类。例如,在街机游戏中太空飞船就会有一个扩展自Actor的SpaceShip类。像所有的类一样,SpaceShip被定义为一个构造函数。但是为了确保SpaceShip的实例能作为角色被正确地初始化,其构造函数必须显式地调用Actor的构造函数。通过将接收者绑定到该新对象来调用Actor可以达到此目的.
function SpaceShip(scene,x,y){
Actor.call(this,scene,x,y);
this.points=0;
}
首先调用Actor的构造函数能确保通过Actor创建的所有实例属性都被添加到了新对象中。然后,SpaceShip可以定义自身的实例属性,如飞船当前的积分数。为了使SpaceShip成为Actor的正确的子类,其原型必须继承自Actor.prototype。做这种扩展的最好方式是使用ES5的Object.create方法。
SpaceShip.prototype=Object.create(Actor.prototype);
构造函数创建子类原型问题
如果我们试图使用Actor的构造函数来创建SpaceShip的原型对象,会有几个问题。
第一个问题是没有任何合理的参数传递给Actor。
SpaceShip.prototype=new Actor();
当初始化SpaceShip原型时,我们尚未创建任何能作为第一个参数来传递的场景。并且SpaceShip的原型加入到场景的注册表中,而这绝不是我们想做的。这是一种使用子类时常用的方法。应当仅仅在子类构造函数中调用父类构造函数,而不是当创建子类原型时调用它。
一旦创建了SpaceShip的原型对象,我们就可以向其添加所有的可被实例共享的属性,包含一个用于在场景的图像数据表中检索的type名,以及一些太空飞船的特定方法。
SpaceShip.protoype.type='spaceShip';
SpaceShip.prototype.scorePoint=function(){
this.points++;
};
SpaceShip.prototype.left=function(){
this.moveTo(Math.max(this.x-10,0),this.y);
};
SpaceShip.prototype.right=function(){
var maxWidth=this.scene.width-this.width();
this.moveTo(Math.min(this.x+10,maxWidth),this.y);
}
图示角色及子类关系
图示SpaceShip实例的继承层次结构图。
红框为基类与子类关系,构造函数也是Function类的一个实例,Function类的原型对象也继承自是Object的原型对象。
注意:scene,x,y属性只被定义在实例对象中,而不是被定义在原型对象中,尽管SpaceShip是被Actor构造函数创建的。
提示
在子类构造函数中显式地传入this作为显式的接收者调用父类构造函数
使用Object.create函数来构造子类的原型对象以避免调用父类的构造函数
[Effective JavaScript 笔记]第38条:在子类的构造函数中调用父类的构造函数的更多相关文章
- [Effective JavaScript 笔记]第67条:绝不要同步地调用异步的回调函数
设想有downloadAsync函数的一种变种,它持有一个缓存(实现为一个Dict)来避免多次下载同一个文件.在文件已经被缓存的情况下,立即调用回调函数是最优选择. var cache=new Dic ...
- [Effective JavaScript 笔记]第62条:在异步序列中使用嵌套或命名的回调函数
异步程序的操作顺序 61条讲述了异步API如何执行潜在的代价高昂的I/O操作,而不阻塞应用程序继续处理其他输入.理解异步程序的操作顺序刚开始有点混乱.例如,下面的代码会在打印"finishe ...
- [Effective JavaScript 笔记]第18条:理解函数调用、方法调用及构造函数调用之间的不同
面向对象编程中,函数.方法.类的构造函数是三种不同的概念. JS中,它们只是单个构造对象的三种不同的使用模式. 三种不同的使用模式 函数调用 function hello(username){ ret ...
- [Effective JavaScript 笔记] 第4条:原始类型优于封闭对象
js有5种原始值类型:布尔值.数字.字符串.null和undefined. 用typeof检测一下: typeof true; //"boolean" typeof 2; //&q ...
- [Effective JavaScript 笔记] 第5条:避免对混合类型使用==运算符
“1.0e0”=={valueOf:function(){return true;}} 是值是多少? 这两个完全不同的值使用==运算符是相等的.为什么呢?请看<[Effective JavaSc ...
- [Effective JavaScript 笔记]第27条:使用闭包而不是字符串来封装代码
函数是一种将代码作为数据结构存储的便利方式,代码之后可以被执行.这使得富有表现力的高阶函数抽象如map和forEach成为可能.它也是js异步I/O方法的核心.与此同时,也可以将代码表示为字符串的形式 ...
- [Effective JavaScript 笔记]第28条:不要信赖函数对象的toString方法
js函数有一个非凡的特性,即将其源代码重现为字符串的能力. (function(x){ return x+1 }).toString();//"function (x){ return x+ ...
- [Effective JavaScript 笔记]第39条:不要重用父类的属性名
假设想给上节讲的场景图库添加收集诊断信息的功能.这对于调试和性能分析很有用. 38条示例续 给每个Actor实例一个唯一的标识数. 添加标识数 function Actor(scene,x,y){ t ...
- [Effective JavaScript 笔记]第64条:对异步循环使用递归
假设需要有这样一个函数,接收一个URL的数组并尝试依次下载每个文件直到有一个文件被成功下载.如果API是同步的,使用循环很简单实现. function downloadOneSync(urls){ f ...
随机推荐
- asp.net Core开启全新的时代,用视频来告诉你,学习就是这么SO easy。
https://channel9.msdn.com/Blogs/NET-Core/What-is-NET-Core 系统大家多发布一些视频的资料,学习起来更方便!我看到很多人发布的博客里面有的时候对于 ...
- jsPlumb插件做一个模仿viso的可拖拉流程图
前言 这是我第一次写博客,心情还是有点小小的激动!这次主要分享的是用jsPlumb,做一个可以给用户自定义拖拉的流程图,并且可以序列化保存在服务器端. 我在这次的实现上面做得比较粗糙,还有分享我在做j ...
- Android调用系统相册和拍照的Demo
最近我在群里看到有好几个人在交流说现在网上的一些Android调用系统相册和拍照的demo都有bug,有问题,没有一个完整的.确实是,我记得一个月前,我一同学也遇到了这样的问题,在低版本的系统中没问题 ...
- 迅雷首席架构师刘智聪:微信小程序的架构与系统设计的几点观感
笔者注:本文来自于迅雷首席工程师刘智聪的个人分享,他毕业于南昌大学化学系,加入迅雷后设计开发了多款迅雷核心产品,凭借“大规模网络流媒体服务关键支撑技术”项目获得2015年国家科学技术进步奖二等奖,同时 ...
- SQL温故系列两篇(一)
1.不允许保存更改.您所做的更改要求删除并重新创建以下表 关于SQL2008 “不允许保存更改.您所做的更改要求删除并重新创建以下表. 打开SQL SERVER 2008 工具-->选项--&g ...
- SpringMVC使用中遇到的问题总结
使用的IDE工具是MyEclipse2014, spring版本为3.1.1 在使用Spring MVC时需要修改web.xml配置文件,web.xml默认放在WEB-INF目录下. 1.web.xm ...
- 编写高质量代码改善C#程序的157个建议[正确操作字符串、使用默认转型方法、却别对待强制转换与as和is]
前言 本文主要来学习记录前三个建议. 建议1.正确操作字符串 建议2.使用默认转型方法 建议3.区别对待强制转换与as和is 其中有很多需要理解的东西,有些地方可能理解的不太到位,还望指正. 建议1. ...
- Mobile Web
Silun来给大家介绍几个常见的移动浏览器标签~ 当当当~ <meta name="apple-mobile-web-app-capable" content="y ...
- 如何使用lessc编译.less文件
LESS :一种动态样式语言. LESS 将 CSS 赋予了动态语言的特性,如 变量, 继承, 运算, 函数. LESS 既可以在 客户端 上运行 (支持IE 6+, Webkit, Firefox) ...
- Linux /proc、/dev Principle
目录 . /proc简介 . 内核机制相关 . 进程信息 . 硬件设备相关 . 系统信息 . /dev简介 . 内存相关 1. /proc简介 在linux的根目录下有一个/proc目录,/proc文 ...