เหตุใดจึงใช้นิพจน์ฟังก์ชันที่มีชื่อ


94

เรามีสองวิธีที่แตกต่างกันในการแสดงฟังก์ชันใน JavaScript:

นิพจน์ฟังก์ชันที่ตั้งชื่อ (NFE) :

var boo = function boo () {
  alert(1);
};

นิพจน์ฟังก์ชันที่ไม่ระบุชื่อ :

var boo = function () {
  alert(1);
};

boo();และทั้งสองของพวกเขาสามารถเรียกว่ามี ฉันไม่เห็นว่าทำไม / เมื่อใดที่ฉันควรใช้ฟังก์ชันที่ไม่ระบุตัวตนและเมื่อใดที่ฉันควรใช้นิพจน์ฟังก์ชันที่มีชื่อ อะไรคือความแตกต่างระหว่างพวกเขา?


คำตอบ:


86

ในกรณีของนิพจน์ฟังก์ชันที่ไม่ระบุชื่อฟังก์ชันจะไม่ระบุชื่อ  - ตามตัวอักษรไม่มีชื่อ ตัวแปรที่คุณกำหนดให้มีชื่อ แต่ฟังก์ชันไม่มี (อัปเดต: นั่นเป็นความจริงผ่าน ES5 ณ ES2015 [aka ES6] โดยมากฟังก์ชันที่สร้างด้วยนิพจน์ที่ไม่ระบุตัวตนจะได้รับชื่อจริง [แต่ไม่ใช่ตัวระบุอัตโนมัติ] อ่านต่อ ... )

ชื่อมีประโยชน์ สามารถดูชื่อได้ในสแต็กเทรซโทรสแต็กรายการเบรกพอยต์ ฯลฯ ชื่อเป็นสิ่งที่ดี

(คุณเคยต้องระวังนิพจน์ฟังก์ชันที่มีชื่อใน IE เวอร์ชันเก่า [IE8 และต่ำกว่า] เนื่องจากพวกเขาสร้างอ็อบเจ็กต์ฟังก์ชันที่แยกจากกันโดยสิ้นเชิงโดยผิดพลาดในเวลาที่แตกต่างกันสองครั้ง [เพิ่มเติมในบทความบล็อกของฉันใช้เวลาสองเท่า ] หากคุณต้องการ สนับสนุน IE8 [!!] ควรใช้นิพจน์ฟังก์ชันแบบไม่ระบุตัวตนหรือการประกาศฟังก์ชันแต่หลีกเลี่ยงนิพจน์ฟังก์ชันที่ระบุชื่อ)

สิ่งสำคัญอย่างหนึ่งเกี่ยวกับนิพจน์ฟังก์ชันที่มีชื่อคือการสร้างตัวระบุในขอบเขตด้วยชื่อนั้นสำหรับฟังก์ชันภายใน functon body:

var x = function example() {
    console.log(typeof example); // "function"
};
x();
console.log(typeof example);     // "undefined"

ตั้งแต่ ES2015 นิพจน์ฟังก์ชัน "ไม่ระบุตัวตน" จำนวนมากจะสร้างฟังก์ชันที่มีชื่อและสิ่งนี้เกิดขึ้นก่อนหน้านี้โดยเอ็นจิ้น JavaScript สมัยใหม่ต่างๆที่ค่อนข้างฉลาดในการอนุมานชื่อจากบริบท ใน ES2015 booแสดงออกฟังก์ชั่นที่ไม่ระบุชื่อของคุณจะส่งผลในฟังก์ชั่นที่มีชื่อ อย่างไรก็ตามแม้จะมีความหมาย ES2015 + ตัวระบุอัตโนมัติจะไม่ถูกสร้างขึ้น:

var obj = {
    x: function() {
       console.log(typeof x);   // "undefined"
       console.log(obj.x.name); // "x"
    },
    y: function y() {
       console.log(typeof y);   // "function"
       console.log(obj.y.name); // "y"
    }
};
obj.x();
obj.y();

การกำหนดชื่อฟังก์ชันเสร็จสิ้นด้วยการดำเนินการนามธรรมSetFunctionName ที่ใช้ในการดำเนินการต่างๆในข้อมูลจำเพาะ

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

var boo = function() { /*...*/ };

(หรืออาจเป็นletหรือconstมากกว่าvar)หรือ

var obj = {
    boo: function() { /*...*/ }
};

หรือ

doSomething({
    boo: function() { /*...*/ }
});

(สองตัวสุดท้ายนี้เหมือนกันจริงๆ)ฟังก์ชันผลลัพธ์จะมีชื่อ ( booในตัวอย่าง)

มีข้อยกเว้นที่สำคัญและโดยเจตนา: การกำหนดให้กับคุณสมบัติบนวัตถุที่มีอยู่:

obj.boo = function() { /*...*/ }; // <== Does not get a name

นี่เป็นเพราะความกังวลเกี่ยวกับการรั่วไหลของข้อมูลที่เกิดขึ้นเมื่อคุณลักษณะใหม่กำลังอยู่ในขั้นตอนการเพิ่ม รายละเอียดในคำตอบของฉันคำถามอื่นที่นี่


