เรียกใช้ฟังก์ชันที่ไม่มีวงเล็บ


275

ฉันบอกในวันนี้ว่าเป็นไปได้ที่จะเรียกใช้ฟังก์ชันที่ไม่มีวงเล็บ วิธีเดียวที่ฉันจะคิดว่าการใช้ฟังก์ชั่นเหมือนหรือapplycall

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

แต่สิ่งเหล่านี้ต้องการวงเล็บapplyและcallปล่อยให้เราอยู่ที่หนึ่งตาราง ฉันยังพิจารณาแนวคิดของการส่งผ่านฟังก์ชันไปยังตัวจัดการเหตุการณ์บางประเภทเช่นsetTimeout:

setTimeout(f, 500);

แต่แล้วคำถามก็กลายเป็น "คุณจะเรียกใช้setTimeoutอย่างไรโดยไม่ต้องใส่วงเล็บ?"

ดังนั้นคำตอบของปริศนานี้คืออะไร? คุณจะเรียกใช้ฟังก์ชันใน Javascript โดยไม่ใช้วงเล็บได้อย่างไร


59
ไม่พยายามหยาบคาย แต่ฉันต้องถามว่า: ทำไม?
jehna1

36
@ jehna1 เพราะฉันตอบคำถามเกี่ยวกับวิธีการเรียกใช้ฟังก์ชั่นและแก้ไขเมื่อฉันพูดว่าพวกเขาต้องการวงเล็บในตอนท้าย
Mike Cluck

3
! ที่น่าตื่นตาตื่นใจ ผมไม่ได้จินตนาการคำถามนี้ว่าจะมีแรงดึงมาก .. บางทีฉันควรจะถามตัวเอง :-)
Amit

5
นี่เป็นคำถามที่ทำให้ใจฉันแตก สิ่งใดที่ดีที่อาจเกิดขึ้นจากการละเมิดและการแสวงหาผลประโยชน์ ฉันต้องการทราบกรณีการใช้งานที่ถูกต้องอย่างใดอย่างหนึ่งที่เป็นอคติที่ดีกว่าหรือมีประโยชน์มากกว่า<insert any solution> f()
ขอบคุณ

5
@Aaron: คุณบอกว่าไม่มีเหตุผลที่ถูกต้อง แต่เมื่อคำตอบของ Alexander O'Maraชี้ให้เห็นเราอาจพิจารณาคำถามว่าการลบวงเล็บเพียงพอที่จะป้องกันการเรียกใช้โค้ดหรือสิ่งที่เกี่ยวกับการแข่งขัน Javascript ที่สับสน? แน่นอนว่ามันเป็นเป้าหมายที่ไร้สาระเมื่อพยายามเขียนรหัสที่สามารถบำรุงรักษาได้ แต่มันเป็นคำถามที่น่าสนุก
PJTraill

คำตอบ:


429

มีหลายวิธีในการเรียกใช้ฟังก์ชันที่ไม่มีวงเล็บ

สมมติว่าคุณได้กำหนดฟังก์ชั่นนี้:

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

จากนั้นที่นี่ทำตามวิธีการโทรgreetโดยไม่ใช้วงเล็บ:

1. เป็นตัวสร้าง

ด้วยnewคุณสามารถเรียกใช้ฟังก์ชั่นโดยไม่ต้องวงเล็บ:

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

จากMDN ในnewoprator :

วากยสัมพันธ์

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. เป็นเครื่องกำเนิดไฟฟ้า

คุณสามารถกำหนดฟังก์ชั่นเครื่องกำเนิดไฟฟ้า (กับ*) ซึ่งส่งกลับiterator คุณสามารถเรียกมันโดยใช้ไวยากรณ์การแพร่กระจายหรือด้วยfor...ofไวยากรณ์

ก่อนอื่นเราต้องใช้ชุดตัวสร้างของgreetฟังก์ชันต้นฉบับ:

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

จากนั้นเราเรียกมันว่าไม่มีวงเล็บโดยการกำหนด@@ iterator method:

