Meminta fungsi tanpa tanda kurung

275

Saya diberitahu hari ini bahwa mungkin untuk menjalankan fungsi tanpa tanda kurung. Satu - satunya cara yang saya bisa pikirkan adalah menggunakan fungsi like applyatau call.

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

Tapi ini membutuhkan tanda kurung applydan callmeninggalkan kita di titik awal. Saya juga mempertimbangkan ide untuk meneruskan fungsi ke semacam pengendali acara seperti setTimeout:

setTimeout(f, 500);

Tetapi kemudian pertanyaannya menjadi "bagaimana Anda memohon setTimeouttanpa tanda kurung?"

Jadi apa solusi untuk teka-teki ini? Bagaimana Anda bisa menjalankan fungsi dalam Javascript tanpa menggunakan tanda kurung?

Mike Cluck
sumber
59
Tidak berusaha bersikap kasar, tetapi saya harus bertanya: Mengapa?
jehna1
36
@ jehna1 Karena saya menjawab pertanyaan tentang bagaimana fungsi dipanggil dan diperbaiki ketika saya mengatakan bahwa mereka membutuhkan tanda kurung pada akhirnya.
Mike Cluck
3
Luar biasa! Saya tidak membayangkan pertanyaan ini akan memiliki daya tarik sebanyak itu .. Mungkin saya seharusnya bertanya sendiri :-)
Amit
5
Ini adalah jenis pertanyaan yang menghancurkan hati saya. Apa manfaatnya melalui pelecehan dan eksploitasi seperti itu? Saya ingin tahu satu kasus penggunaan yang valid di mana <insert any solution>secara objektif lebih baik atau lebih bermanfaat daripada f().
Terima kasih
5
@ Harun: Anda mengatakan tidak ada alasan yang sah, tetapi, seperti yang ditunjukkan oleh jawaban Alexander O'Mara , orang mungkin mempertimbangkan pertanyaan apakah penghapusan tanda kurung sudah cukup untuk mencegah eksekusi kode - atau bagaimana dengan kompetisi Javascript yang dikaburkan? Tentu saja itu adalah tujuan yang absurd ketika mencoba menulis kode yang bisa dipelihara, tetapi itu adalah pertanyaan yang lucu.
PJTraill

Jawaban:

429

Ada beberapa cara berbeda untuk memanggil suatu fungsi tanpa tanda kurung.

Mari kita asumsikan Anda memiliki fungsi ini didefinisikan:

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

Kemudian di sini ikuti beberapa cara untuk menelepon greettanpa tanda kurung:

1. Sebagai Pembuat

Dengan newAnda dapat memanggil fungsi tanpa tanda kurung:

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

Dari MDN pada newoprator :

Sintaksis

new constructor[([arguments])]

2. Sebagai toStringatau valueOfImplementasi

toStringdan valueOfmerupakan metode khusus: mereka dipanggil secara implisit ketika konversi diperlukan:

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

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

Anda dapat (ab) menggunakan pola ini untuk memanggil greettanpa tanda kurung:

'' + { toString: greet };

Atau dengan valueOf:

+{ valueOf: greet };

valueOfdan toStringyang sebenarnya disebut dari @@ toPrimitive metode (sejak ES6), dan sehingga Anda juga dapat menerapkan bahwa metode:

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

2.b Mengesampingkan valueOfPrototipe Fungsi

Anda dapat mengambil ide sebelumnya untuk mengganti valueOfmetode pada Functionprototipe :

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

Setelah Anda selesai melakukannya, Anda dapat menulis:

+greet;

Dan meskipun ada tanda kurung di telepon, doa pemicu yang sebenarnya tidak memiliki tanda kurung. Lihat lebih lanjut tentang ini di blog "Metode pemanggilan dalam JavaScript, tanpa benar-benar memanggilnya"

3. Sebagai Generator

Anda bisa mendefinisikan fungsi generator (dengan *), yang mengembalikan iterator . Anda dapat memanggilnya menggunakan sintaks spread atau dengan for...ofsintaks.

