20 скрытых особенностей JavaScript

Представленные ниже 20 хитростей JavaScript были указаны пользователями Stack Overflow.

1. Работа с аргументами функций

Вам не нужно определять параметры для функции  —  можете просто использовать массивоподобный функциональный объект аргумента:

function sum() {
    var retval = 0;
    for (var i = 0, len = arguments.length; i < len; ++i) {
        retval += arguments[i];
    }
    return retval;
}

sum(1, 2, 3) // возвращает 6

2. Операторы === и !==

Всегда используйте === и !== вместо == и !=.

alert('' == '0'); //false 
alert(0 == ''); // true 
alert(0 == '0'); // true

Оператор == не является транзитивным. Если вы используете ===, то он вернет значение false, как и ожидалось, для всех вышеуказанных случаев.

3. Функции в JavaScript

Функции являются полноправными гражданами в JavaScript:

var passFunAndApply = function (fn,x,y,z) { return fn(x,y,z); };

var sum = function(x,y,z) {
  return x+y+z;
};

alert( passFunAndApply(sum,3,4,5) ); // 12

В частности, функции могут передаваться в качестве параметров, например Array.filter()  —  это функция обратного вызова:

[1, 2, -1].filter(function(element, index, array) { return element > 0 });
// -> [1,2]

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

function PrintName() {
    var privateFunction = function() { return "Steve"; };
    return privateFunction();
}

4. Оператор in

Вы можете использовать оператор in для проверки наличия у объекта такого ключа:

var x = 1; 
var y = 3; 
var list = {0:0, 1:0, 2:0};
x in list; //true 
y in list; //false 
1 in list; //true 
y in {3:0, 4:0, 5:0}; //true

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

function list() {
var x = {}; 
for(var i=0; i < arguments.length; ++i)
 x[arguments[i]] = 0;
 return x 
}

5 in list(1,2,3,4,5) //true

5. Значения по умолчанию

Вы можете использовать || в выражении присваивания, чтобы указать значение по умолчанию:

var a = b || c;

Переменная a получит значение c только в том случае, если b = false (то есть, если значение null, false, не определено, 0, пустая строка или NaN), в противном случае a получит значение b.

Обычно это полезно в функциях, когда вы хотите присвоить аргументу значение по умолчанию, если оно не указано:

function example(arg1) { 
    arg1 || (arg1 = 'default value'); 
}

Например, IE завершается неуспехом в обработчике событий:

function onClick(e) {
    e || (e = window.event); 
}
The debugger

Этот оператор позволяет устанавливать точки прерывания внутри кода:

// ... debugger; // ...

Если отладчик активен, он совершит прерывание прямо в этой строке.

Многострочные литералы:

var str = "This is a \ 
really, really \ 
long line!";

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

6. Область видимости в JavaScript

JavaScript не имеет области видимости блока:

var x = 1; 
{ var x = 2; } 
alert(x); // выводит 2

7. Свойства объекта

Вы можете получить доступ к свойствам объекта, используя [] вместо ‘.’. Это позволяет обнаруживать свойство, соответствующее переменной.

obj = {a:"test"};
var propname = "a";
var b = obj[propname];  // "test"

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

obj["class"] = "test";  // class - зарезервированное слово; obj.class не будет иметь силы.
obj["two words"] = "test2"; // использование оператора-точки невозможно с пробелом.

Некоторые разработчики этого не знают и используют eval (), что на самом деле не очень:

var propname = "a";
var a = eval("obj." + propname);

Это затрудняет чтение кода, усложняет поиск ошибок (вы не можете использовать JSLint), замедляя его выполнение, и может привести к XSS.

8. mdc

Когда вы ведете поиск в Google по теме JavaScript, добавьте “mdc” в свой запрос. Тогда первые результаты будут получены из Центра разработчиков Mozilla (Mozilla Developer Center; сокращенно: MDC).

Например:

Google: сортировка массива javascript mdc

(в большинстве случаев можно обойтись в запросе без “javascript”)