[...{ [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``;

ดู"Tagged แม่แบบตัวอักษร"

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นั่นเป็นวิธีที่น่าสนใจมากในการใช้
Mike Cluck

4
คุณลืมเกี่ยวกับ ES6:func``;
Ismael Miguel

1
คุณใช้วงเล็บด้วยการเรียก eval :)
trincot

1
ในกรณีนั้นฟังก์ชั่นไม่ได้ถูกดำเนินการ แต่ส่งผ่านไปยังผู้โทร
trincot

1
@trincot วิธีการอื่นจะเป็นไปได้ในไม่ช้ากับผู้ดำเนินการไปป์ไลน์ :'1' |> alert
deniro

224

วิธีที่ง่ายที่สุดในการทำเช่นนี้กับnewผู้ประกอบการ

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

new f;

ในขณะที่นอกรีตและผิดธรรมชาติมันทำงานและถูกกฎหมายอย่างสมบูรณ์

newผู้ประกอบการไม่ต้องวงเล็บหากไม่มีการใช้พารามิเตอร์


6
การแก้ไขวิธีที่ง่ายที่สุดในการทำเช่นนี้คือการเพิ่มตัวถูกดำเนินการที่บังคับให้นิพจน์ฟังก์ชันได้รับการประเมิน !fผลลัพธ์ในสิ่งเดียวกันโดยไม่ต้องสร้างอินสแตนซ์ของฟังก์ชันตัวสร้าง (ซึ่งต้องสร้างบริบทใหม่นี้) มันมีประสิทธิภาพมากกว่าและใช้ในห้องสมุดยอดนิยมหลายแห่ง (เช่น Bootstrap)
THETheChad

6
@THEtheChad - การประเมินการแสดงออกไม่เหมือนกับการใช้งานฟังก์ชั่น แต่ถ้าคุณมีวิธีการที่แตกต่างกัน (nicer? more ประหลาดใจ?) ของการเรียกใช้ (ก่อให้เกิดการดำเนินการ) ของฟังก์ชั่น - โพสต์คำตอบ!
Amit

จุด prepending เครื่องหมายอัศเจรีย์หรืออื่น ๆ ตัวเดียวประกอบเอก (ใน+, -, ~) ในห้องสมุดดังกล่าวคือการบันทึกไบต์ในรุ่นลดลงของห้องสมุดที่ค่าตอบแทนไม่จำเป็นต้อง (เช่น!function(){}()) ไวยากรณ์การโทรด้วยตนเองตามปกติคือ(function(){})()(น่าเสียดายที่ไวยากรณ์function(){}()ไม่ใช่เบราว์เซอร์ข้าม) เนื่องจากฟังก์ชันการโทรด้วยตนเองที่ดีที่สุดมักจะไม่มีอะไรจะกลับมาจึงเป็นเป้าหมายของเทคนิคการประหยัดไบต์นี้
Ultimater

1
@THEtheChad สิ่งนี้ถือเป็นจริงหรือไม่ ฉันเพิ่งลองใช้งานใน Chrome และไม่ได้รับการเรียกใช้ฟังก์ชัน function foo() { alert("Foo!") };ตัวอย่างเช่น: !fooส่งคืนfalse
Glenn 'devalias'

95

คุณสามารถใช้ getters และ setters

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"

Disclaimer:

ตามที่ @MonkeyZeus ระบุไว้: คุณจะไม่เคยใช้รหัสนี้ในการผลิตไม่ว่าคุณจะมีเจตนาดีเพียงใด


9
พูดอย่างเคร่งครัดจากมุมมองของ "ฉันเป็นคนขวานควงชายผู้บ้าคลั่งที่ได้รับเกียรติจากรหัสของคุณเพราะคุณได้ย้ายไปยังสิ่งที่ใหญ่กว่าและดีกว่า; แต่ฉันรู้ว่าคุณอยู่ที่ไหน" ฉันหวังว่านี่ไม่ใช่เรื่องปกติ ใช้ภายในปลั๊กอินที่คุณบำรุงรักษายอมรับได้ เขียนโค้ดธุรกิจตรรกะโปรดถือในขณะที่ฉันลับขวานของฉัน :)
MonkeyZeus

3
@MonkeyZeus ฮ่าฮ่าใช่! เพิ่มข้อจำกัดความรับผิดชอบสำหรับนักเขียนโค้ดแห่งอนาคตที่สามารถค้นหาได้จาก Google
jehna1

ที่จริงแล้วข้อจำกัดความรับผิดชอบนี้รุนแรงเกินไป มีกรณีการใช้งานที่ถูกต้องสำหรับ "getter เป็นการเรียกใช้ฟังก์ชัน" ในความเป็นจริงมันถูกใช้อย่างกว้างขวางในห้องสมุดที่ประสบความสำเร็จมาก (คุณต้องการคำใบ้ ?? :-)
Amit

1
โปรดทราบว่า__defineGetter__เลิกใช้แล้ว ใช้Object.definePropertyแทน
เฟลิกซ์คลิง

2
@MonkeyZeus - ตามที่ฉันเขียนไว้อย่างชัดเจนในความคิดเห็น - มีการใช้รูปแบบการเรียกใช้ฟังก์ชั่นนี้อย่างกว้างขวางและตั้งใจในห้องสมุดสาธารณะที่ประสบความสำเร็จอย่าง API ตัวเองไม่ใช่ภายใน
Amit

23

นี่คือตัวอย่างสำหรับสถานการณ์เฉพาะ:

window.onload = funcRef;

แม้ว่าคำสั่งที่ไม่จริงกล่าวอ้างแต่จะนำไปสู่การภาวนาในอนาคต

แต่ฉันคิดว่าพื้นที่สีเทาอาจใช้ได้สำหรับปริศนาเช่นนี้ :)


