Викликання функції без дужок


275

Сьогодні мені сказали, що функцію можна використовувати без дужок. Єдиний спосіб, про який я міг придумати, - це використання таких функцій, як applyабо call.

f.apply(this);
f.call(this);

Але для цього потрібні дужки applyта callзалишати нас на першій площі. Я також розглядав ідею передачі функції деяким обробникам подій, таким як setTimeout:

setTimeout(f, 500);

Але тоді стає питання "як ви звертаєтесь setTimeoutбез дужок?"

То яке рішення цієї загадки? Як можна викликати функцію в Javascript без використання дужок?


59
Не намагаюся бути грубим, але мушу запитати: чому?
jehna1

36
@ jehna1 Оскільки я відповідав на запитання про те, як викликаються функції та чи було виправлено, коли я сказав, що їм потрібні дужки в кінці.
Майк Клук

3
Дивовижний! Я не уявляв, що це запитання матиме таку тягу. Можливо, я мусив би сам це задати :-)
Аміт

5
Це таке питання, яке розбиває моє серце. Що корисного могло прийти через таке зловживання та експлуатація? Я хочу знати один дійсний випадок використання, де <insert any solution>об'єктивно краще чи корисніше, ніж f().
Дякую

5
@Aaron: ви кажете, що немає поважних причин, але, як вказується у відповіді Олександра О'Мари , можна розглянути питання про те, чи достатньо видалення дужок для запобігання виконанню коду - а як бути з прихованою конкуренцією Javascript? Звичайно, це абсурдна мета, коли намагаються написати доцільний код, але це забавна запитання.
PJTraill

Відповіді:


429

Існує кілька різних способів викликати функцію без дужок.

Припустимо, у вас визначена ця функція:

function greet() {
    console.log('hello');
}

Потім виконайте кілька способів дзвінка greetбез дужок:

1. Як конструктор

З newвами може викликати функцію без дужок:

new greet; // parentheses are optional in this construct.

Від MDN на newоратора :

Синтаксис

new constructor[([arguments])]

2. Як toStringчи valueOfреалізація

toStringі valueOfце спеціальні методи: вони викликаються неявно, коли необхідна конверсія:

var obj = {
    toString: function() {
         return 'hello';
    }
}

'' + obj; // concatenation forces cast to string and call to toString.

Ви можете (ab) використовувати цей шаблон для дзвінка greetбез дужок:

'' + { toString: greet };

Або з valueOf:

+{ valueOf: greet };

valueOfі toStringнасправді викликаються методом @@ toPrimitive (починаючи з ES6), і ви також можете реалізувати цей метод:

+{ [Symbol.toPrimitive]: greet }
"" + { [Symbol.toPrimitive]: greet }

2.b Переопределення valueOfв прототипі функції

Ви можете взяти попередню ідею, щоб замінити valueOfметод на Functionпрототипі :

Function.prototype.valueOf = function() {
    this.call(this);
    // Optional improvement: avoid `NaN` issues when used in expressions.
    return 0; 
};

Після цього ви можете написати:

+greet;

І хоча в дужці задіяні круглі дужки, фактичне викликання виклику не має дужок. Детальніше про це див. У блозі "Методи виклику в JavaScript, не дійсно викликаючи їх"

3. Як генератор

Ви можете визначити функцію генератора*), яка повертає ітератор . Ви можете викликати його за допомогою синтаксису розповсюдження або за допомогою for...ofсинтаксису.

Спочатку нам потрібен варіант генератора вихідної greetфункції:

function* greet_gen() {
    console.log('hello');
}

І тоді ми називаємо це без дужок, визначаючи метод @@ iterator :

[...{ [Symbol.iterator]: greet_gen }];

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

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

[,] = { [Symbol.iterator]: greet_gen };

або for ... ofконструкція, але вона має власні дужки:

for ({} of { [Symbol.iterator]: greet_gen });

Зауважте, що ви можете виконати вищезазначене і з оригінальною greetфункцією, але це призведе до виключення в процесі після greet його виконання (протестовано на FF та Chrome). Ви можете керувати винятком за допомогою try...catchблоку.

4. Як Геттер

@ jehna1 має повну відповідь на це, тому дайте йому кредит. Ось спосіб викликати дужки без функцій у глобальній області, уникаючи застарілого__defineGetter__ методу. Він використовує Object.definePropertyзамість цього.

Для цього нам потрібно створити варіант вихідної greetфункції:

Object.defineProperty(window, 'greet_get', { get: greet });

І потім:

greet_get;

Замініть windowбудь-яким вашим глобальним об’єктом.

