Wywoływanie funkcji bez nawiasów

275

Powiedziano mi dzisiaj, że można wywołać funkcję bez nawiasów. Jedynym sposobem, w jaki mogłem wymyślić, było użycie funkcji takich jak applylubcall .

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

Ale wymagają one nawiasów applyi callpozostawienia nas na pierwszym miejscu. Rozważałem także pomysł przekazania funkcji do jakiegoś modułu obsługi zdarzeń, takiego jak setTimeout:

setTimeout(f, 500);

Ale wtedy pojawia się pytanie „jak wywołujesz setTimeoutbez nawiasów?”

Więc jakie jest rozwiązanie tej zagadki? Jak można wywołać funkcję w JavaScript bez użycia nawiasów?

Mike Cluck
źródło
59
Nie próbuję być niegrzeczny, ale muszę zapytać: dlaczego?
jehna1
36
@ jehna1 Ponieważ odpowiadałem na pytanie, w jaki sposób wywoływane są funkcje i został poprawiony, gdy powiedziałem, że na końcu wymagają nawiasów.
Mike Cluck
3
Niesamowity! Nie wyobrażałem sobie, że to pytanie będzie miało taką przyczepność. Być może powinienem był zadać to pytanie :-)
Amit
5
To pytanie łamie mi serce. Co dobrego może przynieść takie nadużycie i wykorzystywanie? Chcę poznać jeden poprawny przypadek użycia, w którym <insert any solution>jest on obiektywnie lepszy lub bardziej przydatny niż f().
Dziękuję
5
@Aaron: mówisz, że nie ma żadnego uzasadnionego powodu, ale, jak wskazuje odpowiedź Alexandra O'Mary , można zastanowić się, czy usunięcie nawiasów jest wystarczające, aby zapobiec wykonaniu kodu - a co z zaciemnioną konkurencją JavaScript? Oczywiście jest to absurdalny cel przy próbie napisania łatwego do utrzymania kodu, ale jest to zabawna kolejka.
PJTraill,

Odpowiedzi:

429

Istnieje kilka różnych sposobów wywoływania funkcji bez nawiasów.

Załóżmy, że masz zdefiniowaną tę funkcję:

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

Następnie skorzystaj z kilku sposobów dzwonienia greetbez nawiasów:

1. Jako konstruktor

Za pomocą newmożesz wywołać funkcję bez nawiasów:

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

Z MDN na newoperatorze :

Składnia

new constructor[([arguments])]

2. Jako toStringlub valueOfwdrożenie

toStringi valueOfsą metodami specjalnymi: są wywoływane niejawnie, gdy konieczna jest konwersja:

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

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

Możesz (ab) użyć tego wzorca do wywołania greetbez nawiasów:

'' + { toString: greet };

Lub z valueOf:

+{ valueOf: greet };

valueOfi toStringsą w rzeczywistości wywoływane z metody @@ na prymitywną (od ES6), więc można również zaimplementować metodę:

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

2.b Przesłanianie valueOfw prototypie funkcji

Możesz wziąć poprzedni pomysł, aby zastąpić valueOfmetodę na Functionprototypie :

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

Gdy to zrobisz, możesz napisać:

+greet;

I chociaż w linii znajdują się nawiasy, faktyczne wywołanie wyzwalające nie ma nawiasów. Więcej informacji na ten temat można znaleźć na blogu „Metody wywoływania w JavaScript bez konieczności wywoływania ich”

3. Jako generator

Możesz zdefiniować funkcję generatora (z *), która zwraca iterator . Możesz to nazwać używając składni spreadu lubfor...of składnią.

Najpierw potrzebujemy wariantu generatora oryginalnej greetfunkcji:

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

Następnie nazywamy to bez nawiasów, definiując metodę iteratora @@ :

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

Zwykle generatory mają yieldgdzieś słowo kluczowe, ale funkcja nie musi być wywoływana.

Ostatnia instrukcja wywołuje funkcję, ale można to również zrobić za pomocą destrukcji :

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

lub for ... ofkonstrukcja, ale ma własne nawiasy:

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

Pamiętaj, że możesz to zrobić również z oryginalną greetfunkcją, ale spowoduje to wyjątek w tym procesie, po greet jej wykonaniu (przetestowane na FF i Chrome). Możesz zarządzać wyjątkiem za pomocątry...catch bloku.

4. Jako Getter

@ jehna1 ma na to pełną odpowiedź, więc daj mu kredyt. Oto sposób na wywołanie funkcji bez nawiasów w zakresie globalnym, unikając przestarzałej__defineGetter__ metody. Object.definePropertyZamiast tego używa .

W tym celu musimy stworzyć wariant oryginalnej greetfunkcji:

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

I wtedy:

greet_get;

Zamień na windowdowolny obiekt globalny.

Możesz wywołać oryginalną greetfunkcję bez pozostawiania śladu na obiekcie globalnym w następujący sposób:

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

