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


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.