addEventListener和‘this’并没有像预期的那样工作

时间:2021-08-23 23:00:02

I am an experienced programmer, but somewhat new to web programming. I am trying to learn Javascript, HTML5 and SVG using VS2010 by writing an HTML page that plays Tic-Tac-Toe with Javascript.

我是一个经验丰富的程序员,但对web编程有些陌生。我正在尝试使用VS2010学习Javascript、HTML5和SVG,编写一个用Javascript玩井字游戏的HTML页面。

I am successfully creating each of the nine squares as SVG <rect...> elements, however I am having trouble with the click event handlers for each square.

我成功地将这9个方块分别创建为SVG 元素,但是我在每个方块的单击事件处理程序上遇到了麻烦。 …>

Here's the base SVG elements as they exist in the HTML file:

下面是HTML文件中存在的基本SVG元素:

  <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"
    id="svgTTT" width="150" height="150" viewBox="0 0 300 300"  >
    <rect width="3" height="300" x="99" fill="#008d46" />
    <rect width="3" height="300" x="199" fill="#000000" />
    <rect width="300" height="3" y="99" fill="#008d46" />
    <rect width="300" height="3" y="199" fill="#d2232c" />
  </svg>

These static <rect> elements draw the for cross-hash lines of the TicTacToe board. The nine board squares are created in a javascript function called from the windows load event (below).

这些静态的 元素绘制TicTacToe棋盘的交叉哈希线。这9个棋盘格是在一个javascript函数中创建的,该函数调用自windows load事件(见下)。

Here's the javascript (in-line in a <script> element in the HTML body):

