我有一个画布元素表,用于模拟平铺图像。我只需要填充视口中当前可见的那些图块。这是我使用的代码
for (var ix = visibleRange.firstX; ix <= visibleRange.lastX; ix++) {
for (var iy = visibleRange.firstY; iy <= visibleRange.lastY; iy++) {
var canvasDispJq = $("canvas#" + frameResId + "-" + ix + "-" + iy + "- disp");
var canvasDisp = canvasDispJq.get(0);
var image = new Image();
var context = canvasDisp.getContext("2d");
image.onload = function() {
context.drawImage(image, 0, 0);
}
image.src = self.baseURL + '/' + canvasDispJq.attr("id");
}
}
};
但问题是只加载范围中的最后一个图块。同样的事情,但以这种方式实施工作正常:
$("table#canvasTable-" + frameResId + " canvas.display").each(function() {
var canvasDispJq = $(this);
if ((canvasDispJq.position().top + self.tileSize + imagePosition.y) > 0 &&
(canvasDispJq.position().left + self.tileSize + imagePosition.x) > 0 &&
(canvasDispJq.position().top + imagePosition.y) < self.containerHeight &&
(canvasDispJq.position().left + imagePosition.x) < self.containerWidth) {
var canvasDisp = canvasDispJq.get(0);
var image = new Image();
var context = canvasRaw.getContext("2d");
image.onload = function() {
context.drawImage(image, 0, 0);
}
image.src = self.baseURL + '/' + canvasDispJq.attr("id");
}
});
计算可见范围的方式的差异无关紧要,因为我检查了Firebug,并且两种方法都正确选择了要渲染的图块,并且从服务器下载了实际图块图像,但是对于第一种方法,仅显示最后一个图块,而对于第二个所有瓷砖都正确呈现。
提前感谢任何建议。
答案 0 :(得分:2)
差异的原因主要在于:
var image = new Image();
var context = canvasDisp.getContext("2d");
image.onload = function() {
context.drawImage(image, 0, 0);
}
这样做是创建一个闭包并将其分配给图像的load
事件。闭包关闭context
和image
变量(以及其他几个变量)。这里的关键是它对这些变量有一个持久引用,而不是在创建函数时它们的值的副本。由于上面的代码是循环的,所以创建的所有函数(闭包)都将引用相同的context
和image
- 循环创建的最后一个。
在each
版本中不会发生这种情况,因为在该版本中,闭包会关闭一个不会更改的变量, one context
和{{通过调用迭代器函数创建的1}},你传递给image
。
有关此处闭包的更多信息:Closures are not complicated
也许稍微偏离主题,也许不是:如果你的each
位于不同的地方,这可能会更清楚。 var
is sometimes misunderstood.特别是, 与其他语言(例如C,C ++,Java和C#)中的变量声明相同,部分原因是JavaScript没有块级范围(仅功能级范围和全局范围)。
这就是JavaScript解释器看到你的第一个版本的方式:
var
请注意,所有var ix, iy, canvasDispJq, canvasDisp, image, context;
for (ix = visibleRange.firstX; ix <= visibleRange.lastX; ix++) {
for (iy = visibleRange.firstY; iy <= visibleRange.lastY; iy++) {
canvasDispJq = $("canvas#" + frameResId + "-" + ix + "-" + iy + "- disp");
canvasDisp = canvasDispJq.get(0);
image = new Image();
context = canvasDisp.getContext("2d");
image.onload = function() {
context.drawImage(image, 0, 0);
};
image.src = self.baseURL + '/' + canvasDispJq.attr("id");
}
}
}
语句都已移至顶部,因为这就是它们的处理方式。无论var
出现在函数的哪个位置,它都会从函数的最开始生效,并且只发生一次。如果var
有初始值设定项,则会成为var
所在的赋值。所以var
实际上是两个完全独立的东西,由解释器在不同的时间处理:var x = 2;
,在其他任何事情发生之前进入函数时发生,var x
,发生在哪里它出现在函数代码的逐步执行中。
(另外,偏离主题:在x = 2;
循环的结束;
之后不需要}
;但是在分配到{{1}后确实需要一个for
我已经把这两个mod都做了aboev。)
按照解释器看到的方式编写代码,现在(无论如何),更明确的是,您分配给onload
的每个闭包都将引用相同的 onload
和context
变量,因此他们都会看到最后的变量。
如果您想使用非image
版本,则只需稍微更改一下,以便each
闭包关闭自己的onload
和context
副本。看起来像这样:
image
现在,var ix, iy, canvasDispJq, canvasDisp, image, context;
for (ix = visibleRange.firstX; ix <= visibleRange.lastX; ix++) {
for (iy = visibleRange.firstY; iy <= visibleRange.lastY; iy++) {
canvasDispJq = $("canvas#" + frameResId + "-" + ix + "-" + iy + "- disp");
canvasDisp = canvasDispJq.get(0);
image = new Image();
context = canvasDisp.getContext("2d");
image.onload = createOnloadHandler(context);
image.src = self.baseURL + '/' + canvasDispJq.attr("id");
}
}
}
function createOnloadHandler(ct, img) {
return function() {
context.drawImage(img, 0, 0);
};
}
函数创建的闭包关闭了createOnloadHandler
和ct
,而不是img
和context
。每个闭包都有自己的副本(传递给创建该闭包的image
调用的副本。)