Приховані особливості JavaScript? [зачинено]


312

Як ви вважаєте, які «приховані функції» JavaScript повинні знати кожен програміст?

Побачивши чудову якість відповідей на наступні запитання, я подумав, що прийшов час задати JavaScript.

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


1
Хіба ви не мали на увазі "Побачивши точки відліку та переглянувши це інше питання, яке я приваблював, я подумав, що я задаю майже те саме питання, щоб підвищити своє"? ;-)
Боббі Джек

1
Звичайно, песиміст. :) Я б подумав зробити це питанням спільноти. Крім того, після отримання певної кількості балів все повернення зменшується.
Аллайн Лалонде

1
Досить справедливо - це не виглядає так, як ніби вам потрібен представник! Я думаю, що у мене просто велика проблема з C # one - мені не зовсім схоже на тип запитання, для якого призначений цей сайт.
Боббі Джек

3
Так, можливо, ні, але я знайшов знання у відповідях чудовими. Я думаю, що вам буде важко виставити середньому програмісту C # на все це в одному місці, якби не так. Потрібні роки грати з ним, щоб скласти такий самий важко виграний список.
Аллайн Лалонде

7
Я пишу JavaScript професійно вже 10 років, і я дізнався щось із трьох із цієї теми. Спасибі, Алан!
Ендрю Хеджес

Відповіді:


373

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

117
Варто зауважити, що хоча аргументи діють як масив, це не власне масив javascript - це просто об’єкт. Таким чином, ви не можете робити join (), pop (), push (), slice () тощо. (Ви можете перетворити його в реальний масив, якщо хочете: "var argArray = Array.prototype.slice.call (аргументи);")
Jacob Mattison

51
Варто також зазначити, що доступ до об’єкту Arguments порівняно дорогий - найкращі приклади - у Safari, Firefox та Chrome nightlies, де лише посилання на argumentsоб'єкт робить виклик функції набагато повільнішим - наприклад. якщо (помилкові) аргументи; зашкодить перф.
olliej

48
У цьому ж аргументі аргументи мають властивість "callee", яка є самою поточною функцією. Це дозволяє робити рекурсію з анонімними функціями, класно!
Вінсент Роберт

4
@Nathan "f (x, y, z)" виглядає краще, ніж "f ([x, y, z])".
Марк Сідаде

16
@ Вінсент Роберт: зауважте, що arguments.calleeйого застаріло.
кен

204

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

Але я візьму для вас лише один, завжди використовуйте ===і !==замість ==і!=

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

==не є перехідним. Якщо ви використовуєте, ===це дасть помилковий для всіх цих тверджень, як очікувалося.


29
Прикро, що так багато людей думають, що Крокфорд всезнає. Зрозуміло, хлопець стоїть прямо на позначці з більшістю своїх зауважень, але я перестаю давати його речі ковдрою схвалення, як так багато дияволів ...
Джейсон Бантінг,

21
Я друге попередження Джейсона. Книга сама по собі дуже цікаво, і це дійсно дає багато хороших порад, але постійного струму далеко надто впевнений , що його спосіб робити речі , це єдино правильний шлях, все інше «дефектний». Якщо вам потрібні кілька прикладів, подивіться його відповіді в групі JSLint Yahoo Group.
Zilk

30
Використовувати === замість == - це корисна порада, якщо вас бентежить динамічне введення тексту та просто хочете, щоб це було "справді" дорівнює. Ті з нас, хто розуміє динамічне введення тексту, можуть продовжувати використовувати == у ситуаціях, коли ми знаємо, що ми хочемо зробити це, як у 0 == '' або 0 == '0'.
thomasrutter

20
Ну == і === не про динамічне введення тексту. == робить тип примусу, який є іншим звіром. Якщо ви знаєте, що ви хочете передати на рядок / число / тощо, тоді ви це робите прямо.
Рене Саарсоо

15
Я думаю, що найстрашніша частина ==- '\n\t\r ' == 0=> true...: D
Шрікант Шарат

189

Функції є громадянами першого класу в 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();
}

3
Існує три способи створення функцій у javascript: сума функцій (x, y, z) {return (x + y + z); } і var sum = нова функція ("x", "y", "z", "return (x + y + z);"); інші способи.
Маріус

