Скрытые особенности JavaScript?

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

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

Несмотря на то, что JavaScript, возможно, является самым важным языком на стороне клиента прямо сейчас (просто спросите Google), он удивляет, как мало кто из веб-разработчиков оценивает, насколько он эффективен.

Ответ 1

Вам не нужно определять какие-либо параметры для функции. Вы можете просто использовать объект 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

Ответ 2

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

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

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

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

Ответ 3

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

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

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

Ответ 4

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

Оператор debugger

Описан в: & sect; 12.15. Оператор отладчика

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

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

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

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

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

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

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

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

Ответ 7

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

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

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.

Ответ 8

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

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

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

Ответ 9

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

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

Ответ 10

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

Объект может иметь частные методы.

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

Ответ 11

Также упоминается в Crockford "Javascript: The Good Parts":

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.

Ответ 12

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

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

fn.foo = 1;

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

Ответ 13

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

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

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

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

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

Ответ 14

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

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

Ответ 15

Вот некоторые интересные вещи:

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

[немного обновился в ответ на хорошие комментарии; см. комментарии]

Ответ 16

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

// 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;

Ответ 17

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

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");

Ответ 18

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

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

obj.prop2 = undefined;

for (var key in obj) {
    ...

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

delete obj.prop2;

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

Ответ 19

with.

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

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

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

// ...

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

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

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

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

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

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

Ответ 20

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

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

Этот код выходит из строя, потому что listNodes не является Array

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

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

Ответ 21

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

Ответ 22

Некоторые назвали бы это вопросом вкуса, но:

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";

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

Ответ 23

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

// 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

Ответ 24

Как насчет закрытия в JavaScript (аналогично анонимным методам в С# 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);

Ответ 25

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

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

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

После этого мы проверяем цепочку наследования, чтобы показать, что у собаки все еще есть тип 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 от Ray Djajadinata.

Ответ 26

Сверху моей головы...

Функции

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"

Ответ 27

Вы можете перехватывать исключения в зависимости от их типа. Цитируется из 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
}

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

Ответ 28

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

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);

Ответ 29

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

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

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

Ответ 30

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

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

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