1
เป็นที่น่าสังเกตว่ามีอย่างน้อยสองแห่งที่การใช้ NFE ยังคงให้ข้อได้เปรียบที่เป็นรูปธรรมประการแรกสำหรับฟังก์ชั่นที่ตั้งใจจะใช้เป็นตัวสร้างผ่านตัวnewดำเนินการ (การตั้งชื่อฟังก์ชันดังกล่าวทั้งหมดจะทำให้.constructorคุณสมบัติมีประโยชน์มากขึ้นในระหว่างการดีบั๊กเพื่อหาว่าห่าอะไร อ็อบเจ็กต์บางตัวเป็นอินสแตนซ์ของ) และสำหรับฟังก์ชันลิเทอรัลถูกส่งผ่านไปยังฟังก์ชันโดยตรงโดยไม่ถูกกำหนดให้กับคุณสมบัติหรือตัวแปรก่อน (เช่นsetTimeout(function () {/*do stuff*/});) แม้แต่ Chrome ก็ยังแสดงสิ่งเหล่านี้(anonymous function)เว้นแต่คุณจะช่วยด้วยการตั้งชื่อ
Mark Amery

4
@MarkAmery: "ยังเป็นจริงหรือเปล่าฉัน ... พยายาม CTRL-F สำหรับกฎเหล่านี้ แต่ไม่พบ" โอ้ใช่ :-) มันเกลื่อนไปทั่วข้อมูลจำเพาะแทนที่จะอยู่ในที่เดียวที่กำหนดชุดของกฎเพียงค้นหา "setFunctionName" ฉันได้เพิ่มลิงค์ย่อยเล็ก ๆ ด้านบน แต่ตอนนี้แสดงในสถานที่ต่างๆ ~ 29 แห่ง ฉันจะแปลกใจเล็กน้อยถ้าsetTimeoutตัวอย่างของคุณไม่ได้รับชื่อจากการโต้แย้งอย่างเป็นทางการที่ประกาศไว้setTimeoutถ้ามี :-) แต่ใช่แล้ว NFE จะมีประโยชน์อย่างแน่นอนหากคุณรู้ว่าคุณจะไม่จัดการกับเบราว์เซอร์เก่า ๆ ที่สร้างแฮช
TJ Crowder

24

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

ตัวอย่างเช่นพิจารณารหัสนี้:

setTimeout(function sayMoo() {
    alert('MOO');
    setTimeout(sayMoo, 1000);
}, 1000);

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

ในอดีตเป็นไปได้ที่จะเขียนโค้ดเช่นนี้แม้จะใช้นิพจน์ฟังก์ชันที่ไม่ระบุชื่อโดยใช้ประโยชน์จากarguments.callee...

setTimeout(function () {
    alert('MOO');
    setTimeout(arguments.callee, 1000);
}, 1000);

... แต่arguments.calleeเลิกใช้แล้วและถูกห้ามโดยสิ้นเชิงในโหมดเข้มงวด ES5 ดังนั้น MDN จึงให้คำแนะนำ:

หลีกเลี่ยงการใช้arguments.callee()โดยตั้งชื่อนิพจน์ฟังก์ชันหรือใช้การประกาศฟังก์ชันที่ฟังก์ชันต้องเรียกตัวเอง

(เน้นเหมือง)


3

ถ้าฟังก์ชันถูกระบุเป็นนิพจน์ฟังก์ชันสามารถกำหนดชื่อได้

จะใช้ได้เฉพาะในฟังก์ชันเท่านั้น (ยกเว้น IE8-)

var f = function sayHi(name) {
  alert( sayHi ); // Inside the function you can see the function code
};

alert( sayHi ); // (Error: undefined variable 'sayHi')

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

นอกจากนี้ชื่อ NFE (Named Function Expression) สามารถเขียนทับได้ด้วยObject.defineProperty(...)วิธีการดังต่อไปนี้:

var test = function sayHi(name) {
  Object.defineProperty(test, 'name', { value: 'foo', configurable: true });
  alert( test.name ); // foo
};

test();

หมายเหตุ: ด้วยการประกาศฟังก์ชันนี้ไม่สามารถทำได้ ชื่อฟังก์ชันภายใน "พิเศษ" นี้ระบุไว้ในไวยากรณ์ของนิพจน์ฟังก์ชันเท่านั้น


2

คุณควรใช้นิพจน์ฟังก์ชันที่มีชื่อเสมอนั่นคือเหตุผลว่าทำไม:

  1. คุณสามารถใช้ชื่อของฟังก์ชันนั้นเมื่อคุณต้องการเรียกซ้ำ

  2. ฟังก์ชันที่ไม่ระบุตัวตนไม่ได้ช่วยในการดีบักเนื่องจากคุณไม่เห็นชื่อของฟังก์ชันที่ทำให้เกิดปัญหา

  3. เมื่อคุณไม่ได้ตั้งชื่อฟังก์ชันในภายหลังมันจะยากกว่าที่จะเข้าใจว่ามันทำอะไร การตั้งชื่อทำให้เข้าใจง่ายขึ้น

var foo = function bar() {
    //some code...
};

foo();
bar(); // Error!

ตัวอย่างเช่นในที่นี้เนื่องจากแถบชื่อถูกใช้ภายในนิพจน์ฟังก์ชันจึงไม่ได้รับการประกาศในขอบเขตภายนอก ด้วยนิพจน์ฟังก์ชันที่มีชื่อชื่อของนิพจน์ฟังก์ชันจะอยู่ภายในขอบเขตของตัวเอง


1

การใช้นิพจน์ฟังก์ชันที่ตั้งชื่อจะดีกว่าเมื่อคุณต้องการอ้างอิงฟังก์ชันที่เป็นปัญหาโดยไม่ต้องพึ่งพาคุณสมบัติที่เลิกใช้งานเช่นarguments.callee.


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