【问题标题】:Change global variable from event function从事件函数更改全局变量
【发布时间】:2020-02-19 13:17:55
【问题描述】:

我正在努力学习 Javascript。

我在 Youtube 上观看了一些 Javascript 课程,并正在尝试创建我的第一个项目。

项目应该是简单的井字游戏。

功能: 首先单击框应填写“X” 第二次单击另一个框应填写“Y” 第三次单击另一个框应再次填充“X”,依此类推,直到所有框都被字符填充。

这是我的代码 HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
<div class="main">
<table>
    <tr>
        <td class="b1-1"></td>
        <td class="b1-2"></td>
        <td class="b1-3"></td>
    </tr>
    <tr>
        <td class="b2-1"></td>
        <td class="b2-2"></td>
        <td class="b2-3"></td>
    </tr>
    <tr>
        <td class="b3-1"></td>
        <td class="b3-2"></td>
        <td class="b3-3"></td>
    </tr>
</table>
</div>
<script src="script.js" type="text/javascript"></script>
</body>
</html>

CSS

.main {
    padding: 100px 0;
    width: 360px;
    margin: 0 auto;
}

table, tbody {
    margin: 0;
    padding: 0;
    width: 360px;
    height: 360px;
}

tr {
    width:360px;
    height: 120px;
    margin: 0;
    padding: 0;
}

td {
    text-align: center;
    width:120px;
    height: 120px;
    border: 1px solid #333;
    margin: 0;
    padding: 0;
    font-size: 50px;
}

Javascript

var action = document.querySelectorAll('td');
var gameState = 0;
for (var i = 0; i <= action.length - 1; i++) {
    getClassName = "." + action[i].classList.value;
        if (gameState === 0) {
            document.querySelector(getClassName).addEventListener("click", chasviX(i));
        } else {
            document.querySelector(getClassName).addEventListener("click", chasviO(i));
        }
}

function chasviX(cord) {
        document.querySelector("." + action[cord].classList.value).addEventListener("click", event => {
            document.querySelector("." + action[cord].classList.value).textContent = "X";
            gameState = 1;
        });
};

function chasviO(cord) {
        document.querySelector("." + action[cord].classList.value).addEventListener("click", event => {
            document.querySelector("." + action[cord].classList.value).textContent = "O";
            gameState = 0;
        });
};

还有项目链接- https://jsfiddle.net/qwy2k571/

目前每个框都用“X”填充。 我知道我并不完全理解闭包和作用域链,但请给我一个正确的代码以通过示例来理解它。

在此先致谢并致以最诚挚的问候。