6
Концепція функцій як даних, безумовно, виграє великі моменти в моїй книзі.
Джейсон Бантінг

Я щойно оновив зразок, щоб показати, як використовувати "приватну" функцію, яка існує лише в межах певної функції.
Кріс Пітшманн

new Function()так само зло, як і eval. Не використовувати.
Ніколас

11
не впевнений, що це прихована функція ... більше схожа на основну функцію.
Клавдіу

162

Ви можете скористатися оператором 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

22
Не настільки розумно, що перевіряє наявність ключа, а не значення. x у списку; працює лише тому, що x [1]! = null, а не тому, що значення 1 є.
Армін Ронахер

1
Я не використовував техніку в той час, тому я забув, що раніше я фактично використовував об'єктні літерали. Дякуємо за виправлення.
Марк Сідаде

34
Також будьте уважні: оператор in також випробовує прототип ланцюга! Якщо хтось поставив властивість під назвою "5" на Object.prototype, другий приклад поверне справжнє значення, навіть якщо ви назвали "5 у списку (1, 2, 3, 4)" ... Ви б краще скористатись hasOwnProperty метод: list (1, 2, 3, 4) .hasOwnProperty (5) поверне помилковий, навіть якщо Object.prototype має властивість '5'.
Martijn

3
Для самого загального рішення, яке може перевірити, чи має Об'єкт своє властивість, навіть якщо він названий "hasOwnProperty", вам потрібно пройти весь шлях до: Object.prototype.hasOwnProperty.call (об'єкт, ім'я) ;
Кріс Коваль

1
@Kris, за винятком випадків, коли хтось перезаписує Object.prototype.hasOwnProperty;)
Нік

153

Призначення змінних за замовчуванням

Ви можете використовувати логічний або оператор ||у виразі призначення, щоб надати значення за замовчуванням:

var a = b || c;

aМінлива отримає значення cтільки тоді , коли bце falsy (якщо є 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!";

Ви повинні бути обережними, тому що символ поряд із \ обов'язковим повинен бути термінатором рядка, якщо у вас є пробіл після, \наприклад, код буде виглядати точно так само, але він підніме «a» SyntaxError.


28
Не, якщо це недійсне, якщо воно вважається помилковим. a = 0 || 42; дасть вам 42. Це можна порівняти з Python або, а не C # 's ?? оператор. Якщо ви хочете поведінку C #, зробіть a = (b === null)? в: б;
Армін Ронахер

Він також працює у Visual Studio, якщо ви розвиваєтесь на ASP.NET :)
chakrit

2
Я хотів би, щоб там було || лише для невизначених. Мене сьогодні це покусало за 0, оскільки я хотів створити емуляцію перевантаженого методу, щоб останній аргумент був необов’язковим і замість цього використовувалося значення за замовчуванням.
egaga

+1 цей трюк використовується фрагментом Google Analytics за замовчуванням. `var _gaq = _gaq || []; `; це заважає зайвим користувачам не перезаписати власну роботу.
Яхель

2
Я не знав про багаторядкову буквену техніку. Це фантастично, дякую.
Charlie Flowers

145

У JavaScript немає області блоку (але він закритий, так що давайте назвемо його навіть?).

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

3
Це добре. Це дійсно важлива відмінність від більшості мов на зразок C.
Мартін Кларк

9
Ви завжди можете виконати "var tmp = function () {/ * block range * /} ();". Синтаксис некрасивий, але він працює.
Joeri Sebrechts

3
Або ви можете використовувати "хай", якщо це лише Firefox: stackoverflow.com/questions/61088/…
Євген Йокота

10
або просто: (функція () {var x = 2;}) (); попередження (typeof x); // невизначено
Пім Джагер

@Pim: JSLint каже: "Перемістіть виклик у паролі, які містять функцію." Поряд із "Очікується рівно один пробіл між 'функцією' та '('.".
Hello71,

144

Ви можете отримати доступ до властивостей об'єкта за допомогою, []а не з.

Це дозволяє шукати властивість, що відповідає змінній.

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 подвигів.


eval є злим, хоча рідко необхідним
Doug Domeny

