ФорумПрограммированиеПыхнуть хотите?F.A.Q. → Фишки и мелочи JS

Фишки и мелочи JS

  • vasa_c

    Сообщения: 3127 Репутация: N Группа: в ухо

    Spritz 16 сентября 2007 г. 5:44

    Кидаем сюда основы синтаксиса, неоднозначности, базовые приемы для самых маленьких.


    [size=12]Завершение операций (";" и без нее)[/size]

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

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

    Классический пример:

    return
    x;


    Будет возвращено значение x? Фигушки. JS прочтет это как:


    return; // return ничего
    x; // Тоже, кстати, верная операция, ничего не выполняющая и возвращающая значение переменной в никуда.


    Как и во всех классических примерах, здесь, конечно, виноват сам программист. Ну зачем он потащил этот бедный x на другую строчку? Однако, встречаются выражения, которые грех не разбить на несколько строчек. И тут нужно всегда быть осторожным и, там где не помешает, не скупиться на скобки:


    return (
    function ()
    {
    // body
    }
    );
  • vasa_c

    Сообщения: 3127 Репутация: N Группа: в ухо

    Spritz 16 сентября 2007 г. 5:45, спустя 16 секунд

    [size=12]Логические операции "&&", "||" и т.п.[/size]

    Неполное вычисление

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


    var x = f1() && f2();


    Можно подумать, что здесь будет выполнены две функции, f1 и f2, их результаты будут приведены к логическому типу (true/false), а потом над ними будет проведена операция логического "И".
    Нет. Сначала будет выполнена, f1(). Если она вернет false (или приводимое к нему значение), то результат операции станет известен сразу же - это false, вне зависимости от результата f2(). f2() вызываться в этом случае не будет.

    Аналогично:


    var x = true || f2(); // f2() не выполниться, так как это не может повлиять на конечный результат.



    Возвращаемое значение

    Многие думают, что логические операции возвращают значение логического типа (true или false). Самое печальное, так думают даже авторы многих книжек.
    На самом деле возвращается последнее вычисленное выражение:

    var x = (2 + 3) || 3;

    Первым делом будет вычисленно выражение слева от оператора - (2 + 3 = 5). 5 будет приведено к логическому типу — true. В связи с вышесказанным о неполном вычислении, вычисление результата операции "||" будет тут же закончено (логическое "ИЛИ" с одним из операторов true, всегда true). Переменная "x" же получит в качестве значение не "true", а "5".

    Можете проверить:


    var x = 5 || 3;
    if (x === true) {
    alert("Как и надо");
    } else {
    alert("Ой, моё мировозрение поколебленно");
    }



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

    Простой пример: есть массив A, нам нужно получить A[1][2][3][4][5] (ну или если такого индекса нет - undefined). При этом мы не знает структуры переменной. Например, если A[1][2] не существует, а мы напрямую попробуем получить наше значение, вылезет ошибка - попытка доступа к несуществующей переменной, как к массиву. По идее надо делать так:

    var x = undefined;
    if (A[1]) {
    if (A[1][2]) {
    if (A[1][2][3]) {
    if (A[1][2][3][4]) {
    if (A[1][2][3][4][5]) {
    x = A[1][2][3][4][5];
    }
    }
    }
    }
    }


    А можно так:

    var x = A[1] && A[1][2] && A[1][2][3] && A[1][2][3][4] && A[1][2][3][4][5];


    Или так :) :

    var y = A[1];
    var x = (y) && (y = y[2]) && (y = y[3]) && (y = y[4]) && (y = y[5]);
  • vasa_c

    Сообщения: 3127 Репутация: N Группа: в ухо

    Spritz 16 сентября 2007 г. 5:45, спустя 11 секунд

    [size=12]Восьмеричные числа[/size]

    JS поддерживает большой набор литералов для задания чисел. Примеры: 2, 2.5, .5, -2E5, 4.E+3.

    Кроме этого числа можно задавать в 16-ричной и 8-ричной кодировках: 0xFF, 0123 (начинающеяся с "0" число - восьмеричное).

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

    Большинство программистов, обычно либо не знает об этой слабоиспользуемой фишке, либо просто забывет. А потом думают, что за фигня:


    var x = 025;
    alert(x); // 21


    Впрочем, большинство, конечно, в коде так не пишет. Основные проблемы происходят, когда обрабатываются введенные пользователем данные форм. Полученные из формы числовые данные (в виде строки), обычно пропускаются через parseInt(), а он так же понимает восьмиричную запись. Поэтому, не забываем об этой особенности и обрезаем начальные нули (не забывая о том, что строка может просто содержать "0"):


    var x = (str == "0") ? 0 : parseInt(str.replace(/^0+/, ""));
  • vasa_c

    Сообщения: 3127 Репутация: N Группа: в ухо

    Spritz 16 сентября 2007 г. 5:45, спустя 6 секунд

    [size=12]Приведение типов и оператор "+"[/size]

    Как известно в JS есть такое понятие, как "приведение (преобразование) типов" (как, впрочем, и в PHP и других слаботипизированных языках). Значение нелогических типов в логических операторах приводятся к логическим, при сложении с числом, false приводится к 0 и т.п. Полный список правил можно найти в документации.

    В JS для двух различных операций (конкатентации строк и сложения чисел) используется один и тот же оператор - "+" (в PHP сложение - "+", конкатентация - ".").


    alert("Раз " + "Два"); // Строки - конкатентация: "Раз Два"
    alert(1 + 2); // Числа - сложение: 3


    Однако, что будет, если один из операндов строка, а другой — число? А будет конкатентация.


    var x = 1 + "2"; // Один из операндов строка. Второй приводится к строке. Итог - "12"
    var x = "1" + 2; // Тоже "12"


    Забыв ненадолго об этой особенности, можно получить на свою голову много ненужных неприятностей.

    Что же делать, чтобы указать на тип операции?

    Если нужно сложение, то придется все переменные насильно приводить к числам:


    var one = "1";
    var two = "2";
    var three = 3;
    var four = "4";
    alert(parseInt(one) + parseInt(two) + parseInt(three) + parseInt(four));


    Если же нужно составить из чисел строку, то следует либо один из операндов привести к строке, либо добавить фиктивный операнд:


    var one = 1;
    var two = 2;
    var three = 3;
    var four = 4;
    alert(one.toString() + two + three + four);
    alert("" + one + two + three + four);
  • vasa_c

    Сообщения: 3127 Репутация: N Группа: в ухо

    Spritz 16 сентября 2007 г. 6:38, спустя 53 минуты 9 секунд

    [size=12]Внедрение сценариев в документ. <SCRIPT>[/size]

    Формат

    Как правильно включать сценарий в документ с помощью тега <SCRIPT>?
    Правильно так:

    <script type="text/javascript">
    // Содержимое внедренного тега
    </script>
    <script type="text/javascript" src="Внешний источник"></script>


    Правильно использовать атрибут type. Без него html-код будет невалидным.
    Атрибут language использовать неправильно. С ним верстка будет невалидна. Единственным его преимуществом, указываемым многими является возможность переключаться между версиями javascript или между javascript/jscript, например, language="Javascript1.2". Однако, здесь два момента:
    1. Если вы указываете атрибут type (а его надо указывать), language будет просто проигнорирован.
    2. Давно прошли те времена, когда переключение версий имело смысл. На данный момент всё можно делать совершенно другими и более удобными методами. Во всяком случае это верно для 98% обычных web-приложений.

    В соответствии с высокими стандартами и спецификациями, даже type="text/javascript" неправильно. Правильно: type="application/javascript" или type="application/ecmascript", именно они должны постепенно прийти на смену text/javascript. Однако, пока не пришли и не факт что придут. Так что сейчас верно type="text/javascript".


    Комментарии

    Многие очень любят заключать содержимое <SCRIPT> в комментарии:


    <script type="text/javascript">
    <!–
    alert("Жабаскрипт!");
    // –>
    </script>


    Часть из практикующих это не может даже внятно объяснить зачем это делать. Просто все делают. Другая часть гордо поднимает указательный палец к верху и объясняет: "Это надо, чтобы старые браузеры, не знающие тега SCRIPT не выводили содержимое".
    Граждане! Те браузеры давно вымерли. Не осталось ни одного вменяемого человека, который бы помнил как их звали и какие у них были порядковые версии. Однако дело их живет и процветает.

    В ответ на это, опять-таки, любят заявлять: но все равно JS может быть выключено в браузере, а так же существует большое количество браузеров (например, для мобильных устройств), не поддерживающих его. Однако, браузер понимающий, хотя бы HTML версии 3 (а если он её не понимает, он не понимает ничего), знает, что содержимое SCRIPT выводить нельзя, вне зависимости от того, будет он его выполнять или нет.

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


    <![[CDATA

    С переходом на XHTML, однако, появляется новая напасть. Текстовое содержимое (а JS-код с точки зрения HTML, это просто текст внутри тега) не должно содержать очень многие символы из тех, что повсеместно используются в JS. Браузеры, конечно, не дураки и подобные вещи пропускают. Но особо ушлые верстальщики, все-таки хотят делать "как надо". Приходится содержимое SCRIPT заключать в секцию CDATA:

    <script type="text/javascript">
    //<![CDATA[
    alert("Содержимое");
    //]]>
    </script>


    Комментарии "//" необходимы, т.к. JS не знает о том, что такое CDATA


    <head> или <body>

    Многим интересно, куда нужно вставлять сценарии. Только в <head> или можно и в <body>. Эксперимент показывает, что можно и в BODY. Остается вопрос, верно ли это с точки зрения стандартов? Ответ — с точки зрения стандартов это верно. С точки же зрения практики, это зависит от ситуации.
    Общепринято библиотеки (набор функций) подключать в <head>. Код же производящий действия в момент подключения может понадобиться включить после html-кода элементов, к которым он совершает обращение.

    О некоторых вещах из данной области можно почитать в теме "обработка документа в браузере".
  • AlexB

    Сообщения: 4290 Репутация: N Группа: в ухо

    Spritz 16 сентября 2007 г. 10:05, спустя 3 часа 26 минут 59 секунд

    И того, что разсказал vasa_c про возвращаемое значение логической операции ||, следует элегантный способ смоделировать "значение по умолчанию" формального параметра функции.


    function func(param, param_with_default)
    {
    param_with_default = param_with_default || "default value";

    }
  • Dagdamor

    Сообщения: 47 Репутация: N Группа: Кто попало

    Spritz 16 сентября 2007 г. 10:41, спустя 36 минут 10 секунд

    vasa_c
    Очень интересно, спасибо за статьи :)
  • vasa_c

    Сообщения: 3127 Репутация: N Группа: в ухо

    Spritz 17 сентября 2007 г. 4:10, спустя 17 часов 29 минут

    [size=12]Объявление переменных. "Var".[/size]

    Переменные нужно определять с помощью ключевого слова var. Всегда.


    var x = 10;
    var y;
    var a, b = "var";


    Некоторые вообще не используют "var". А некоторые используют при определении локальных переменных. Потому что кто-то когда-то сказал, что определение переменной без var используется для определения глобальных переменных. Но это не так (не совсем так).

    Например:


    function func()
    {
    var c; // Определяем переменную
    c = 10; // Записываем значение в переменную
    }


    Здесь "c = 10" обычная инструкция присвоения значения переменной. От того, что мы уберем "var c", что-то изменится в сути "c = 10"? Конечно, нет. Это так и останется присвоением десятки переменной "c". Единственно, не найдя локальной переменной "c", JS пойдет искать её выше.


    var c = 1;
    function func() {c = 2;}
    func();


    Здесь будет найдена глобальная переменная "c" и значение будет присвоено ей.



    var c = 1;
    function func1() {
    var c = 2;
    function func2() {
    c = 3;
    }
    func2();
    }
    func();


    А здесь в func2 происходит присвоение тройки локальной переменной из func1, так как она встретится раньше на пути, чем глобальная "c".



    function func() {
    c = 1;
    }
    func();


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

    Необязательно понимать, что я здесь написал про поиск переменных, главное понять одно — переменная всегда должна быть объявлена с помощью "var". Даже не из-за вышеописанных "темных моментов", а потому что это придает коду намного больше четкости, сразу видно в каком контексте определена используемая переменная.

    Если в функции нужно задать значение глобальной переменной, объявите её заранее:


    var global_var;
    function func() {global_var = 10;}


    Если совсем уж невмоготу и нужно создать несуществующую глобальную переменную внутри функции, воспользуйтесь тем, что глобальным переменным соответствуют свойства объекта window:


    function func() {
    window["global_var"] = 10;
    }


    Это тоже совершенно не здорово, но здесь, хотя бы, точно указывается, что создается глобальная переменная.
  • vasa_c

    Сообщения: 3127 Репутация: N Группа: в ухо

    Spritz 17 сентября 2007 г. 4:33, спустя 22 минуты 33 секунды

    [size=12]Значение аргументов по умолчанию[/size]

    Как известно, в JS поскупились добавить возможность задавать для аргументов функции значения по умолчанию. Т.е. так написать нельзя:


    function func(x, y = 1, z = "зэ") {…}


    Будет ошибка. К счастью, передавать все описанные для функции аргументы каждый раз не нужно. Ошибки не произойдет, а соответствующая переменная примет значение undefined. Здесь можно воспользоваться способом от AlexаB, предложенным выше:


    function func(x, y, z)
    {
    y = y || 1; // Если y не пришел, зададим ему значение по умолчанию
    z = z || "зэ";

    }
    func(1);
    func(1, 2, 3);
    func(1, 2);


    Если переменная может принимать приводимое к false значение, то придется проверять на undefined более четко:


    if (y === undefined) {
    y = "by default";
    }


    Если же undefined так же является допустимым для переменной значением, то можно воспользоваться массивом arguments, содержащим полученные аргументы:


    if (arguments.length < 2) { // y - второй аргумент функции
    y = "by default";
    }


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

    function sum()
    {
    var sum = 0;
    for (var i = 0; i < arguments.length; i++) {
    sum += arguments;
    }
    return sum;
    }

    alert(sum(1, 2));
    alert(sum(1, 2, 3, 4, 5, 6, 7));
  • vasa_c

    Сообщения: 3127 Репутация: N Группа: в ухо

    Spritz 17 сентября 2007 г. 4:50, спустя 17 минут 36 секунд

    [size=12]Статические переменные функции[/size]

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

    Например в PHP:


    function func()
    {
    static $counter = 0;
    $counter++;
    print 'Вы эту бедную функцию уже в '.$counter.'-й раз вызываете';

    }


    В JS так не сделать. Для реализации подобного поведения, некоторые используют глобальные переменные. Однако, здесь можно вспомнить о том, что в JS практически всё является объектом. В том числе и функции. А объектам можно задавать свойства.

    function func()
    {
    func.counter++;
    alert('Вызов за номером ' + func.counter);
    }
    func.counter = 0;

    func();
    func();
    func();


    Здесь мы в качестве статической переменной используем свойства объекта, соответствующего функции func(). В плане сокрытия переменной, это аналогично использованию глобальной переменной — кто угодно может прочитать и перезаписать свойство. Однако, основного мы добились — перестали засорять глобальную область видимости и привязали переменную к конкретной функции.

    PS. Внутри функции, для указания на нее, вместо имени, лучше использовать agruments.callee

    function func()
    {
    arguments.callee.counter++;
    alert('Вызов за номером ' + arguments.callee.counter);
    }
  • vasa_c

    Сообщения: 3127 Репутация: N Группа: в ухо

    Spritz 17 сентября 2007 г. 5:19, спустя 29 минут 10 секунд

    [size=12]Оператор with[/size]

    Во многих языках есть такой полезный оператор with. В JS он тоже есть.
    Только вот в JS его использовать не следует. Почему? А потому.

    Если вам нужно обработать объект с длинным именем, используйте промежуточную переменную.

    Вместо:

    one.two.three.four.five.x = one.two.three.four.five.y + one.two.three.four.five.z;


    Пишите:

    var tmp = one.two.three.four.five;
    tmp.x = tmp.y + tmp.z;


    Или :) :

    function(obj) {
    obj.x = obj.y + obj.z;
    }(one.two.three.four.five);
  • vasa_c

    Сообщения: 3127 Репутация: N Группа: в ухо

    Spritz 21 сентября 2007 г. 3:35, спустя 3 дня 22 часа 15 минут

    [size=12]Анонимная функция, несколько фишек[/size]

    Как известно, основными двумя способами создания функции являются:


    /* Статический способ */
    function func(arg) {
    // body
    }

    /* Динамический */
    var func = function(arg) {/* body */};


    Во втором случае создается анонимная функция, присваиваемая переменной. Дальше с переменной можно работать, как и с функцией, определенной "классическим" образом. Т.е. запускать.
    Однако, мы можем не присваивать функцию никакой переменной, а сразу же взять и выполнить:


    (function() {
    alert(1);
    })();


    Здесь сразу же будет вызван alert(). После чего функция исчезнет, так как она не доступна ни через одну переменную.
    Чем это отличается от того, чем просто выполнить нужные нам инструкции, не заключая их в анонимную функцию?
    Отличается это в первую очередь тем, что для функции в момент её исполнения создается локальный контекст, исчезающий, после её завершения. Чем это может быть полезно в данном случае?
    Например, нам нужно при загрузке страницы, выполнить какие-то более-менее сложные действия. Например, пройтись по всем ссылкам и сделать так, чтобы при клике на них пользователя спрашивали, уверен ли он, что хочет перейти по ссылке:


    <html>

    <body>

    <script type="text/javascript">
    var anchors = document.getElementsByTagName("a");
    for (var i = 0; i < anchors.length; i++) {
    var a = anchors.item(i);
    a.onclick = (function() {return confirm("Sure?");});
    }
    </script>
    </body>
    </html>


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


    (function () {
    var anchors = document.getElementsByTagName("a");
    for (var i = 0; i < anchors.length; i++) {
    var a = anchors.item(i);
    a.onclick = (function() {return confirm("Sure?");});
    }
    })();


    Создается функция, в её контексте выполняется код, контекст вместе с функцией умирает. Всё, никаких новых глобальных переменных не появилось.


    Прекращение выполнения кода

    Часто бывает такая ситуация при подключении внешнего сценария, что при выполнении кода в нем, в зависимости от какого-то условия, выполнение нужно прекратить. Однако, в JS нет никакого подобия оператора exit(), так что многим приходится создавать навороченные циклы. А можно просто, заключить весь код файла в анонимную функцию и в нужном месте выйти из неё.


    (function () {
    // script.js
    if (expression) return;
    // script.js
    })();


    Однако, помните, что если подключаемый файл — библиотека функций, их не нужно заключать в анонимную. Т.к. они будут определены внутри её контекста и не будут видны извне.
  • adw0rd

    Сообщения: 22905 Репутация: N Группа: в ухо

    Spritz 18 декабря 2007 г. 13:43, спустя 88 дней 11 часов 7 минут

    Спасибо за статью)
    adw/0
  • phpdude

    Сообщения: 26624 Репутация: N Группа: в ухо

    Spritz 15 декабря 2008 г. 8:43, спустя 362 дня 19 часов

    хорошая статья. и правда норм.
    Сапожник без сапог
  • Шурикен

    Сообщения: 14 Репутация: N Группа: Кто попало

    Spritz 19 января 2010 г. 1:50, спустя 399 дней 17 часов 7 минут


    (function() {
    alert(1);
    })();

    ^объясните плиз, чет не работает у меня.

Пожалуйста, авторизуйтесь, чтобы написать комментарий!