这里是javascript(在HTML主体中的

<script type="text/javascript">
  function getEl(s) { return document.getElementById(s); }

  var svg;  // get the TTT svg

  // execute after HTML has rendered
  window.onload = function () {
    svg = getEl("svgTTT");
    tttSetupSquares(svg);
    alert("click-squares are setup.");
  }

  var cells = new Array(3);
  // setup the click squares
  function tttSetupSquares(brd) {
    for (var i = 0; i < 3; i++) {
      cells[i] = new Array(3);
      for (var j = 0; j < 3; j++) {
        var x = 3 + (i * 100);
        var y = 3 + (j * 100);
        var newId = "svgTTTsqr" + i.toString() + j.toString();
        // add in new rect with html
        brd.innerHTML += "<rect id='"+newId+"' width='93' height='93' "
                        + "fill='#00DD00' x='" + x.toString() + "' y ='" + y.toString() + "'"
                        + " />";
        //find it using the newId
        var rect = document.getElementById(newId);
        //make a cell object to contain all of this
        var cell = {
          col: i,
          row: j,
          pSvg: svg,
          rect: rect,

          handleClick: function (event) {
            try {
              // this line fails because `this` is the target, not the cell
              var svgNS = this.pSvg.namespaceURI;

              var line = document.createElementNS(svgNS, 'line');
              this.pSvg.appendChild(line);
            }
            catch (err) {
              alert("handlClick err: " + err);
            }
          }
        }

        //add a click handler for the rect/cell
        cell.rect.addEventListener("click", cell.handleClick, false);
        //(only seems to work the last time it is executed)

        //save the cell object for later use
        cells[i][j] = cell;
      }
    }
  }
</script>

(I can supply the full page source, but it's just the HTML elements that contain these.)

(我可以提供完整的页面源代码,但包含这些内容的只是HTML元素。)

The problem is two-fold:

问题是双重的:

  1. Only the last addEventListener seems to work. Clicking on all of the other squares does nothing. Clicking on the last square (svgTTTsqr22) does run the cell.handleClick but leads to problem 2 (below). Chrome Developer Tools (F12) shows all of the <rect> elements except the last as having no event listener.

    似乎只有最后一个addEventListener管用。点击所有其他方块什么都不做。单击最后一个正方形(svgTTTsqr22)会运行单元格。handleClick但是导致了问题2(以下)。Chrome Developer Tools (F12)显示所有 元素,但最后一个元素没有事件监听器。

  2. When the cell.handleClick does run, it fails on the fist line (var svgNS = this.pSvg.namespaceURI;) with an error like "undefined object does not have a property named "namespaceURI" Inspection in Devleoper Tools shows that it is failing because this is not set to the cell object but rather to the SVG <rect> element that was clicked.

    当细胞。handleClick运行,不能在拳头行(var svgNS = this.pSvg.namespaceURI;)这样的一个错误“未定义的对象没有一个属性命名为“namespaceURI“Devleoper工具检查表明,它是失败的,因为这不是细胞对象而是SVG点击 <矩形> 元素。

So my questions are:

所以我的问题是:

A. What am I doing wrong here, and

我在这里做错了什么

B. How can/should I be doing this?

我该怎么做?

3 个解决方案

#1


4  

1. Missing event handlers

1。失踪事件处理程序

Using innerHTML to alter an element's internal structure will cause all of its children to be removed and the element's DOM sub-tree to be rebuilt by re-parsing the HTML content. By removing the child elements all previously registered event listener will get lost and will not automatically be restored when rebuilding the DOM from HTML. To circumvent this behaviour it's good practice to avoid innerHTML, if possible, and use direct DOM manipulation instead. You could use something like this to insert your <rect>s:

使用innerHTML修改元素的内部结构将导致删除它的所有子元素,并通过重新解析HTML内容重新构建元素的DOM子树。通过删除子元素,所有先前注册的事件侦听器将丢失,并且在从HTML重新构建DOM时不会自动恢复。为了避免这种行为,最好避免使用innerHTML(如果可能的话),并使用直接DOM操作。你可以用这样的东西插入你的 s:

// Use DOM manipulation instead of innerHTML
var rect = document.createElementNS(svg.namespaceURI, 'rect');
rect.setAttributeNS(null, "id", newId);
rect.setAttributeNS(null, "fill", "#00DD00");
rect.setAttributeNS(null, "width", "93");
rect.setAttributeNS(null, "height", "93");
rect.setAttributeNS(null, "x", x);
rect.setAttributeNS(null, "y", y);
svg.appendChild(rect);

2. this context inside of event handlers

2。事件处理程序内部的上下文

Whenever an event listener gets called this will get bound to the element where the event got triggered by. In your code, however, you won't need this because all information is available by the parameter brd which gets passed in to function .tttSetupSquares().

无论何时调用事件监听器,它都会被绑定到事件触发的元素。但是,在您的代码中,您不需要这样做,因为所有的信息都是由参数brd提供的,而brd被传递到函数. tttsetupsquare()中。

handleClick: function (event) {
    try {
        var svgNS = brd.namespaceURI;
        var line = document.createElementNS(svgNS, 'line');
        brd.appendChild(line);
    }
    catch (err) {
        alert("handlClick err: " + err);
    }
}

See the following snippet for a working example:

请参阅下面的代码片段,了解一个工作示例:

function getEl(s) { return document.getElementById(s); }

  var svg;  // get the TTT svg
  var cells = new Array(3);

  // execute after HTML has rendered
  !(function () {
    svg = getEl("svgTTT");
    tttSetupSquares(svg);
    alert("click-squares are setup.");
  }());

  // setup the click squares
  function tttSetupSquares(brd) {
    for (var i = 0; i < 3; i++) {
      cells[i] = new Array(3);
      for (var j = 0; j < 3; j++) {
        var x = 3 + (i * 100);
        var y = 3 + (j * 100);
        var newId = "svgTTTsqr" + i.toString() + j.toString();
        
        // Use DOM manipulation instead of innerHTML
		var rect = document.createElementNS(svg.namespaceURI, 'rect');
        rect.setAttributeNS(null, "id", newId);
        rect.setAttributeNS(null, "fill", "#00DD00");
        rect.setAttributeNS(null, "width", "93");
        rect.setAttributeNS(null, "height", "93");
        rect.setAttributeNS(null, "x", x);
        rect.setAttributeNS(null, "y", y);
        svg.appendChild(rect);
        
        //make a cell object to contain all of this
        var cell = {
          col: i,
          row: j,
          pSvg: brd,
          rect: rect,

          handleClick: function (event) {
            try {
              //console.log(this);
              var svgNS = brd.namespaceURI;
              var line = document.createElementNS(svgNS, 'line');
              line.setAttributeNS(null, "x1", this.x.baseVal.value);
              line.setAttributeNS(null, "y1", this.y.baseVal.value);
              line.setAttributeNS(null, "x2", this.x.baseVal.value + this.width.baseVal.value);
              line.setAttributeNS(null, "y2", this.y.baseVal.value + this.height.baseVal.value);
              brd.appendChild(line);
            }
            catch (err) {
              alert("handlClick err: " + err);
            }
          }
        }

        //add a click handler for the rect/cell
        cell.rect.addEventListener("click", cell.handleClick, false);

        //save the cell object for later use
        cells[i][j] = cell;
      }
    }
  }
line {
  stroke: red;
}
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"
    id="svgTTT" width="150" height="150" viewBox="0 0 300 300"  >
    <rect width="3" height="300" x="99" fill="#008d46" />
    <rect width="3" height="300" x="199" fill="#000000" />
    <rect width="300" height="3" y="99" fill="#008d46" />
    <rect width="300" height="3" y="199" fill="#d2232c" />
  </svg>

#2


3  

As explained above, the problem with "this" is that it gets bound to a context, which is not always the context you want. There are many solution for this type of problem. From the infamous self=this trick to .bind(). There are also many many answers to this, and in general it might by a duplicate. A good answer and some followup reading can be found here:
var self = this?
or here: How to change the context of a function in javascript
or here: http://ryanmorr.com/understanding-scope-and-context-in-javascript/
or here: https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Operators/this

如前所述,“this”的问题在于它被绑定到一个上下文,而这个上下文并不总是您想要的上下文。对于这种类型的问题,有许多解决方案。从臭名昭著的self=这个技巧到。bind()。这个问题也有很多答案,一般来说它可能是一个副本。一个好的答案和一些后续阅读可以在这里找到:var self = this?或者这里:如何更改javascript中的函数上下文,或者这里:http://ryanmorr.com/understand -scope-and-context-in javascript/或者这里:https://developer.mozilla.org/de/docs/web/cript/reference/operators/this

Though the real answer to your question is very specific. In the case of event handlers, there is a solution to the "this"-problem. You just have to implement an EventListener interface. This sounds more complicated than it is. In fact its quite easy. Your object just has to implement one function: .handleEvent. When you pass an object to an addEventListener() function, it is this function that gets called automatically. The nice thing about this is, that using this method, the context of "this" will automatically be right. No need for hacks or workarounds. It sure is good to know the workarounds for the general case, but for this specific case, .handleEvent is the solution.

虽然你的问题的真正答案是非常具体的。在事件处理程序的情况下,有一个解决“这个”问题的方法。您只需实现一个EventListener接口。这听起来比实际要复杂得多。事实上很容易。对象只需实现一个函数:. handleevent。当您将一个对象传递给addEventListener()函数时,自动调用的就是这个函数。这个方法的好处是,使用这个方法,“this”的上下文将自动正确。不需要黑客或变通方法。了解一般情况的解决方案当然很好,但是对于这个特定的情况,. handleevent是解决方案。

here is a complete working example:

这里有一个完整的工作示例:

  function getEl(s) { return document.getElementById(s); }

  var svg;  // get the TTT svg

  // execute after HTML has rendered
  window.onload = function () {
    svg = getEl("svgTTT");
    tttSetupSquares(svg);
    //alert("click-squares are setup.");
  }

  var cells = new Array(3);
  // setup the click squares
  function tttSetupSquares(brd) {
    for (var i = 0; i < 3; i++) {
      cells[i] = new Array(3);
      for (var j = 0; j < 3; j++) {
        var x = 3 + (i * 100);
        var y = 3 + (j * 100);
        var rect= document.createElementNS("http://www.w3.org/2000/svg","rect")
rect.setAttribute("x",x);
rect.setAttribute("y",y);
rect.setAttribute("width",100);
rect.setAttribute("height",100);
rect.setAttribute("fill","grey")

        brd.appendChild(rect)
        var cell = {
          col: i,
          row: j,
          pSvg: svg,
          rect: rect,

          handleEvent: function (event) {
            try {
              // this line fails because `this` is the target, not the cell
              var svgNS = this.pSvg.namespaceURI;

              var line = document.createElementNS(svgNS, 'line');
              line.setAttribute("x1",this.rect.getAttribute("x"))
              line.setAttribute("y1",this.rect.getAttribute("y"))
              line.setAttribute("x2",this.rect.getAttribute("x")*1+this.rect.getAttribute("width")*1)
              line.setAttribute("y2",this.rect.getAttribute("y")*1+this.rect.getAttribute("height")*1)
              line.setAttribute("stroke","red")
              this.pSvg.appendChild(line);
              document.getElementById("out").innerHTML="rect("+this.col+","+this.row+") was clicked"
            }
            catch (err) {
              alert("handlClick err: " + err);
            }
          }
        }

        //add a click handler for the rect/cell
        cell.rect.addEventListener("click", cell, false);
        //(only seems to work the last time it is executed)

        //save the cell object for later use
        cells[i][j] = cell;
      }
    }
  }
  <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"
    id="svgTTT" width="150" height="150" viewBox="0 0 300 300"  >
    <rect width="3" height="300" x="99" fill="#008d46" />
    <rect width="3" height="300" x="199" fill="#000000" />
    <rect width="300" height="3" y="99" fill="#008d46" />
    <rect width="300" height="3" y="199" fill="#d2232c" />
  </svg>

<div id="out"></div>

in the case of EventHandlers, this is the correct solution, everything else is a hack!

对于偶数的情况,这是正确的解决方案,其他的都是黑客!

#3


2  

Some advice:

一些建议:

You could look into using event delegation. If you use a framework or library like jQuery or Angular or React, it will do event delegation for you automatically. Having many event handlers on separate DOM elements can hurt performance. What you could do instead is to have a "click" handler on the wrapping element, and use the event.target property to see which element that was actually clicked.

您可以考虑使用事件委托。如果您使用框架或库(如jQuery、angle或React),它将自动为您执行事件委托。在单独的DOM元素上拥有多个事件处理程序会影响性能。相反,您可以在包装元素上使用“单击”处理程序,并使用该事件。目标属性以查看实际单击的元素。

svg.addEventListener("click", function (e) {
    if (e.target.nodeName === "rect" && (/^svgTTTsqr/).test(e.target.id)) {
        // Use a regexp on  e.target.id to find your
        // cell object in `cells`
    }
});

A regexp might be a little dirty so perhaps you should use data attributes instead.

regexp可能有点脏,所以您应该使用数据属性。

// Generating the HTML
brd.innerHTML += "<rect id='"+newId+"' data-i='" + i + "' data-j='" + j + "' "

// The event handler:
svg.addEventListener("click", function (e) {
    if (e.target.nodeName === "rect" && (/^svgTTTsqr/).test(e.target.id)) {
        var i = e.target.getAttribute("data-i");
        var j = e.target.getAttribute("data-j");
        var cell = cells[i][j];
        cell.handleClick();
    }
});

If you do this you can also easily do another performance tweak which is to generate the entire HTML string first, and append it to the DOM in a single operation, since you no longer have to insert the HTML into the DOM and add the event listener while looping.

如果这样做,您还可以轻松地进行另一项性能调整,即首先生成整个HTML字符串,然后在单个操作中将其附加到DOM,因为您不再需要在循环时将HTML插入DOM并添加事件侦听器。

As for your questions,

至于你的问题,

1) Sorry, can't help you there :( Would need to set up an executable example and poke around, can't think of anything when reading the code. I'm going to post this answeranyway as hopefully the event delegation solution explained above would make the problem go away.

1)抱歉,在这里帮不上忙:(需要建立一个可执行的例子,在阅读代码的时候什么都想不出来。我将发布这个答案,但愿以上解释的事件授权解决方案能让问题消失。

2) Raw functions called plainly have their 'this' bound to whatever scope they are called in. The caller can also explicitly set what 'this' is bound to. The solution is to create a new function that is wrapped so that 'this' is forced to be whatever you want it to be. Use the built-in function () {}.bind(cell) which will return a new function that wraps the original and in the original this will always be set to cell regardless of the context the function returned by bind is called in.