Można jednak argumentować, że mamy tutaj nawiasy (chociaż nie są one zaangażowane w faktyczne wywołanie).

5. Jako funkcja znacznika

Od wersji ES6 można wywołać funkcję przekazującą jej dosłowność szablonu za pomocą następującej składni:

greet``;

Zobacz „Oznaczone literały szablonów” .

6. Jako osoba obsługująca proxy

Od wersji ES6 możesz zdefiniować serwer proxy :

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

A następnie odczyt dowolnej wartości właściwości wywoła greet:

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

Istnieje wiele odmian tego. Jeszcze jeden przykład:

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

1 in proxy; // triggers greet

7. Jako kontroler instancji

instanceofOperator wykonuje @@hasInstancesposób w drugim argumencie, gdy określona:

1 instanceof { [Symbol.hasInstance]: greet } // triggers greet
trincot
źródło
1
To bardzo interesujące podejście valueOf.
Mike Cluck,
4
Zapomniałeś o ES6:func``;
Ismael Miguel
1
Użyłeś
1
W takim przypadku funkcja nie jest wykonywana, ale przekazywana do dzwoniącego.
trincot
1
@trincot Wkrótce będzie możliwa inna metoda z operatorem rurociągu :'1' |> alert
deniro
224

Najłatwiej to zrobić z newoperatorem:

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

new f;

Jest to niekonwencjonalne i nienaturalne, ale działa i jest całkowicie legalne.

newOperator nie wymaga nawiasów jeśli nie stosuje się parametry.

Amit
źródło
6
Korekta, najłatwiejszym sposobem na to jest przygotowanie operandu, który wymusza ocenę wyrażenia funkcji. !fdaje to samo, bez tworzenia instancji funkcji konstruktora (co wymaga utworzenia nowego tego kontekstu). Jest znacznie wydajniejszy i jest wykorzystywany w wielu popularnych bibliotekach (takich jak Bootstrap).
THEChad
6
@THEtheChad - ocena wyrażenia nie jest tym samym, co wykonywanie funkcji, ale jeśli masz inną (ładniejszą? Bardziej zaskakującą?) Metodę wywoływania (powodującą wykonanie) funkcji - opublikuj odpowiedź!
Amit
Punktem poprzedzenie wykrzyknik lub jakikolwiek inny pojedynczy znak jednoargumentowy operator ( +, -, ~) w takich bibliotek jest zapisać bajt w zminimalizowanej wersji biblioteki, gdzie wartość powrotna nie jest potrzebne. (tj. !function(){}()) Zwykłą składnią samo-wywołującą jest (function(){})()(niestety function(){}()nie jest to przeglądarka internetowa) Ponieważ najwyższa funkcja samo-wywoływania zwykle nie ma nic do zwrócenia, zwykle jest to cel tej techniki zapisywania bajtów
Ultimater
1
@THEtheChad Czy to prawda? Właśnie wypróbowałem to w Chrome i nie otrzymałem wykonania funkcji. function foo() { alert("Foo!") };Na przykład: !foozwracafalse
Glenn „devalias”
95

Możesz używać pobierających i ustawiających.

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

Uruchom ten skrypt tylko z:

h.ello  // Fires up alert "world"

Edytować:

Możemy nawet argumentować!

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

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

Edycja 2:

Możesz także zdefiniować funkcje globalne, które można uruchomić bez nawiasów:

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

I z argumentami:

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

Zrzeczenie się:

Jak stwierdził @MonkeyZeus: Nigdy nie wolno używać tego fragmentu kodu w produkcji, bez względu na to, jak dobre są twoje intencje.

jehna1
źródło
9
Ściśle mówiąc z POV: „Jestem toporem dzierżącym topór, który ma zaszczyt przejąć twój kod, ponieważ przeszedłeś do większych i lepszych rzeczy, a jednak wiem, gdzie mieszkasz”. Naprawdę mam nadzieję, że to nie jest normalny lol. Używanie go wewnątrz wtyczki, którą utrzymujesz, jest dopuszczalne. Pisząc kod logiki biznesowej, proszę trzymać, a ja wyostrzam topór :)
MonkeyZeus,
3
@MonkeyZeus haha, tak! Dodano wyłączenie odpowiedzialności dla programistów przyszłości, którzy mogą to znaleźć od Google
jehna1
W rzeczywistości to wyłączenie odpowiedzialności jest zbyt surowe. Istnieją uzasadnione przypadki użycia „gettera jako wywołania funkcji”. W rzeczywistości jest szeroko stosowany w bardzo udanej bibliotece. (chciałbyś podpowiedź? :-)
Amit
1
Uwaga: __defineGetter__jest przestarzała. Użyj Object.definePropertyzamiast tego.
Felix Kling
2
@MonkeyZeus - jak wyraźnie napisałem w komentarzu - ten styl wywoływania funkcji jest używany , szeroko i celowo, w bardzo udanej bibliotece publicznej jako sam interfejs API, a nie wewnętrznie.
Amit
23

