JavaScript Puzzle: Scope - PullRequest
       31

JavaScript Puzzle: Scope

4 голосов
/ 30 января 2012

Итак, я пытаюсь разгадать эту загадку:

function fun1(){
    var result = [];
    for (var i = 0; i < 5; i++){
        result.push( function() {return i} );
    }
    return result;
}

console.log(fun1()[0]()) // returns 5?

Разве первый элемент этого массива не должен возвращать функцию, которая возвращает '0'?

Ответы [ 6 ]

6 голосов
/ 30 января 2012

Это происходит потому, что ваша функция (замыкание) поддерживает

  • ссылку на i

вместо

  • снимок i в том виде, в каком он существовал во время каждой конкретной итерации .

Это означает, что ваша функция при выполнении будет знать текущее значение iв этот момент.Поскольку цикл уже завершится в этот момент, это будет последнее значение i, установленное циклом.Итак, как получить снимок значение вместо ссылка ?К счастью, числовые параметры передаются по значению ... поэтому вы можете избежать проблемы, передав i второй функции ... ( fiddle ):

function fun1(){
    var result = [];
    for (var i = 0; i < 5; i++){
        (function(x){
            result.push(function() { 
                return x; // x now has i's snapshotted value
            });
        })(i); // passes i by value since it's numeric
    }
    return result;
}

alert(fun1()[0]())

[Редактировать] Давайте возьмем цикл из уравнения ( fiddle ):

var i = 1;
var fn = function() { // this closure maintains a reference to i 
    alert(i); 
};
i = 10; // such that when we change i's value here
fn(); // it's reflected by the function call: alerting 10

Итак, чтобы «исправить» это ( скрипка ):

var i = 1;
var fn = (function(x) { // this is a self-executing anonymous function
    return function() { // this closure maintains a reference to x
        alert(x); 
    };
})(i); // we're passing i by value such that x will retain that value
i = 10; // when we change i here, it has no affect on x
fn(); // this will alert 1
6 голосов
/ 30 января 2012

Нет, должно возвращаться 5.

Функция все еще имеет ссылку на i, то есть после 5 для.

3 голосов
/ 07 января 2015

Давайте разберем, что происходит, шаг за шагом:

  • Мы объявляем функцию fun1(), которая возвращает массив result.

  • Цикл for повторяется 5 раз, причем каждая итерация увеличивается i.

  • Обратите внимание, что анонимная функция, возвращающая i, не вызывается на протяжениивыполнение.

  • Цикл for заканчивается значением i, равным 5.

  • Вызов fun1()[0] возвращает массив result[], которая имеет функцию, которая хранит ссылку от до i, а не значение из i.

  • Вызов анонимногоЗатем функция следует за этой ссылкой и возвращает значение i, которое равно 5.

2 голосов
/ 30 января 2012

Значение «i» во внутренней функции будет значением «i» во внешней функции при ее возврате.Таким образом, все 5 элементов в массиве будут возвращать «5» при вызове.

2 голосов
/ 30 января 2012

Вы должны прочитать немного о закрытиях Javascript, посмотреть его.

Переменная i из функции fun1 доступна из функции в цикле, но это та же самая переменная, а не клон.

1 голос
/ 31 января 2012

Что за вопрос!

Проблема в том, что в javascript, как и в java, целочисленные значения передаются по значению, а не по ссылке. Вот что все остальные здесь говорят вам. Но я предполагаю, что вы не знаете, что это значит. Это довольно стандартно для всех языков, так что давайте посмотрим, смогу ли я объяснить!

ПРИМЕЧАНИЕ: это искаженное объяснение. Я действительно не знаю, как javascript передает значения, и такие забавные термины, как «stack», «heap» и «pass by reference value» (что я считаю технически правильным?) Могут появиться в более академических ответах. Надеюсь, я могу пропустить все это и предположить, что

  1. есть одно место, где компьютер хранит память для программ (нет)
  2. он хранится в шестнадцатеричной записи в оперативной памяти (может быть, да? Не знаю)
  3. мы только передаем по ссылке и передаем по значению (подробнее об этом ... сейчас!)

Итак, у нас есть передача по ссылке и передача по значению. В памяти программы при передаче по значению переменная «i» указывает на буквальное число. Когда вы передаете по ссылке, то, на что будет указывать переменная «i», будет ячейкой памяти какого-то другого объекта.

Что это значит?

line 1. i = new Object();
line 2. i = new Object();

после строки 1. программа создаст небольшой «дом» в памяти для «i», и это даст расположение - я не знаю, какое-то значение оперативной памяти (скажем, я действительно не знаю, капюшон javascript). Затем, в этом значении, будет установлено другое значение. Итак имеем:

i -> 0x67 -> 0x68

, поэтому программа знает, что, когда она видит значение «i», она переходит в область памяти 0x67 и получает значение 0x68. Он также знает, что значение ТО указывает на другое место в памяти. В этом случае у нас есть

...
0x67 -> 0x68 (in bytes)
0x68 -> some byte representation of a new Object()
0x69 -> i don't know! some more bytes. they're not important right now.
...

После запуска строки 2 у нас будет

i ->0x67 -> 0x69 (in bytes)

и

0x69 -> some byte representation of a new Object()

дать:

...
0x67 -> 0x68 (in bytes)
0x68 -> some byte representation of a new Object()
0x69 -> some byte representation of a new Object()
...

Теперь, как это происходит, если бы вы были настолько равны в двух новых объектах, вы бы прекрасно, что они одинаковы (я думаю). Здесь важно отметить, что после второй строки значение i изменилось с первой ячейки памяти на вторую.