Я ніколи не використовую eval і пам'ятаю, коли це відкрив. Це мене дуже зраділо.

Підводячи підсумок, доступ до властивостей об’єкта можна отримати як через крапки, так і підписи
Russ Cam

9
Цікаво відзначити, що точкове посилання - це насправді синтаксичний цукор для брекетрефа. foo.bar, за словами специфікації, все одно поводиться так само foo["bar"]. також зауважте, що все є властивістю рядка. навіть коли ви робите доступ до масиву, array[4]4 перетворюється на рядок (знову-таки, принаймні відповідно до специфікації ECMAScript v3)
Клавдіу,

Я думаю, кожен програміст JS повинен це знати.
Cem Kalyoncu

144

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

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

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


50
Нічого, чудовий ресурс. Миттєво краще, ніж шалені школи w3schools ...
НезадоволенняЗаголошено

11
Вам навіть не потрібно Google, якщо ви перебуваєте на Firefox: просто введіть "array mdc" в адресний рядок і натисніть Enter.
Саша Чедигов

2
найкраща частина - це питання про переповнення стека на першій сторінці результатів :)
Jiaaro,

5
Пропозиція цього: promojs.com , початкова SEO-ініціатива для подальшого покращення результатів MDC в результатах пошуку Google.
Яхель

3
Зараз центр MDN doc, тому ключове слово 'mdc' все ще діє :)
Алеадам,

143

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

Встановіть Firebug і використовуйте console.log ("привіт"). Набагато краще, ніж використання випадкового сигналу (); я пам'ятаю, що робив багато років тому.


12
Просто не забудьте видалити консольні заяви перед тим, як випускати свій код іншим, у кого може не встановлено Firebug.
Кріс Ное

161
журнал функцій (msg) {if (console) console.log (msg) else alert (msg)}
Josh,

4
Ще краще, передуйте операторам журналу з ';;;' а потім minify піклується про це за вас. (Принаймні, модуль Perl, який я використовую, має цю особливість, і стверджує, що це звичайне явище.)
Кев

10
Джош: Це не буде працювати, оскільки консоль не визначена. Ви можете перевірити консоль typeof! == "undefined" або window.console.
Елі Грей

23
Завжди включайте: if (typeof ('console') == 'undefined') {console = {log: function () {}}; } тоді ви можете продовжувати використовувати console.log, і він просто нічого не робить.
gregmac

120

Приватні методи

Об'єкт може мати приватні методи.

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

16
Це насправді не приватна функція - це більше змінна функції в локальній області.
Кіт

6
Щоправда, але за всіма оперативними визначеннями я можу придумати, що це метод. Це блок коду з ім'ям, який має доступ до стану екземпляра і його може бачити лише той екземпляр. Яке ваше визначення приватного методу?
Аллайн Лалонде

14
@Zach, точно! Легко, витративши роки, працюючи з навчальними мовами на базі класу, забути, що вони є лише однією реалізацією концепцій ОО. Звичайно, різні бібліотеки, які намагаються втиснути OO, що базується на
квазікласових класах, також

5
Цікаво, чи person1 має законний блог? ;-)
travis

4
+1 для заарештованого посилання на розробку
Доменік

99

Також згадується в "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.

13
Роблячи огляди коду, завжди шукайте цей. Відмова від "10" - це поширена помилка, яка залишається непоміченою у більшості тестів.
Дуг Домені

Мене опікували проблеми радіації років тому і ніколи не забували щось таке контрінтуїтивне як таке. Чудова річ, яку слід зазначити, адже це змусить вас задуматися на деякий час.
JamesEggers

4
Чому б не використовувати Math.floorабо Number? 10 === Math.floor("010"); 10 === Number("010");пливе:42 === Math.floor("42.69"); 42.69 === Number("42.69");
просто хтось

1
@ Infinity Якщо ви вже не розмістили відповідь, вам слід. Я не мав уявлення, що це так просто, як це переосмислити вбудовану функціональну поведінку. Звичайно, слід зблизитись з будь-якими кодовими пакетами, які вони позичають на інших сайтах. Цю нешкідливу parseIntфункцію легко можна було б зробити, щоб зробити щось не таке нешкідливе.
боб-