2)所谓的原始功能,就是把它们的“this”绑定到它们所调用的任何范围。调用者还可以显式地设置“this”绑定到什么。解决方案是创建一个包装好的新函数,使“this”成为您想要的任何值。使用内置函数(){}.bind(cell),该函数将返回一个新的函数来包装原始函数,在原始函数中,无论调用bind返回的函数是什么上下文,这个函数都将被设置为cell。

#1


4  

1. Missing event handlers

1。失踪事件处理程序

Using innerHTML to alter an element's internal structure will cause all of its children to be removed and the element's DOM sub-tree to be rebuilt by re-parsing the HTML content. By removing the child elements all previously registered event listener will get lost and will not automatically be restored when rebuilding the DOM from HTML. To circumvent this behaviour it's good practice to avoid innerHTML, if possible, and use direct DOM manipulation instead. You could use something like this to insert your <rect>s:

使用innerHTML修改元素的内部结构将导致删除它的所有子元素,并通过重新解析HTML内容重新构建元素的DOM子树。通过删除子元素,所有先前注册的事件侦听器将丢失,并且在从HTML重新构建DOM时不会自动恢复。为了避免这种行为,最好避免使用innerHTML(如果可能的话),并使用直接DOM操作。你可以用这样的东西插入你的 s:

// Use DOM manipulation instead of innerHTML
var rect = document.createElementNS(svg.namespaceURI, 'rect');
rect.setAttributeNS(null, "id", newId);
rect.setAttributeNS(null, "fill", "#00DD00");
rect.setAttributeNS(null, "width", "93");
rect.setAttributeNS(null, "height", "93");
rect.setAttributeNS(null, "x", x);
rect.setAttributeNS(null, "y", y);
svg.appendChild(rect);