ТАК, если вы сделали это:

i = new Object()
j = i
i = new Object()

вы получите:

i -> 0x67 -> 0x68 ->byte for a new Object
j -> 0x69 -> 0x68 -> byte for a new Object
i -> 0x67 -> 0x70 -> byte for another new Object. Same bytes as above.

См? «j» получает действительные значения байтов в «i», что означает, что «j» получает значение 0x68, которое является ячейкой памяти для нового объекта. После третьей строки j имеет местоположение первого объекта, а «i» - местоположение второго объекта.

ОДНАКО, любые изменения, которые вы вносите в j - скажем,

j.x = "moo"

не появится в объекте, на который указывает «i». Он появится только в объекте "j". Потому что, помните, j указывает на совершенно другой объект в совершенно другом месте, чем объект, на который я указываю.

Хорошо, с , что вне пути, давайте вернемся к передаче по значению.

Здесь

i = 6;
j = i;
i = 7;

так мы получаем

i -> 0x67 -> 6 (well, the bytes for 6, anyway)
j -> 0x68 -> 6 (again, bytes for 6. the same bytes. We're pointing to i here)
i -> 0x67 -> 7

И, если бы мы посмотрели на j?

j -> 0x68 -> 6

итак, потому что мы обновили «i», мы также не обновили «j», понятно? Байты хранятся, я получаю свою собственную копию байтов здесь.

ОК, так что со всем вышеперечисленным, давайте проверим ваш код:

function fun1(){
    var result = [];
    for (var i = 0; i < 5; i++){
        result.push( function() {return i} );
    }
    return result;
}

верно, поэтому в цикле for вы создаете функцию , которая содержит ссылку на i , и действие цикла for заключается в увеличении значения i.

Вы, вероятно, собираетесь "держись!" или что-то - вы думаете, что внутренне так? - потому что я только что заметил, как данные передаются по значению для целых чисел (или чисел, как правило). Проблема вот в чем:

С этой страницы: https://developer.mozilla.org/en/JavaScript/Guide/Closures

Закрытие - это особый вид объекта, который объединяет две вещи: функцию и среду, в которой эта функция была создана. Среда состоит из любых локальных переменных, которые находились в области действия во время создания замыкания.

Итак ... и я вставил вышеупомянутое по причине - закрытие - это объект, который является функцией (скажем, fun1) и средой, в которой была создана функция. А окружающая среда? Он состоит из локальных переменных (о, как насчет i?) В области видимости.

Итак, чтобы быть тупым ... так же, как i все еще находится в области действия все время цикла for, то же самое "i" находится в области действия для каждого замыкания, которое мы генерируем. А что я? что я получаю обновленную стоимость. Вы не передаете снимок из i, вы передаете, насколько я могу определить, сам опорный объект. Это больше не передача по значению, мы вроде как возвращаемся по ссылке.

Итак, в памяти у нас есть

i -> 0x67 -> 0
i -> 0x67 -> 1
i -> 0x67 -> 2
i -> 0x67 -> 3
i -> 0x67 -> 4
i -> 0x67 -> 5 (yeah, the loop increments i, then fails the test of i <5. but is still 5)

в вашем массиве вы помещаете функцию, которая просто выплевывает значение i. Но вы увеличиваете я все время. Каждая функция, которую вы помещаете в этот массив, будет выдавать «5».

Чтобы сделать еще один шаг вперед ...

function fun1(){
    var result = [];
    for (var i = 0; i < 5; i++){
        result.push( function() {return i++;} );
    }
    return result;
}

alert(fun1()[0]());
alert(fun1()[0]());
alert(fun1()[4]());

Здесь мы проверяем, можем ли мы «сохранить» изменения за пределами исходной среды. Если вы запустите это, вы не увидите сохраненных изменений. В первом примере мы увеличиваем «i» до 6 - но когда мы снова запускаем пример, я снова равняюсь 5 - приращение не сохраняется.

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

Итак, вам нужно как-то заморозить во времени значение i в момент его поступления в массив

И это довольно сложно.

function fun1(){
    var result = [];
    for (var i = 0; i < 5; i++){
        result.push( eval("(function() { return " + i+";})"));
    }
    return result;
}

Где "eval" - это необычная функция javascript, которая создает код для запуска во время его запуска - я имею в виду, динамически. И я окружаю его круглыми скобками, потому что

Почему для eval в JavaScript нужны скобки для оценки данных JSON?

Я не знаю много о eval!

Далее, одно из других решений показывает это:

(function(x){
    result.push( function() {return x} );
})(i);

, который помещается в контекст цикла for - чтобы дать

function fun1(){
    var result = [];
    for (var i = 0; i < 5; i++){
        (function(x){
            result.push( function() {return x} );
        })(i);
    }
    return result;
}

Для этого создается новая среда, в которой существует замыкание. И в этой среде «x» всегда и навсегда фиксируется как значение, которое i в настоящее время (т.е. в точке создания экземпляра). Поэтому, когда наш массив пытается получить доступ к заданному замыканию, он существует в новой среде, где x фиксирован. Если бы мы сделали это вместо этого:

function fun1(){
    var result = [];
    for (var i = 0; i < 5; i++){
        (function(){
            result.push( function() {return i;} );
        })();
    }
    return result;
}

Тогда мы столкнулись с той же проблемой. «I» still существует в среде (или области, если хотите) цикла, поэтому он будет выбран, независимо от того, сколько раз вы вставляете его в функции.

Если кто-то читает это и думает, что я написал глупости, пожалуйста, дайте мне знать! Мне очень нравится слышать, как js работает с управлением памятью.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...