Вызов функции без скобок

275

Сегодня мне сказали, что можно вызывать функцию без скобок. Единственное, о чем я мог думать, это использовать такие функции, как applyили call.

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

Но они требуют скобок applyи callоставляют нас на первом месте. Я также рассмотрел идею передачи функции некоторому обработчику событий, например setTimeout:

setTimeout(f, 500);

Но тогда возникает вопрос "как вы вызываете setTimeoutбез скобок?"

Так в чем же решение этой загадки? Как вы можете вызвать функцию в Javascript без использования скобок?

Майк Клак
источник
59
Не пытаюсь быть грубым, но я должен спросить: почему?
jehna1
36
@ jehna1 Потому что я отвечал на вопрос о том, как функции вызываются и был исправлен, когда я сказал, что в конце они требуют скобок.
Майк Клак
3
Удивительный! Я не предполагал, что у этого вопроса будет такая большая тяга .. Возможно, я должен был задать его сам :-)
Amit
5
Этот вопрос разбивает мне сердце. Что хорошего может принести такое злоупотребление и эксплуатация? Я хочу знать один действительный случай использования, где <insert any solution>объективно лучше или полезнее, чем f().
Спасибо,
5
@ Аарон: вы говорите, что нет веской причины, но, как указывает ответ Александра О'Мара , можно подумать над вопросом, достаточно ли удаления скобок для предотвращения выполнения кода - или как насчет запутанной конкуренции Javascript? Конечно, это абсурдная цель при попытке написания поддерживаемого кода, но это забавный вопрос.
PJTraill

Ответы:

429

Есть несколько разных способов вызова функции без скобок.

Давайте предположим, что вы определили эту функцию:

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

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

1. Как конструктор

С помощью newвы можете вызвать функцию без скобок:

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

Из МДН на 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
trincot
источник
1
Это очень интересный подход valueOf.
Майк Клак
4
Вы забыли про ES6:func``;
Исмаэль Мигель
1
Вы использовали скобки, позвонив в eval :)
trincot
1
В этом случае функция не выполняется, но передается вызывающей стороне.
Trincot
1
@trincot Еще один метод будет возможен в ближайшее время с оператором конвейера :'1' |> alert
deniro
224

Самый простой способ сделать это с newоператором:

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

new f;

Хотя это неортодоксально и неестественно, это работает и совершенно законно.

newОператор не требует скобок , если не используется никаких параметров.

Amit
источник
6
Коррекция, самый простой способ сделать это - добавить операнд, который заставляет вычислить выражение функции. !fприводит к тому же самому без создания экземпляра функции конструктора (которая требует создания нового контекста). Он намного более производительный и используется во многих популярных библиотеках (например, Bootstrap).
ThetheChad
6
@THEtheChad - оценка выражения - это не то же самое, что выполнение функции, но если у вас есть другой (более приятный? Более удивительный?) Метод вызова (вызывающий выполнение) функции - отправьте ответ!
Амит
Точка предваряя восклицательный знак, или любой другой одиночного символ унарного оператора ( +, -, ~) в таких библиотеках, чтобы сохранить байты в свернутой версии библиотеки , где возвращаемое значение не требуется. (т.е. !function(){}()) Обычный синтаксис самозвонка (function(){})()(к сожалению, синтаксис function(){}()не кроссбраузерный) Поскольку самой самозванной функции обычно нечего возвращать, она обычно является целью этого метода сохранения байтов
Ultimater
1
@THEtheChad Это правда? Я только попробовал это в Chrome, и не получал выполнение функции. function foo() { alert("Foo!") };Например: !fooвозвращаетfalse
Гленн '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: Никогда не используйте этот кусок кода в производстве, независимо от того, насколько хороши ваши намерения.

jehna1
источник
9
Строго говоря, от POV: «Я - сумасшедший, владеющий топором, который имеет честь взять ваш код, потому что вы перешли к более крупным и лучшим вещам; но я знаю, где вы живете». Я действительно надеюсь, что это не нормально, смеется. Использование его внутри плагина, который вы поддерживаете, приемлемо. Написание кода бизнес-логики, пожалуйста, подождите, пока я точу свой топор :)
MonkeyZeus
3
@MonkeyZeus, ха-ха, да! Добавлен отказ от ответственности для кодировщиков будущего, которые могут найти это в Google
jehna1
На самом деле этот отказ от ответственности слишком резок. Существуют законные варианты использования для «getter как вызов функции». На самом деле он широко используется в очень успешной библиотеке. (хотел бы намек ?? :-)
Амит
1
Обратите внимание, что __defineGetter__не рекомендуется. Используйте Object.definePropertyвместо этого.
Феликс Клинг
2
@MonkeyZeus - как я явно писал в комментарии - это стиль вызова функции используется , широко и намеренно, в очень успешной публичной библиотеке как сам API, а не внутренне.
Амит
23

Вот пример для конкретной ситуации:

window.onload = funcRef;

Хотя это утверждение на самом деле не вызывает, но приведет к будущему вызову .

Но я полагаю, что серые области могут подойти для загадок, подобных этой :)

Jonathan.Brink
источник
6
Это хорошо, но это не JavaScript, а DOM.
Амит
6
@Amit, DOM является частью JS-среды браузера, которая до сих пор остается JavaScript.
zzzzBov
3
@zzzzBov Не все ECMAScript запускаются в веб-браузере. Или вы среди людей, которые используют «JavaScript», чтобы ссылаться именно на сочетание ES с HTML DOM, в отличие от Node?
Дамиан Йеррик
9
@DamianYerrick, я считаю, что DOM - это библиотека, доступная в некоторых средах JS, что делает ее не менее JavaScript, чем подчеркивание доступности в некоторых средах. Это все еще JavaScript, но это не та часть, которая указана в спецификации ECMAScript.
zzzzBov
3
@zzzzBov, «Хотя доступ к DOM часто осуществляется с помощью JavaScript, он не является частью языка JavaScript. К нему также могут обращаться другие языки». , Например, FireFox использует XPIDL и XPCOM для реализации DOM. Не совсем очевидно, что вызов указанной вами функции обратного вызова реализован в JavaScript. DOM не API, как другие библиотеки JavaScript.
Trincot
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
@trincot Что-то вроде того, о чем я говорил в ответе, это подход к латеральному мышлению, который показывает причину, по которой это может быть сделано. Я сделал это более ясным в ответе.
Александр О'Мара
Кроме того, кто бы ни проголосовал за удаление этого, просмотрите рекомендации по удалению ответов .
Александр О'Мара
1

В ES6 у вас есть то, что называется тегами литералов шаблонов .

Например:

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

foo`Tagged Template Literals`;

Идан Даган
источник
3
Действительно, это ответ, который я опубликовал за 1,5 года до твоего ... не уверен, почему он заслуживает нового ответа?
трино
2
потому что некоторые другие люди, которые хотят реализовать то же самое прямо сейчас, могут использовать новый ответ.
Мехта-Рохан
3
@ mehta-rohan, я не понимаю этот комментарий. Если это имеет смысл, то почему бы не дублировать один и тот же ответ снова и снова? Я не вижу, как это было бы полезно.
Trincot