Примечание: Mozilla Developer Center был переименован в Mozilla Developer Network (MDN). Ключевое слово “mdc” все еще работает, но вскоре вам, возможно, его поменяют на “mdn”.

9. Капитан Очевидность

Установите Firebug и используйте console.log("hello"). Это гораздо удобнее, чем использовать случайные оповещения alert();

10. Закрытые методы

Объекты могут иметь закрытые методы:

function Person(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;

    // Частный метод, видимый только из этого конструктора 
    function calcFullName() {
       return firstName + " " + lastName;    
    }

    // Публичный метод, доступный каждому 
    this.sayHello = function () {
        alert(calcFullName());
    }

}
//Использование:
var person1 = new Person("Bob", "Loblaw");
person1.sayHello();

// Так не получится, поскольку метод не виден из этой области
alert(person1.calcFullName());

11. parseInt()  —  JavaScript

Метод parseInt()требует осторожного использования. Если вы передадите ему строку, не сообщив простой базис, она может вернуть неожиданные числа. Например, parseInt('010')возвращает 8, а не 10. Передача базы в parseInt приводит к корректной работе:

parseInt('010') // возвращает 8! (в FF3)
parseInt('010', 10); // возвращает 10, потому что мы сообщили, с какой базой работать.

12. Функции

Функции являются объектами и, следовательно, могут иметь свойства:

fn = function(x) {
   // ...
}

fn.foo = 1;

fn.next = function(y) {
  //
}

13. Параметры объекта

Сколько параметров ожидается функцией:

function add_nums(num1, num2, num3 ){
    return num1 + num2 + num3;
}
add_nums.length // 3 - количество ожидаемых параметров.

Сколько параметров получает функция:

function add_many_nums(){
    return arguments.length;
}    
add_many_nums(2,1,122,12,21,89); //возвращает 6

14. Методы

Методы (или функции) могут вызываться для объектов, не относящихся к тому типу, для которого они были предназначены. Если осуществляется вызов нативных (быстрых) методов для пользовательских объектов, то все замечательно

var listNodes = document.getElementsByTagName('a');
listNodes.sort(function(a, b){ ... });

Этот код аварийно завершает работу, потому что listNodes не является Array

Array.prototype.sort.apply(listNodes, [function(a, b){ ... }]);

Этот код работает, потому что listNodesопределяет достаточно массивоподобных свойств (длину, оператор []), которые будут использоваться sort().

15. Прототипное наследование

Наследование через прототипы (популяризированное Дугласом Крокфордом) полностью революционизирует ваши представления о множестве вещей в JavaScript:

Object.beget = (function(Function){
    return function(Object){
        Function.prototype = Object;
        return new Function;
    }
})(function(){});

Жаль, что этой фичей почти никто не пользуется.

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

var A = {
  foo : 'greetings'
};  
var B = Object.beget(A);

alert(B.foo);     // 'greetings'// 

изменения и дополнения к А отражены в В 
A.foo = 'hello';
alert(B.foo);     // 'hello'

A.bar = 'world';
alert(B.bar);     // 'world'

// ...но не наоборот
B.foo = 'wazzap';
alert(A.foo);     // 'hello'

B.bar = 'universe';
alert(A.bar);     // 'world'

16. Замыкание