2. this context inside of event handlers

2。事件处理程序内部的上下文

Whenever an event listener gets called this will get bound to the element where the event got triggered by. In your code, however, you won't need this because all information is available by the parameter brd which gets passed in to function .tttSetupSquares().

无论何时调用事件监听器,它都会被绑定到事件触发的元素。但是,在您的代码中,您不需要这样做,因为所有的信息都是由参数brd提供的,而brd被传递到函数. tttsetupsquare()中。

handleClick: function (event) {
    try {
        var svgNS = brd.namespaceURI;
        var line = document.createElementNS(svgNS, 'line');
        brd.appendChild(line);
    }
    catch (err) {
        alert("handlClick err: " + err);
    }
}

See the following snippet for a working example:

请参阅下面的代码片段,了解一个工作示例:

function getEl(s) { return document.getElementById(s); }

  var svg;  // get the TTT svg
  var cells = new Array(3);

  // execute after HTML has rendered
  !(function () {
    svg = getEl("svgTTT");
    tttSetupSquares(svg);
    alert("click-squares are setup.");
  }());

  // setup the click squares
  function tttSetupSquares(brd) {
    for (var i = 0; i < 3; i++) {
      cells[i] = new Array(3);
      for (var j = 0; j < 3; j++) {
        var x = 3 + (i * 100);
        var y = 3 + (j * 100);
        var newId = "svgTTTsqr" + i.toString() + j.toString();
        
        // Use DOM manipulation instead of innerHTML
		var rect = document.createElementNS(svg.namespaceURI, 'rect');
        rect.setAttributeNS(null, "id", newId);
        rect.setAttributeNS(null, "fill", "#00DD00");
        rect.setAttributeNS(null, "width", "93");
        rect.setAttributeNS(null, "height", "93");
        rect.setAttributeNS(null, "x", x);
        rect.setAttributeNS(null, "y", y);
        svg.appendChild(rect);
        
        //make a cell object to contain all of this
        var cell = {
          col: i,
          row: j,
          pSvg: brd,
          rect: rect,

          handleClick: function (event) {
            try {
              //console.log(this);
              var svgNS = brd.namespaceURI;
              var line = document.createElementNS(svgNS, 'line');
              line.setAttributeNS(null, "x1", this.x.baseVal.value);
              line.setAttributeNS(null, "y1", this.y.baseVal.value);
              line.setAttributeNS(null, "x2", this.x.baseVal.value + this.width.baseVal.value);
              line.setAttributeNS(null, "y2", this.y.baseVal.value + this.height.baseVal.value);
              brd.appendChild(line);
            }
            catch (err) {
              alert("handlClick err: " + err);
            }
          }
        }

        //add a click handler for the rect/cell
        cell.rect.addEventListener("click", cell.handleClick, false);

        //save the cell object for later use
        cells[i][j] = cell;
      }
    }
  }