6
@ Infinity: що з переосмисленням fn, щоб виділити "помилку кодування"? __parseInt = parseInt; parseInt = function (str, base) { if (!base) throw new Error(69, "All your base belong to us"); return __parseInt(str, base); }
JBRWilkinson

97

Функції є об'єктами і тому можуть мати властивості.

fn = функція (x) {
   // ...
}

fn.foo = 1;

fn.next = функція (y) {
  //
}

13
Це дуже корисна порада. Наприклад, ви можете встановити значення за замовчуванням як властивість функції. Наприклад: myfunc.delay = 100; Тоді користувачі можуть змінити значення за замовчуванням, і всі виклики функцій використовуватимуть нове значення за замовчуванням. Наприклад: myfunc.delay = 200; myfunc ();
BarelyFitz

Корисно ... і небезпечно!
palswim

Виглядає неохайно, навіщо використовувати це замість змінної?
імгненцесуна

1
@instantsetsuna: Чому є ще одна окрема змінна? Як завжди, це зводиться до "використовувати його, коли це доречно / корисно" ;-)
VolkerK

91

Я повинен би сказати функції самовиконання.

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

Оскільки у Javascript немає області блоку , ви можете використовувати функцію самовиконання, якщо ви хочете визначити локальні змінні:

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

Тут myvarне втручається чи не забруднює глобальну сферу, і зникає, коли функція припиняється.


2
Для чого це корисно? Ви отримуєте такі ж результати, якщо ставити попередження поза функцією.
PotatoEngineer

7
Справа не в оповіщенні, а в тому, щоб визначити та виконати функцію відразу. Ви могли б зробити так, що функція самовиконання повертає значення і передає функцію як параметр іншій функції.
ScottKoon

5
@Paul це добре для інкапсуляції.
Майк Робінсон,

22
Це також добре для визначення рівня блоків.
Джим Хунзікер

24
Так, я вкладаю всі свої .jsфайли в анонімну функцію самовиконання і додаю до windowоб'єкта все, що мені хочеться, глобально доступне всередині нього . Запобігає забрудненню глобального простору імен.
cdmckay

83

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

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

23
Ніколи не знав про першу частину. Приємно!
mcjabberz

1
Аналогічно можна дізнатися, скільки аргументів очікує функція function.length.
Хаві

6
@Xavi, що є першою частиною відповіді
pramodc84

79

Ось кілька цікавих речей:

  • За порівнянні NaNз чим - або (навіть NaN) завжди помилково, що включає в себе ==, <і >.
  • NaN Позначає не число, але якщо ви запитаєте тип, він фактично повертає число.
  • Array.sort може приймати функцію порівняння та викликається драйвером, що нагадує швидкий корт (залежить від реалізації).
  • Регулярне вираження "константи" може підтримувати стан, як і останнє, що вони відповідали.
  • Деякі версії JavaScript дозволяють вам отримати доступ $0, $1, $2члени на регулярний вираз.
  • nullна відміну від усього іншого. Це не є ні об'єктом, ні булевим числом, ні числом, а й рядком undefined. Це трохи схоже на "замінник" undefined. (Примітка: typeof null == "object")
  • У найвіддаленішому контексті thisвиходить інакше неіменний [Глобальний] об’єкт.
  • Оголошення змінної за допомогою var, замість того, щоб просто покладатися на автоматичне оголошення змінної, дає реальним шансам оптимізувати доступ до цієї змінної.
  • withКонструкція буде знищувати такий optimzations
  • Імена змінних можуть містити символи Unicode.
  • Регулярні вирази JavaScript фактично не є регулярними. Вони ґрунтуються на регексах Perl, і можна побудувати вирази за допомогою головних головок, які оцінюють дуже багато часу.
  • Блоки можна мітити і використовувати як цілі break. Петлі можна мітити і використовувати як ціль continue.
  • Масиви не рідкісні. Встановлення 1000-го елемента порожнього масиву має заповнити його undefined. (залежить від реалізації)
  • if (new Boolean(false)) {...} виконає {...}блок
  • Двигун регулярних виразів Javascript є специфічним для впровадження: наприклад, можна записати "не портативні" регулярні вирази.

[оновлено трохи у відповідь на хороші коментарі; будь ласка, дивіться коментарі]