Как насчет замыканий в JavaScript (аналогичных анонимным методам в C# версии 2.0+)? Вы можете создать функцию, которая вызывает функцию или выражение.

Пример замыкания:

//Берет функцию, которая фильтрует числа, и вызывает другую функцию //для построения списка чисел, удовлетворяющих этой функции
function filter(filterFunction, numbers)
{
  var filteredNumbers = [];
for (var index = 0; index < numbers.length; index++)
  {
    if (filterFunction(numbers[index]) == true)
    {
      filteredNumbers.push(numbers[index]);
    }
  }
  return filteredNumbers;
}

//Создает функцию (закрытие), которая запомнит переданное значение
"lowerBound" 
//и сохранит его копию
function buildGreaterThanFunction(lowerBound)
{
  return function (numberToCheck) {
    return (numberToCheck > lowerBound) ? true : false;
  };
}

var numbers = [1, 15, 20, 4, 11, 9, 77, 102, 6];

var greaterThan7 = buildGreaterThanFunction(7);
var greaterThan15 = buildGreaterThanFunction(15);

numbers = filter(greaterThan7, numbers);
alert('Greater Than 7: ' + numbers);

numbers = filter(greaterThan15, numbers);
alert('Greater Than 15: ' + numbers);

17. Объекты вместо переключателей

Большую часть времени можно использовать объекты вместо переключательных элементов:

function getInnerText(o){
    return o === null? null : {
        string: o,
        array: o.map(getInnerText).join(""),
        object:getInnerText(o["childNodes"])
    }[typeis(o)];
}

Примечание 1: если вам кажется, что предварительная оценка случаев неэффективна (почему вы беспокоитесь об эффективности на раннем этапе разработки программы??), можете сделать следующее:

function getInnerText(o){
    return o === null? null : {
        string: function() { return o;},
        array: function() { return o.map(getInnerText).join(""); },
        object: function () { return getInnerText(o["childNodes"]; ) }
    }[typeis(o)]();
}

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

Примечание 2: с предлагаемыми для ES.next расширениями синтаксиса, это будет выглядеть так:

let getInnerText = o -> ({
    string: o -> o,
    array: o -> o.map(getInnerText).join(""),
    object: o -> getInnerText(o["childNodes"])
}[ typeis o ] || (->null) )(o);

18. hasOwnProperty

Обязательно используйте метод hasOwnProperty при повторном просмотре свойств объекта:

for (p in anObject) {
    if (anObject.hasOwnProperty(p)) {
        //Do stuff with p here
    }
}

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

19. Скрытые переменные с открытым интерфейсом

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

var test = function () {
    //частные участники 
    var x = 1;
    var y = function () {
        return x * 2;
    };
    //публичный интерфейс
    return {
        setx : function (newx) {
            x = newx;
        },
        gety : function () {
            return y();
        }
    }
}();

assert(undefined == test.x);
assert(undefined == test.y);
assert(2 == test.gety());
test.setx(5);
assert(10 == test.gety());

20. Вот еще несколько интересных лайфхаков:

  • Сравнение NaNс чем-либо (даже с NaN) всегда ложно, включая ==, < и > .
  • NaN (Not-a-Number) означает “не число”, но если вы запросите тип, NaNвернет число.
  • Array.sort может выполнять функцию сравнения и вызывается драйвером, подобным быстрой сортировке (зависит от реализации).
  • Некоторые версии JavaScript позволяют вам получать доступ к элементам $0, $1, $2 в регулярном выражении.
  • null не похож ни на что другое. Это не объект, не логическое значение, не число, не строка и не undefined. Это немного похоже на “альтернативный” undefined. (Примечание: typeof null == "object")
  • В самом внешнем контексте this вызывает противоположный неназванный [глобальный] объект.
  • Объявление переменной с помощью var вместо расчета на автоматическое объявление переменной, дает среде выполнения реальный шанс оптимизировать доступ к этой переменной.
  • Конструкция with уничтожит такую оптимизацию.
  • Имена переменных могут содержать символы Юникода.
  • Регулярные выражения JavaScript на самом деле не являются регулярными. Они основаны на регулярных выражениях Perl. Можно создавать выражения с предварительным просмотром, для оценки которых требуется очень много времени.
  • Блоки можно пометить и использовать в качестве целей break, а циклы в качестве целей continue .
  • Массивы не являются разреженными. Установка 1000-го элемента в противоположном пустом массиве должна заполнить его undefined (зависит от реализации).
  • if (new Boolean(false)) {...} выполнит блок {...}
  • Механизмы регулярных выражений JavaScript зависят от реализации: например, можно писать “непереносимые” регулярные выражения.

Читайте также:

Читайте нас в TelegramVK и Яндекс.Дзен


Перевод статьи Aboelez, 20 Hidden JavaScript Features You Probably Don’t Know About

Предыдущая статьяЧто делает сайты медленнее?
Следующая статья3 применения исключений, которые улучшат навыки программирования на Java