• [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实例的继承层次结构图。
    image

    红框为基类与子类关系,构造函数也是Function类的一个实例,Function类的原型对象也继承自是Object的原型对象。
    注意:scene,x,y属性只被定义在实例对象中,而不是被定义在原型对象中,尽管SpaceShip是被Actor构造函数创建的。

    提示

    • 在子类构造函数中显式地传入this作为显式的接收者调用父类构造函数

    • 使用Object.create函数来构造子类的原型对象以避免调用父类的构造函数

  • 相关阅读:
    很多的技术招聘面试方式不务实-导致不仅难以招聘到人,而且严重损害公司形象
    技术工作者上升到思想,哲学层面也许更好
    程序员与架构师的区别
    (转载)创业型公司如何管理-吸引人才
    C#图片转成流,流转成图片,字节转图片,图片转字节的方法
    C# Linq获取两个List或数组的差集交集
    C# List排序,附加使用Linq排序
    C#Qrcode生成二维码支持中文,带图片,带文字
    C#判断本地文件,网络文件是否存在是否存在
    C#WebBrowser控件使用教程与技巧收集
  • 原文地址:https://www.cnblogs.com/wengxuesong/p/5592972.html
Copyright © 2020-2023  润新知