อธิบายไวยากรณ์ของฟังก์ชันที่ไม่ระบุชื่อที่ถูกห่อหุ้ม


372

สรุป

คุณสามารถอธิบายเหตุผลที่อยู่เบื้องหลังไวยากรณ์สำหรับฟังก์ชั่นที่ไม่ระบุชื่อที่หุ้มใน JavaScript ได้หรือไม่? ทำไมงานนี้: (function(){})();แต่ตอนนี้ไม่ได้: function(){}();?


สิ่งที่ฉันรู้

ใน JavaScript หนึ่งสร้างฟังก์ชั่นที่มีชื่อดังนี้:

function twoPlusTwo(){
    alert(2 + 2);
}
twoPlusTwo();

คุณสามารถสร้างฟังก์ชั่นที่ไม่ระบุชื่อและกำหนดให้กับตัวแปร:

var twoPlusTwo = function(){
    alert(2 + 2);
};
twoPlusTwo();

คุณสามารถแค็ปซูลบล็อกของรหัสโดยการสร้างฟังก์ชั่นที่ไม่ระบุชื่อจากนั้นห่อในวงเล็บและดำเนินการทันที:

(function(){
    alert(2 + 2);
})();

สิ่งนี้มีประโยชน์เมื่อสร้างสคริปต์แบบแยกส่วนเพื่อหลีกเลี่ยงความยุ่งเหยิงของขอบเขตปัจจุบันหรือขอบเขตโกลบอลที่อาจมีตัวแปรที่ขัดแย้งกัน - เช่นในกรณีของสคริปต์ Greasemonkey, ปลั๊กอิน jQuery เป็นต้น

ตอนนี้ฉันเข้าใจว่าทำไมงานนี้ วงเล็บใส่เนื้อหาและเปิดเผยเฉพาะผล (ฉันแน่ใจว่ามีวิธีที่ดีกว่าที่จะอธิบายว่า) (2 + 2) === 4เช่นเดียวกับ


สิ่งที่ฉันไม่เข้าใจ

แต่ฉันไม่เข้าใจว่าทำไมสิ่งนี้ถึงใช้งานไม่ได้เช่นกัน:

function(){
    alert(2 + 2);
}();

คุณช่วยอธิบายให้ฉันฟังได้ไหม


39
ฉันคิดว่าสัญกรณ์ที่หลากหลายเหล่านี้และวิธีการกำหนด / การตั้งค่า / ฟังก์ชั่นการโทรเป็นส่วนที่สับสนที่สุดในการเริ่มทำงานกับจาวาสคริปต์ ผู้คนมักไม่พูดถึงพวกเขาเช่นกัน มันไม่ได้เป็นจุดเน้นในคู่มือหรือบล็อก มันทำให้ฉันกลัวเพราะมันเป็นสิ่งที่ทำให้คนส่วนใหญ่สับสนและคนที่พูดภาษา js ต้องผ่านมันเช่นกัน มันเหมือนกับความจริงข้อห้ามที่ว่างเปล่าที่ไม่เคยพูดถึง
ahnbizcad

1
อ่านเพิ่มเติมเกี่ยวกับวัตถุประสงค์ของการสร้างนี้หรือตรวจสอบคำอธิบาย ( ด้านเทคนิค ) ( ที่นี่ ) สำหรับตำแหน่งของวงเล็บให้ดูที่คำถามนี้เกี่ยวกับสถานที่ของพวกเขา
Bergi

OT: สำหรับคนที่ต้องการที่จะทราบว่าฟังก์ชั่นที่ไม่ระบุชื่อเหล่านี้จะใช้เป็นจำนวนมากโปรดอ่านadequatelygood.com/JavaScript-Module-Pattern-In-Depth.html
Alireza Fattahi

นี่เป็นกรณีทั่วไปของนิพจน์ฟังก์ชันที่เรียกใช้ทันที (IIFE)
Aminu Kano

คำตอบ:


410

มันไม่ทำงานเพราะมันจะถูกแยกวิเคราะห์เป็นFunctionDeclarationและระบุชื่อของการประกาศฟังก์ชั่นเป็นผู้ได้รับมอบอำนาจ

เมื่อคุณล้อมรอบด้วยวงเล็บจะถูกประเมินเป็น a FunctionExpressionและนิพจน์ฟังก์ชันสามารถตั้งชื่อได้หรือไม่

