ทำไมคุณสมบัติ arguments.callee.caller เลิกใช้ใน JavaScript


214

เหตุใดarguments.callee.callerคุณสมบัติจึงเลิกใช้ใน JavaScript

มีการเพิ่มและเลิกใช้แล้วใน JavaScript แต่ถูกตัดออกทั้งหมดโดย ECMAScript เบราว์เซอร์บางตัว (Mozilla, IE) รองรับเสมอและไม่มีแผนใด ๆ บนแผนที่เพื่อลบการสนับสนุน อื่น ๆ (Safari, Opera) ได้นำการสนับสนุนมาใช้ แต่การสนับสนุนเบราว์เซอร์รุ่นเก่านั้นไม่น่าเชื่อถือ

มีเหตุผลที่ดีที่จะใช้ฟังก์ชั่นที่มีค่านี้ในบริเวณขอบรกหรือไม่?

(หรือสลับกันมีวิธีที่ดีกว่าในการจับที่จับบนฟังก์ชั่นการโทรหรือไม่)


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

4
(เบราว์เซอร์เกือบทั้งหมดทำสิ่งนี้ในครั้งเดียวหรืออย่างอื่นเช่นฟีเจอร์นี้ (และตัว JS เอง) มาจาก Netscape, XHR ที่เกิดขึ้นใน IE, Canvas ใน Safari ฯลฯ บางส่วนมีประโยชน์และเบราว์เซอร์อื่น ๆ เมื่อเวลาผ่านไป (js, canvas, xhr เป็นตัวอย่างทั้งหมด), บางส่วน (.callee) ไม่ใช่
olliej

@olliej ความคิดเห็นของคุณเกี่ยวกับการสนับสนุนเพราะมีการใช้งานและไม่ได้เพราะมันเป็นมาตรฐาน (หรือแม้จะมีการคัดค้านในมาตรฐาน) เป็นจริงมาก! นี่คือเหตุผลที่ฉันเริ่มเพิกเฉยต่อมาตรฐานเมื่อใดก็ตามที่ฉันรู้สึกว่าพวกเขาไม่ได้ช่วยฉัน เราในฐานะนักพัฒนาสามารถกำหนดทิศทางของมาตรฐานโดยใช้สิ่งที่ได้ผลและไม่ใช่สิ่งที่สเป็คบอกว่าเราควรทำ นี่คือวิธีที่เราได้รับ<b>และ<i>กลับมา (ใช่สิ่งเหล่านั้นถูกคัดค้าน ณ จุดหนึ่ง)
Stijn de Witt

คำตอบ:


252

JavaScript เวอร์ชันก่อนหน้านี้ไม่อนุญาตการแสดงออกของฟังก์ชั่นที่มีชื่อและเนื่องจากว่าเราไม่สามารถสร้างนิพจน์ฟังก์ชันแบบเรียกซ้ำ:

 // This snippet will work:
 function factorial(n) {
     return (!(n>1))? 1 : factorial(n-1)*n;
 }
 [1,2,3,4,5].map(factorial);


 // But this snippet will not:
 [1,2,3,4,5].map(function(n) {
     return (!(n>1))? 1 : /* what goes here? */ (n-1)*n;
 });

ในการหลีกเลี่ยงสิ่งนี้arguments.calleeถูกเพิ่มเข้ามาเพื่อให้เราทำ:

 [1,2,3,4,5].map(function(n) {
     return (!(n>1))? 1 : arguments.callee(n-1)*n;
 });

อย่างไรก็ตามนี่เป็นวิธีแก้ปัญหาที่ไม่ดีจริง ๆ เช่นนี้ (ร่วมกับข้อโต้แย้งอื่น ๆ ประเด็น callee และปัญหาผู้โทร) ทำให้การเรียกใช้แบบอินไลน์และหางเป็นไปไม่ได้ในกรณีทั่วไป (คุณสามารถบรรลุในกรณีที่เลือกผ่านการติดตาม ฯลฯ ย่อยที่ดีที่สุดเนื่องจากการตรวจสอบที่ไม่จำเป็นอย่างอื่น) ปัญหาที่สำคัญอื่น ๆ คือการเรียกซ้ำจะได้รับthisค่าที่แตกต่างตัวอย่างเช่น

var global = this;
var sillyFunction = function (recursed) {
    if (!recursed)
        return arguments.callee(true);
    if (this !== global)
        alert("This is: " + this);
    else
        alert("This is the global");
}
sillyFunction();

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

 [1,2,3,4,5].map(function factorial(n) {
     return (!(n>1))? 1 : factorial(n-1)*n;
 });

สิ่งนี้มีประโยชน์มากมาย:

  • ฟังก์ชั่นนี้สามารถเรียกได้ว่าเหมือนกับฟังก์ชั่นอื่น ๆ จากข้างในโค้ดของคุณ

  • มันไม่สร้างมลภาวะเนมสเปซ

  • ค่าของthisจะไม่เปลี่ยนแปลง

  • มันมีประสิทธิภาพมากกว่า (การเข้าถึงอ็อบเจกต์อาร์กิวเมนต์มีราคาแพง)

อ๊ะ

เพิ่งตระหนักว่านอกเหนือไปจากทุกสิ่งทุกอย่างคำถามที่เกี่ยวกับหรือมากขึ้นโดยเฉพาะarguments.callee.callerFunction.caller

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

เช่น. หากเราไม่สามารถรับประกันได้ว่าฟังก์ชั่นfจะไม่เรียกฟังก์ชั่นที่ไม่รู้จักนั้นจะไม่สามารถอินไลน์fได้ โดยทั่วไปหมายความว่าไซต์การโทรใด ๆ ที่อาจไม่สามารถ inlinable เก็บสะสมยามจำนวนมากได้:

 function f(a, b, c, d, e) { return a ? b * c : d * e; }

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

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


12
คุณกำลังจะบอกว่ามันถูกลดความสำคัญเพียงเพราะมันยากที่จะเพิ่มประสิทธิภาพ? นั่นมันโง่นะ
Thomas Eding

11
ไม่ฉันแสดงเหตุผลหลายประการนอกเหนือจากทำให้ยากที่จะปรับให้เหมาะสม (แม้ว่าในประวัติศาสตร์ทั่วไปได้แสดงให้เห็นว่าสิ่งที่ยากต่อการปรับให้เหมาะสมยังมีความหมายที่ผู้คนมีปัญหาต่อไปนี้)
olliej

17
นี้อาร์กิวเมนต์เป็นบิตปลอมค่าของมันสามารถตั้งค่าได้โดยการเรียกถ้ามันเป็นสิ่งสำคัญ โดยปกติแล้วมันไม่ได้ใช้ (อย่างน้อยฉันไม่เคยมีปัญหากับมันในฟังก์ชั่นแบบเรียกซ้ำ) การเรียกใช้ฟังก์ชันตามชื่อมีปัญหาเดียวกันกับthisดังนั้นฉันคิดว่ามันไม่เกี่ยวข้องกับการพิจารณาว่าcalleeดีหรือไม่ดี นอกจากนี้ผู้ถูกเรียกและโทรเป็นเพียงการ "เลิก" ในโหมดที่เข้มงวด (ECMAscript ed 5 ธ.ค. 2009) แต่ผมเดาว่าไม่เป็นที่รู้จักในปี 2008 เมื่อ olliej โพสต์
RobG

8
) ฉันยังไม่เห็นเหตุผล ในภาษาใด ๆ ที่มีฟังก์ชั่นชั้นหนึ่งมีคุณค่าที่ชัดเจนในการสามารถกำหนดฟังก์ชั่นของร่างกายที่สามารถอ้างถึงตัวเองโดยไม่ต้องรู้ว่าฉัน
Mark Reed

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