Ви можете викликати оригінальну greetфункцію, не залишаючи сліду на глобальному об'єкті так:

Object.defineProperty({}, 'greet', { get: greet }).greet;

Але можна стверджувати, що у нас тут є круглі дужки (хоча вони не задіяні у фактичному виклику).

5. Як функція тегів

Оскільки ES6 ви можете викликати функцію, передаючи їй шаблон буквально з цим синтаксисом:

greet``;

Див. "Літеральні теги шаблонів" .

6. Як проксі-обробник

Оскільки ES6, ви можете визначити проксі :

var proxy = new Proxy({}, { get: greet } );

Тоді читання будь-якого значення властивості призведе до greet:

proxy._; // even if property not defined, it still triggers greet

Існує багато варіацій цього. Ще один приклад:

var proxy = new Proxy({}, { has: greet } );

1 in proxy; // triggers greet

7. Як перевірка екземплярів

instanceofОператор виконує @@hasInstanceметод на другий операнд, коли визначається:

1 instanceof { [Symbol.hasInstance]: greet } // triggers greet

1
Це дуже цікавий підхід із використанням valueOf.
Майк Клук

4
Ви забули про ES6:func``;
Ісмаель Мігель

1
Ви скористалися дужками, зателефонувавши до eval :)
trincot

1
У цьому випадку функція не виконується, а передається абоненту.
trincot

1
@trincot Інший метод буде можливий незабаром у оператора трубопроводу :'1' |> alert
denro

224

Найпростіший спосіб зробити це з newоператором:

function f() {
  alert('hello');
}

new f;

Хоча це неортодоксально і неприродно, воно працює і є абсолютно законним.

newОператор не вимагає дужок , якщо не використовується ніяких параметрів.


6
Виправлення, найпростіший спосіб зробити це - додати операнд, який змушує оцінювати вираження функції. !fпризводить до того ж, не створюючи примірник екземпляра функції конструктора (що вимагає створення нового цього контексту). Він набагато ефективніший і використовується в багатьох популярних бібліотеках (наприклад, Bootstrap).
THE TheChad

6
@THEtheChad - оцінка виразу - це не те саме, що виконання функції, але якщо у вас інший (приємніший? Дивніший?) Метод виклику (спричинення виконання) функції - опублікуйте відповідь!
Аміт

Точка випереджаючи знак оклику, або будь-який інший одиночного символ унарна оператора ( +, -, ~) в таких бібліотеках, щоб зберегти байти в згорнутої версії бібліотеки , де повертається значення не потрібно. (тобто !function(){}()) Звичайний синтаксис самовиклику - це (function(){})()(на жаль, синтаксис function(){}()не є перехресним браузером) Оскільки найпопулярніша функція самовиклику зазвичай не має нічого повертати, зазвичай це ціль цієї техніки збереження байтів
Ultimater

1
@THEtheChad Це правда? Я просто спробував це в Chrome і не отримував виконання функції. function foo() { alert("Foo!") };Наприклад: !fooповертаєтьсяfalse
Glenn 'devalias'

95

Можна використовувати геттери та сетери.

var h = {
  get ello () {
    alert("World");
  }
}

Запустіть цей сценарій лише за допомогою:

h.ello  // Fires up alert "world"

Редагувати:

Ми можемо навіть робити аргументи!

var h = {
  set ello (what) {
    alert("Hello " + what);
  }
}

h.ello = "world" // Fires up alert "Hello world"

Редагувати 2:

Ви також можете визначити глобальні функції, які можна запустити без дужок:

window.__defineGetter__("hello", function() { alert("world"); });
hello;  // Fires up alert "world"

І з аргументами:

window.__defineSetter__("hello", function(what) { alert("Hello " + what); });
hello = "world";  // Fires up alert "Hello world"

Відмова від відповідальності:

Як заявив @MonkeyZeus: Ніколи не використовуйте цей фрагмент коду у виробництві, незалежно від того, наскільки ваші наміри.


9
Суворо кажучи з POV, "я сокиру, що володіє божевільною людиною, яка має честь перейняти ваш код, тому що ви перейшли до більших і кращих речей; але я знаю, де ви живете". Я дуже сподіваюся, що це не нормально, хаха. Використання його всередині плагіна, в якому ви підтримуєте, прийнятне. Написання бізнес-логічного коду, будь ласка, тримайте, поки я загострюю сокиру :)
MonkeyZeus

3
@MonkeyZeus ха-ха, так! Додано відмову від кодерів майбутнього, які можуть знайти це від Google
jehna1

