Скрытые возможности JavaScript?

Как вы думаете, какие "скрытые возможности" JavaScript должен знать каждый программист?

После того, как я увидел отличное качество ответов на следующие вопросы, я подумал, что пришло время задать его для JavaScript.

Хотя JavaScript является, пожалуй, самым важным языком на стороне клиента (просто спросите Google), удивительно, как мало большинство веб-разработчиков понимают, насколько он действительно мощный.

99 ответов

Вам не нужно определять какие-либо параметры для функции. Вы можете просто использовать функцию arguments массивоподобный объект.

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

sum(1, 2, 3) // returns 6

Я мог бы процитировать большую часть превосходной книги Дугласа Крокфорда " JavaScript: хорошие части".

Но я возьму только один для вас, всегда используйте === а также !== вместо == а также !=

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

== не является переходным. Если вы используете === это дало бы ложь для всех этих заявлений как ожидалось.

Функции являются гражданами первого класса в 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

Методы функционального программирования могут быть использованы для написания элегантного JavaScript.

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

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

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

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

Вы можете использовать оператор 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

Если вы находите литералы объекта слишком некрасивыми, вы можете объединить их с функцией без параметров tip:

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

Присвоение значений по умолчанию переменным

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

var a = b || c;

aпеременная получит значениеcтолько если b ложно (если есть null,false, undefined, 0, empty string, или же NaN), иначе aполучит значение b,

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

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

Пример отката IE в обработчиках событий:

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

Следующие языковые функции были с нами в течение долгого времени, все реализации JavaScript поддерживают их, но они не были частью спецификации до выпуска ECMAScript 5th Edition:

debuggerзаявление

Описано в:§ 12.15 Оператор отладчика

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

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

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

В противном случае, если отладчик отсутствует или активен, этот оператор не имеет видимого эффекта.

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

Описано в: § 7.8.4 Строковые литералы

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

Вы должны быть осторожны, потому что персонаж рядом с \ должен быть ограничителем строки, если после пробела \ например, код будет выглядеть точно так же, но это вызовет SyntaxError,

У JavaScript нет блочной области видимости (но у него есть замыкание, поэтому давайте назовем его даже?).

var x = 1;
{
   var x = 2;
}
alert(x); // outputs 2

Вы можете получить доступ к свойствам объекта с [] вместо .

Это позволяет вам искать свойство, соответствующее переменной.

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

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

obj["class"] = "test";  // class is a reserved word; obj.class would be illegal.
obj["two words"] = "test2"; // using dot operator not possible with the space.

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

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

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

Если вы ищете Google справочник JavaScript по определенной теме, включите в свой запрос ключевое слово "mdc", и ваши первые результаты будут получены из Центра разработчиков Mozilla. Я не ношу с собой никаких офлайновых ссылок или книг. Я всегда использую трюк с ключевым словом "mdc", чтобы напрямую получить то, что я ищу. Например:

Google: сортировка массива javascript, MDC
(в большинстве случаев вы можете опустить "javascript")

Обновление: Центр разработчиков Mozilla переименован в Сеть разработчиков Mozilla. Трюк с ключевым словом "mdc" все еще работает, но довольно скоро нам, возможно, придется начать использовать вместо него "mdn".

Может быть, немного очевидным для некоторых...

Установите Firebug и используйте console.log("привет"). Намного лучше, чем использование random alert(); я помню, что это делал много лет назад.

Частные Методы

Объект может иметь приватные методы.

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

    // A private method only visible from within this constructor
    function calcFullName() {
       return firstName + " " + lastName;    
    }

    // A public method available to everyone
    this.sayHello = function () {
        alert(calcFullName());
    }
}

//Usage:
var person1 = new Person("Bob", "Loblaw");
person1.sayHello();

// This fails since the method is not visible from this scope
alert(person1.calcFullName());

Также упоминается в "Javascript: Хорошие части" Крокфорда:

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

parseInt('010') // returns 8! (in FF3)
parseInt('010', 10); // returns 10 because we've informed it which base to work with.

Функции являются объектами и поэтому могут иметь свойства.

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

fn.foo = 1;

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

Я должен был сказать, что выполняющиеся функции.

(function() { alert("hi there");})();

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

(function() {
  var myvar = 2;
  alert(myvar);
})();

Вот, myvar is не вмешивается и не загрязняет глобальную область и исчезает, когда функция завершается.

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

function add_nums(num1, num2, num3 ){
    return num1 + num2 + num3;
}
add_nums.length // 3 is the number of parameters expected.

Знайте, сколько параметров получено функцией

function add_many_nums(){
    return arguments.length;
}    
add_many_nums(2,1,122,12,21,89); //returns 6

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

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

[немного обновлено в ответ на хорошие комментарии; пожалуйста, смотрите комментарии]

