Компания
1 039,91
рейтинг
16 августа 2014 в 20:08

Разработка → 10 самых распространённых ошибок при программировании на JavaScript перевод



Сегодня JavaScript лежит в основе большинства современных веб-приложений. При этом за последние годы появилось большое количество JavaScript-библиотек и фреймворков для разработчиков Single Page Application (SPA), графики, анимации и даже серверных платформ. Для веб-разработки JavaScript используется повсеместно, и поэтому качество кода обретает всё большее значение.

На первый взгляд, этот язык может показаться довольно простым. Встраивание в веб-страницу базового функционала JavaScript — это не проблема для любого опытного разработчика, даже если он ранее не сталкивался с этим языком. Однако это обманчивое впечатление, поскольку JavaScript гораздо сложнее, мощнее и чувствительнее к нюансам, чем кажется поначалу. Немало тонкостей в этом языке приводит к большому количеству распространённых ошибок. Сегодня мы рассмотрим некоторые из них. На эти ошибки нужно обратить особое внимание, если вы хотите отлично программировать на JavaScript.

1. Неправильные ссылки на this

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

Например, выполнение этого кода:
Game.prototype.restart = function () {
    this.clearLocalStorage();
    this.timer = setTimeout(function() {
        this.clearBoard();    // что здесь "this"?
    }, 0);
};

Приводит к ошибке:
Uncaught TypeError: undefined is not a function

Почему это происходит? Всё дело в контексте. Когда вы вызываете setTimeout(), то на самом деле вызываете window.setTimeout(). В результате, анонимная функция, передаваемая в setTimeout(), определяется в контексте объекта window, который не имеет метода clearBoard().

Традиционное решение, совместимое со старыми браузерами, предполагает простое сохранение ссылки на this в переменной, которая может быть сохранена в замыкании:
Game.prototype.restart = function () {
    this.clearLocalStorage();
    var self = this;   // сохраним ссылку на 'this', пока это все еще 'this'!
    this.timer = setTimeout(function(){
        self.clearBoard();    // все в порядке
    }, 0);
};

Для новых браузеров можно использовать метод bind(), позволяющий связать функцию с контекстом исполнения:
Game.prototype.restart = function () {
    this.clearLocalStorage();
    this.timer = setTimeout(this.reset.bind(this), 0);  // связываем 'this'
};

Game.prototype.reset = function(){
    this.clearBoard();    // возвращаемся в контекст правильного 'this'!
};

2. Область видимости на уровне блоков

Разработчики часто считают, что JavaScript создаёт новую область видимости для каждого блока кода. Хоть это и справедливо для многих других языков, но в JavaScript этого не происходит. Посмотрим на этот код:
for (var i = 0; i < 10; i++) {
    /* ... */
}
console.log(i);  // что здесь выведется?

Если вы думаете, что вызов console.log() повлечёт за собой вывод undefined или ошибку, то вы ошибаетесь: будет выведено «10». Почему? В большинстве других языков этот код привёл бы к появлению ошибки, потому что область видимости переменной i была бы ограничена блоком for. Однако в JavaScript эта переменная остаётся в области видимости даже после завершения цикла for, сохраняя своё последнее значение (такое поведение известно как «var hoisting»). Надо заметить, что поддержка области видимости на уровне блоков введена в JavaScript начиная с версии 1.7 с помощью дескриптора let.

3. Утечки памяти

Утечки памяти практически неизбежны, если во время работы вы не будете их сознательно избегать. Существует немало причин для появления утечек, но мы остановимся лишь на самых частых.

Ссылки на несуществующие объекты. Проанализируем этот код:
var theThing = null;
var replaceThing = function () {
    var priorThing = theThing;
    var unused = function () {
        // 'unused' - единственное место, где используется 'priorThing',
        // но 'unused' никогда не вызывается
        if (priorThing) {
            console.log("hi");
        }
    };
    theThing = {
        longStr: new Array(1000000).join('*'),  // создаем 1Mб объект
        someMethod: function () {
            console.log(someMessage);
        }
    };
};
setInterval(replaceThing, 1000);    // вызываем 'replaceThing' каждую секунду

Если запустить выполнение этого кода, то можно обнаружить массивную утечку памяти со скоростью около мегабайта в секунду. Создается впечатление, что мы теряем память выделенную под longStr при каждом вызове replaceThing. В чём причина?

Каждый объект theThing содержит свой собственный объект longStr размером 1Мб. Каждую секунду при вызове replaceThing, функция сохраняет ссылку на предыдущий объект theThing в переменной priorThing. Это не проблема, ведь каждый раз предыдущая ссылка priorThing будет перетерта (priorThing = theThing;). Так в чём же причина утечки?

Типичный способ реализации замыкания — это создание связи между каждым объектом-функцией и объектом-словарем, представляющим собой лексическую область видимости для этой функции. Если обе функции (unused и someMethod), определенные внутри replaceThing, реально используют priorThing, то важно понимать, что они получают один и тот же объект, даже если priorThing переписывается снова и снова, так как обе функции используют одну и ту же лексическую область видимости. И как только переменная используется в любом из замыканий, то она попадает в лексическую область видимости, используемую всеми замыканиями в этой области видимости. И этот маленький нюанс приводит к мощной утечке памяти.

Циклические ссылки. Рассмотрим пример кода:
function addClickHandler(element) {
    element.click = function onClick(e) {
        alert("Clicked the " + element.nodeName)
    }
}

Здесь onClick имеет замыкание, в котором сохраняется ссылка на element. Назначив onClick в качестве обработчика события click для element, мы создали циклическую ссылку: element -> onClick -> element -> onClick -> element

Даже если удалить element из DOM, то циклическая ссылка скроет element и onClick от сборщика мусора и произойдет утечка памяти. Как лучше всего избегать возникновения утечек? Управление памятью в JavaScript (и в частности сборка мусора) в значительной степени основано на понятии достижимости объекта. Следующие объекты считаются достижимыми и известны как корневые:
  • ссылки на которые содержатся в стеке вызова (все локальные переменные и параметры функций, которые в настоящий момент вызываются, а также все переменные в области видимости замыкания);
  • все глобальные переменные.

Объекты сохраняется в памяти лишь до тех пор, пока доступны из корневых по ссылке или цепочке ссылок.

В браузерах встроен сборщик мусора, который очищает память от недостижимых объектов. То есть объект будет удалён из памяти только если сборщик мусора решит, что он недостижим. К сожалению, довольно легко могут накопиться неиспользуемые большие объекты, которые считаются «достижимыми».

4. Непонимание равенства

Одним из преимуществ JavaScript является то, что он автоматически преобразует любое значение к булевому значению, если оно используется в булевом контексте. Однако бывают случаи, когда это удобство может ввести в заблуждение:
// Все эти сравнения выдадут 'true'!
console.log(false == '0');
console.log(null == undefined);
console.log(" \t\r\n" == 0);
console.log('' == 0);