Pertama kita membutuhkan varian generator dari greetfungsi aslinya :

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

Dan kemudian kita menyebutnya tanpa tanda kurung dengan mendefinisikan metode @@ iterator :

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

Biasanya generator memiliki yieldkata kunci di suatu tempat, tetapi fungsi tersebut tidak diperlukan untuk dipanggil.

Pernyataan terakhir memanggil fungsi, tetapi itu juga bisa dilakukan dengan merusak :

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

atau for ... ofkonstruk, tetapi memiliki tanda kurung sendiri:

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

Perhatikan bahwa Anda dapat melakukan hal di atas dengan greetfungsi asli juga, tetapi itu akan memicu pengecualian dalam proses, setelah greet dieksekusi (diuji pada FF dan Chrome). Anda dapat mengelola pengecualian dengan try...catchblok.

4. Sebagai Getter

@ jehna1 punya jawaban penuh untuk ini, jadi beri dia pujian. Berikut adalah cara untuk memanggil fungsi kurung-kurang pada lingkup global, menghindari metode yang sudah usang__defineGetter__ . Ini menggunakan Object.definePropertysebagai gantinya.

Kita perlu membuat varian greetfungsi asli untuk ini:

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

Lalu:

greet_get;

Ganti windowdengan apa pun objek global Anda.

Anda bisa memanggil greetfungsi asli tanpa meninggalkan jejak pada objek global seperti ini:

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

Tetapi orang dapat berargumen bahwa kami memiliki tanda kurung di sini (meskipun mereka tidak terlibat dalam doa yang sebenarnya).

5. Sebagai Fungsi Tag

Karena ES6 Anda dapat memanggil fungsi yang melewatinya sebagai templat literal dengan sintaks ini:

greet``;

Lihat "Literal Templat Tagged" .

6. Sebagai Proxy Handler

Sejak ES6, Anda dapat menetapkan proxy :

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

Dan kemudian membaca nilai properti apa pun akan meminta greet:

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

Ada banyak variasi dari ini. Satu lagi contoh:

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

1 in proxy; // triggers greet

7. Sebagai pemeriksa contoh

The instanceofOperator mengeksekusi @@hasInstancemetode pada operan kedua, ketika didefinisikan:

1 instanceof { [Symbol.hasInstance]: greet } // triggers greet
trincot
sumber
1
Itu pendekatan yang sangat menarik digunakan valueOf.
Mike Cluck
4
Anda lupa tentang ES6:func``;
Ismael Miguel
1
Anda menggunakan tanda kurung dengan memanggil eval :)
trincot
1
Dalam hal ini fungsi tidak dieksekusi, tetapi diteruskan ke pemanggil.
trincot
1
@trincot Metode lain akan segera dimungkinkan dengan operator pipa :'1' |> alert
deniro
224

Cara termudah untuk melakukannya adalah dengan newoperator:

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

new f;

Walaupun itu tidak lazim dan tidak wajar, ia bekerja dan sangat legal.

The newOperator tidak memerlukan tanda kurung jika tidak ada parameter yang digunakan.

Amit
sumber
6
Koreksi, cara termudah untuk melakukan ini adalah dengan menambahkan sebuah operan yang memaksa ekspresi fungsi untuk dievaluasi. !fmenghasilkan hal yang sama, tanpa membuat instance fungsi konstruktor (yang membutuhkan pembuatan konteks baru ini). Ini jauh lebih berkinerja dan digunakan di banyak perpustakaan populer (seperti Bootstrap).
THEtheChad
6
@THEtheChad - mengevaluasi suatu ekspresi tidak sama dengan mengeksekusi suatu fungsi, tetapi jika Anda memiliki metode yang berbeda (lebih bagus? Lebih mengejutkan?) Untuk memanggil (menyebabkan eksekusi) suatu fungsi - memposting jawaban!
Amit
Titik mengawali tanda seru, atau karakter tunggal unary operator lainnya ( +, -, ~) di perpustakaan tersebut, adalah untuk menyimpan byte dalam versi diminimalkan perpustakaan di mana nilai kembali tidak diperlukan. (Yaitu !function(){}()) Sintaks pemanggilan diri yang biasa adalah (function(){})()(sayangnya sintaksnya function(){}()bukan cross-browser) Karena fungsi pemanggilan diri paling atas biasanya tidak memiliki apa-apa untuk dikembalikan, biasanya target dari teknik penyimpanan byte ini
Ultimater
1
@THEtheChad Apakah ini benar? Saya baru saja mencobanya di Chrome, dan tidak mendapatkan eksekusi fungsi. function foo() { alert("Foo!") };Sebagai contoh: !foopengembalianfalse
Glenn 'devalias'
95