89

arguments.callee.callerจะไม่เลิกแม้ว่ามันจะทำให้การใช้งานของคุณสมบัติ ( จะให้การอ้างอิงฟังก์ชันปัจจุบัน)Function.callerarguments.callee

  • Function.callerแม้ว่าจะไม่ได้มาตรฐานตาม ECMA3 จะดำเนินการในเบราว์เซอร์ที่สำคัญทั้งหมดในปัจจุบัน
  • arguments.caller จะเลิกในความโปรดปรานของและไม่ได้มีการดำเนินการในเบราว์เซอร์ที่สำคัญบางอย่างในปัจจุบัน (เช่น Firefox 3)Function.caller

ดังนั้นสถานการณ์ที่น้อยกว่าเหมาะ แต่ถ้าคุณต้องการที่จะเข้าถึงฟังก์ชั่นการโทรใน Javascript เบราว์เซอร์ที่สำคัญทั้งหมดคุณสามารถใช้คุณสมบัติการเข้าถึงทั้งโดยตรงบนอ้างอิงฟังก์ชั่นที่มีชื่อหรือจากภายในฟังก์ชั่นที่ไม่ระบุชื่อผ่านคุณสมบัติFunction.callerarguments.callee


5
นี่เป็นคำอธิบายที่ดีที่สุดว่าอะไรคือสิ่งที่ไม่ได้เลิกใช้มีประโยชน์มาก สำหรับตัวอย่างที่ดีของสิ่งที่ Function.caller ไม่สามารถทำได้ (รับการติดตามสแต็กของฟังก์ชันแบบเรียกซ้ำ) ดูที่developer.mozilla.org/en/JavaScript/Reference/Global_Objects/
Juan Mendes