// И эти тоже!
if ({}) // ...
if ([]) // ...

С учётом последних двух строк, даже будучи пустыми, {} и [] фактически являются объектами. А любой объект в JavaScript соответствует булевому значению true. Однако многие разработчики считают, что значение будет false.

Как показывают два приведённых примера, автоматическое преобразование типа иногда может мешать. Как правило лучше использовать === и !== вместо == и !=, чтобы избежать побочных эффектов преобразования типов.

Кстати, сравнение NaN с чем-либо (даже с NaN!) всегда даст результат false. Таким образом, нельзя использовать операторы равенства (==, ===, !=, !==) для определения соответствия значения NaN. Вместо этого нужно использовать встроенную глобальную функцию isNaN():
console.log(NaN == NaN);    // false
console.log(NaN === NaN);   // false
console.log(isNaN(NaN));    // true

5. Нерациональное использование DOM

В JavaScript можно легко работать с DOM (в том числе добавлять, изменять и удалять элементы), но часто разработчики делают это неэффективно. Например, добавляют серии элементов по одному за раз. Однако операция добавления элементов весьма затратна, и последовательного её выполнения нужно избегать.

Если нужно добавить несколько элементов, то, в качестве альтернативы, можно использовать фрагменты документа:
var div = document.getElementsByTagName("my_div");

var fragment = document.createDocumentFragment();

for (var e = 0; e < elems.length; e++) {
    fragment.appendChild(elems[e]);
}
div.appendChild(fragment.cloneNode(true));

Также рекомендуем сначала создавать и модифицировать элементы, а потом уже добавлять в DOM, это также существенно повышает производительность.

6. Некорректное использование определений функций внутри циклов for

Рассмотрим пример кода:
var elements = document.getElementsByTagName('input');
var n = elements.length;    // предположим, у нас есть 10 элементов
for (var i = 0; i < n; i++) {
    elements[i].onclick = function() {
        console.log("This is element #" + i);
    };
}

При клике на любом из 10 элементов появлялось бы сообщение «This is element #10». Причина в том, что к тому времени, когда onclick вызывается любым из элементов, вышестоящий цикл for будет завершён, а значение i будет равно 10.

Пример правильного кода:
var elements = document.getElementsByTagName('input');
var n = elements.length;    // предположим, у нас есть 10 элементов
var makeHandler = function(num) {  // внешняя функция
     return function() {   // внутренняя функция
         console.log("This is element #" + num);
     };
};
for (var i = 0; i < n; i++) {
    elements[i].onclick = makeHandler(i+1);
}

makeHandler немедленно запускается на каждой итерации цикла, получает текущее значение i+1 и сохраняет его в переменной num. Внешняя функция возвращает внутреннюю функцию (которая также использует переменную num) и устанавливает ее в качестве обработчика onclick. Это позволяет гарантировать, что каждый onclick получает и использует правильное значение i.

7. Неправильное наследование через прототипы

Удивительно много разработчиков не имеют ясного понимания механизма наследования через прототипы. Рассмотрим пример кода:
BaseObject = function(name) {
    if(typeof name !== "undefined") {
        this.name = name;
    } else {
        this.name = 'default'
    }
};

var firstObj = new BaseObject();
var secondObj = new BaseObject('unique');

console.log(firstObj.name);  // -> в 'default'
console.log(secondObj.name); // -> в 'unique'

Но если бы мы написали так:
delete secondObj.name;

то получили бы:
console.log(secondObj.name); // -> в 'undefined'

Но не лучше ли вернуть значение к default? Это можно легко сделать, если применить наследование через прототипы:
BaseObject = function (name) {
    if(typeof name !== "undefined") {
        this.name = name;
    }
};

BaseObject.prototype.name = 'default';

Каждый экземпляр BaseObject наследует свойство name своего прототипа, в котором ему присвоено значение default. Таким образом, если конструктор вызван без name, свойство name по умолчанию будет default. И точно так же, если свойство name будет удалено из экземпляра BaseObject, будет произведен поиск по цепочке прототипов и свойство name будет получено из объекта prototype, в котором оно по-прежнему равно default:
var thirdObj = new BaseObject('unique');
console.log(thirdObj.name);  // -> в 'unique'

delete thirdObj.name;
console.log(thirdObj.name);  // -> в 'default'

8. Создание неправильных ссылок на методы экземпляров

Определим простой конструктор и с помощью него создадим объект:
var MyObject = function() {}

MyObject.prototype.whoAmI = function() {
    console.log(this === window ? "window" : "MyObj");
};

var obj = new MyObject();

Для удобства, создадим ссылку на метод whoAmI:
var whoAmI = obj.whoAmI;

Выведем значение нашей новой переменной whoAmI:
console.log(whoAmI);

В консоли будет выведено:
function () {
    console.log(this === window ? "window" : "MyObj");
}

А теперь обратите внимание на разницу при вызовах obj.whoAmI() и whoAmI():
obj.whoAmI();  // выведет "MyObj" (как и ожидалось)
whoAmI();      // выведет "window"

Что пошло не так? Когда мы присвоили var whoAmI = obj.whoAmI;, новая переменная была определена в глобальном пространстве имён. В результате значение this оказалось равным window, а не obj, экземпляру MyObject. Таким образом, если нам действительно нужно создать ссылку на существующий метод объекта, необходимо сделать это в пределах пространства имён этого объекта. Например:
var MyObject = function() {}

MyObject.prototype.whoAmI = function() {
    console.log(this === window ? "window" : "MyObj");
};

var obj = new MyObject();
obj.w = obj.whoAmI;   // в пространстве имен объекта

obj.whoAmI();  // выведет "MyObj" (как и ожидалось)
obj.w();       // выведет "MyObj" (как и ожидалось)

9. Использование строки в качестве первого аргумента в setTimeout или setInterval

Само по себе это не является ошибкой. И дело тут не только в производительности. Дело в том, что когда вы передаете строковую переменную первым аргументом в setTimeout или setInterval, она будет передана конструктору Function для преобразования в новую функцию. Этот процесс может быть медленным и неэффективным. Альтернативой является использование функции в качестве первого аргумента:
setInterval(logTime, 1000);   // передаем функцию logTime в setInterval

setTimeout(function() {       // передаем анонимную функцию в setTimeout
    logMessage(msgValue);     // (msgValue здесь всё ещё доступна)
}, 1000);

10. Отказ от использования «strict mode»

