问题
Using CreateJS, I have output a canvas with instances of various objects, animating in various ways. Almost all is working ok, except for one issue, which I think is a scope issue, but I'm not sure how to fix. I have an instance referenced by '_pulsars', containing 11 instances of an object 'pulsar', and code runs like this:
function attachPulseFuncs() {
var defaultFreq = 0.05; // controls the likelihood of a 'pulse' happening
for (var i = 0; i < _pulsars.children.length; i++) {
var myParent = _pulsars.children[i];
myParent.isPulsing = false;
_myParent.pulse = function() {
if (myParent.isPulsing == false) {
myParent.isPulsing = true;
myParent.__frame = 1
var tween = createjs.Tween.get(myParent).to({__frame:65}, 1500).call(myParent.resetPulse);
tween.addEventListener("change", function() {
myParent.gotoAndStop(myParent.__frame);
});
}
}
myParent.resetPulse = function() {
myParent.gotoAndStop(1);
myParent.isPulsing = false;
}
myParent.callRandomPulse = function() {
var ran = Math.random();
if (ran < freqNumber) {
myParent.pulse();
}
}
createjs.Ticker.addEventListener("tick", myParent.callRandomPulse);
}
}
What happens is that only the last pulsar animates (no matter how many are in the group). I'm wondering if it's because only one event listener is being attached? Or somehow all the listeners are being added to one 'pulsar'? Please help!
EDIT: Success! Thanks to Robert (and also Bergi's link to the similar issue), I was helped to understand a little more about closure and unbinding the variable being passed into the function - as the link on the other thread says:
"if we pass a parameter [the] function makes its own local copy of the variable (if it is not object type which pass by reference)"
This allows each pulsar to have a discrete reference to its parent as defined by each iteration of myParent, rather than the single outer-scope reference that was being held by the final value of myParent. (@Robert Koritnik please let me know if I have misunderstood or this is incorrect! Thanks for your help, I would +1 if I wasn't so new to the site. Soon, hopefully)
回答1:
The problem you're having is that you iterate over your pulsars incrementing i along the way and adding those event listeners. So when your listers actually fire, they use myParent (mind your likely typo sometimes using underscore in front of it and sometimes not) variable which was last set to the last pulsar when your for loop completed (last value of i);
You'll have to change this piece of code
myParent.pulse = function() {
if (myParent.isPulsing == false) {
myParent.isPulsing = true;
myParent.__frame = 1
var tween = createjs.Tween.get(myParent).to({__frame:65}, 1500).call(myParent.resetPulse);
tween.addEventListener("change", function() {
myParent.gotoAndStop(myParent.__frame);
});
}
}
to this:
myParent.pulse = (function(parent) {
return function() {
if (parent.isPulsing === false) {
parent.isPulsing = true;
parent.__frame = 1
var tween = createjs.Tween.get(parent).to({__frame:65}, 1500).call(parent.resetPulse);
tween.addEventListener("change", function() {
parent.gotoAndStop(parent.__frame);
});
}
};
})(myParent);
What this change does it creates a new function scope capturing current value of myParent and storing it in local variable (parameter) parent and then returning a function that uses this new captured value.
Maybe I've captured even too much in this and I should only create a new function scope for the tween.addEventListener function call. You should know which part need capturing. My code captures much more code than likely required so it should work just as well, but it may seem confusing after a while when you'll get back to it introducing some change.
Therefore reduce scoping to the minimum set of statements like:
myParent.pulse = function() {
if (myParent.isPulsing == false) {
myParent.isPulsing = true;
myParent.__frame = 1
var tween = createjs.Tween
.get(myParent)
.to({__frame:65}, 1500)
.call(myParent.resetPulse);
tween.addEventListener("change", (function(parent) {
return function() {
parent.gotoAndStop(parent.__frame);
};
})(myParent));
}
}
But as mentioned this may not work as it uses __frame variable of the last myParent pulse instance. So you may end up with my first code change.
来源:https://stackoverflow.com/questions/28108646/scope-i-think-challenge-in-javascript