112

Вот упрощенная версия о чем-то, что я пытаюсь запустить:Как передать значение (а не ссылку) переменной JS в функцию?

for (var i = 0; i < results.length; i++) { 
    marker = results[i]; 
    google.maps.event.addListener(marker, 'click', function() { 
     change_selection(i); 
    }); 
} 

, но я нахожу, что каждый слушатель использует значение results.length (значение, когда цикл завершается). Как добавить слушателей таким образом, чтобы каждый из них использовал значение i в момент его добавления, а не ссылку на i?

ответ

158

Вам нужно создать отдельную сферу, которая сохраняет переменную в его текущем состоянии, передав его в качестве параметра функции:

for (var i = 0; i < results.length; i++) { 
    (function (i) { 
    marker = results[i]; 
    google.maps.event.addListener(marker, 'click', function() { 
     change_selection(i); 
    }); 
    })(i); 
} 

Путем создания анонимной функции и вызов ее с переменной в качестве первого аргумента, вы передаете значение функции и создаете закрытие.

+3

Вы хотите добавить 'var' перед' marker', чтобы не загрязнять глобальное пространство имен. – ThiefMaster

+2

@ThiefMaster: как ни странно, я просто подумал о том же, посмотрев на этот ответ впервые. Однако, глядя на код OP, мы не можем быть полностью уверены, что 'marker' еще не является глобальной переменной. –

+0

, используя API карты Google, мы можем с уверенностью утверждать, что область этого маркера находится вне цикла for. Ницца поймал Энди. –

2

Вы завершаете закрытие. Here's an article on closures и как с ними работать. Проверьте пример 5 на странице; это сценарий, с которым вы имеете дело.

EDIT: Четыре года спустя эта связь мертва. Корень вышеупомянутой проблемы заключается в том, что петли for образуют замыкания (в частности, на marker = results[i]). По мере того, как передается в addEventListener, вы видите побочный эффект закрытия: общая «среда» обновляется с каждой итерацией цикла до того, как она окончательно «сохраняется» через закрытие после последней итерации. MDN explains this very well.

12

укупорочные:

for (var i = 0, l= results.length; i < l; i++) { 
    marker = results[i]; 
    (function(index){ 
     google.maps.event.addListener(marker, 'click', function() { 
      change_selection(index); 
     }); 
    })(i); 
} 

EDIT, 2013: Они теперь обычно называют как IIFE

+0

Ничего * неправильного * здесь, но -1 только потому, что Энди Е первым пришел с большим объяснением; этот ответ не добавляет ничего на страницу в ее нынешнем виде. –

+3

Я не уверен, что вы понимаете причины для downvoting. И этот ответ добавляет информацию в дополнение к замечанию Энди (отлично): IIFE. –

35

А также закрытия, вы можете использовать function.bind:

google.maps.event.addListener(marker, 'click', change_selection.bind(null, i)); 

передает значение i i n как аргумент функции при вызове. (null для связывания this, что вам не нужно в этом случае.)

function.bind была введена в рамках прототипа и был стандартизован в ECMAScript Пятое издание. До тех пор пока браузеры вообще не поддерживают его изначально, вы можете добавить свои собственные function.bind поддержки с помощью затворов:

if (!('bind' in Function.prototype)) { 
    Function.prototype.bind= function(owner) { 
     var that= this; 
     var args= Array.prototype.slice.call(arguments, 1); 
     return function() { 
      return that.apply(owner, 
       args.length===0? arguments : arguments.length===0? args : 
       args.concat(Array.prototype.slice.call(arguments, 0)) 
      ); 
     }; 
    }; 
} 
+2

Только что заметил это, +1. Я довольно поклонник 'bind' и не могу дождаться развертывания собственных реализаций. –

+0

Какие браузеры поддерживают это? Любые мобильные браузеры? – NoBugs

+2

@NoBugs: в настоящее время: IE9 +. Fx4 +, последние версии Chrome и Opera. Не Safari, а не iPhone, браузер Android имеет это с Ice Cream Sandwich. – bobince

-4

Я думаю, что мы можем определить временную переменную для хранения значения I.

for (var i = 0; i < results.length; i++) { 
var marker = results[i]; 
var j = i; 
google.maps.event.addListener(marker, 'click', function() { 
    change_selection(j); 
}); 
} 

Я еще не протестировал его.

+4

нет, это не работает – newacct

+5

Причина, по которой это не будет работать, заключается в том, что JavaScript не имеет облаков на уровне блока. Все области охвата являются функциональными. Вы можете создавать только новую область, вызывая функцию, что мы видим в других ответах. Не вызывая функцию для каждой итерации цикла, нет никакого способа обеспечить другое закрытие каждого обратного вызова-события-получателя. Это проблема, которая решена прозрачно для вас всякий раз, когда вы используете хедера-итерации, например '$ .each()' или '_.each()'. – Keen

-2
for (var i = 0; i < results.length; i++) { 
    marker = results[i]; 
    google.maps.event.addListener(marker, 'click', (function(i) { 
     return function(){ 
      change_selection(i); 
     } 
    })(i)); 
} 
+7

Это был бы лучший ответ, если бы вы объяснили, почему это работает. –