Я знаю, что опоздал на вечеринку, но я просто не могу поверить + Полезность оператора не упоминалась за исключением "преобразования чего-либо в число". Может быть, как хорошо это скрыто?

// Quick hex to dec conversion:
+"0xFF";              // -> 255

// Get a timestamp for now, the equivalent of `new Date().getTime()`:
+new Date();

// Safer parsing than parseFloat()/parseInt()
parseInt("1,000");    // -> 1, not 1000
+"1,000";             // -> NaN, much better for testing user input
parseInt("010");      // -> 8, because of the octal literal prefix
+"010";               // -> 10, `Number()` doesn't parse octal literals 

// A use case for this would be rare, but still useful in cases
// for shortening something like if (someVar === null) someVar = 0;
+null;                // -> 0;

// Boolean to integer
+true;                // -> 1;
+false;               // -> 0;

// Other useful tidbits:
+"1e10";              // -> 10000000000
+"1e-4";              // -> 0.0001
+"-12";               // -> -12

Конечно, вы можете сделать все это, используя Number() вместо этого, но + Оператор намного красивее!

Вы также можете определить числовое возвращаемое значение для объекта, переопределив прототип valueOf() метод. Любое преобразование числа, выполненное на этом объекте, не приведет к NaN, но возвращаемое значение valueOf() метод:

var rnd = {
    "valueOf": function () { return Math.floor(Math.random()*1000); }
};
+rnd;               // -> 442;
+rnd;               // -> 727;
+rnd;               // -> 718;

" Методы расширения в JavaScript" через свойство prototype.

Array.prototype.contains = function(value) {  
    for (var i = 0; i < this.length; i++) {  
        if (this[i] == value) return true;  
    }  
    return false;  
}

Это добавит contains метод для всех Array объекты. Вы можете вызвать этот метод, используя этот синтаксис

var stringArray = ["foo", "bar", "foobar"];
stringArray.contains("foobar");

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

var obj = { prop1: 42, prop2: 43 };

obj.prop2 = undefined;

for (var key in obj) {
    ...

Свойство prop2 по- прежнему будет частью итерации. Если вы хотите полностью избавиться от prop2, вы должны вместо этого сделать:

delete obj.prop2;

Свойство prop2 больше не будет появляться, когда вы перебираете свойства.

with,

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

Например: объектные литералы очень удобны для быстрой настройки свойств нового объекта. Но что, если вам нужно изменить половину свойств существующего объекта?

var user = 
{
   fname: 'Rocket', 
   mname: 'Aloysus',
   lname: 'Squirrel', 
   city: 'Fresno', 
   state: 'California'
};

// ...

with (user)
{
   mname = 'J';
   city = 'Frostbite Falls';
   state = 'Minnesota';
}

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

var user = 
{
   fname: "John",
// mname definition skipped - no middle name
   lname: "Doe"
};

with (user)
{
   mname = "Q"; // creates / modifies global variable "mname"
}

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

Смотрите также: Существуют ли законные варианты использования оператора "with" в JavaScript?

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

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

Этот код падает, потому что listNodes это не Array

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

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

Наследование прототипа (популяризируемое Дугласом Крокфордом) полностью революционизирует ваше представление о множестве вещей в 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'

// changes and additionns to A are reflected in B
A.foo = 'hello';
alert(B.foo);     // 'hello'

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


// ...but not the other way around
B.foo = 'wazzap';
alert(A.foo);     // 'hello'

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

Некоторые называют это делом вкуса, но:

aWizz = wizz || "default";
// same as: if (wizz) { aWizz = wizz; } else { aWizz = "default"; }

Оператор трины может быть прикован к цепочке, чтобы действовать как схема (cond ...):

(cond (predicate  (action  ...))
      (predicate2 (action2 ...))
      (#t         default ))

можно записать как...

predicate  ? action( ... ) :
predicate2 ? action2( ... ) :
             default;

Это очень "функционально", так как ветвит ваш код без побочных эффектов. Так что вместо:

if (predicate) {
  foo = "one";
} else if (predicate2) {
  foo = "two";
} else {
  foo = "default";
}

Ты можешь написать:

foo = predicate  ? "one" :
      predicate2 ? "two" :
                   "default";

Хорошо работает с рекурсией тоже:)

Числа также являются объектами. Таким образом, вы можете делать классные вещи, как:

// convert to base 2
(5).toString(2) // returns "101"

// provide built in iteration
Number.prototype.times = function(funct){
  if(typeof funct === 'function') {
    for(var i = 0;i < Math.floor(this);i++) {
      funct(i);
    }
  }
  return this;
}


(5).times(function(i){
  string += i+" ";
});
// string now equals "0 1 2 3 4 "

var x = 1000;

x.times(function(i){
  document.body.innerHTML += '<p>paragraph #'+i+'</p>';
});
// adds 1000 parapraphs to the document

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

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

//Takes a function that filters numbers and calls the function on 
//it to build up a list of numbers that satisfy the function.
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;
}

//Creates a function (closure) that will remember the value "lowerBound" 
//that gets passed in and keep a copy of it.
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);