Oto przykład konkretnej sytuacji:

window.onload = funcRef;

Chociaż to stwierdzenie w rzeczywistości nie wywołuje, ale prowadzi do przyszłego wywołania .

Ale wydaje mi się, że szare obszary mogą być odpowiednie dla takich zagadek :)

Jonathan.Brink
źródło
6
Fajnie, ale to nie JavaScript, to DOM.
Amit
6
@Amit, DOM jest częścią środowiska JS przeglądarki, które wciąż jest JavaScript.
zzzzBov,
3
@zzzzBov Nie wszystkie ECMAScript działa w przeglądarce internetowej. A może należysz do osób, które używają „JavaScript” w odniesieniu do łączenia ES z HTML DOM, a nie Węzła?
Damian Yerrick
9
@DamianYerrick, uważam DOM za bibliotekę dostępną w niektórych środowiskach JS, co nie sprawia, że ​​JavaScript jest mniejszy niż podkreślenie dostępne w niektórych środowiskach. To wciąż JavaScript, to po prostu nie jest część, która jest określona w specyfikacji ECMAScript.
zzzzBov,
3
@zzzzBov, „Mimo że dostęp do DOM jest często uzyskiwany za pomocą JavaScript, nie jest on częścią języka JavaScript. Można go również uzyskać w innych językach.” . Na przykład FireFox używa XPIDL i XPCOM do implementacji DOM. Nie jest tak oczywiste, że wywołanie określonej funkcji wywołania zwrotnego jest zaimplementowane w JavaScript. DOM nie jest interfejsem API, jak inne biblioteki JavaScript.
trincot,
17

Jeśli zaakceptujemy podejście oparte na myśleniu bocznym, w przeglądarce istnieje kilka interfejsów API, których możemy użyć do wykonania dowolnego kodu JavaScript, w tym wywołania funkcji, bez żadnych znaków w nawiasach.

1. locationi javascript:protokół:

Jedną z takich technik jest nadużywanie javascript:protokołu przy locationprzydzielaniu.

Przykład roboczy:

location='javascript:alert\x281\x29'

Chociaż technicznie \x28 i \x29nadal są w nawiasach po ocenie kodu, rzeczywisty (i )znak nie pojawiają się. Nawiasy są zamykane za pomocą ciągu JavaScript, który jest oceniany przy przypisaniu.


2. onerrori eval:

Podobnie, w zależności od przeglądarki, możemy nadużywać globalnego onerror, ustawiając go evali rzucając czymś, co skreśli poprawny JavaScript. Ten jest trudniejszy, ponieważ przeglądarki są niespójne w tym zachowaniu, ale oto przykład dla Chrome.

Przykład roboczy dla Chrome (nie Firefox, inne niesprawdzone):

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

Działa to w Chrome, ponieważ throw'test'przejdzie 'Uncaught test'jako pierwszy argument onerror, który jest prawie prawidłowym JavaScript. Jeśli zamiast tego zrobimy, throw';test'to minie 'Uncaught ;test'. Teraz mamy prawidłowy JavaScript! Po prostu zdefiniuj Uncaughti zamień test na ładowność.


Podsumowując:

Taki kod jest naprawdę okropny i nigdy nie powinien być używany , ale czasami jest wykorzystywany w atakach XSS, więc morał tej historii nie polega na filtrowaniu nawiasów, aby zapobiec XSS. Dobrym pomysłem byłoby również użycie CSP w celu zapobiegania takiemu kodowi.

Alexander O'Mara
źródło
Czy to nie to samo, co używanie evali pisanie łańcucha bez użycia znaków w nawiasach.
Zev Spitz
@ZenSpitz Tak, ale evalzwykle wymaga nawiasów, stąd hackowanie tego filtra.
Alexander O'Mara,
5
Zapewne \x28i \x29nadal są nawiasami. '\x28' === '('.
trincot
@trincot To jest rodzaj punktu, który omówiłem w odpowiedzi, jest to podejście myślenia lateralnego, które pokazuje powód, dla którego można to zrobić. Uściśliłem to w odpowiedzi.
Alexander O'Mara,
Każdy, kto głosował za usunięciem, zapoznaj się z wytycznymi dotyczącymi usuwania odpowiedzi .
Alexander O'Mara,
1

W ES6 masz tak zwane literały szablonów tagowanych .

Na przykład:

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

foo`Tagged Template Literals`;

Idan Dagan
źródło
3
Rzeczywiście, jest to odpowiedź, którą zamieściłem 1,5 roku przed twoją ... nie wiesz, dlaczego zasługuje na nową odpowiedź?
trincot
2
ponieważ niektóre inne osoby, które chcą teraz wdrożyć to samo, mogą skorzystać z nowej odpowiedzi.
mehta-rohan
3
@ mehta-rohan, nie rozumiem tego komentarza. Jeśli miałoby to sens, dlaczego nie powielać tej samej odpowiedzi raz za razem? Nie widzę, jak by to się przydało.
trincot