line {
  stroke: red;
}
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"
    id="svgTTT" width="150" height="150" viewBox="0 0 300 300"  >
    <rect width="3" height="300" x="99" fill="#008d46" />
    <rect width="3" height="300" x="199" fill="#000000" />
    <rect width="300" height="3" y="99" fill="#008d46" />
    <rect width="300" height="3" y="199" fill="#d2232c" />
  </svg>

#2


3  

As explained above, the problem with "this" is that it gets bound to a context, which is not always the context you want. There are many solution for this type of problem. From the infamous self=this trick to .bind(). There are also many many answers to this, and in general it might by a duplicate. A good answer and some followup reading can be found here:
var self = this?
or here: How to change the context of a function in javascript
or here: http://ryanmorr.com/understanding-scope-and-context-in-javascript/
or here: https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Operators/this

如前所述,“this”的问题在于它被绑定到一个上下文,而这个上下文并不总是您想要的上下文。对于这种类型的问题,有许多解决方案。从臭名昭著的self=这个技巧到。bind()。这个问题也有很多答案,一般来说它可能是一个副本。一个好的答案和一些后续阅读可以在这里找到:var self = this?或者这里:如何更改javascript中的函数上下文,或者这里:http://ryanmorr.com/understand -scope-and-context-in javascript/或者这里:https://developer.mozilla.org/de/docs/web/cript/reference/operators/this

