在函数调用之前执行非函数代码

3
我正在制作一个简单的Vue游戏应用程序,其中有按钮可以在点击时以范围内的随机数字减少玩家或“恶魔”的健康值。在我的Vue实例中有一个名为attack的方法来计算伤害并检查游戏是否结束(如果结果导致玩家或恶魔的健康值降至/低于零)。当前,如果健康值达到或低于零,则调用JS内置的确认函数来宣布玩家赢得或输掉比赛,并询问他们是否想开始新游戏。然而,我想首先检查玩家或恶魔的健康状况是否小于等于0,如果是,则将其设置为0,以便在“健康条”中反映出来,因为现在它显示的是在健康值降至/低于0之前的值。
 checkWin: function() {
      if (this.demonHealth <= 0) {
        // this.demonHealth = 0; <-- applied after confirm();
        if (confirm('You won! New game?')) {
          this.startGame();
        } else {
          this.gameIsRunning = false;
        }
        return true;
      } else {
        // this.playerHealth = 0; <-- applied after confirm();
        if (this.playerHealth <= 0) {
          if (confirm('You lost. New game?')) {
            this.startGame();
          } else {
            this.gameIsRunning = false;
          }
          return true;
        }
      }
    }

完整代码: https://codepen.io/nataliecardot/pen/XWrMedm

下面是完整的Vue实例:

new Vue({
  el: '#app',
  data: {
    playerHealth: 100,
    demonHealth: 100,
    gameIsRunning: false
  },
  methods: {
    startGame: function() {
      this.gameIsRunning = true;
      this.playerHealth = 100,
      this.demonHealth = 100
    },
    attack: function() {
      this.demonHealth -= this.calculateDamage(3, 10);
      if (this.checkWin()) {
        return;
      }

      this.playerHealth -= this.calculateDamage(5, 12);
      this.checkWin();
    },
    calculateDamage: function(min, max) {
      return Math.max(Math.floor(Math.random() * max) + 1, min);
    },
    checkWin: function() {
      if (this.demonHealth <= 0) {
        // this.demonHealth = 0; <-- applied after confirm();
        if (confirm('You won! New game?')) {
          this.startGame();
        } else {
          this.gameIsRunning = false;
        }
        return true;
      } else {
        // this.playerHealth = 0; <-- applied after confirm();
        if (this.playerHealth <= 0) {
          if (confirm('You lost. New game?')) {
            this.startGame();
          } else {
            this.gameIsRunning = false;
          }
          return true;
        }
      }
    }
  }
});

我也尝试了删除checkWin()函数,添加一个新的方法检查得分是否<=0,如果是则将其设置为零,并将原本在checkWin()中的代码放入回调函数中。这导致零被应用,但confirm()没有被调用,游戏仍在进行:

methods: {
    startGame: function() {
      this.gameIsRunning = true;
      this.playerHealth = 100,
      this.demonHealth = 100
    },
    scoreCheck: function() {
      if (this.demonHealth < 0) {
        this.demonHealth = 0;
      }
      if (this.playerHealth < 0) {
        this.playerHealth = 0;
      }
    },
    attack: function() {
      this.demonHealth -= this.calculateDamage(3, 10);
      this.scoreCheck(() => {
        if (this.demonHealth === 0) {
          if (confirm('You won! New game?')) {
            this.startGame();
          } else {
            this.gameIsRunning = false;
          }
          return;
        }
      });

      this.playerHealth -= this.calculateDamage(5, 12);
      this.scoreCheck(() => {
        if (this.playerHealth === 0) {
          if (confirm('You lost. New game?')) {
            this.startGame();
          } else {
            this.gameIsRunning = false;
          }
        }
      });
    },
    calculateDamage: function(min, max) {
      return Math.max(Math.floor(Math.random() * max) + 1, min);
    },
  }

编辑:我也尝试在原始代码中使用$nextTick(),但结果与之前相同:

checkWin: function() {
  if (this.demonHealth <= 0) {
    this.demonHealth = 0;
    this.$nextTick(function() {
      if (confirm('You won! New game?')) {
        this.startGame();
      } else {
        this.gameIsRunning = false;
      }
      return true;
    });
  } else {
    if (this.playerHealth <= 0) {
      this.playerHealth = 0;
      this.$nextTick(function() {
        if (confirm('You lost. New game?')) {
          this.startGame();
        } else {
          this.gameIsRunning = false;
        }
        return true;
      });
    }
  }
}