Это режим, в котором накладывается ряд ограничений на исполняемый код, что повышает безопасность и может предотвратить появление некоторых ошибок. Конечно, отказ от использования «строгого режима» не является ошибкой как таковой. Просто в этом случае вы лишаете себя ряда преимуществ:

  • Облегчение процесса отладки. Ошибки в коде, которые были бы проигнорированы или не замечены, приведут к появлению предупреждений и генерации исключений, которые быстрее приведут вас к источнику проблемы.
  • Предотвращение случайного появления глобальных переменных. Присвоение значения необъявленной переменной автоматически создаёт глобальную переменную с таким именем. Это одна из наиболее распространённых ошибок в JavaScript. В «строгом режиме» это приведёт к появлению сообщения об ошибке.
  • Запрет на дублирование названий свойств или значений параметров. Если при включённом «строгом режиме» у объекта обнаруживается дублирование названий свойств (например, var object = {foo: "bar", foo: "baz"};) или названий аргументов у функции, то будет выведено сообщение об ошибке. Это позволяет быстро обнаружить и устранить баг.
  • Уменьшение потенциальной опасности eval(). В «строгом режиме» переменные и функции, объявленные внутри eval(), не создаются в текущей области видимости.
  • Получение сообщения об ошибке при ошибочном использовании оператора delete. Этот оператор не может быть применён к свойствам объекта, у которых флаг configurable равен false, и при попытке это сделать будет выведено сообщение об ошибке.

В завершение

Чем лучше понимаешь, как и почему работает JavaScript, тем более надёжным будет код, тем эффективнее можно использовать возможности этого языка. И наоборот, недопонимание заложенных в JavaScript парадигм становится причиной большого количества багов в программных продуктах.

Поэтому изучение нюансов и тонкостей языка является наиболее эффективной стратегией повышения своего профессионализма и продуктивности, а также поможет избежать многих распространённых ошибок при написании JavaScript-кода.
Автор: @ZaValera Ryan J. Peterson