5
null насправді є (спеціальним) об'єктом. typeof nullповертає "об'єкт".
Атес Горал

4
Ви також можете отримати об’єкт [Global] з будь-якого місця: var glb = function () {return this; } ();
Zilk

2
Глобальним об’єктом у JavaScript у браузері є об’єкт вікна. Коли в глобальному масштабі роблять: window.a == a;
Пім Джагер

8
"Масиви не рідкі" залежить від реалізації. Якщо встановити значення [1000] і подивитися на [999], так, так undefined, але це лише значення за замовчуванням, яке ви отримуєте, шукаючи індекс, який не існує. Якщо ви перевірили [2000], це теж було б undefined, але це не означає, що ви ще виділили пам'ять. У IE8 деякі масиви щільні, а деякі - рідкісні, залежно від того, як почувався двигун JScript у той час. Детальніше читайте тут: blogs.msdn.com/jscript/archive/2008/04/08/…
Кріс Нільсен

2
@Ates і @SF: typeof повертає "об'єкт" для ряду різних типів. Але як тільки ви знаєте, як він працює і які типи ідентифікують як «об’єкт», він принаймні є надійним та послідовним у своїй реалізації.
thomasrutter

77

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

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

Ви можете робити просто 0xFFі т.д., не потрібно +"0xFF".
nyuszika7h

9
@ Nyuszika7H: ти начебто не вистачаєш точки, яка примушує інші примітиви та об'єкти до чисел. Звичайно, ви можете просто писати 0xFF, майже так само, як ви можете писати 1замість +true. Я пропоную вам використати +("0x"+somevar)як альтернативу parseInt(somevar, 16), якщо хочете.
Енді Е

75

" Методи розширення в 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
Це, як правило, вважається поганою ідеєю, оскільки інший код (не ваш) може робити припущення щодо об’єкта Array.
Кріс Ное

39
Загалом вважається поганою ідеєю робити припущення щодо об’єкта Array. :(
безглазкість

Uhmmmm .. додаткові масиви javascript 1.6? indexOf? дзвонить якісь дзвони?
Бретон

2
@Breton: Це не щось специфічне для класу Array, це лише приклад. Я використовую це для розширення нової дати (). ToString (); метод, що дозволяє використовувати рядок маски. Будь-який об’єкт можна розширити, і всі його екземпляри отримують новий метод.
Естебан Кюбер

1
@Mathias: мова не йде про DOM.
долмен

60

Щоб правильно видалити властивість з об’єкта, слід видалити властивість, а не просто встановити його на невизначений :

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

obj.prop2 = undefined;

for (var key in obj) {
    ...

Властивість prop2 все ще буде частиною ітерації. Якщо ви хочете повністю позбутися prop2 , вам слід:

delete obj.prop2;

Властивість prop2 більше не відображатиметься під час перегляду властивостей.


3
Зауважте, що оператор видалення не обходиться без специфічних для браузера химерностей. Наприклад, це не вдасться з великою помилкою, якщо ви спробуєте його в IE, і об'єкт не є власним об'єктом JS (навіть при видаленні властивості, яке ви додали самі). Він також не призначений для видалення змінної, як при видаленні myvar; але я думаю, що це працює в деяких браузерах. Код у наведеній вище відповіді здається досить безпечним.
thomasrutter

до речі, невизначена може бути і змінною! Спробуйте var undefined = "щось"
Йоганн Філіп Стратхаузен

57

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 оператора для такого призначення.

Дивіться також: Чи є законне використання для JavaScript оператора "з"?


29
Слід уникати загальноприйнятої мудрості та витримки. Якщо об'єкт користувача не мав жодного з згаданих вами властивостей, змінна поза псевдоскладом блоку буде змінена. Таким чином лежать помилки. Більше інформації на yuiblog.com/blog/2006/04/11/with-statement-considered-harmful
Alan Storm

1
Шог, заперечення полягають не в тому, що змінені написаними помилками, а в тому, що вони переглядають блок коду і вміють з упевненістю сказати, що робить будь-який конкретний рядок у цьому блоці. Оскільки об’єкти Javascript настільки динамічні, ви не можете точно сказати, які властивості / учасники мають у будь-який момент.
Алан Шторм

2
Амінь - якщо я побачив у будь-якому знайденому JS твердження "з", я би його усунув і поставив під сумнів розробника, який його написав, щоб переконатися, що він знає, чому це не гарна річ використовувати його ... "приховану функцію?" Більше схоже на "огидну функцію".
Джейсон Бантінг

1
розгляньте складніший ланцюг abcd "з (abc) {d.foo = bar;} є потужним і не є властивим до помилок. Ключ полягає в тому, щоб зменшити корінь на один рівень вгору. І неправильно написати ім'я змінної? Ви вводите помилку якщо ви робите це там , де ви це робите, незалежно від "с".
annakata

4
Дуглас Крокфорд нещодавно сказав, що "з" - це одна з найгірших частин JavaScript в .NET Rocks! подкаст.
серцевина

51

Методи (або функції) можна викликати на об'єкті, не такого типу, з яким вони були розроблені для роботи. Це чудово, щоб викликати рідні (швидкі) методи на користувацьких об'єктах.

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

Цей код виходить з ладу, оскільки listNodesвін не єArray

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

Цей код працює тому, що listNodesвизначає достатньо властивостей, схожих на масив (довжина, [] оператор), якими повинен користуватися sort().


43

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

42

Дехто назвав би це питанням смаку, але:

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

Тринаціональний оператор може бути прикований до дії, як схема (умова ...):

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

Добре працює і з рекурсією :)


Мені подобається синтаксис предиката, який ви даєте. Я ніколи не думав подібного ланцюга. акуратний.
Аллайн Лалонде

2
Так, у JavaScript є оператор switch (). :-)
статик