那里有很多要尝试和理解的内容,但我认为您想在 $nextTick 回调中执行 confirm,例如 this.$nextTick(() => { if (confirm(...)) { ... } })。请参阅 https://vuejs.org/v2/api/#vm-nextTick。 - Phil
谢谢您的建议--我尝试了一下,结果和之前一样。我编辑了问题并发布了我使用的代码。 - nCardot
2个回答

3

以下答案不能在chrome上工作,因此我在GitHub上提交了一个问题

像@Phil建议的那样,使用$nextTick将解决您的问题...它基本上是一个包含回调的方法,在同步更新时在DOM更新后执行,或在异步更新时返回一个承诺(我在这里使用异步),而即使它位于更新状态的条件循环(dh || ph <=0)中,您的confirm在DOM更新之前执行的原因是 confirm实际上会暂停HTML的渲染并运行整个JavaScript文件,然后再继续HTML的渲染...关于您的条形动态宽度,我添加了一些条件,因此如果玩家/恶魔的健康值低于0,则健康栏将为空。

checkWin: function() {
  if (this.demonHealth <= 0) {
    this.$nextTick().then(() => {
      if (confirm('You won! New game?')) {
        this.startGame();
      } else {
        this.gameIsRunning = false;
      }
    })
   return true;
  } else {
    if (this.playerHealth <= 0) {
      this.$nextTick().then(() => {
        if (confirm('You lost. New game?')) {
          this.startGame();
        } else {
          this.gameIsRunning = false;
        }
      })
     return true;
    }
  }
}

<div :style="{ width: playerHealth >= 0 ?  playerHealth + '%' : 0  }">
<div :style="{ width: demonHealth >= 0 ?  demonHealth + '%' : 0   }">

这是一个演示:codepen


1
很遗憾,$nextTick仍然执行得太快,导致对DOM的更新被打断了。 - Phil
1
@Phil 我觉得你在用 Chrome,我刚试了一下,不行……在 Mozilla 上可以。 - Abdelillah Aissani
1
你的回答需要考虑到checkWin不再返回布尔值。实际上,你的方法不返回任何东西。 - Phil
1
请查看 https://github.com/vuejs/vue/issues/9200 - 这意味着不等待浏览器渲染。 - Estradiaz
1
@Phil 一个单独的rAF实际上在当前帧中触发,因此需要双重rAF来推迟到下一帧。修复的codepen - tony19
显示剩余3条评论

3

实际上我想把这个标记为重复的 - 但是后来我意识到,大多数解决方案建议使用setTimout - 这会在javascript和浏览器渲染之间创建不必要的竞争条件。

当您在vue范围内更改对象属性(即它们是反应性的)并且希望等待dom更新和dom渲染时,可以执行以下操作:

首先是await vm.$nextTick(),它将计算dom,然后通过doublerequestAnimationFrame 给浏览器时间喘息。

以下是一个实现示例:

Vue.skipFrame = function(){

  return new Promise(resolve => {
    requestAnimationFrame(() => 
    requestAnimationFrame(resolve)
    )
  })
}
let app = new Vue({
  el: '#app',
  data: {
    monster: 10,
    hero: 100
  },

  methods: {
    attack() {
      this.monster = Math.max(
        this.monster - (Math.random() * 10)|0, 
        0
      )
      
      
      this.checkWin()
    },
    async checkWin(){
      await this.$nextTick()
      await Vue.skipFrame()
      if(this.monster == 0){
        confirm('You won! Again?') ?
        this.start():
        this.quit()
      }
    },
    start() {
      this.hero = 100;
      this.monster = 100;
    },
    quit(){
      this.monster = 1000;
      
    }
  }

  });
.as-console-wrapper{
  height: 0px !important
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
  
  <h1>Monster HP: {{monster}}</h1>
  <h1>Hero HP: {{hero}}</h1>
  <button @click="attack">Attack</button>
</div>


如果这个回答成为DOM更新和像alertconfirm这样的对话框问题的规范答案,那将是非常棒的。这是一个很好的答案,比setTimeout好得多。 - Phil

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接