ไวยากรณ์ของFunctionDeclarationหน้าตาแบบนี้:

function Identifier ( FormalParameterListopt ) { FunctionBody }

และFunctionExpressions:

function Identifieropt ( FormalParameterListopt ) { FunctionBody }

ตามที่คุณเห็นโทเค็นIdentifier(ตัวระบุการเลือก ) FunctionExpressionเป็นทางเลือกดังนั้นเราสามารถมีนิพจน์ฟังก์ชันโดยไม่มีชื่อที่กำหนดไว้:

(function () {
    alert(2 + 2);
}());

หรือชื่อฟังก์ชั่นการแสดงออก:

(function foo() {
    alert(2 + 2);
}());

วงเล็บ (เรียกอย่างเป็นทางการว่าGrouping Operator ) สามารถล้อมรอบนิพจน์เท่านั้นและนิพจน์ฟังก์ชันจะถูกประเมิน

การผลิตไวยากรณ์ทั้งสองนั้นอาจคลุมเครือและอาจมีลักษณะเหมือนกันทุกประการเช่น:

function foo () {} // FunctionDeclaration

0,function foo () {} // FunctionExpression

ตัวแยกวิเคราะห์รู้ว่าเป็นFunctionDeclarationหรือFunctionExpressionขึ้นอยู่กับบริบทที่ปรากฏ

ในตัวอย่างข้างต้นตัวที่สองคือนิพจน์เนื่องจากตัวดำเนินการ Commaสามารถจัดการนิพจน์ได้เท่านั้น

ในทางกลับกันFunctionDeclarations สามารถปรากฏขึ้นได้ในสิ่งที่เรียกว่าProgramรหัส "" เท่านั้นความหมายของรหัสที่อยู่นอกขอบเขตทั่วโลกและภายในFunctionBodyฟังก์ชันอื่น ๆ

ควรหลีกเลี่ยงการใช้งานฟังก์ชั่นภายในบล็อคเพราะสามารถนำไปสู่พฤติกรรมที่คาดเดาไม่ได้เช่น:

if (true) {
  function foo() {
    alert('true');
  }
} else {
  function foo() {
    alert('false!');
  }
}

foo(); // true? false? why?

รหัสข้างต้นจริงควรผลิตSyntaxErrorตั้งแต่Blockเท่านั้นที่สามารถมีงบ (และ ECMAScript จำเพาะไม่ได้กำหนดคำสั่งฟังก์ชั่นใด ๆ ) 'false!'แต่การใช้งานส่วนใหญ่มีความอดทนและก็จะใช้ฟังก์ชั่นที่สองหนึ่งซึ่งการแจ้งเตือน

การใช้งาน Mozilla -Rhino, SpiderMonkey - มีพฤติกรรมที่แตกต่าง ไวยากรณ์ของพวกเขามีคำสั่งฟังก์ชั่นที่ไม่ได้มาตรฐานซึ่งหมายความว่าฟังก์ชั่นจะได้รับการประเมินในเวลาทำงานไม่ใช่เวลาในการแยกวิเคราะห์ตามที่เกิดขึ้นกับFunctionDeclarations ในการใช้งานเหล่านั้นเราจะได้รับฟังก์ชั่นแรกที่กำหนดไว้


ฟังก์ชั่นสามารถประกาศได้หลายวิธีเปรียบเทียบสิ่งต่อไปนี้ :

1- ฟังก์ชั่นที่กำหนดด้วยฟังก์ชั่นคอนสตรัคที่กำหนดให้กับตัวแปรคูณ :

var multiply = new Function("x", "y", "return x * y;");

2- การประกาศฟังก์ชันของฟังก์ชันที่ชื่อทวีคูณ :

function multiply(x, y) {
    return x * y;
}

3- การแสดงออกของฟังก์ชั่นที่กำหนดให้กับตัวแปรคูณ :

var multiply = function (x, y) {
    return x * y;
};

4- ชื่อฟังก์ชั่นการแสดงออกfunc_name ที่กำหนดให้กับตัวแปรคูณ :

var multiply = function func_name(x, y) {
    return x * y;
};

2
คำตอบของ CMS ถูกต้อง สำหรับคำอธิบายที่ดีเยี่ยมในเชิงลึกของการประกาศฟังก์ชั่นและการแสดงออกดูบทความนี้โดย kangax
Tim Down

