Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > JavaScript: Общие вопросы > Особенности Closure


Автор: Се ля ви 30.6.2008, 15:06
Не перестаю удивляться JavaScript`у. smile
Вот, тут заметил, что, с одной стороны, аргументы конструкторов автоматически попадают в категорию private-полей:
Код
function c1 (x){
    
    this.getArg = function(){
        return x;
    }    
}

//Test:
alert((new c1(5)).getArg());//5


С другой стороны, оно не просто является private-полем, но ещё и является неистребимым - попытка их удалить, что бы не таскать за собой, приводит к неудаче:
Код
function c1 (x){
    
    alert(delete x);//false!
    
    this.getArg = function(){
        return x;
    }
    

}

//Test:
alert((new c1(5)).getArg());//5


Так что на этот раз придётся Магомету идти к горе и обзывать входящие параметры конструктора так, как вы хотите назвать private-поля, ими инициализируемые. Это не есть интуитивно понятно, если не знать этой хитрости, но если её знать и грамотно использовать - это может заметно сэкономить объём вашего кода ;)

Автор: Се ля ви 30.6.2008, 15:36
Насчёт delete x проверил - вообще-то ни одно private-поле так не удалишь, так что нужно об этом помнить, когда его создаёшь. (Для освобождения памяти, который занимает объект, доступный по ссылке, можно присвоить ссылке значение null, но на самом деле это не равнозначно отсутствию ссылки - ссылка будет всё равно).

Но если вам на самом деле не нужен тот аргумент, который вы приняли в конструкторе, в последствии - то следует отказаться от именованных аргументов вообще и обращаться к ним по индексам "массива" arguments - это гарантирует удаление ссылок после выполнения конструктора.

Автор: Се ля ви 30.6.2008, 16:10
В общем, резюме такое: за конструкцией

Код
function f1(x){
    //...
}


на самом деле стоит следующее:
Код
function f2(){
    var x = arguments[0];

    //...
}

Автор: dsCode 30.6.2008, 19:17
Цитата(Се ля ви @  30.6.2008,  15:36 Найти цитируемый пост)
то следует отказаться от именованных аргументов вообще и обращаться к ним по индексам "массива" arguments - это гарантирует удаление ссылок после выполнения конструктора. 

неа, скоп внешней функции "оседает" в замыкании при создании замыкания (единственная разница (проблема), что вы не сможете различить arguments внешней функции по этому имени ("arguments"), т.к. объект с таким именем "не свободный" (т.е. будет найден сразу в скопе замыкания)):

Код

function a() {
  var args = arguments; // имя args только для того, чтобы различить arguments внутри b
  return function b() {
    alert([args[0], arguments[0]]);
  }
}

a(5)(6);


Цитата(Се ля ви @  30.6.2008,  15:06 Найти цитируемый пост)
С другой стороны, оно не просто является private-полем, но ещё и является неистребимым


любым var'ам ставится {DontDelete} (и нет никаких различий между "просто private-полями" и "неистребимыми" =)) объявите глобальную переменную var a = 10; - alert(window['a']); - она никакая не приватная, delete window['a'] - ничего не получится.

Цитата(Се ля ви @  30.6.2008,  16:10 Найти цитируемый пост)
В общем, резюме такое:


в общем, резюме опять лежит в http://javascript.ru/ecmascript/part10#a-10.1.3 и то, что вы написали var x = arguments[0]; от первого случая ничем не отличается, кроме дополнительной работы - и там, и там x станет свойством Variable Object'a (правда, объявление undefined'ых var'ов внутри функции никак не "перезатрет" формальный параметр):

Код

function a(x) {
  var x; // казалось бы, - должны перезаписать
  alert(x); // но нет, x - 5, а не undefined
}

a(5);


Но с другой стороны, объявление функции (которая тоже будет свойством Variable Object'a) имеет больший приоритет и перезаписывает var'ы:

Код

function a(x) {
  x(); // x - функция (хоть и объявлена ниже), а не 5
  function x() {
    alert('x');
  }
}
a(5);


Автор: Се ля ви 1.7.2008, 11:07
Цитата(dsCode @  30.6.2008,  19:17 Найти цитируемый пост)
скоп внешней функции "оседает" в замыкании при создании замыкания (единственная разница (проблема), что вы не сможете различить arguments внешней функции по этому имени ("arguments"), т.к. объект с таким именем "не свободный" (т.е. будет найден сразу в скопе замыкания)):
    
Код
function a() {
  var args = arguments; // имя args только для того, чтобы различить arguments внутри b
  return function b() {
    alert([args[0], arguments[0]]);
  }
}
a(5)(6);

Мда... А ты уверен, что arguments дочерней функции именно перекрывает, а не выталкивает arguments родительской? Ведь поведение этого фрагмента кода можно объяснить и по-другому - что ты оставляешь дополнительную ссылку на arguments родителя и в следствии этого она не забирается сборщиком мусора, а не было бы ссылки - может, собралась бы или была бы замещена новым arguments?..

Но если правда то, что ты пишешь, тогда, блин, я не знаю, что себе думали создатели спецификации... Получается, что использовать аргументы в конструкторах и в функциях, которые генерят другие функции - это таскать за собой эти аргументы хвостом в течении всего жизненного цикла этих объектов и этих генерируемых функций... А если они нафиг не нужны - их даже прямо удалить нельзя... :(((

Как-то это расточительно выглядит... :(

Получается, что единственный выход - обnullять вручную формальные параметры в тех случаях, когда они могут попасть в closure - что бы хоть ненужные объекты память не захламляли. При чём обnullять как по именам, так и в "массиве" arguments, что бы ссылок на эти объекты не оставалось. Тогда мы будем таскать за собой пустые ссылки, но хотя бы не будем таскать объекты...

Хотя выглядит это, конечно, не очч... :(((

Автор: dsCode 1.7.2008, 12:01
Цитата(Се ля ви @  1.7.2008,  11:07 Найти цитируемый пост)
А ты уверен

ага

Цитата(Се ля ви @  1.7.2008,  11:07 Найти цитируемый пост)
таскать за собой эти аргументы хвостом в течении всего жизненного цикла этих объектов и этих генерируемых функций

именно так работают замыкания; на момент использования внутренней функции, внешней уже может не быть, поэтому весь (!) скоп (набор variable object'ов (VO)) приплюсовывается к скопу замыкания (это и есть scope-chain, который хранится во внутреннем свойстве [[scope]])

Цитата(Се ля ви @  1.7.2008,  11:07 Найти цитируемый пост)
При чём обnullять как по именам, так и в "массиве" arguments

"именованные" и индексные взаимозаменяемы:

Код

(function a(x) {
  alert(x === arguments[0]); // true
  var x = 10;
  arguments[0] = 5;
  alert(x); // 5
})(5);


а вот, когда обnullять - до или после определения замыкания - не важно - переменная x (пример ниже) - "свободная" для VO функции b (т.е. ее нет в родном VO функции b), поэтому будет искаться по цепи скопов и найдется в VO функции a (которой уже может не быть); мы обnullяем свойство x VO функции a, поэтому alert выдает null, а не 5:

Код

(function a(x) {
  function b() {
    alert(x);
  }
  x = null;
  return b;
})(5)();

Автор: Се ля ви 2.7.2008, 10:38
Цитата(dsCode @  1.7.2008,  12:01 Найти цитируемый пост)
"именованные" и индексные взаимозаменяемы

Тогда для предотвращения утечек памяти можно просто в конце каждой функции, где может произойти "короткое замыкание" ;) вставлять примерно следующее:

Код
if (arguments.length)
    for (arguments[0] = arguments.length; arguments[0];)
        arguments[--arguments[0]] = null;


P.S. Кстати, обнаружил, что var`ы - чрезвычайно живучи. До этого думал, что они по аналогии с Java - умирают когда выходишь за пределы любого блока, в котором они объявлены, но оказывается - нифига:
Код
if (true) {
        var y = 5;
    }
    
    alert(y);//5

