Parantez olmadan bir işlevi başlatma


275

Bugün bana parantez olmadan bir işlevi çağırmanın mümkün olduğu söylendi. Aklıma gelen tek yol, applyveya gibi işlevler kullanmaktı call.

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

Ancak bunlar parantezleri gerektirir applyve callbizi birinci karede bırakır. Ayrıca, işlevi bir tür olay işleyicisine geçirme fikrini de düşündüm setTimeout:

setTimeout(f, 500);

Ama sonra soru " setTimeoutparantez olmadan nasıl çağırıyorsunuz ?"

Peki bu bilmecenin çözümü nedir? Javascript'te bir işlevi parantez kullanmadan nasıl çağırabilirsiniz?


59
Kaba olmaya çalışmıyorum, ama şunu sormalıyım: Neden?
Ocak 16:20

36
@ jehna1 Çünkü fonksiyonların nasıl çağrıldığı ve sonunda parantez istedikleri söylendiğinde düzeltilen bir soruya yanıt veriyordum .
Mike Cluck

3
İnanılmaz! Bu sorunun bu kadar çekiş olacağını düşünmemiştim .. Belki de kendim sormalıydım :-)
Amit

5
Bu kalbimi kıran bir soru. Böyle bir istismar ve sömürüden ne kadar iyi gelebilir? <insert any solution>Nesnel olarak daha iyi veya daha yararlı olduğu geçerli bir kullanım durumunu bilmek istiyorum f().
Teşekkürler

5
@Aaron: geçerli bir neden olmadığını söylüyorsunuz, ancak Alexander O'Mara'nın cevabının belirttiği gibi, parantezin kaldırılmasının kod yürütülmesini önlemek için yeterli olup olmadığı sorusu düşünülüyor olabilir - ya da gizli bir Javascript yarışmasına ne dersiniz? Elbette sürdürülebilir kod yazmaya çalışırken saçma bir hedeftir, ancak eğlenceli bir sorudur.
PJTraill

Yanıtlar:


429

Parantezsiz bir işlevi çağırmanın birkaç farklı yolu vardır.

Bu işlevin tanımlanmış olduğunu varsayalım:

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

Ardından greet, parantez olmadan arama yapmanın bazı yollarını izleyin :

1. Yapıcı Olarak

İle newparantez olmadan bir işlevi çağırabilirsiniz:

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

Gönderen üzerine MDN'yi newoprator :

Sözdizimi

new constructor[([arguments])]

2. Olarak toStringveya valueOfUygulama

toStringve valueOfözel yöntemlerdir: bir dönüşüm gerektiğinde dolaylı olarak çağrılırlar:

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

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

greetParantez olmadan aramak için bu deseni kullanabilirsiniz (ab) :

'' + { toString: greet };

Veya valueOf:

+{ valueOf: greet };

valueOfve toStringaslında çağrılan @@ toPrimitive (ES6 beri) yöntemiyle, ve böylece de uygulayabilirsiniz olduğunu yöntemi :

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

2.b Geçersiz Kılma valueOf İşlev Prototipinde

PrototipvalueOf üzerindeki yöntemi geçersiz kılmak için önceki fikri kullanabilirsiniz :Function

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

Bunu yaptıktan sonra şunları yazabilirsiniz:

+greet;

Ve çizgide yer alan parantezler olmasına rağmen, gerçek tetikleme çağrısının parantezleri yoktur. Bununla ilgili daha fazla bilgiyi "Gerçekten çağırmadan JavaScript'te arama yöntemleri" başlıklı blogda bulabilirsiniz.

3. Jeneratör Olarak

Yineleyici döndüren bir üreteç işlevi (ile *) tanımlayabilirsiniz . Forma sözdizimini kullanarak veyafor...of .

İlk önce orijinal greetfonksiyonun bir jeneratör varyantına ihtiyacımız var :

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

Ve sonra @@ iterator yöntemini tanımlayarak parantez olmadan çağırıyoruz :

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

Normalde jeneratörler yield yerde anahtar kelimesi , ancak işlevin çağrılması gerekmez.

Son ifade işlevi çağırır, ancak bu yıkımla da yapılabilir :

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

veya bir for ... ofyapı, ancak kendi parantezleri vardır:

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