Anda dapat menggunakan getter dan setter.

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

Jalankan skrip ini hanya dengan:

h.ello  // Fires up alert "world"

Edit:

Kita bahkan dapat berdebat!

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

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

Edit 2:

Anda juga dapat mendefinisikan fungsi global yang dapat dijalankan tanpa tanda kurung:

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

Dan dengan argumen:

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

Penolakan:

Seperti @MonkeyZeus menyatakan: Jangan pernah Anda menggunakan kode ini dalam produksi, tidak peduli seberapa bagus niat Anda.

jehna1
sumber
9
Sebenarnya dari POV tentang "Saya kapak yang menggunakan orang gila yang mendapat kehormatan mengambil alih kode Anda karena Anda telah beralih ke hal-hal yang lebih besar dan lebih baik; namun saya tahu di mana Anda tinggal". Saya sangat berharap ini bukan lol normal. Menggunakannya di dalam plug-in yang Anda kelola, dapat diterima. Menulis kode logika bisnis, harap tahan sementara saya mengasah kapak saya :)
MonkeyZeus
3
@MonkeyZeus haha, ya! Menambahkan penafian untuk pembuat kode masa depan yang dapat menemukan ini dari Google
jehna1
Sebenarnya penafian ini terlalu keras. Ada kasus penggunaan yang sah untuk "getter sebagai panggilan fungsi". Bahkan itu digunakan secara luas di perpustakaan yang sangat sukses. (Anda ingin petunjuk ?? :-)
Amit
1
Perhatikan bahwa __defineGetter__sudah usang. Gunakan Object.definePropertysebagai gantinya.
Felix Kling
2
@MonkeyZeus - seperti yang saya tulis secara eksplisit dalam komentar - gaya pemanggilan fungsi ini digunakan , secara luas & sengaja, di perpustakaan umum yang sangat sukses sebagai API itu sendiri, bukan secara internal.
Amit
23

Berikut ini contoh untuk situasi tertentu:

window.onload = funcRef;

Meskipun pernyataan itu tidak benar-benar memohon tetapi akan menyebabkan doa di masa depan .

Tapi, saya pikir area abu-abu mungkin ok untuk teka-teki seperti ini :)

Jonathan. Minumlah
sumber
6
Itu bagus tapi itu bukan JavaScript, itu DOM.
Amit
6
@Amit, DOM adalah bagian dari lingkungan JS browser, yang masih JavaScript.
zzzzBov
3
@zzzzBov Tidak semua ECMAScript berjalan di browser web. Atau Anda termasuk di antara orang-orang yang menggunakan "JavaScript" untuk merujuk secara khusus menggabungkan ES dengan HTML DOM, yang bertentangan dengan Node?
Damian Yerrick
9
@DamianYerrick, saya menganggap DOM sebagai perpustakaan yang tersedia di beberapa lingkungan JS, yang tidak membuatnya kurang JavaScript daripada garis bawah yang tersedia di beberapa lingkungan. Ini masih JavaScript, itu hanya bukan bagian yang dispesifikasikan dalam spesifikasi ECMAScript.
zzzzBov
3
@zzzzBov, "Meskipun DOM sering diakses menggunakan JavaScript, ini bukan bagian dari bahasa JavaScript. Ini juga dapat diakses oleh bahasa lain." . Misalnya, FireFox menggunakan XPIDL dan XPCOM untuk implementasi DOM. Tidak begitu jelas bahwa panggilan fungsi panggilan balik yang Anda tentukan diimplementasikan dalam JavaScript. DOM bukan API seperti perpustakaan JavaScript lainnya.
trincot
17