【问题讨论】:

    标签: javascript function variables tic-tac-toe scope-chain


    【解决方案1】:

    由于您使用的是提供集合的 querySelectorAll,因此您可以对其进行迭代并直接向其添加事件侦听器。此外,您正在添加多个不需要的事件侦听器

    var action = document.querySelectorAll('td');
    var gameState = 0;
    action.forEach((item) => {
      item.addEventListener('click', (e) => {
        if (gameState === 0) {
          e.target.textContent = "X";
          gameState = 1;
        } else {
          e.target.textContent = "0";
          gameState = 0;
        }
      })
    
    })
    .main {
      padding: 100px 0;
      width: 360px;
      margin: 0 auto;
    }
    
    table,
    tbody {
      margin: 0;
      padding: 0;
      width: 360px;
      height: 360px;
    }
    
    tr {
      width: 360px;
      height: 120px;
      margin: 0;
      padding: 0;
    }
    
    td {
      text-align: center;
      width: 120px;
      height: 120px;
      border: 1px solid #333;
      margin: 0;
      padding: 0;
      font-size: 50px;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <div class="main">
      <table>
        <tr>
          <td class="b1-1"></td>
          <td class="b1-2"></td>
          <td class="b1-3"></td>
        </tr>
        <tr>
          <td class="b2-1"></td>
          <td class="b2-2"></td>
          <td class="b2-3"></td>
        </tr>
        <tr>
          <td class="b3-1"></td>
          <td class="b3-2"></td>
          <td class="b3-3"></td>
        </tr>
      </table>
    </div>

    【讨论】:

      【解决方案2】:

      在这里我做了一个工作 jsfiddle:https://jsfiddle.net/1jnkepLg/1/

      我将 JS 部分简化为:

      var gameState = 0; // Holds the current game state
      var cells = document.getElementsByTagName("td"); // list of cells of the game board
      
      // attach an event listener at each cell. 
      for (var i=0;i<cells.length;i++) 
      {    
          cells[i].addEventListener("click", chasvi);
      }   
      
      function chasvi() 
      {
             // in event callbacks "this" refers to the element that triggered the event. In this case, it is the TD element.
             this.textContent = gameState ? "X" : "0";
      
             // switch the game state
             gameState = !gameState;
      
      }
      

      在这种情况下,一个事件监听器就足够了,你可以在回调函数中确定要绘制什么 X|0。

      【讨论】:

        【解决方案3】:

        在您的第一个循环中:for (var i = 0; i &lt;= action.length - 1; i++) { ... } 您正在向每个单元格添加事件侦听器。由于变量gameState 被初始化为0,因此条件if (gameState === 0) { ... } 在该循环中将始终为真。

        无需在初始化期间检查游戏状态,只需像您一样为每个单元格添加一个事件侦听器。

        为确保将正确的参数传递给事件,您需要将回调包装在function() { chasvi(param); } 中,并将整个循环体包装在另一个匿名函数中,将另一个变量设置为坐标 i :

        for (var i = 0; i <= action.length - 1; i++) {
            (function(){
                  let coor = i;
                  let getClassName = "." + action[i].classList.value;
                  document.querySelector(getClassName).addEventListener("click", function() { chasvi(coor); });
            }());
        }
        

        请注意,我将函数名称更改为chasvi,因为您可以在该函数中管理XO 的情况。


        在每个函数chasviXchasviO 中,您再次添加了一个事件侦听器。这不好,因为每次点击,您都会多添加一个事件。

        在点击之前,已经有一个事件了。

        点击一次后,有2个事件。再点击一次,就出现了 3 个事件,以此类推……

        我建议您将这些函数更改为 1 个处理这两种情况的函数:

        function chasvi(cord)
        {
            let TextToDisplay;
        
            if (gameState === 0)
            {
                TextToDisplay = "X";
                gameState = 1;
            }
            else
            {
                TextToDisplay = "O";
                gameState = 0;
            }
            document.querySelector("." + action[cord].classList.value).textContent = TextToDisplay;
        }
        

        由于gameState 只有两种状态,您可以使用布尔值。可以初始化为var gameState = false; X 随机选false。

        然后,函数可以简化为:

        function chasvi(cord)
        {
            let TextToDisplay;
        
            if (gameState === false)
            {
                TextToDisplay = "X";
            }
            else
            {
                TextToDisplay = "O";
            }
            gameState = !gameState; // This swaps the state true/false
            document.querySelector("." + action[cord].classList.value).textContent = TextToDisplay;
        }
        

        这种表示法:

        let TextToDisplay;
        
        if (gameState === false)
        {
            TextToDisplay = "X";
        }
        else
        {
            TextToDisplay = "O";
        }
        

        可以使用三元表达式进行简化:

        let TextToDisplay = gameState ? "O" : "X"; // This means if gameState is true, then "O" is returned, else "X"
        

        最终代码可能如下所示:

        var action = document.querySelectorAll('td');
            var gameState = false;
            for (var i = 0; i <= action.length - 1; i++) {
                (function(){
                  let coor = i;
                  getClassName = "." + action[i].classList.value;
                  document.querySelector(getClassName).addEventListener("click", function() { chasvi(coor); });
                 }());
            }
            
            function chasvi(cord) {
                let TextToDisplay = gameState ? "O" : "X";
                gameState = !gameState;
                document.querySelector("." + action[cord].classList.value).textContent = TextToDisplay;
            }
        .main {
                padding: 100px 0;
                width: 360px;
                margin: 0 auto;
            }
            
            table, tbody {
                margin: 0;
                padding: 0;
                width: 360px;
                height: 360px;
            }
            
            tr {
                width:360px;
                height: 120px;
                margin: 0;
                padding: 0;
            }
            
            td {
                text-align: center;
                width:120px;
                height: 120px;
                border: 1px solid #333;
                margin: 0;
                padding: 0;
                font-size: 50px;
            }
        <!DOCTYPE html>
            <html lang="en">
            <head>
                <meta charset="UTF-8">
                <meta name="viewport" content="width=device-width, initial-scale=1.0">
                <title>Document</title>
                <link rel="stylesheet" href="style.css">
            </head>
            <body>
            <div class="main">
            <table>
                <tr>
                    <td class="b1-1"></td>
                    <td class="b1-2"></td>
                    <td class="b1-3"></td>
                </tr>
                <tr>
                    <td class="b2-1"></td>
                    <td class="b2-2"></td>
                    <td class="b2-3"></td>
                </tr>
                <tr>
                    <td class="b3-1"></td>
                    <td class="b3-2"></td>
                    <td class="b3-3"></td>
                </tr>
            </table>
            </div>
            <script src="script.js" type="text/javascript"></script>
            </body>
            </html>

        【讨论】:

        • 我终于找到了 var 和 let 的区别。你是个好人
        • @LevanSakvarelidze 检查this 问题
        【解决方案4】:

        您的解决方案的问题在于,您编写的 for 循环分配了事件侦听器,用于在板上设置一个“X”到每个正方形之前,甚至在板上单击一次。这意味着您的 if 逻辑永远不会分配“O”,因为所有点击都会调用 chasviX()

        您应该改用 NodeListforEach() 函数,例如您的 action 变量:

         var gameState = false;
         action.forEach(function(act) {
            act.addEventListener("click", function(event) {
                // If gameState is false(logical 0), place an 'X' on the board
                // Otherwise, place a 'O'
                event.target.textContent = gameState ? 'X' : 'O';
                // Invert state at the end
                gameState = !gameState;
            });
        })
        

        【讨论】:

          【解决方案5】:

          您的错误在于,在进行任何移动之前,您以仅依赖于 gameState 的当前值的方式创建事件。您需要检查并更改事件内部gameState 的值:

          var action = document.querySelectorAll('td');
          var gameState = 0;
          for (var i = 0; i <= action.length - 1; i++) {
              action[i].addEventListener("click", function() {
                this.textContent = (gameState = (gameState + 1) % 2) ? 'x' : '0';
            })
          }
          

          https://jsfiddle.net/fby3g12v/

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2013-03-14
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-12-12
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多