นี่คือคำตอบที่ดี ดูเหมือนว่าจะมีการเชื่อมโยงอย่างใกล้ชิดกับวิธีการแยกวิเคราะห์ข้อความต้นฉบับและโครงสร้างของ BNF ในตัวอย่างที่ 3 ของคุณฉันควรจะบอกว่ามันคือการแสดงออกของฟังก์ชั่นเพราะมันเป็นไปตามเครื่องหมายเท่ากับ wheras รูปแบบที่เป็นฟังก์ชั่นการประกาศ / คำสั่งเมื่อมันปรากฏบนเส้นด้วยตัวเอง? ฉันสงสัยว่าจุดประสงค์ของสิ่งนั้นคืออะไร - มันแค่ตีความว่าเป็นการประกาศฟังก์ชั่นที่มีชื่อ แต่ไม่มีชื่อ? มีจุดประสงค์อะไรที่จะให้บริการถ้าคุณไม่ได้กำหนดให้ตัวแปรตั้งชื่อหรือเรียกมัน
Breton

1
เอเอชเอ มีประโยชน์มาก. ขอบคุณ CMS ส่วนนี้ของเอกสาร Mozilla ที่คุณเชื่อมโยงด้วยกำลังให้ความกระจ่างเป็นพิเศษ: developer.mozilla.org/En/Core_JavaScript_1.5_Reference/
......

1
+1 ถึงแม้ว่าคุณจะมีวงเล็บปิดในตำแหน่งที่ไม่ถูกต้องในการแสดงออกของฟังก์ชั่น :-)
NickFitz

1
@GovindRai, ไม่การประกาศฟังก์ชั่นได้รับการจัดการในเวลารวบรวมและการประกาศฟังก์ชั่นที่ซ้ำกันจะแทนที่การประกาศก่อนหน้านี้ ณ รันไทม์การประกาศฟังก์ชันมีอยู่แล้วและในกรณีนี้อันที่ใช้ได้คืออันที่แจ้งเตือนว่า "เท็จ" สำหรับข้อมูลเพิ่มเติมให้อ่านคุณไม่รู้ JS
Saurabh Misra

50

แม้ว่านี่จะเป็นคำถามและคำตอบเก่า ๆ แต่ก็ยังกล่าวถึงหัวข้อที่ทุกวันนี้ทำให้นักพัฒนาหลายคนต้องวนซ้ำ ฉันไม่สามารถนับจำนวนผู้สมัครพัฒนาจาวาสคริปต์ที่ฉันสัมภาษณ์ที่ไม่สามารถบอกความแตกต่างระหว่างการประกาศฟังก์ชั่นและการแสดงออกของฟังก์ชั่นและผู้ที่ไม่รู้ว่าการแสดงออกของฟังก์ชั่นที่เรียกใช้ทันทีนั้นคืออะไร

แม้ว่าฉันจะพูดถึงสิ่งสำคัญอย่างหนึ่งซึ่งก็คือข้อมูลโค้ดของ Premasagar ไม่สามารถใช้งานได้แม้ว่าเขาจะให้รหัสตัวระบุชื่อก็ตาม

function someName() {
    alert(2 + 2);
}();

เหตุผลที่ใช้ไม่ได้คือกลไก JavaScript ตีความว่านี่เป็นการประกาศฟังก์ชันตามด้วยตัวดำเนินการจัดกลุ่มที่ไม่เกี่ยวข้องอย่างสมบูรณ์ซึ่งไม่มีนิพจน์และตัวดำเนินการจัดกลุ่มจะต้องมีนิพจน์ ตามจาวาสคริปต์โค้ดตัวอย่างข้างต้นนั้นเทียบเท่ากับโค้ดต่อไปนี้

function someName() {
    alert(2 + 2);
}

();

อีกสิ่งหนึ่งที่ฉันอยากจะชี้ให้เห็นว่าบางคนอาจใช้กับคนอื่นก็คือตัวระบุชื่อใด ๆ ที่คุณให้ไว้สำหรับการแสดงออกของฟังก์ชั่นนั้นไม่มีประโยชน์อะไรมากในบริบทของรหัสยกเว้นจากภายในนิยามฟังก์ชันเอง

var a = function b() {
    // do something
};
a(); // works
b(); // doesn't work

var c = function d() {
    window.setTimeout(d, 1000); // works
};

