addEventListener和' this '没有按预期工作
addEventListener and `this` not working as expected
我是一名经验丰富的程序员,但对web编程有些陌生。我正在尝试学习Javascript, HTML5和SVG使用VS2010编写一个HTML页面,玩井字游戏与Javascript。
我成功地将九个正方形中的每一个创建为SVG <rect...>
元素,但是我在每个正方形的单击事件处理程序上遇到了麻烦。
下面是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>
这些静态的<rect>
元素绘制了TicTacToe棋盘的交叉散列线。9个棋盘方块是在一个javascript函数中创建的,该函数从windows load事件中调用(见下)。
下面是javascript(内联在HTML主体的<script>
元素中):
<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>
(我可以提供完整的页面源代码,但它只是包含这些的HTML元素。)
问题是双重的:
似乎只有最后一个
addEventListener
起作用。点击其他方块没有任何作用。单击最后一个方格(svgTTTsqr22)确实运行cell.handleClick
,但会导致问题2(见下文)。Chrome开发者工具(F12)显示所有的<rect>
元素,除了最后一个没有事件监听器。当
cell.handleClick
确实运行时,它在第一行(var svgNS = this.pSvg.namespaceURI;
)上出现错误,如"未定义对象没有名为"namespaceURI"的属性"。在开发人员工具中检查显示它失败,因为this
没有设置为cell
对象,而是设置为单击的SVG<rect>
元素。
我的问题是:
。我哪里做错了,
B。我该怎么做?
1。缺少事件处理程序
使用innerHTML
更改元素的内部结构将导致它的所有子元素被删除,并通过重新解析HTML内容来重建元素的DOM子树。通过删除子元素,所有以前注册的事件侦听器都将丢失,并且在从HTML重新构建DOM时不会自动恢复。为了避免这种行为,最好的做法是避免innerHTML
,如果可能的话,而是使用直接的DOM操作。你可以像这样插入 <rect>
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
事件处理程序内部的上下文
当事件监听器被调用时,this
将被绑定到触发事件的元素。然而,在您的代码中,您不需要this
,因为所有信息都可以通过参数brd
获得,该参数传递给函数.tttSetupSquares()
。
handleClick: function (event) {
try {
var svgNS = brd.namespaceURI;
var line = document.createElementNS(svgNS, 'line');
brd.appendChild(line);
}
catch (err) {
alert("handlClick err: " + err);
}
}
请参阅下面的代码片段以获得工作示例:
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>
如上所述,"this"的问题在于它被绑定到一个上下文,而这个上下文并不总是您想要的。对于这类问题有很多解决方案。从臭名昭著的self=this
到.bind()
。这个问题也有很多很多的答案,一般来说它可能是重复的。一个很好的答案和一些后续阅读可以在这里找到:
Var self = this?
如何在javascript中更改函数的上下文
或者在这里:http://ryanmorr.com/understanding-scope-and-context-in-javascript/
或在这里:https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Operators/this
虽然,你的问题的真正答案是非常具体的。对于事件处理程序,有一种解决"this"问题的方法。你只需要实现一个EventListener接口。这听起来比实际情况更复杂。事实上,这很简单。你的对象只需要实现一个函数:.handleEvent
。当您将对象传递给addEventListener()函数时,将自动调用该函数。这样做的好处是,使用这个方法,"this"的上下文将自动正确。不需要黑客或变通方法。知道一般情况下的变通方法当然很好,但是对于这个特定情况,.handleEvent
是解决方案。
下面是一个完整的工作示例:
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>
在eventhandler的情况下,这是正确的解决方案,其他的都是一个hack!
一些建议:
你可以考虑使用事件委托。如果你使用像jQuery、Angular或React这样的框架或库,它会自动为你做事件委托。在不同的DOM元素上使用许多事件处理程序会影响性能。你可以做的是在换行元素上有一个"click"处理程序,并使用event.target
属性来查看实际被点击的元素。
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`
}
});
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();
}
});
如果您这样做,您还可以轻松地进行另一个性能调整,即首先生成整个HTML字符串,并在单个操作中将其附加到DOM,因为您不再需要将HTML插入DOM并在循环时添加事件侦听器。
关于你的问题,
1)对不起,不能帮助你那里:(需要设置一个可执行的例子和戳,不能想到任何阅读代码时。无论如何,我都要把这个答案贴出来,希望上面解释的事件委托解决方案能让这个问题消失。
2)直接调用的原始函数将其'this'绑定到调用它们的任何作用域。调用者还可以显式设置"this"绑定到什么。解决方案是创建一个包装的新函数,以便"this"被强制为您想要的任何内容。使用内置的function () {}.bind(cell)
,它将返回一个包装原始的新函数,并且在原始的this
中将始终设置为cell
,而不管bind
返回的函数是在什么上下文中调用的。
- $(this) .prop() 只在第一次工作
- $(this.el).find()在事件处理程序中工作,而不是在初始化函数(主干.js)中工作
- Jquery$(this).最接近('form');点击按钮后不工作
- 它是如何工作的?target=$this.attr('data target')|| e.prevent
- Javascript this.firstChild.nextSibling在Internet Explorer中不工作
- 我没有'我不了解$(this)jQuery对象是如何工作的
- this.style.backgroundColor don'我不在IE7/8工作
- 为什么$(this)选择器在我的函数内部工作,而在外部工作
- this.style.cursor='手',当我把鼠标悬停在元素上时,第一次没有工作
- 为什么jQuery插件中的“this”没有按预期工作
- jQuery beforeSend:$(this).不工作之后
- JQuery函数以及它们如何协同工作(after(),$(this).next().html())
- “this”在此代码主干示例中如何工作
- 'this"收尾工作
- Javascript函数和对象使用关键字'this'不工作
- 为什么调用array.prototype. foreach .call()时将数组设置为THIS对象不能工作?
- “this"关键字不工作在jquery函数内
- JQuery $(this)不能在函数参数中工作
- 'this'在javascript中工作
- addEventListener和' this '没有按预期工作