Yukarıdakileri orijinal işlevle de yapabileceğinizi unutmayın greet, ancak yürütüldükten sonra işlemde bir istisna tetikleyecektir greet(FF ve Chrome'da test edilmiştir). İstisna birtry...catch blokla .

4. Getter olarak

@ jehna1 bu konuda tam bir cevabı var, bu yüzden ona kredi verin. Aşağıda, kullanım dışı yöntemden kaçınarak, genel kapsamda işlev parantezlerini çağırmanın bir yolu vardır . Bunun yerine kullanır .__defineGetter__Object.defineProperty

Bunun için orijinal greetfonksiyonun bir varyantını oluşturmamız gerekiyor :

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

Ve sonra:

greet_get;

windowGlobal nesneniz ne olursa olsun değiştirin .

Orijinal greetnesneye böyle bir iz bırakmadan orijinal işlevi çağırabilirsiniz :

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

Ancak burada parantezlerimiz olduğu iddia edilebilir (gerçek çağrıyla uğraşmasalar da).

5. Etiket Fonksiyonu Olarak

ES6'dan beri , bu sözdizimiyle bir şablon değişmezi ileten bir işlevi çağırabilirsiniz :

greet``;

Bkz. "Etiketli Şablon Değişmezleri" .

6. Proxy İşleyicisi Olarak

ES6'dan beri bir proxy tanımlayabilirsiniz :

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

Ve sonra herhangi bir özellik değerini okumak greet:

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

Bunun birçok varyasyonu var. Bir örnek daha:

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

1 in proxy; // triggers greet

7. Örnek denetleyicisi olarak

instanceofOperatör yürütür @@hasInstancetanımlandığı zaman ikinci işlenen hakkında bir yöntem:

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

1
Bunu kullanmak çok ilginç bir yaklaşım valueOf.
Mike Cluck

4
ES6'yı unuttun:func``;
Ismael Miguel

1
Eval

1
Bu durumda işlev yürütülmez, ancak arayana iletilir.
trincot

1
@trincot Boru hattı operatörü ile yakında başka bir yöntem mümkün olacak :'1' |> alert
deniro

224

Bunu yapmanın en kolay yolu newoperatördür:

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

new f;

Alışılmadık ve doğal olmayan bir şey olsa da, çalışır ve tamamen yasaldır.

newHiçbir parametre kullanılırsa operatör parantez gerektirmez.


6
Düzeltme, bunu yapmanın en kolay yolu, işlev ifadesini değerlendirmeye zorlayan bir işlenenin başına gelmektir. !fyapıcı işlevinin bir örneğini (bu bağlamda yeni bir içerik oluşturmayı gerektirir) somutlaştırmadan aynı şeyle sonuçlanır. Çok daha performanslı ve birçok popüler kütüphanede (Bootstrap gibi) kullanılıyor.
THETheChad

6
@THEtheChad - bir ifadeyi değerlendirmek bir işlevi yürütmekle aynı şey değildir, ancak bir işlevi çağırmak (yürütmeye neden olmak) için farklı (daha hoş? Daha şaşırtıcı?) Bir yönteminiz varsa - bir yanıt gönderin!
Amit

Ünlem işareti veya başka bir tek karakter tekli operatör (prepending noktası +, -, ~bu tür kütüphanelerde), dönüş değeri gerekli değildir kütüphanesinin minimize sürümünde bir byte kurtarmaktır. (ie !function(){}()) Normal kendi kendine çağrı sözdizimi (function(){})()(maalesef sözdizimi function(){}()çapraz tarayıcı değildir) En üstteki kendi kendine çağrı işlevinin genellikle geri döndürülecek bir şeyi olmadığından, genellikle bu bayt tasarrufu tekniğinin hedefi
Ultimater

1
@THEtheChad Bu doğru mu? Sadece Chrome'da denedim ve işlev yürütme alamıyordum. function foo() { alert("Foo!") };Örneğin: !foodönerfalse
Glenn 'devalias'

95

Alıcıları ve ayarlayıcıları kullanabilirsiniz.

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

Bu betiği sadece şununla çalıştır:

h.ello  // Fires up alert "world"

Düzenle:

Tartışma bile yapabiliriz!

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

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

Düzenleme 2:

Parantez olmadan çalıştırılabilen genel işlevleri de tanımlayabilirsiniz:

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

Ve argümanlarla:

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

Yasal Uyarı:

@MonkeyZeus'un belirttiği gibi: Niyetleriniz ne kadar iyi olursa olsun, asla bu kod parçasını üretimde kullanamazsınız.


9
"Daha büyük ve daha iyi şeylere geçtiğiniz için kodunuzu ele geçirme onuruna sahip bir deli adamım, ama nerede yaşadığınızı biliyorum." Umarım bu normal lol değildir. Bakımını yaptığınız bir eklentinin içinde kullanmak kabul edilebilir. İş-mantık kodu yazma, ben balta keskinleştirmek ederken lütfen tutun :)
MonkeyZeus

3
@MonkeyZeus haha, evet! Bunu Google'dan bulabilecek geleceğin kodlayıcıları için bir feragatname eklendi
jehna1

Aslında bu feragatname çok sert. "Bir işlev çağrısı olarak getter" için meşru kullanım durumları vardır. Aslında çok başarılı bir kütüphanede yaygın olarak kullanılmaktadır. (bir ipucu ister misiniz ?? :-)
Amit

1
__defineGetter__Onaylanmadığını unutmayın . Object.definePropertyBunun yerine kullanın .
Felix Kling

2
@MonkeyZeus - yorumda açıkça yazdığım gibi - bu işlev çağırma stili, dahili olarak değil, API'nin kendisi olarak çok başarılı bir halk kütüphanesinde yoğun ve kasıtlı olarak kullanılmaktadır .
Amit