Насправді ця відмова від відповідальності занадто жорстка. Існують законні випадки використання "getter як виклик функції". Насправді він широко використовується у дуже вдалій бібліотеці. (ви хочете натяк ?? :-)
Аміт

1
Зауважте, що __defineGetter__ застаріле. Використовуйте Object.definePropertyзамість цього.
Фелікс Клінг

2
@MonkeyZeus - як я прямо писав у коментарі - цей стиль виклику функції використовується , широко і навмисно, у дуже успішній публічній бібліотеці як сам API, а не внутрішньо.
Аміт

23

Ось приклад для конкретної ситуації:

window.onload = funcRef;

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

Але я вважаю, що сірі області можуть бути нормальними для таких загадок :)


6
Це добре, але це не JavaScript, це DOM.
Аміт

6
@Amit, DOM є частиною JS-середовища браузера, яка все ще залишається JavaScript.
zzzzBov

3
@zzzzBov Не всі ECMAScript працюють у веб-переглядачі. Або ви серед людей, які використовують "JavaScript" для посилання саме на поєднання ES з HTML DOM, на відміну від Node?
Damian Yerrick

9
@DamianYerrick, я вважаю, що DOM є бібліотекою, доступною в деяких середовищах JS, що не робить його меншим JavaScript, ніж підкреслення, доступне в деяких середовищах. Це все ще JavaScript, це просто не частина, яка прописана в специфікації ECMAScript.
zzzzBov

3
@zzzzBov, "Хоча до DOM часто звертаються за допомогою JavaScript, він не є частиною мови JavaScript. До нього також можна отримати доступ до інших мов." . Наприклад, FireFox використовує XPIDL та XPCOM для реалізації DOM. Це не так очевидно, що виклик вказаної функції зворотного виклику реалізований у JavaScript. DOM - це не API, як інші бібліотеки JavaScript.
тринькот

17

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

1. locationіjavascript: протокол:

Однією з таких методик є зловживання javascript:протоколом наlocation дорученні.

Приклад роботи:

location='javascript:alert\x281\x29'

Хоча технічно \x28 і \x29все ще є дужки, коли код оцінюється, фактичний (і )символ не відображається. Круглі дужки виводяться в рядок JavaScript, який оцінюється при призначенні.


2. onerrorіeval :

Аналогічно, залежно від браузера, ми можемо зловживати глобальним onerror, встановлюючи йогоeval і кинувши щось, що буде строковим до дійсного JavaScript. Цей складніше, тому що браузери непослідовні в такій поведінці, але ось приклад для Chrome.

Робочий приклад для Chrome (не Firefox, інші перевірені):

window.onerror=eval;Uncaught=0;throw';alert\x281\x29';

Це працює в Chrome, тому що він throw'test'буде переданий 'Uncaught test'як перший аргумент onerror, що є майже дійсним JavaScript. Якщо ми замість цього зробимо, throw';test'це пройде 'Uncaught ;test'. Тепер у нас дійсний JavaScript! Просто визначте Uncaughtі замініть тест корисним навантаженням.


На закінчення:

Такий код справді жахливий , і його ніколи не слід використовувати , але інколи використовується в атаках XSS, тому мораль історії не покладатися на фільтрування дужок для запобігання XSS. Використання CSP для запобігання такого коду також було б хорошою ідеєю.


Це не те саме, що використовувати evalі записувати рядок без фактичного використання дужок.
Зев Шпіц

@ZenSpitz Так, але evalзазвичай потрібні круглі дужки, звідси цей викид фільтра зламається .
Олександр О'Мара

5
Можливо, \x28і \x29досі є дужками. '\x28' === '('.
тринькот

@trincot Який вигляд, про який я висвітлював у відповіді, це підхід бічного мислення, який показує причину, в якій це може бути зроблено. Я зрозумів це у відповіді.
Олександр О'Мара

Крім того, хто б проголосував за видалення цього запиту, перегляньте вказівки щодо видалення відповідей .
Олександр О'Мара

1

У ES6 у вас є те, що називається літерами з тегами шаблонів .

Наприклад:

function foo(val) {
    console.log(val);
}

foo`Tagged Template Literals`;


3
Дійсно, це відповідь, яку я опублікував за 1,5 року до вашої ... не впевнений, чому він заслуговує нової відповіді?
трінкот

2
тому що деякі інші люди, які хочуть реалізувати те саме зараз, можуть використовувати нову відповідь.
mehta-rohan

3
@ mehta-rohan, я не розумію цього коментаря. Якщо це матиме сенс, то чому б не дублювати одну і ту ж відповідь над і над виграшем? Я не бачу, як це було б корисно.
trincot
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.