Так что это верно только для функции в целом, но не для её под-блоков. Поэтому я в качестве счётчика в предыдущем примере использовал "подручный материал", а не стал заводить новую переменную, как я это обычно делаю в той же Java.

Автор: dsCode 2.7.2008, 11:54
Цитата(Се ля ви @  2.7.2008,  10:38 Найти цитируемый пост)
но оказывается - нифига:

конечно, нифига =) но, в http://developer.mozilla.org/en/docs/New_in_JavaScript_1.7#Block_scope_with_let алтернативой var'ам (чей локальный скоп - функция), появились let'ы, которые создают локальный скоп в нужном месте (их как раз  можно использовать в циклах)

Автор: Се ля ви 2.7.2008, 12:55
Цитата(dsCode @  2.7.2008,  11:54 Найти цитируемый пост)
в версии 1.7 алтернативой var'ам (чей локальный скоп - функция), появились let'ы, которые создают локальный скоп в нужном месте (их как раз  можно использовать в циклах)

Здорово! Жаль только, что, http://en.wikipedia.org/wiki/JavaScript#Versions, поддерживается всё, что дальше JS 1.5 - только в Gecko и Safari... :(((

Автор: Ghirik 5.7.2008, 06:15
Цитата

но оказывается - нифига


В JavaScript-е var для того и есть чтобы задать живучесть объявляемуму элементу. И видимо, по задумке сочинителей, он должен поддерживать контекст в котором задается, но соблюсти это правило удается не всем создателям интерпретаторов. Это в большей мере касается вложенных функций и головоломок, вроде наследования свойств, объявленных не понятно под каким соусом. Вообще, манера программирования в древовидном стиле, меня почему то бесит, что то есть в этом нездоровое. Зачем? Потом не можем разобраться кто и где родился, и сколько проживет. Не проще ли просто избегать таких ситуаций?

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