Я не є великим прихильником перекладацьких заяв - вони артефакт С, а не функціональне програмування. У моєму прикладі оператор перемикання все ще потребує трьох окремих висловлювань, починаючи з "foo =" - очевидного непотрібного повторення.
Андрій Федоров

14
Я, наприклад, вітаю термального оператора.
thomasrutter

8
Перечитавши, я хотів би зазначити, що це не "створення коду схожим на іншу мову", а фактично спрощення семантичного значення коду: коли ви намагаєтесь сказати "встановіть foo на один з трьох речі ", це твердження, яке повинно починатися з" foo = ... ", а не з" якщо ".
Андрій Федоров

41

Числа - це також об’єкти. Тож ви можете робити цікаві речі, такі як:

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

О БОЖЕ МІЙ! Я не знав про toString (radix) ...
Атес Горал

1
Ця реалізація timesне є ефективною: Math.floorвикликається кожен раз, а не один раз.
долмен

33

Як щодо закриття в 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);

1
Я не впевнений, але можу повернутись (numberToCheck> lowerBound)? правда: хибна; просто станьте віддачею (numberToCheck> LowerBound); просто намагаюся підвищити моє розуміння ...
Давид спить

4
Я б сказав, що анонімні функції в C # еквівалентні закриттю, а не навпаки :)
vava

11
Закриття та анонімні функції - це окремі, чіткі поняття. Те, що функції можна створити без імені, має анонімні функції. Що змінна в області "створення" пов'язана зі створеною функцією - це закриття. Коротше кажучи, закриття більше нагадує приховану глобальну змінну.
slebetman

1
Це правда. Тільки коли анонімні методи використовують змінну з області створення, це схоже на закриття. Я оновив англійську відповідь. Це все ще залишає бажати кращого, але я розгублений за правильну англійську мову.
Тайлер

2
Я не думаю, що це найкращий чи найпростіший для розуміння приклад того, що таке закриття. Просто кажу. Сенс закриття полягає в тому, що навіть коли купа змінних «виходить із сфери застосування», вони все ще можуть залишатися доступними функції, яка була спочатку визначена в цій області. У наведеному вище прикладі це означає, що змінна LowerBound все ще доступна тією внутрішньою, анонімною функцією, навіть коли зовнішня функція buildGreaterThanFunction закінчується.
thomasrutter

32

Ви також можете розширювати (успадковувати) класи та переосмислювати властивості / методи за допомогою прототипу ланцюжка-прототипу16, на який посилалися.

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