แน่นอนว่าการใช้ตัวระบุชื่อพร้อมคำจำกัดความของฟังก์ชั่นของคุณนั้นมีประโยชน์เสมอเมื่อพูดถึงการดีบักโค้ด แต่นั่นเป็นอย่างอื่น ... :-)


17

คำตอบที่ยอดเยี่ยมมีการโพสต์แล้ว แต่ฉันต้องการที่จะทราบว่าการประกาศฟังก์ชั่นกลับระเบียนเสร็จสิ้นที่ว่างเปล่า:

14.1.20 - ความหมายรันไทม์: การประเมินผล

FunctionDeclaration : function BindingIdentifier ( FormalParameters ) { FunctionBody }

  1. ส่งคืนNormalCompletion (ว่าง)

ความจริงเรื่องนี้ไม่ใช่เรื่องง่ายที่จะสังเกตเห็นเพราะวิธีส่วนใหญ่ของการพยายามรับค่าที่ส่งคืนจะแปลงการประกาศฟังก์ชันเป็นนิพจน์ฟังก์ชัน อย่างไรก็ตามevalแสดง:

var r = eval("function f(){}");
console.log(r); // undefined

การเรียกเร็กคอร์ดการเสร็จสิ้นว่างเปล่านั้นไม่มีเหตุผล นั่นเป็นเหตุผลที่function f(){}()ไม่สามารถทำงานได้ ในความเป็นจริงแล้วเอ็นจิน JS ไม่แม้แต่พยายามเรียกมันวงเล็บถูกพิจารณาว่าเป็นส่วนหนึ่งของคำสั่งอื่น

แต่ถ้าคุณหุ้มฟังก์ชั่นในวงเล็บมันจะกลายเป็นการแสดงออกของฟังก์ชั่น:

var r = eval("(function f(){})");
console.log(r); // function f(){}

การแสดงออกของฟังก์ชั่นกลับวัตถุฟังก์ชั่น (function f(){})()และดังนั้นคุณสามารถเรียกมันว่า:


ความละอายคำตอบนี้ถูกมองข้าม แม้ว่าจะไม่ครอบคลุมเท่าคำตอบที่ยอมรับ แต่ก็ให้ข้อมูลพิเศษที่มีประโยชน์มากและสมควรได้รับคะแนนเสียงเพิ่มเติม
Avrohom Yisroel

10

ใน JavaScript นี้เรียกว่าการแสดงออกของฟังก์ชั่นทันที-เรียก (IIFE)

ในการทำให้ฟังก์ชั่นเป็นนิพจน์คุณต้องทำดังนี้

  1. ใส่โดยใช้ ()

  2. วางโมฆะโอเปอเรเตอร์ก่อนหน้านั้น

  3. กำหนดให้กับตัวแปร

มิฉะนั้นจะถือว่าเป็นคำจำกัดความของฟังก์ชั่นจากนั้นคุณจะไม่สามารถโทร / เรียกใช้ในเวลาเดียวกันด้วยวิธีต่อไปนี้:

 function (arg1) { console.log(arg1) }(); 

ข้างต้นจะทำให้คุณผิดพลาด เพราะคุณสามารถเรียกใช้ฟังก์ชันนิพจน์ได้ทันที

วิธีนี้สามารถทำได้สองวิธี: วิธีที่ 1:

(function(arg1, arg2){
//some code
})(var1, var2);

วิธีที่ 2:

(function(arg1, arg2){
//some code
}(var1, var2));

วิธีที่ 3:

void function(arg1, arg2){
//some code
}(var1, var2);

วิธีที่ 4:

  var ll = function (arg1, arg2) {
      console.log(arg1, arg2);
  }(var1, var2);

ด้านบนทั้งหมดจะเรียกใช้ฟังก์ชันนิพจน์ทันที


3

ฉันมีเพียงคำพูดเล็ก ๆ อีก รหัสของคุณจะทำงานเมื่อมีการเปลี่ยนแปลงเล็กน้อย:

var x = function(){
    alert(2 + 2);
}();

ฉันใช้ไวยากรณ์ข้างต้นแทนรุ่นที่แพร่หลายมากขึ้น:

var module = (function(){
    alert(2 + 2);
})();

เพราะฉันไม่ได้จัดการให้การเยื้องทำงานอย่างถูกต้องสำหรับไฟล์ javascript ในกลุ่ม ดูเหมือนว่ากลุ่มไม่ชอบวงเล็บปีกกาภายในวงเล็บเปิด