Though the real answer to your question is very specific. In the case of event handlers, there is a solution to the "this"-problem. You just have to implement an EventListener interface. This sounds more complicated than it is. In fact its quite easy. Your object just has to implement one function: .handleEvent. When you pass an object to an addEventListener() function, it is this function that gets called automatically. The nice thing about this is, that using this method, the context of "this" will automatically be right. No need for hacks or workarounds. It sure is good to know the workarounds for the general case, but for this specific case, .handleEvent is the solution.

虽然你的问题的真正答案是非常具体的。在事件处理程序的情况下,有一个解决“这个”问题的方法。您只需实现一个EventListener接口。这听起来比实际要复杂得多。事实上很容易。对象只需实现一个函数:. handleevent。当您将一个对象传递给addEventListener()函数时,自动调用的就是这个函数。这个方法的好处是,使用这个方法,“this”的上下文将自动正确。不需要黑客或变通方法。了解一般情况的解决方案当然很好,但是对于这个特定的情况,. handleevent是解决方案。

here is a complete working example:

这里有一个完整的工作示例:

  function getEl(s) { return document.getElementById(s); }

  var svg;  // get the TTT svg

  // execute after HTML has rendered
  window.onload = function () {
    svg = getEl("svgTTT");
    tttSetupSquares(svg);
    //alert("click-squares are setup.");
  }

  var cells = new Array(3);
  // setup the click squares
  function tttSetupSquares(brd) {
    for (var i = 0; i < 3; i++) {
      cells[i] = new Array(3);
      for (var j = 0; j < 3; j++) {
        var x = 3 + (i * 100);
        var y = 3 + (j * 100);
        var rect= document.createElementNS("http://www.w3.org/2000/svg","rect")
rect.setAttribute("x",x);
rect.setAttribute("y",y);
rect.setAttribute("width",100);
rect.setAttribute("height",100);
rect.setAttribute("fill","grey")

        brd.appendChild(rect)
        var cell = {
          col: i,
          row: j,
          pSvg: svg,
          rect: rect,

          handleEvent: function (event) {
            try {
              // this line fails because `this` is the target, not the cell
              var svgNS = this.pSvg.namespaceURI;

              var line = document.createElementNS(svgNS, 'line');
              line.setAttribute("x1",this.rect.getAttribute("x"))
              line.setAttribute("y1",this.rect.getAttribute("y"))
              line.setAttribute("x2",this.rect.getAttribute("x")*1+this.rect.getAttribute("width")*1)
              line.setAttribute("y2",this.rect.getAttribute("y")*1+this.rect.getAttribute("height")*1)
              line.setAttribute("stroke","red")
              this.pSvg.appendChild(line);
              document.getElementById("out").innerHTML="rect("+this.col+","+this.row+") was clicked"
            }
            catch (err) {
              alert("handlClick err: " + err);
            }
          }
        }

        //add a click handler for the rect/cell
        cell.rect.addEventListener("click", cell, false);
        //(only seems to work the last time it is executed)

        //save the cell object for later use
        cells[i][j] = cell;
      }
    }
  }
  <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"
    id="svgTTT" width="150" height="150" viewBox="0 0 300 300"  >
    <rect width="3" height="300" x="99" fill="#008d46" />
    <rect width="3" height="300" x="199" fill="#000000" />
    <rect width="300" height="3" y="99" fill="#008d46" />
    <rect width="300" height="3" y="199" fill="#d2232c" />
  </svg>