Після цього ми створюємо клас Dog, який розширює Pet і переосмислює метод .toString (), знову змінюючи його поведінку (поліморфізм). Крім того, ми додаємо ще деякі властивості до дочірнього класу.

Після цього ми перевіряємо ланцюжок спадкування, щоб виявити, що собака все ще типу типу собака, типу 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 Рея Джаджадіната.


31

Ви можете ловити винятки залежно від їх типу. Цитується з 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, і тому на нього не можна покладатися, окрім окремих веб-переглядачів.


29
Я не міг у цьому допомогти: ловиш (мене, якщо ти зможеш)
Ates Goral

6
Прочитайте примітку із цитованої вами сторінки MDC: Застереження про умовний вилов - це розширення Netscape (а отже, Mozilla / Firefox), яке не є частиною специфікації ECMAScript, і тому на нього не можна покладатися, окрім окремих веб-переглядачів.
Jason S

31

Зверху моєї голови ...

Функції

argument.callee посилається на функцію, яка розміщує змінну "аргументи", тому її можна використовувати для повторного анонімного функціонування:

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"

Речі, які ви згадуєте ... Чи реалізовані вони у всіх браузерах?
cllpse

4
Ні. Я майже впевнений, що мозаїки не вистачає більшості з них.
jsight

2
Функції javascript, так, вони реалізовані у всіх основних браузерах (IE6 / 7, FF2 / 3, Opera 9+, Safari2 / 3 та Chrome). document.querySelectorAll ще не підтримується у всіх браузерах (це версія W3C $ JQuery $ () та $$ ()) прототипу
Лев

6
arguments.calleeзастаріло і буде
викинуто

не зовсім правда. Об'єктний ключ не може (а точніше, не повинен) використовувати рядок "hasOwnProperty" як ім'я, оскільки це перекриє вбудований метод об'єкта.
Бретон

29

Ви можете використовувати об'єкти замість комутаторів більшу частину часу.

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

3
Ось так Python проходить без заяви коммутатора.
outis

2
Проблема в тому, що вона завжди оцінює всі випадки.
Корнель

@porneL це правда, але це дає певні переваги: ​​Це логічно чистіше: випадки - це рядки, які шукаються на хештебі, а не вирази, які кожен повинен оцінювати на рівність, поки один не повернеться істиною. Таким чином, хоча більше "значень" оцінюється, менше "клавіш" оцінюється. Об'єкти можна динамічно генерувати та змінювати для подальшої масштабованості, відображати для друку користувальницького інтерфейсу або створювати документи і навіть замінювати на динамічну функцію "пошуку", що краще, ніж копіювати / вставляти випадки. Немає плутанини щодо перерв, провалів або значень за замовчуванням. Можна JSON серіалізувати ...
Бретон

@porneL о так, і знову ж таки, що стосується масштабованості, об'єкт навіть легко може бути виведений у зовнішню конфігурацію або файл даних, дещо простішу зміну, ніж із заявою перемикача. Але тривіальним, якщо розроблено з урахуванням об'єкта з.
Бретон

Я знаю, що це пізній запис, але якщо у вас є певна логіка перевірки типу, коли масив коли-небудь буде працювати з вашим прикладом? var arr = []; typeof arr; // object
keeganwatkins

25

Обов’язково використовуйте метод hasOwnProperty під час ітерації через властивості об'єкта:

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

Це робиться для того, щоб ви отримали доступ лише до прямих властивостей anObject , а не використовувати властивості, що знаходяться вниз по ланцюгу прототипу.


23

Приватні змінні з публічним інтерфейсом

Він використовує акуратний маленький трюк з визначенням функції самовиклику. Все, що знаходиться всередині об'єкта, який повертається, доступне в загальнодоступному інтерфейсі, а все інше - приватне.

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

1
Це називається модульним шаблоном, як його озвучили Ерік Міралія на yuiblog.com/blog/2007/06/12/module-pattern Я думаю, що назва вводить в оману, його слід називати Singleton Pattern або щось подібне. Я також можу додати, що загальнодоступні методи також можуть викликати інші загальнодоступні методи, використовуючи об’єкт 'цей'. Цей шаблон я постійно використовую в своєму коді, щоб зберігати впорядковані та чисті речі.
mikeycgto
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.