Вы также можете расширять (наследовать) классы и переопределять свойства / методы, используя цепочку прототипов spoon16, на которую ссылаются.

В следующем примере мы создаем класс Pet и определяем некоторые свойства. Мы также переопределяем метод.toString(), унаследованный от Object.

После этого мы создаем класс Dog, который расширяет Pet и переопределяет метод.toString(), снова изменяя его поведение (полиморфизм). Кроме того, мы добавляем некоторые другие свойства в дочерний класс.

После этого мы проверяем цепочку наследования, чтобы показать, что Dog по-прежнему имеет тип Dog, тип Pet и тип Object.

// Defines a Pet class constructor 
function Pet(name) 
{
    this.getName = function() { return name; };
    this.setName = function(newName) { name = newName; };
}

// Adds the Pet.toString() function for all Pet objects
Pet.prototype.toString = function() 
{
    return 'This pets name is: ' + this.getName();
};
// end of class Pet

// Define Dog class constructor (Dog : Pet) 
function Dog(name, breed) 
{
    // think Dog : base(name) 
    Pet.call(this, name);
    this.getBreed = function() { return breed; };
}

// this makes Dog.prototype inherit from Pet.prototype
Dog.prototype = new Pet();

// Currently Pet.prototype.constructor
// points to Pet. We want our Dog instances'
// constructor to point to Dog.
Dog.prototype.constructor = Dog;

// Now we override Pet.prototype.toString
Dog.prototype.toString = function() 
{
    return 'This dogs name is: ' + this.getName() + 
        ', and its breed is: ' + this.getBreed();
};
// end of class Dog

var parrotty = new Pet('Parrotty the Parrot');
var dog = new Dog('Buddy', 'Great Dane');
// test the new toString()
alert(parrotty);
alert(dog);

// Testing instanceof (similar to the `is` operator)
alert('Is dog instance of Dog? ' + (dog instanceof Dog)); //true
alert('Is dog instance of Pet? ' + (dog instanceof Pet)); //true
alert('Is dog instance of Object? ' + (dog instanceof Object)); //true

Оба ответа на этот вопрос были кодами, модифицированными из великолепной статьи MSDN Рэя Джаджадината.

Вы можете ловить исключения в зависимости от их типа. Цитируется из MDC:

try {
   myroutine(); // may throw three exceptions
} catch (e if e instanceof TypeError) {
   // statements to handle TypeError exceptions
} catch (e if e instanceof RangeError) {
   // statements to handle RangeError exceptions
} catch (e if e instanceof EvalError) {
   // statements to handle EvalError exceptions
} catch (e) {
   // statements to handle any unspecified exceptions
   logMyErrors(e); // pass exception object to error handler
}

ПРИМЕЧАНИЕ. Условные предложения catch - это расширение Netscape (и, следовательно, Mozilla/Firefox), которое не является частью спецификации ECMAScript и, следовательно, на него нельзя положиться, за исключением отдельных браузеров.

С верхней части моей головы...

функции

arguments.callee ссылается на функцию, которая содержит переменную "arguments", поэтому ее можно использовать для рекурсии анонимных функций:

var recurse = function() {
  if (condition) arguments.callee(); //calls recurse() again
}

Это полезно, если вы хотите сделать что-то вроде этого:

//do something to all array items within an array recursively
myArray.forEach(function(item) {
  if (item instanceof Array) item.forEach(arguments.callee)
  else {/*...*/}
})

Объекты

Интересная вещь о членах объекта: у них может быть любая строка в качестве их имен:

//these are normal object members
var obj = {
  a : function() {},
  b : function() {}
}
//but we can do this too
var rules = {
  ".layout .widget" : function(element) {},
  "a[href]" : function(element) {}
}
/* 
this snippet searches the page for elements that
match the CSS selectors and applies the respective function to them:
*/
for (var item in rules) {
  var elements = document.querySelectorAll(rules[item]);
  for (var e, i = 0; e = elements[i++];) rules[item](e);
}

Струны

String.split может принимать регулярные выражения в качестве параметров:

"hello world   with  spaces".split(/\s+/g);
//returns an array: ["hello", "world", "with", "spaces"]

String.replace может принимать регулярное выражение в качестве параметра поиска и функцию в качестве параметра замены:

var i = 1;
"foo bar baz ".replace(/\s+/g, function() {return i++});
//returns "foo1bar2baz3"

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

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

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

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)]();
}

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

update2: с предлагаемыми расширениями синтаксиса для ES.next, это становится

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

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

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

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

Закрытые переменные с открытым интерфейсом

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

var test = function () {
    //private members
    var x = 1;
    var y = function () {
        return x * 2;
    };
    //public interface
    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());
Другие вопросы по тегам