<div id="out"></div>

in the case of EventHandlers, this is the correct solution, everything else is a hack!

对于偶数的情况,这是正确的解决方案,其他的都是黑客!

#3


2  

Some advice:

一些建议:

You could look into using event delegation. If you use a framework or library like jQuery or Angular or React, it will do event delegation for you automatically. Having many event handlers on separate DOM elements can hurt performance. What you could do instead is to have a "click" handler on the wrapping element, and use the event.target property to see which element that was actually clicked.

您可以考虑使用事件委托。如果您使用框架或库(如jQuery、angle或React),它将自动为您执行事件委托。在单独的DOM元素上拥有多个事件处理程序会影响性能。相反,您可以在包装元素上使用“单击”处理程序,并使用该事件。目标属性以查看实际单击的元素。

svg.addEventListener("click", function (e) {
    if (e.target.nodeName === "rect" && (/^svgTTTsqr/).test(e.target.id)) {
        // Use a regexp on  e.target.id to find your
        // cell object in `cells`
    }
});

A regexp might be a little dirty so perhaps you should use data attributes instead.

regexp可能有点脏,所以您应该使用数据属性。

// Generating the HTML
brd.innerHTML += "<rect id='"+newId+"' data-i='" + i + "' data-j='" + j + "' "

// The event handler:
svg.addEventListener("click", function (e) {
    if (e.target.nodeName === "rect" && (/^svgTTTsqr/).test(e.target.id)) {
        var i = e.target.getAttribute("data-i");
        var j = e.target.getAttribute("data-j");
        var cell = cells[i][j];
        cell.handleClick();
    }
});

If you do this you can also easily do another performance tweak which is to generate the entire HTML string first, and append it to the DOM in a single operation, since you no longer have to insert the HTML into the DOM and add the event listener while looping.

如果这样做,您还可以轻松地进行另一项性能调整,即首先生成整个HTML字符串,然后在单个操作中将其附加到DOM,因为您不再需要在循环时将HTML插入DOM并添加事件侦听器。

As for your questions,

至于你的问题,

1) Sorry, can't help you there :( Would need to set up an executable example and poke around, can't think of anything when reading the code. I'm going to post this answeranyway as hopefully the event delegation solution explained above would make the problem go away.

1)抱歉,在这里帮不上忙:(需要建立一个可执行的例子,在阅读代码的时候什么都想不出来。我将发布这个答案,但愿以上解释的事件授权解决方案能让问题消失。

2) Raw functions called plainly have their 'this' bound to whatever scope they are called in. The caller can also explicitly set what 'this' is bound to. The solution is to create a new function that is wrapped so that 'this' is forced to be whatever you want it to be. Use the built-in function () {}.bind(cell) which will return a new function that wraps the original and in the original this will always be set to cell regardless of the context the function returned by bind is called in.

2)所谓的原始功能,就是把它们的“this”绑定到它们所调用的任何范围。调用者还可以显式地设置“this”绑定到什么。解决方案是创建一个包装好的新函数,使“this”成为您想要的任何值。使用内置函数(){}.bind(cell),该函数将返回一个新的函数来包装原始函数,在原始函数中,无论调用bind返回的函数是什么上下文,这个函数都将被设置为cell。