Скрытые возможности JavaScript?
Как вы думаете, какие "скрытые возможности" JavaScript должен знать каждый программист?
После того, как я увидел отличное качество ответов на следующие вопросы, я подумал, что пришло время задать его для JavaScript.
- Скрытые возможности HTML
- Скрытые возможности CSS
- Скрытые возможности PHP
- Скрытые возможности ASP.NET
- Скрытые возможности C#
- Скрытые возможности Java
- Скрытые возможности Python
Хотя 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());