Комментарии (126)

  • –39
    а ведь мне нравился JS, раньше, полчаса назад, когда я ещё не прочитал эту статью
    • +39
      Это у вас отношения на расстоянии что ли — ничего про язык не знали, он вам абстрактно нравился?
  • +20
    Эти ошибки совершают разве что только джуниоры.
    • +8
      Их много, а поэтому, в общей массе, эти 10 ошибок как раз самые распространённые. Как-то так.
    • +23
      Третий пункт (про утечки памяти) неочевиден и мало где хорошо описан. В остальном — да, хоть и strict mode далеко не все используют.
      • +10
        И даже в этом примере (пункт 3) не все так однозначно с утечками памяти, оптимизаторы могут убрать из замыкания переменную если она не используется в нем, и тогда утечка памяти не возникнет. В этом случае
        var theThing = null;
        var replaceThing = function () {
            var priorThing = theThing;  // hold on to the prior thing
            theThing = {
                longStr: new Array(1000000).join('*'),  // создаем 1Mб объект
                someMethod: function() {console.log('xxx');}
            };
        };
        setInterval(replaceThing, 1000);
        

        оптимизатор исключит из замыкания priorThing, и утечки не будет

        а вот в этом случае

        var theThing = null;
        var replaceThing = function () {
            var priorThing = theThing;  // hold on to the prior thing
            theThing = {
                longStr: new Array(1000000).join('*'),  // создаем 1Mб объект
                someMethod: function() {console.log(eval(''));}
            };
        };
        setInterval(replaceThing, 1000);
        

        возникает eval(), который понижает уровень оптимизации, так что и для не джуниоров это не всегда очевидно, поскольку действия оптимизаторов не стандартизированы, т.е. их работа остается за кадром
        • –1
          var theThing = null;
          var replaceThing = function () {
              var priorThing = theThing;  // hold on to the prior thing
              theThing = {
                  longStr: new Array(1000000).join('*'),  // создаем 1Mб объект
                  someMethod: function() {console.log('xxx');}
              };
          };
          setInterval(replaceThing, 1000);
          


          то что Вы описали не есть работа оптимизатора, просто так работает сборщик мусора.
          в данном примере после отработки функции replaceThing, не остается ни одной «живой» области видимости с записанной переменной priorThing, поэтому сборщик и очищает память.

          другими словами после отработки функции сборщик мусора пытается очистить память и руководствуется только одним — нужна ли эта переменная еще кому-то?
          • +3
            Согласно источнику, мною очень уважаемому при создании функции, в моем примере someMethod:function() {...} внутреннему свойству функции [[Scope]] будет присвоено значение текущего узла цепочки областей видимости в этом узле согласно стандарту уже содержится ссылка на priorThing, далее это ([[Scope]]) свойство уже не меняется никогда. А теперь цитата из источника
            Как уже было сказано выше, в целях оптимизации, когда функция не использует свободные переменные, реализации могут не запоминать лексический контекст, однако в спецификации ECMA-262-3 об этом ничего не сказано; поэтому формально (и по технического алгоритму) – все функции запоминают [[Scope]] ещё при создании.

            Таким образом когда вызывается функция replaceThing, в этот момент начинается разбор и интерпретация тела этой функции, будет создан theThing, его свойству someMethod будет присвоена ссылка на функцию-выражение, свойство [[Scope]] которой будет указывать на объект переменных (VO) вышестоящего контекста, которым является функция replaceThing. И в этом объекте согласно стандарту должна быть priorThing. И если ее там не будет, то только в процессе создания функции replaceThing оптимизатор проанализировав ее тело может принять решение о том включать ее в VO или нет. Т.е. еще до исполнения (активизации) функции replaceThing определяется судьба priorThing. А сборщик мусора начинает свою работу на много позже, и если оптимизатор не исключит из VO priorThing, то мы всегда сможем добраться из глобальной переменной theThing до всех созданных в предыдущих вызовах объектов, а в следствии чего сборщик не сможет удалить эти объекты.
            • 0
              дополню, когда оптимизатор встречает в теле функции eval, то начинает судорожно биться об стенку, ведь он не знает, что там сделает этот черт, и тогда как правило принимает решение в пользу темных сил, и в объект переменных записывает все без разбора
    • +8
      А вот мне интересно, если так много плюсующих ваше утверждение людей, будьте добры напишите мне кто-нибудь, пожалуйста, хоть один пример того, в чем может ошибаться профи, но или люди, которых вы считаете не джуниорами, с точки зрения знания языка, повторяю только с точки зрения языковых конструкций, а не того что можно сделать с помощью этого языка. Если вы считаете, что все остальные, кто не джуниоры понимают и знают все нюансы языка, то тогда можете и не писать, но мне кажется знать каждый все нюансы не может. А если мне это правильно кажется, то еще раз, хоть один пример, того что может не знать не джуниор, напишите пожалуйста.
      • –5
        В Firefox/Spidermonkey:
        [] + [] # ''
        [] + {} # '[object Object]'
        {} + [] # 0
        {} + {} # NaN
        • +1
          Сколько будет «5 воробьёв + 12 яблок». На это вопрос я бы ответил — «Ты что, идиот?». А на вопрос «12 яблок + 5 воробьёв» я бы ответил: «отвали от меня, задрал со своими тупыми вопросами».
          • –4
            Вот языки, которые так себя ведут (даже «великий и ужасный» C, не говоря уже о всяких python'ах) я уважаю. А JavaScript, который «тихо» тебе «поможет» и в ответ на такие вопросы вернёт-таки какую-нибудь чушь — нет. Из распространённых есть только два языка подобной степени убогости: JavaScript и PHP. Но если PHP можно спокойно себе выкинуть на помойку и перейти на что-то поприличнее, то с JavaScript'ом этот номер не пройдёт.
            • +4
              За все… ну тысяч 10-15 строк на JS я точно написал.
              Так вот, за все эти 10-15 тысяч строк мне ни разу не пришлось складывать массивы или объекты с другими массивами или объектами)
              • +3
                Написал более 50 тысяч строк кода на JS точно и ни разу не приходило на ум складывать объект с массивом. Очевидно же, что получится хрень какая-то. Полностью разделяю мнение TheShock по этому вопросу (выше в этой ветке).
                А результат такой потому, что сам виноват :)
      • +10
        «Неджуниор» такие ошибки не совершит исключительно исходя из собственного опыта: даже за 2-3 года программирования на яваскрипте все эти неочевидные тонкости с замыканиями, ссылками, приведением типов и областями видимости встретятся не раз и их решения доведутся до автоматизма практикой. Перефразирую автора исходного комментария: «Такие ошибки могут допустить только люди без практического опыта программирования на яваскрипте» (что, в общем-то, и тождественно «джуниорам»).
        Позволю себе небольшую аналогию: представьте, что у вас есть права категории «С» (грузовая категория), которые вы получили десять лет. То есть формально вы умеете управлять грузовиком, но когда вы сядете в салон, начнётся мучительное зачитывание некоего чеклиста: «так, снять с ручника, завести, прогреть, проверить давление в тормозном контуре ». И теперь представьте, что в этот же грузовик садится водитель с реальным стажем в 10 лет вождения именно этого грузовика. Все те действия, что вы пытались запомнить и осмыслить, он сделает на автомате только потому, что у него много практики.
        • +2
          Ну вот видите, и даже вы не привели пример того вопроса, на который может не ответить и «неджуниор», тогда утверждение «Эти ошибки совершают разве что только джуниоры» неконструктивное, в общем то пустое, его тогда можно интепретировать так, если вы что-то не знаете, то вы джуниор, а это не всегда так.
          • 0
            Ошибки описанные в статье совершает человек, не изучивший javascript, т.е. буквально не прочитал как язык должен работать и занимается т.н. «Voodoo programming», а не созданием инженерных решений.
            Все ошибки в статье идут от реального непонимания того что происходит, а не просто вещи о которых все знают, но иногда ошибаются.
            Я приведу пример что же реально случается на проектах, вызывая путаницу:
            — потеря запятых (где угодно, хоть в объявлении переменных, периодически выкидывая переменные в глобальный скоуп,
            — постоянно забывают какие методы изменяют объект, а какие возвращают новый объект, сохраняя старый (путаница вообще связанная с мутабельностью данных)
            — события в DOM поднимаются и иногда в некоторых ситуациях мы ловим уже отработанные и отрабатываем еще раз, случайно, конечно
            — вытаскивая value из элементов DOM получаем строки, а не то что положили
            — «плавающий набор параметров». иногда мы думаем что сможем всегда контроллировать входящие переменные в функцию и позволять себе догадываться из типов переменных о структуре аргументов. почти всегда это провал, тем более когда речь идет о статической проверке кода.
            Если что-нибудь еще вспомню — обязательно напишу. А статья бесполезная — куда умнее будет мельком пролистать спеку языка.
    • +3
      Джуниоры чаще всего только что прочитали Флэннагана и помнят об этих ошибках. Своими глазами видел, как senior создал переменную без var. А 'use strict' вообще мало кто использует, к сожалению.
      • 0
        А как же JSHint/JSLint? Неужели, их никто из вашего окружения не использует? Они же сразу потребуют от программиста поставить 'use strict'.
    • +5
      По моему глупая уверенность. Абсолютно любой разработчик может ошибиться, просто джуниоры это делают чаще сеньеров.
  • –34
    Зачем использовать чистый javascript? Сравните, ваш код на Javascript, вариант №1
    Game.prototype.restart = function () {
    	this.clearLocalStorage();
    	var self = this;   // сохраним ссылку на 'this', пока это все еще 'this'!
    	this.timer = setTimeout(function(){
    		self.clearBoard();    // все в порядке
    	}, 0);
    };


    Вариант №2:
    Game.prototype.restart = function () {
    	this.clearLocalStorage();
    	this.timer = setTimeout((function(){
    		this.clearBoard();    // все в порядке
    	}).bind(this), 0);
    };


    И теперь почему бы не посмотреть в сторону CoffeeScript, исполненного ruby-плюшек?
    Game::restart = ->
    	@clearLocalStorage()
    	@timer = setTimeout ( => @clearBoard() ), 0


    Или еще более крутого LiveScript, движимого мощью Haskell? (это незаметно на указанном участке кода)
    Game::restart = ->
    	@clearLocalStorage!
    	@timer = setTimeout ( ~> @clearBoard! ), 0

    • +32
      Непонимание глубинных процессов не нивелируешь высокоуровневыми фреймворками или диалектами. Тот, кто не знает этих основ, не нуждается в кофе, тот, кто пользуется кофе, скорее всего и так знает эти десять пунктов.
      В любом случае, подход «если я не понимаю какой-то процесс, то я его прикрою абстракцией сверху» очень опасен, хотя сразу это и не очень очевидно.
      • –10
        Дело не в сокрытии абстракций, а в уменьшении монструозности выражения существующих. Например, ни CS, ни LS не решают за программиста, биндить ли функцию к текущему this или нет — это решает программист, но насколько компактнее же это выглядит!

        Вот еще пример из статьи:
        var elements = document.getElementsByTagName('input');
        var n = elements.length;    // предположим, у нас есть 10 элементов
        var makeHandler = function(num) {  // внешняя функция
            return function() {   // внутренняя функция
                console.log("This is element #" + num);
            };
        };
        for (var i = 0; i < n; i++) {
            elements[i].onclick = makeHandler(i+1);
        }

        И его воплощение в CoffeeScript:
        for el, i in document.getElementsByTagName 'input'
            do (i) ->
                el.onclick = -> console.log "This is element ##{num}"

        Никакие абстракции при этом не скрыты, но и код не превратился в %writeonly_programming_language%. Так почему же не сделать себе проще жизнь? :)
        • +24
          Каким бы сахаром вы не посыпали ваш код, это не спасет от ошибок, вызванных непониманием базовых основ JavaScript.
          • +2
            Да где же я писал, что спасет, или что понимание базовых основ JavaScript не нужно? Или, может, где-то приведенный мною пример скрывает какие-то детали реализации, и можно, не зная этого, сделать какую-то фатальную ошибку, или написать программу, которая выглядит правильно, но не работает из-за того, что автор не учел какие-то JS-специфичные тонкости?

            Я лишь о том, чтобы делать точно то же самое, что и просто в JS, но при этом создавать более легко читаемый, а, значит, и более легко сопровождаемый код. Но нет, меня уже все записали в апологеты построения строк путем дописывания символов по одному, и радуются тому, какую я чушь написал.
            • +11
              Как ваш первый комментарий вообще относится к данной статье?
              На вопрос «зачем» я могу легко ответить «затем». К примеру есть некоторая группа разработок, не позволяющая использовать фреймворки.
        • +9
          Сделать циклическую ссылку с последующей утечкой памяти можно и на кофескрипте, ума много не нужно. Только без знания нативного яваскрипта решение этой ситуации может стать серьёзной проблемой, потому что нужно как минимум понять причину. Ситуаций, когда при использовании абстракций приходится спускаться на уровень ниже, может быть масса. И это касается не только яваскрипта.
          Впрочем, я не хочу участвовать в очередном унылом холиваре про препроцессоры, извините. Нравится этим пользоваться — пользуйтесь, бога ради. Только не нужно это советовать как универсальный способ решения проблем, вызванных недостаточным уровнем знаний яваскрипта и его рантайма (а пост ведь об этом).
        • –1
          В том же coffee надо хорошо понимать, во что будет скомпилирован код. Если код вашего же примера обернуть в функцию, мы получим неявный return коллекции.
        • 0
          Поверьте, проще на ванили и когда все прозрачно. Я куда быстрее найду ошибку или пойму что вообще происходит. Плюс ко всему неизвестен результат компиляции, может компилятор использует такой кошмар, что уж лучше без него.
          Это мне напоминает тех, кто удивляется тому, что можно без jQuery и оно не фрэймворк.
    • +5
      TypeScrtipt — тоже ничо так.
    • 0
      Game.prototype.restart = function () {
          this.clearLocalStorage();
          this.timer = setTimeout((function(){
              this.clearBoard();    // все в порядке
          }).bind(this), 0);
      };
      


      Самое главное в этом примере узнать как работает
      (function(){}).bind()
      
      и по чему именно так.
    • –1
      Ваш коммент вообще не в тему
  • 0
    Про память есть интересная библиотека node-memwatch, которая облегчает слежку за памятью в модулях для ноды.
    • НЛО прилетело и опубликовало эту надпись здесь
  • +4
    По поводу пункта №8 лучше использовать метод bind,
    и вместо
    var whoAmI = obj.whoAmI;
    

    написать
    var whoAmI  = obj.whoAmI.bind(obj);
    

    Для любителей jQuery, есть многофункциональный метод $.proxy.

    А то что указано как «решение» в статье, выглядит, имхо, немного странно.
    • +1
      Пример и вправду притянут, но сама ошибка типична и по сути та же, что и первая рассмотренная в статье. Новичкам, часто сложно понять, что this определяется в момент вызова функции. И одна и та же функция может отработать по разному, из-за разных контекстов.
  • +6
    Все примеры здесь в основном сложные, точнее, требующие продвинутых знаний о JS, которых у большинства или нет, или они неточные и их стараются избегать или тщательно отлаживать. Ошибки же чаще совершаются в простых вещах, единственный представитель которых — п.4 (приведение типов). Поэтому больше соответствовали бы названию статьи ошибки в приведении типов или по необычному поведению примитивов JS. Много такого описано, например, в переводной статье "20 и 1 примочка Javascript, которые я никак не могу запомнить". А к сложным вещам по мере изучения, программист подходит более ответственно.

    Первое, что вспоминается из простого:

    4.а. '-1' < '-10' //true
    

    Ошибки — чаще в нестандартной простоте.

    Или

    077 + 3 // 66.
    1+'1' // 11
    

    Когда это вуалируется в переменных — раздолье для ошибок.

    Или
    
    var о = 2; // русская буква
    console.log(o); //exception на латинской, и это ещё хорошо, когда сразу exception...
    

    Вот это — частые ошибки, а не с наследованием или неправильным его пониманием.
    • +1
      Примеры несколько притянуты за уши. В случае '-1' < '-10' скорее будут сравнивать с числом, и тут приведение типов сработает верно.

      Никто не пишет в коде:

      > 077 + 3 // 66.

      Скорее будет так:

      > parseInt('077') + 3

      и тут, внезапно, всё правильно — ответ 80, так как в ECMAScript 5 убрали никому не нужное автоматическое восьмеричное исчисление в parseInt.
  • –5
    что-то не могу припомнить ЯП который в блоке for/foreach создает изолированную область видимости (пункт 2 в посте)…
    • +10
      C++?
      #include <cstdio>
      
      int main(int argc, char *argv[]) {
      	int i = 1000000;
      	
      	printf("%d\n", i); /* 1000000 */
      	
      	for (int i = 0; i < 10; i++) {
      		printf("%d ", i); /* 0 1 2 3 4 5 6 7 8 9 */
      	}
      	
      	printf("\n%d\n", i); /* 1000000 */
      	
      	return 0;
      }
      • 0
        точно :)
    • 0
      pascal, modula II.
      про oberon не вспомню, правда

      а не, вру, в P и M2 надо сперва переменную раньше создать.
    • 0
      Почему-то думал C# и Java так же себя ведут, как C++, я ошибался. Ну хоть Lua подходит:
      local i = 1000
      io.write(i, "\n") -- 1000
      for i = 0, 9 do
          io.write(i, " ") -- 0 1 2 3 4 5 6 7 8 9
      end
      io.write("\n", i) -- 1000
      
      • 0
        Они ведут себя похожим образом, просто не позволяют объявить в одной функции две одноименные переменные с перекрывающимися областями видимости. Но это ограничение не отменяет того факта, что объявленная в цикле переменная не будет видна за пределами цикла.
    • +1
      Perl
      • –3
        нет, перл не подходит

        #!/usr/bin/perl
        
        $i = 10;
        for ($i = 0; $i <=5; $i++) { print "\n", $i; }
        print "\n", $i, "\n";
        

        результат
        0 1 2 3 4 5 6
        но можно сделать
        for (my $i = 0; $i < 10; $i++) {}
        
        • +2
          Ну так, my и надо делать, иначе ты будешь использовать переменную из контекста выше.
          В Javascript'е же даже var внутри for не делает изолированную область.

          нет, перл не подходит

          так что перл подходит, подходит.
          • –2
            В Javascript'е же даже var внутри for не делает изолированную область.

            let
        • –1
          Вы говорите
          нет, перл не подходит

          и приводите пример, где в for нет my:
          #!/usr/bin/perl
          
          $i = 10;
          for ($i = 0; $i <=5; $i++) { print "\n", $i; }
          print "\n", $i, "\n";
          


          Ну если я int забуду в цикле for, то он тоже будет «не подходить»
          ...
          for (i = 0; i < 10; i++) {
             printf("%d ", i); /* 0 1 2 3 4 5 6 7 8 9 */
          }
          ...
          

          Результат:
          1000000
          0 1 2 3 4 5 6 7 8 9
          10

          Тем не менее про C++ вы почему-то согласились, что он подходит.
    • 0
  • 0
    Про утечку памяти при использовании цикличиских ссылок актуальная информация? Мне кажется, это было возможно на старых js движках. Это известная проблема и все ее научились решать.

    Здесь javascript.info/tutorial/memory-leaks#circular-references-collection еще нашел пример похожий на ваш, пишут что все хорошо.
    • 0
      Эта информация актуальна для IE. Там DOM — это все еще ActiveX, а ActiveX неподвластен сборщику мусора, у него есть только подсчет ссылок.
      • 0
        Это вы про «недавно» выпущенные IE >= 9 или про IE <= 8?
        • 0
          Неужели все-таки поправили? Как-то не верится, IE и утечки памяти — это же как Сбербанк и очереди...
          • +1
            Более свежая статья на эту тему: learn.javascript.ru/memory-leaks.

            В английском варианте могут быть неточности, это всё же первая версия была, я сейчас только русский учебник поддерживаю.
  • +5
    > Почему это происходит? Всё дело в контексте. Когда вы вызываете setTimeout(), то на самом деле вызываете window.setTimeout(). В результате, анонимная функция, передаваемая в setTimeout(), определяется в контексте объекта window, который не имеет метода clearBoard().

    Неверное объяснение. То, что обработчик таймаута вызывается в глобальном контексте никак не связано с тем, что setTimeout лежит в window.
    • 0
      а какое правильное? потому что сложенная функция определяется уже в лексическом контексте внешней функции?
      • 0
        Нет. Если функция вызывается просто как функция — someFunc(), то она вызывается в глобальном контексте. И не важно, где и как она была определена.
        • 0
          а если как метод, то будет ошибка, что this вложенной функции undefined? Непонятно, почему замыкания работает для переменной, но не для this
          • –2
            this никогда не бывает undefined. this — это не переменная, а ключевое слово, которое дает доступ к контексту исполнения функции. Если функция вызывается как метод некоего объект, то она будет исполняться в контексте этого объекта и this в данном случае будет ссылаться на этот объект. Судя по вопросам, который вы задается, вам интересен язык и вы планируете всерьез им заниматься. В таком случае, без тщательного изучения языка вам не обойтись. А для полного осознания замыканий я бы рекомендовал вам тщательно проработать примеры из того же Флэнагана, пусть он и занудный, но раскладывает все по полочкам. Ну и конечно практика, без нее все эти книги просто макулатура.
            • –2
              js мне что-то вообще не хочется заниматься. Слишком уж он зависит от реализации браузера. Учу, потому что надо. Это используется везде.

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

              Вот и непонятно, почему этот this нельзя просто через замыкания протолкнуть во вложенные функции метода. Ведь извне отдельно вложенную функцию мы не можем вызвать. Даже если вернем ее, то она будет выполняться в лексической области видимости, в которой объявлялась
            • +1
              this никогда не бывает undefined
              (function() { 'use strict'; console.log(this); })();
  • +2
    7. Неправильное наследование через прототипы
    Странный раздел статьи. Непонятно, как его содержимое связано с названием.
    var thirdObj = new BaseObject('unique');
    console.log(thirdObj.name);  // -> в 'unique'
    
    delete thirdObj.name;
    console.log(thirdObj.name);  // -> в 'default'

    А для этого куска кода уже вам в ответ нужна статья «10 ошибок при проектировании API».
  • +1
    Само по себе это не является ошибкой. И дело тут не только в производительности. Дело в том, что когда вы передаете строковую переменную первым аргументом в setTimeout или setInterval, она будет передана конструктору Function для преобразования в новую функцию. Этот процесс может быть медленным и неэффективным. Альтернативой является использование функции в качестве первого аргумента:


    Странный набор текста. «Дело тут не только в производительности...» а далее описывается лишь производительность: «Этот процесс может быть медленным и неэффективным.».
    • +1
      Да и к тому же, лично я не разу не видел, чтобы кто так делал. Так что пункт ваще не понятно зачем
      • 0
        Лет 8-10 назад писать строки вместо функций было общепринятым, даже в учебниках.
        • 0
          Жесть, хорошо, что 10 лет назад я еще не был к этому причастен
      • 0
        К сожалению, я встречал передачу строки в конструктор Function в одной из JS-библиотек.
        • 0
          Эта операция совершенно корректна, и не приводит к снижению производительности, ведь строка парсится лишь один раз — а потом функция может сколько угодно раз использоваться. Более того, такой подход иногда еще и ускоряет выполнение программы.
          • 0
            Никто и не отрицает, что такая операция корректна. Я не исключаю, что в каких-то жизненных ситуациях без Function не обойтись, но в 99% его использования стоит избегать.
            Если вас не затруднит, приведите пожалуйста пример, когда использование конструктора ускоряет выполнение программы по сравнению с литеральной формой записи функции.
            Вот простой синтетический тест (очень синтетический), и в нем итеративное создание одной и той же функции через конструктор просто трещит по швам:
            jsfiddle.net/kuv8m8na/ против jsfiddle.net/cb51fzp2/
            И не мудрено, потому что конструктор Function компилирует функцию на каждой итерации, в то время как при использование функционального литерала этого не происходит.
            • +1
              • 0
                Ваш пример некорректен (но, как будет видно дальше, доказывает ваши слова). Вы сравниваете работу разных функций. Первая функция полностью копирует объект, вторая — копируют только те свойства объекта, которые существовали на момент определения функции. Я доработал ваш тест, чтобы результат работы функций был идентичен jsfiddle.net/kuv8m8na/2/.
                Вариант с использование конструктора выигрывает приблизительно в 2 раза (50мс на моей машине). Красивый пример, не поспоришь.
                Но, за это вы платите абсолютно нечитаемым кодом, проблемами при отладке (тела функции как такового нет, функция записана в одну строчку, остановиться на конкретной строчке вы не можете) и сложностью в поддержке.
                На мой взгляд это гораздо важнее, чем выигрыш 50мс на 100000 итераций.
                • 0
                  Разумеется, все это важнее — до тех пор, пока создание новых объектов не становится узким местом. Разумеется, без профилирования использовать программирование на лету не стоит никогда.
        • 0
          Да я вобщем тоже видел в доках SmartClient'а, но чтобы так делать, увольте
  • +1
    Кто-нибудь проверял производительность createDocumentFragment?

    На синтетике разницы нет (кроме репейнта)

    jsperf.com/dynamic-update
    • 0
      А какая там в принципе может быть разница (кроме репейнта)?
      • +1
        Ну, теоретически, хотелось что-то выиграть не трогая живой DOM n раз.
        • 0
          Ну, репейнт и выиграли…
    • 0
      При работе с большим количеством массивных кусков DOM, выигрыш от использования DocumentFragment и соответственного сокращения количества репейнтов можно измерять секундами.
      • 0
        Насколько большими? Я хочу запилить тест-кейс
        • 0
          В моем тесте вот такой вот кусок html:

          <div class="item">
              <input type="checkbox" data-time="1408354462252">
              <span class="span-text span-text__active">20</span>
              <span class="span-second" style="display: inline;">span</span>
              <input class="simple-input">
          </div>
          

          в количестве 5000 штук с использованием DocumentFragment отрисовывается за 3-4 секунды, без него — 8-9 секунд.
          Можно взять более сложный html-блок и уменьшить количество.
          • 0
            Ну значит у меня в тестах где-то ошибка, либо Chrome как-то хитро всё оптимизирует, что результаты одинаковые получаются.

            jsperf.com/dynamic-update/2
            • 0
              Хотел было привести простой пример, где использование DocumentFragment дает прирост, но не тут то было. Судя по всему, современные браузеры настолько все оптимизируют, что в большинстве случаев разницу не почувствуешь. В моем тесте, о котором я говорил, тестируется производительность моей библиотеки. Сам тест получился очень массивный и в нем действительно использование DocumentFragment дает выигрыш чуть ли не в 2 раза. В попытках докопаться до того, где же все-таки проседает, я остановился на месте, где в одной из функций jQuery есть обращение к свойству объекта CSSStyleDeclaration. И именно это обращение просаживает производительность в варианте без DocumentFragment.
              Если мне удастся вычленить эту проблему и сделать под нее наглядный пример, я об этом обязательно напишу.
              • 0
                Ну я для себя решил всё-таки через DocumentFragment рендерить коллекции, теоретически тут есть ещё куда оптимизировать, хотя сейчас разница только в FF на синтетике.
  • –1
    Еще месяц назад наткнулся на такую статью: 10-most-common-javascript-mistakes.
    Может данный пост нужно пометить как «Перевод»?
    • +2
      Ну он вообще-то помечен как перевод.
      • +2
        Простите меня за мою невнимательность. =)
  • –7
    Здравствуйте. У Вас есть в моём мире косяк такого плана:

    При добавлении в моём мире домена в зоне.сайт.онлайн, бяда…

    Баг номер 1
    Добавляем в статус:
    Добавление проходит, сайт виден, например — объявлений.сайт
    Кликаем попадаем в никуда, т.к. ссылка битая… на странице в кодировке UTF-8, а редиректит на
    расшифровываем 7-bit ASCII → CP866 + CP1251 → KOI8-R… вообщем в никуда редиректит.

    Баг номер 2
    Добавляем сайт в punycode… и итоге видим на своей странице:
    вместо xn--90acjmfjpd3i2b.xn--80aswg — обрезает до xn--90acjmfjpd3i2b.xn

    Проблема видимо от того, что ваш сайт был недавно в кодировке CP1251, а теперь UTF-8
    И где-то в далёких закромах… остались конфликты. Пожалуйста, поправляйте.

  • 0
    А какую книгу лучше читать начинающему?
    Посмотрел курс от специалиста (про прототипы вообще толком ничего не было), потом head first последний прочитал. Сейчас подробное руководство читаю, но она нереальная нудотина со сплошной теорией.

    Знакомый говорит, чтобы я забил на js и учил jquery. Сам он хорошо зарабатывает, но чистого js не знает.

    П.с. Переучиваюсь с дизайнера.
    • +1
      Ну по хорошему надо начинать с чего-то общего, но вобще Подробное руководство Фленагана
      • 0
        Я с него и начал. А после 200 страницы начал читать Head First на английском. Фленагана сейчас продолжаю читать, но очень он занудно и сложно пишет. Практических занятий нет. Куча воды про Java, C++ и заумных слов.
        • +1
          Ну можно посмотреть в сторону learn.javascript.ru тогда, ну и практика, конечно

          На этом же сайте есть ещё курсы за деньги, но они мне не особо понравились, если честно.
          • 0
            спасибо за советы. Думаю уже быстро и без зубрежки добить подробное руководство и перескочить на jquery.
            • 0
              Обратите особо внимание как эмулируется ООП в JS и на паттерн «модуль».

              addyosmani.com/resources/essentialjsdesignpatterns/book/
              • –2
                спасибо, но что-то мне не хочется больше читать новые книги по js. Тут столько нюансов из-за кроссбраузерности. Лучше потом про библиотеке. Там хоть голова за это особо болеть не будет.
              • 0
                Прототипное наследование я понял. Перечитал в нескольких книгах пока не понял.

                А вот идея модулей мне непонятна. Присваивают глобальной переменной результат вызова безымянной функции, в которой только декларации функций. Как потом эти внутренние функции вызывают, не знаю
                • 0
                  Я где-то читал, что «в js большая часть времени уходит на изучение паттернов», без их понимания код лучше точно не станет, а разобраться с jQ можно за 2 дня, что бы овладеть им на достаточном на первое время уровне.

                  Если будете разбираться с jQ гуглите паралельно «jquery best practices» и пр. всё что найдете поможет быстрее въехать. И посмотрите как в jQ UI используется Widget Factory, достаточно занимательно.
                  • 0
                    Спасибо.

                    Я, наверное, дочитаю руководство и перескочу на пхп. Хочется попробовать уже что-то наподобии сайта сделать и то, что не зависит от браузеров. Потом вернусь на js с его паттернами, если понадобится.

                    Пока мне js напоминает конкретный гемморой. В основном из-за модели реализации ИЕ. Мне такое не нравится. Хочется единый стандарт, а то больше усилий уходит на запоминание костылей )
                    • 0
                      PHP не избавит вас от мук клиентского программирования. И вы не думайте, что на серверной стороне все так просто)
                      А зачем вы запоминаете костыли? Большую часть проблем кроссбраузерности решает тот самый jQuery.
                      • 0
                        Спасибо. Наверное, действительно слишком много внимания костылями в книге уделяют, а не базовым основам языка. )
                • 0
                  Лучше всего модули понимаешь когда начинаешь использовать RequireJS (+ получаешь бесплатно сборщик).
                • 0
                  Эти «внутренние функции» находятся внутри литерала объекта, который возвращается — и попадает в глобальную переменную.
                  • 0
                    то есть они просто становятся методами 1 объекта как Math.random()? Тогда зачем все так сложно делать, когда можно создать просто объект со всеми методами?
                    • +1
                      Для того, чтобы у модуля были приватные переменные и функции, которые не видны снаружи.

                      var Module = (function() {
                        var foo = 5; // Приватная переменная, не видна снаружи
                        function bar() { } // Приватная функция, не видна снаружи
                      
                        return {
                          baz: "Hello, world!", // Публичное поле
                          do_something: function() {}, // Публичный метод
                        };
                      })();
                      

                      Обратите внимание — литерал объекта, который идет после return — попадает в переменную Module, поэтому к baz и do_something можно добраться как Module.baz и Module.do_something. А вот foo и bar снаружи замыкания не видны.
              • +1
                Позволю себе не согласиться, в JS ООП не эмулируется. JS — самый что ни на есть объектно-ориентированный язык. Наверное вы имели в виду эмулирование классического наследования?! Знать об этом не помешает, но лично я бы не советовал углубляться в эту область.
                Я бы посоветовал еще вот эту книгу
                shop.oreilly.com/product/9780596806767.do
                А что касается jQuery-программирования, то это тупиковая ветвь. jQuery — потрясающий инструмент, но без знаний самого языка вам будет крайне сложно развиваться.
                • 0
                  Ну да, классического
    • –2
      Начинайте с того, что прикрепляйте к html JQuery-сниппеты. Но постоянно пытайтесь углубляться. Впринципе, это неплохой способ.
      • 0
        а HTML&CSS программисту не обязательно же отлично знать? Там столько нюансов из-за IE8 и подобных
        • 0
          Можно поучить исключительно IE9+. IE8 поддерживают уже только совсем закостенелые разработчики.
          • 0
            Спасибо за ответы.

            я прочитал по CSS3 большую книгу.

            П.С, Вообще больше хотелось бы дизайном заниматься (наверное, потому что знаю как все сделать), но на программистов спрос больше + зп выше :)
            • 0
              Попробуйте какой-нибудь юнити?
        • 0
          Вам понадобится представление о DOM модели, скорей всего
  • 0
    7. Неправильное наследование через прототипы


    Вы рекомендуете в этом разделе паттерн, которые негативно скажется на производительности, если эти объекты используются в горячих методах, поскольку он приводит к полифорфизму формы («скрытого класса») объекта в зависимотси от того имеет ли свойство значение по умолчанию или нет.
  • 0
    Одним из преимуществ JavaScript является то, что он автоматически преобразует любое значение к булевому значению, если оно используется в булевом контексте

    Неправда ведь
    • 0
      Вообще правда. Было бы лучше, если бы автор статьи обобщил (я не стал менять, хотя и очень хотелось) — если значение одного типа используется в контексте, требующем значение некоего другого типа, интерпретатор JavaScript автоматически пытается преобразовать это значение.
      Операторы равенства имеют свою логику при сравнении. Т.е. нельзя сказать, что операнды оператора равенства используются в том или ином контексте.
  • –1
    7. Неправильное наследование через прототипы

    Вы предлагаете вариант, который сам может содержать грубейшую ошибку. Только immutable данные могут быть прописаны в прототипе. Например примитивы или строки. Иначе изменение значений внутри объекта данных, прописанного в прототипе приведет к их изменению для всех объектов созданных из данного класса.
    • +1
      Только immutable данные могут быть прописаны в прототипе.

      Вы слишком категоричны. Т.е. функции в прототип класть нельзя?
      Да и в объектах, находящихся в прототипе, я вижу ничего страшного, если это сделано осознанно.
      • –2
        А что функции уже стали данными? И если да, то они стали mutable?
        Если целью ставится shared object, то использование возможно, хотя я бы использовал другой метод, поскольку присвоение нового объекта мгновенно обрезает связь.
        • +1
          Не надо тащить в JS терминологию из других языков (КЛАССических) и пытаться сделать JS похожим на эти языки. В JS всё является объектами и функции тоже. В прототипе вы можете хранить все что вам вздумается. Прототип — это объект и не более того. Не надо к нему относиться как к чему-то особенному и накладывать на него ограничения. Хранить в нем объект — отличный способ получить к этому объекту доступ из всех экземпляров.
          • 0
            а null — это тоже объект? Или просто значение, что данному значению не присвоена ссылка на объект?

            с undefined тоже не очень понятно. Это просто значение, что нет значения?
            • 0
              Методов и свойств у обоих этих типов данных нет. Это и путает.
        • +1
          поскольку присвоение нового объекта мгновенно обрезает связь.

          Если я правильно понял, то вы имеете в виду присвоение нового объекта экземпляру?!
          В таком случае собственное свойство экземпляра скрывает свойство прототипа (если по-простому). Если это вам кажется странным и неуместным, то значит вы мало знакомы с наследованием на базе прототипов и пытаетесь превратить его в наследование на базе классов.
  • 0
    Спрошу здесь, а все знакомые программисты «я пишу js только на jquery». А мне эти вопросы не дают покоя, пока книгу читаю.

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

    2. Это какое-то абстрактное вместилище/пространство, которое имеется у каждой вложенной функции, куда копируются значения таких неопределенных переменных.

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

    Области видимости лично для себя представляю как мыльные пузыри, которые впадают другие вложенные :-)
    • 0
      Про замыкания очень много статей на просторах рунета. Каждый человек пытается объяснить их по-своему. Чем больше подобных материалов вы прочитаете, тем выше вероятность, что вы найдете подходящий именно вам вариант и разберетесь в этом страшном звере по имени «замыкание». Кому-то хватает объяснения Флэнагана, кому-то нужны картинки и объяснение на пальцах.
    • +1
      У вас в обоих объяснениях есть слово «копируются». Это вредное слово, надо от него избавиться. Родительская область видимости не копируется в дочернюю, она доступна из нее по ссылке.

      Пример, на котором это хорошо видно.
      function foo() {
        var a;
        return {
          get: function() { return a;},
          set : function(v) { a = v; },
        };
      }
      var f = foo();
      f.set(5);
      console.log(f.get()); // 5
      

      Если бы переменные копировались в замыкания, то f.get() вернуло бы undefined

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

      PS Я считаю, самое понятное объяснение — в стандарте языка.

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

Самое читаемое Разработка