【问题标题】:Unwanted Javascript effect: Prototype is sharing closure between instances不需要的 Javascript 效果:原型在实例之间共享闭包
【发布时间】:2015-02-25 22:29:54
【问题描述】:

我正在制作一款带有定时关卡的游戏。一个关卡时长2分钟,右上角显示倒计时。马里奥风格。

跟踪经过时间的一种方法是让我的Level 对象将elapsed 作为成员变量。

Level.init = function(){
    this.elapsed = 0;  //member variable!
    return this;
};

Level.update = function(){
    this.countdown();
};

Level.countdown = function(){
    this.elapsed += 1;
    var remaining = (GAME_LENGTH*60) - (this.elapsed/TICKS);
    var minutes = Math.floor(remaining/60);
    var seconds = Math.floor(remaining%60);
    this.countdown.html(minutes + ":" + ("0"+seconds).slice(-2));       

    if (this.elapsed/TICKS >= GAME_LENGTH*60) {
        this.level_end();
    }
};

当游戏引擎需要一个新的关卡时,它会以这种方式创建一个:

NewLevel = Object.create(Level).init();

但是,我认为使用闭包可能会很好; elapsed 仅由countdown 函数使用,因此它不需要是Level 的成员变量。 Level 不需要知道elapsed 的存在。

//member variable this.elapsed has been removed
Level.init = function(){
    return this;
};

Level.update = function(){
    this.countdown();
};

//a closure is used to keep track of elapsed time
Level.countdown = (function(){
    var elapsed = 0;        
    return function() {
        elapsed += 1;
        var remaining = (GAME_LENGTH*60) - (elapsed/TICKS);
        var minutes = Math.floor(remaining/60);
        var seconds = Math.floor(remaining%60);
        this.countdown.html(minutes + ":" + ("0"+seconds).slice(-2));       

        if (elapsed/TICKS >= GAME_LENGTH*60) {
            elapsed = 0;
            this.end_level();
        }
    };
})();

但现在我有一个不同的问题。所有创建的Level 副本共享countdown 闭包,因为它位于原型链中。当用户提前退出关卡,然后开始新关卡(丢弃旧的Level 对象并实例化新的Level 对象)时,闭包中的elapsed 变量不会重置。

换一种说法,用户在还剩 30 秒时提前退出关卡。然后用户再次开始关卡,但现在倒计时仍然显示 30 秒,而不是整整 2 分钟。

有没有一种优雅的方法来解决这个问题并且仍然使用闭包?还是我必须恢复到以前的解决方案,忘记闭包,将elapsed 设为成员变量?

【问题讨论】:

    标签: javascript closures prototype


    【解决方案1】:

    有没有一种优雅的方法来解决这个问题并且仍然使用闭包?或者我必须恢复到以前的解决方案,忘记闭包,并让 elapsed 成为成员变量?

    它必须是特定于实例的;这并不意味着它必须是对象的属性。您可以在 init 方法中创建闭包:

    //member variable this.elapsed has been removed
    Level.init = function(){
        //a closure is used to keep track of elapsed time
        this.countdown = createCountdownMethod();
        return this;
    };
    
    Level.update = function(){
        this.countdown();
    };
    
    function createCountdownMethod(){
        var elapsed = 0;        
        return function() {
            elapsed += 1;
            var remaining = (GAME_LENGTH*60) - (elapsed/TICKS);
            var minutes = Math.floor(remaining/60);
            var seconds = Math.floor(remaining%60);
            this.countdown.html(minutes + ":" + ("0"+seconds).slice(-2));       
    
            if (elapsed/TICKS >= GAME_LENGTH*60) {
                elapsed = 0;
                this.end_level();
            }
        };
    }
    

    或者这可能更容易阅读:

    //member variable this.elapsed has been removed
    Level.init = function(){
        var elapsed = 0;        
    
        //a closure is used to keep track of elapsed time
        Level.countdown = countdown;
        return this;
    
        function countdown() {
            elapsed += 1;
            var remaining = (GAME_LENGTH*60) - (elapsed/TICKS);
            var minutes = Math.floor(remaining/60);
            var seconds = Math.floor(remaining%60);
            this.countdown.html(minutes + ":" + ("0"+seconds).slice(-2));       
    
            if (elapsed/TICKS >= GAME_LENGTH*60) {
                elapsed = 0;
                this.end_level();
            }
        }
    };
    
    Level.update = function(){
        this.countdown();
    };
    

    【讨论】:

      【解决方案2】:

      如果您想让elapsedLevel 实例完全私有,您可以将countdown 的定义移动到init。看起来像:

      //member variable this.elapsed has been removed
      Level.init = function(){
          var elapsed = 0; // elapsed local to init
          this.countdown = function() {
              elapsed += 1;
              var remaining = (GAME_LENGTH*60) - (elapsed/TICKS);
              var minutes = Math.floor(remaining/60);
              var seconds = Math.floor(remaining%60);
              this.countdown.html(minutes + ":" + ("0"+seconds).slice(-2));       
              if (elapsed/TICKS >= GAME_LENGTH*60) {
                  elapsed = 0;
                  this.end_level();
              }
          };
          return this;
      };
      
      Level.update = function(){
          this.countdown();
      };
      

      如果不每次都创建一个新变量,就不可能对每个实例使用像 elapsed 这样的变量的闭包。

      【讨论】:

        猜你喜欢
        • 2013-05-30
        • 2023-03-27
        • 2020-08-06
        • 2019-04-02
        • 2019-10-06
        • 1970-01-01
        • 2018-02-17
        • 1970-01-01
        • 2012-09-27
        相关资源
        最近更新 更多