6
ไม่เป็นไร แต่ก็ไม่ใช่ JavaScript มันคือ DOM
Amit

6
@Amit DOM เป็นส่วนหนึ่งของสภาพแวดล้อม JS ของเบราว์เซอร์ซึ่งยังคงเป็น JavaScript
zzzzBov

3
@zzzzBov ไม่ใช่ ECMAScript ทั้งหมดที่ทำงานในเว็บเบราว์เซอร์ หรือคุณเป็นคนที่ใช้ "JavaScript" เพื่ออ้างถึงการรวม ES กับ HTML DOM โดยเฉพาะเมื่อเทียบกับโหนด
Damian Yerrick

9
@ DamianYerrick ฉันคิดว่า DOM เป็นห้องสมุดที่มีอยู่ในบางสภาพแวดล้อมของ JS ซึ่งไม่ได้ทำให้ JavaScript น้อยกว่าขีดล่างที่มีอยู่ในบางสภาพแวดล้อม ยังคงเป็น JavaScript แต่ไม่ใช่ส่วนที่ระบุไว้ในข้อกำหนด ECMAScript
zzzzBov

3
@zzzzBov "ถึงแม้ว่า DOM จะเข้าถึงได้บ่อยครั้งโดยใช้ JavaScript แต่ก็ไม่ได้เป็นส่วนหนึ่งของภาษา JavaScript และยังสามารถเข้าถึงได้โดยภาษาอื่น ๆ " . ตัวอย่างเช่น FireFox ใช้ XPIDL และ XPCOM สำหรับการใช้งาน DOM ไม่ชัดเจนว่าการโทรของฟังก์ชั่นการโทรกลับที่คุณระบุมีการใช้งานใน JavaScript DOM ไม่ใช่ API เหมือนกับไลบรารี JavaScript อื่น
trincot

17

หากเรายอมรับวิธีคิดด้านข้างในเบราว์เซอร์มี API หลายตัวที่เราสามารถใช้จาวาสคริปต์โดยพลการรวมถึงการเรียกใช้ฟังก์ชันโดยไม่ใช้อักขระวงเล็บ

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และการเขียนสตริงโดยไม่ใช้อักขระวงเล็บ
Zev Spitz

@ZenSpitz Yep แต่evalโดยปกติต้องใช้วงเล็บดังนั้นการแฮ็กตัวกรองนี้
Alexander O'Mara

5
เนื้อหา\x28และ\x29ยังคงเป็นวงเล็บ '\x28' === '('.
trincot

@ TRINOT ซึ่งเป็นจุดที่ฉันครอบคลุมในคำตอบนี้เป็นวิธีคิดด้านข้างซึ่งแสดงเหตุผลซึ่งสิ่งนี้อาจจะทำ ฉันทำให้ชัดเจนขึ้นในคำตอบ
Alexander O'Mara

นอกจากนี้ใครก็ตามที่ได้รับการโหวตลบรายการนี้โปรดตรวจสอบแนวทางในการลบคำตอบ
Alexander O'Mara

1

ใน ES6 คุณมีสิ่งที่เรียกว่าTagged แม่แบบตัวอักษร

ตัวอย่างเช่น:

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

foo`Tagged Template Literals`;


3
อันที่จริงนี่คือคำตอบที่ฉันโพสต์ 1.5 ปีก่อนหน้าคุณ ... ไม่แน่ใจว่าทำไมมันสมควรได้รับคำตอบใหม่?
trincot

2
เพราะบางคนที่ต้องการใช้สิ่งเดียวกันตอนนี้สามารถใช้คำตอบใหม่ได้
mehta-rohan

3
@ mehta-rohan ฉันไม่เข้าใจความคิดเห็นนั้น ถ้านี่จะสมเหตุสมผลแล้วทำไมไม่ทำซ้ำคำตอบเดียวกันซ้ำไปซ้ำมา? ฉันไม่เห็นว่ามันจะมีประโยชน์
trincot
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.