Цитата

Жаль только, что, согласно WikiPedia`и, поддерживается всё, что дальше JS 1.5 - только в Gecko и Safari... 


Ну да, давайте ещё леты поддержим во всех браузерах!
Для какой такой экстренной помощи потребовалось?

Блочность данных и так можно спокойно обеспечивать и за ненадобностью удалять, всё вместе со всеми объявлениями. Если создавать все элементы навешивая их на любой действующий объект дерева, то при удалении самого объекта память высвобождается в любом браузере сразу. Там уже действуют обычные DOM-правила. Такой подход очень удобен при работе с html-элементами. Главное, ни var ни let вообще не нужны. Создавайте любой массив свойством к имеющемуся в дереве объекту. Тысячу массивов... Хакните элемент, - и массивы хакнутся, и память очистится. А передавать параметры обработчикам вообще не потребуется, вы их до того повесите на этот элемент свойствами. Мне нравится такой подход.



Автор: dsCode 8.7.2008, 21:17
Цитата(Ghirik @  5.7.2008,  06:15 Найти цитируемый пост)
Мне нравится такой подход.

Хороший подход, только обращение к свойствам DOM-объектам - медленнее, чем к переменным JS. Простой пример: при очень больших циклах целесообразно (для оптимизации скорости) вынести свойство length в переменную (т.к. оно вычисляется на каждой итерации цикла):

Код

for (var k = 0, length = объектDOM.length; k < length; k++) {
 ...
}


А теперь, возвращаясь к теме "неистребимых" var'ов (чтобы более точно сказать о них) - в 10.2.2 Eval Code ничего не сказано про {DontDelete}, поэтому var'ы, полученные через eval, можно удалить.

Код

(function () {
  eval('var a = 10;');
  alert(a);
  delete a;
  alert(a);
})();


Автор: Се ля ви 22.7.2008, 16:27
А ещё можно let`ы эмулировать так:
Код

...
(function(){
    var x;
    x = 5;
...
})();
...

smile

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)