เหตุใดไวยากรณ์นี้จึงทำงานเมื่อคุณกำหนดผลลัพธ์ที่ถูกดำเนินการให้กับตัวแปร แต่ไม่ใช่แบบสแตนด์อโลน
paislee

1
@paislee - เนื่องจากเครื่องมือ JavaScript ตีความคำสั่ง JavaScript ที่ถูกต้องใด ๆ ที่เริ่มต้นด้วยfunctionคำหลักเป็นการประกาศฟังก์ชั่นในกรณีที่การติดตามต่อท้าย()ถูกตีความว่าเป็นผู้ประกอบการจัดกลุ่มซึ่งตามกฎไวยากรณ์ JavaScript เท่านั้นและจะต้องมีการแสดงออก JavaScript
natlee75

1
@bosonix - ไวยากรณ์ที่คุณต้องการใช้งานได้ดี แต่ควรใช้ "รุ่นที่แพร่หลายมากขึ้น" ที่คุณอ้างถึงหรือตัวแปรที่()อยู่ภายในตัวดำเนินการจัดกลุ่ม (ที่ Douglas Crockford แนะนำอย่างยิ่ง) เพื่อความมั่นคง: เป็นเรื่องธรรมดาที่จะใช้ IIFE โดยไม่ต้องกำหนดให้กับตัวแปรและเป็นเรื่องง่ายที่จะลืมใส่วงเล็บในกรณีที่คุณไม่ใช้อย่างสม่ำเสมอ
natlee75

0

บางทีคำตอบที่สั้นกว่านั้นก็คือ

function() { alert( 2 + 2 ); }

เป็นฟังก์ชันตามตัวอักษรที่กำหนดฟังก์ชั่น (ไม่ระบุชื่อ) ส่วนเพิ่มเติม () - คู่ซึ่งถูกตีความว่าเป็นนิพจน์ไม่คาดว่าจะอยู่ที่ระดับบนสุดมีเพียงตัวอักษรเท่านั้น

(function() { alert( 2 + 2 ); })();

อยู่ในคำสั่ง expressionที่เรียกใช้ฟังก์ชันที่ไม่ระบุชื่อ


0
(function(){
     alert(2 + 2);
 })();

ด้านบนเป็นไวยากรณ์ที่ถูกต้องเพราะสิ่งใดก็ตามที่ส่งผ่านภายในวงเล็บถือเป็นนิพจน์ฟังก์ชัน

function(){
    alert(2 + 2);
}();

ด้านบนไม่ใช่ไวยากรณ์ที่ถูกต้อง เนื่องจากตัวแยกวิเคราะห์ไวยากรณ์ของจาวาสคริปต์ค้นหาชื่อฟังก์ชั่นหลังจากฟังก์ชั่นคีย์เวิร์ด


2
ในขณะที่คำตอบของคุณไม่ถูกต้องคำตอบที่ได้รับการยอมรับแล้วครอบคลุมทั้งหมดนี้ในฝ่าย
จนถึงอาร์โนลด์

0

คุณยังสามารถใช้งานได้เช่น:

! function() { console.log('yeah') }()

หรือ

!! function() { console.log('yeah') }()

!- ปฏิเสธสหกรณ์แปลงนิยาม Fn เพื่อแสดงออก Fn ()ดังนั้นคุณสามารถเรียกใช้งานได้ทันทีด้วย เหมือนกับการใช้0,fn defหรือvoid fn def


-1

สามารถใช้กับพารามิเตอร์ - อาร์กิวเมนต์เช่น

var x = 3; 
var y = 4;

(function(a,b){alert(a + b)})(x,y)

จะเป็น 7


-1

วงเล็บพิเศษเหล่านั้นจะสร้างฟังก์ชั่นที่ไม่ระบุชื่อพิเศษระหว่างโกลบอลสเปซส่วนกลางและฟังก์ชันที่ไม่ระบุตัวตนที่มีรหัส และในฟังก์ชั่น Javascript ที่ประกาศไว้ในฟังก์ชั่นอื่น ๆ สามารถเข้าถึงเนมสเปซของฟังก์ชั่นหลักที่มีพวกเขา เนื่องจากมีวัตถุพิเศษ (ฟังก์ชั่นไม่ระบุชื่อ) ระหว่างขอบเขตทั่วโลกและการกำหนดขอบเขตที่แท้จริงของรหัสจะไม่ถูกเก็บไว้

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