Jika kami menerima pendekatan berpikir lateral, di browser ada beberapa API yang dapat kami penyalahgunaan untuk mengeksekusi JavaScript sewenang-wenang, termasuk memanggil fungsi, tanpa karakter tanda kurung yang sebenarnya.

1. locationdan javascript:protokol:

Salah satu teknik tersebut adalah dengan menyalahgunakan javascript:protokol pada locationtugas.

Contoh kerja:

location='javascript:alert\x281\x29'

Meskipun secara teknis \x28 dan \x29masih tanda kurung setelah kode dievaluasi, aktual (dan )karakter tidak muncul. Tanda kurung dilepaskan dalam serangkaian JavaScript yang akan dievaluasi saat penugasan.


2. onerrordan eval:

Demikian pula, tergantung pada peramban kita dapat menyalahgunakan global onerror, dengan menyetelnya ke eval, dan melemparkan sesuatu yang akan dikencangkan menjadi JavaScript yang valid. Yang ini lebih rumit, karena peramban tidak konsisten dalam perilaku ini, tetapi inilah contoh untuk Chrome.

Contoh kerja untuk Chrome (bukan Firefox, yang lain belum diuji):

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

Ini berfungsi di Chrome karena throw'test'akan diteruskan 'Uncaught test'sebagai argumen pertama onerror, yang merupakan JavaScript yang hampir valid. Jika kita melakukannya, throw';test'itu akan berlalu 'Uncaught ;test'. Sekarang kami memiliki JavaScript yang valid! Cukup tentukan Uncaught, dan ganti tes dengan payload.


Kesimpulannya:

Kode seperti itu benar-benar mengerikan , dan tidak boleh digunakan , tetapi kadang-kadang digunakan dalam serangan XSS, sehingga moral cerita tidak bergantung pada penyaringan tanda kurung untuk mencegah XSS. Menggunakan CSP untuk mencegah kode seperti itu juga merupakan ide bagus.

Alexander O'Mara
sumber
Bukankah ini sama dengan menggunakan evaldan menulis string tanpa benar-benar menggunakan karakter tanda kurung.
Zev Spitz
@ ZenSpitz Yap, tetapi evalbiasanya membutuhkan tanda kurung, karena itu filter ini menghindari hack.
Alexander O'Mara
5
Bisa dibilang \x28dan \x29masih kurung. '\x28' === '('.
trincot
@trincot Yang merupakan jenis poin yang saya bahas dalam jawaban, ini adalah pendekatan berpikir lateral yang menunjukkan alasan mengapa hal ini dapat dilakukan. Saya sudah membuatnya lebih jelas dalam jawabannya.
Alexander O'Mara
Juga, siapa pun yang memilih untuk menghapus ini, silakan tinjau pedoman untuk menghapus jawaban .
Alexander O'Mara
1

Di ES6, Anda memiliki apa yang disebut Tagged Template Literals .

Sebagai contoh:

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

foo`Tagged Template Literals`;

Idan Dagan
sumber
3
Memang, ini ada dalam jawaban yang saya posting 1,5 tahun sebelum Anda ... tidak yakin mengapa itu layak mendapat jawaban baru?
trincot
2
karena beberapa orang lain, yang ingin mengimplementasikan hal yang sama sekarang dapat menggunakan jawaban baru.
mehta-rohan
3
@ mehta-rohan, saya tidak mengerti komentar itu. Jika ini masuk akal maka mengapa tidak menduplikasi jawaban yang sama berulang kali? Saya tidak bisa melihat bagaimana itu akan berguna.
trincot