23

İşte belirli bir duruma bir örnek :

window.onload = funcRef;

Her ne kadar bu ifade aslında çağırmıyorsa da gelecekteki bir çağrışmaya yol açacaktır .

Ama, gri alanların böyle bilmeceler için iyi olabileceğini düşünüyorum.


6
Bu güzel ama JavaScript değil, DOM.
Amit

6
@Amit, DOM hala JavaScript olan tarayıcının JS ortamının bir parçasıdır.
zzzzBov

3
@zzzzBov Tüm ECMAScript bir web tarayıcısında çalışmaz. Yoksa özellikle Düğüm yerine ES'yi HTML DOM ile birleştirmek için "JavaScript" kullanan kişiler arasında mısınız?
Damian Yerrick

9
@DamianYerrick, DOM'u bazı JS ortamlarında bulunan bir kütüphane olarak görüyorum, bu da bazı ortamlarda alt çizginin kullanılmasından daha az JavaScript yapmıyor. Hala JavaScript, sadece ECMAScript spesifikasyonunda belirtilen bir bölüm değil.
zzzzBov

3
@zzzzBov, "DOM'a genellikle JavaScript kullanılarak erişilse de, JavaScript dilinin bir parçası değildir. Diğer diller tarafından da erişilebilir." . Örneğin, FireFox DOM uygulaması için XPIDL ve XPCOM kullanır. Belirttiğiniz geri arama işlevinin çağrısının JavaScript'te uygulandığı açık değildir. DOM, diğer JavaScript kitaplıkları gibi bir API değildir.
trincot

17

Yanal düşünme yaklaşımını kabul edersek, bir tarayıcıda, herhangi bir gerçek parantez karakteri olmadan bir işlevi çağırmak da dahil olmak üzere rastgele JavaScript çalıştırmak için kötüye kullanabileceğimiz birkaç API vardır.

1. locationve javascript:protokol:

Böyle bir teknik atama javascript:protokolünü kötüye kullanmaktır location.

Çalışma Örneği:

location='javascript:alert\x281\x29'

Teknik olarak \x28 ve \x29kod değerlendirildikten sonra hala parantez olmasına rağmen , gerçek (ve )karakter görünmez. Parantezler, atama sırasında değerlendirilen bir JavaScript dizesinde kaçar.


2. onerrorve eval:

Benzer şekilde, tarayıcıya bağlı olarak, genel olarak kötüye kullanabilir onerror, ayarlayabilir evalve geçerli JavaScript'e yayılacak bir şey atabiliriz . Bu daha zordur, çünkü tarayıcılar bu davranışta tutarsızdır, ancak Chrome için bir örnek.

Chrome için çalışma örneği (Firefox değil, diğerleri test edilmemiştir):

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

Bu Chrome'da çalışır, çünkü neredeyse geçerli JavaScript olan ilk argüman olarak throw'test'geçecektir . Bunun yerine geçersek geçecek . Şimdi geçerli bir JavaScriptimiz var! Testi tanımlayın ve yükü yük ile değiştirin.'Uncaught test'onerrorthrow';test''Uncaught ;test'Uncaught


Sonuç olarak:

Böyle bir kod gerçekten korkunçtur ve asla kullanılmamalıdır , ancak bazen XSS saldırılarında kullanılır, bu nedenle hikayenin ahlaki XSS'yi önlemek için parantezin filtrelenmesine dayanmaz. Böyle bir kodu önlemek için bir CSP kullanmak da iyi bir fikir olacaktır.


Bu eval, parantez karakterlerini kullanmadan dizeyi kullanmak ve yazmakla aynı şey değildir .
Zev Spitz

@ZenSpitz Evet, ama evalnormalde parantez gerektirir, bu nedenle bu filtre kaçırma hack.
Alexander O'Mara

5
Tartışmalı \x28ve \x29hala parantez. '\x28' === '('.
trincot

@trincot Cevapta ele aldığım bir nokta olan bu, bunun yapılabileceği bir nedeni ortaya koyan bir yanal düşünme yaklaşımıdır. Cevapta daha açık konuştum.
Alexander O'Mara

Ayrıca, bunu silmek isteyenler, lütfen yanıtları silme yönergelerini gözden geçirin .
Alexander O'Mara

1

ES6'da, Etiketli Şablon Değişmezleri olarak adlandırılan şeye sahipsiniz .

Örneğin:

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

foo`Tagged Template Literals`;


3
Gerçekten, bu sizden 1,5 yıl önce gönderdiğim cevapta ... neden yeni bir cevabı hak ettiğinden emin değil misiniz?
trincot

2
çünkü şu anda aynı şeyi uygulamak isteyen diğer bazı insanlar yeni cevabı kullanabilir.
mehta-rohan

3
@ mehta-rohan, bu yorumu anlamıyorum. Eğer bu mantıklı geliyorsa, aynı cevabı neden tekrar tekrar kazanmıyorsunuz? Bunun nasıl faydalı olacağını göremiyorum.
trincot
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.