1
แม้ว่าจะarguments.calleeถูกห้ามในโหมดเข้มงวด มันทำให้ฉันเศร้าเช่นกัน แต่จะดีกว่าถ้าไม่ใช้อีกต่อไป
Gras Double

1
arguments.calleeเชื่อมโยงหลายมิติที่คุณต้อง MDN บอกว่ามันจะถูกลบออกในโหมดที่เข้มงวด นั่นไม่เหมือนกับเลิกหรือไม่
styfle

1
โปรดทราบว่าarguments.callee.callerเลิกใช้งานในโหมดเข้มงวด ES5: "คุณสมบัติอื่นที่เลิกใช้แล้วคือ arguments.callee.caller ( แหล่งที่มา )
thdoan

29

มันจะดีกว่าที่จะใช้ฟังก์ชั่นที่มีชื่อกว่า arguments.callee:

 function foo () {
     ... foo() ...
 }

ดีกว่า

 function () {
     ... arguments.callee() ...
 }

ฟังก์ชั่นที่มีชื่อจะมีการเข้าถึงผู้โทรผ่านคุณสมบัติผู้โทร :

 function foo () {
     alert(foo.caller);
 }

ซึ่งดีกว่า

 function foo () {
     alert(arguments.callee.caller);
 }

เลิกเป็นเพราะ ECMAScript ปัจจุบันหลักการออกแบบ


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

27
หากคุณใช้ callee ในฟังก์ชันที่ไม่ระบุชื่อคุณมีฟังก์ชันที่ไม่ควรระบุชื่อ
Prestaul

3
บางครั้งวิธีแก้ปัญหาที่ง่ายที่สุดคือ. caller () ในกรณีเช่นนี้ชื่อฟังก์ชั่นจะไม่ช่วยคุณกำลังพยายามหาว่าฟังก์ชันใดกำลังทำการโทร
SamGoody

6
กำหนดได้ดีขึ้น ตัวอย่างเช่น IE6-8 มีชื่อฟังก์ชันฟังก์ชั่นในขณะที่ arguments.callee ทำงาน
cmc

1
นอกเหนือจากนิสัยใจคอ IE6-8 มันยังทำให้โค้ดคู่กันแน่น หากชื่อไปยังวัตถุและ / หรือฟังก์ชั่นเป็น hardcoded แล้วเป็น ardsasd และ rsk82 พูดถึงมีอันตราย refactoring สำคัญซึ่งจะเพิ่มขึ้นเฉพาะเมื่อขนาดฐานรหัสเพิ่มขึ้น การทดสอบหน่วยเป็นแนวป้องกันและฉันใช้ แต่ก็ยังไม่ได้คำตอบที่ทำให้ฉันพอใจกับการเข้ารหัสฮาร์ดโค้ดนี้อย่างแท้จริง
จัสมิน Hegman

0

เพียงแค่นามสกุล ค่าของ "this" เปลี่ยนแปลงระหว่างการเรียกซ้ำ ในตัวอย่าง (แก้ไข) ต่อไปนี้แฟกทอเรียลจะได้รับวัตถุ {foo: true}

[1,2,3,4,5].map(function factorial(n) {
  console.log(this);
  return (!(n>1))? 1 : factorial(n-1)*n;
},     {foo:true}     );

แฟกทอเรียลที่เรียกว่าครั้งแรกรับวัตถุ แต่นี่ไม่เป็นความจริงสำหรับการโทรซ้ำ


1
เพราะคุณทำผิด หากthisจำเป็นต้องได้รับการบำรุงรักษาเขียนfactorial.call(this, n-1)ฉันพบจริงในการเขียนรหัสซ้ำที่มักจะไม่มีthisหรือthisหมายถึงบางโหนดในต้นไม้และจริง ๆ แล้วมันก็ดีที่มันเปลี่ยน
Stijn de Witt
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.