Mengapa properti arguments.callee.caller tidak digunakan lagi dalam JavaScript?

214

Mengapa arguments.callee.callerproperti tersebut tidak lagi digunakan dalam JavaScript?

Itu ditambahkan dan kemudian ditinggalkan dalam JavaScript, tetapi dihilangkan sama sekali oleh ECMAScript. Beberapa browser (Mozilla, IE) selalu mendukungnya dan tidak memiliki rencana pada peta untuk menghapus dukungan. Lainnya (Safari, Opera) telah mengadopsi dukungan untuk itu, tetapi dukungan pada browser lama tidak dapat diandalkan.

Apakah ada alasan bagus untuk meletakkan fungsi yang berharga ini dalam keadaan sulit?

(Atau bergantian, apakah ada cara yang lebih baik untuk mengambil pegangan pada fungsi panggilan?)

pcorcoran
sumber
2
Ini didukung oleh peramban lain karena setiap fitur yang mendapat jumlah luas penggunaan akan menjadi bug kompatibilitas untuk peramban lain. Jika sebuah situs menggunakan fitur yang hanya ada di satu browser, situs tersebut rusak di semua browser lainnya, dan biasanya pengguna berpikir bahwa itu adalah browser yang rusak.
olliej
4
(Hampir semua browser pernah melakukan ini pada satu waktu atau yang lain, mis. Fitur ini (dan JS itu sendiri) berasal dari Netscape, XHR berasal dari IE, Canvas di Safari, dll. Beberapa di antaranya berguna dan diambil oleh browser lain. dari waktu ke waktu (js, kanvas, xhr adalah semua contoh), beberapa (.callee) tidak
olliej
@olliej Komentar Anda tentang mendukungnya karena digunakan dan bukan karena itu adalah standar (atau meskipun sudah usang dalam standar) sangat benar! Inilah sebabnya saya mulai mengabaikan standar setiap kali saya merasa mereka tidak membantu saya. Kita sebagai pengembang dapat menentukan arah standar dengan menggunakan apa yang berfungsi dan bukan apa yang harus dilakukan oleh spesifikasi. Ini adalah bagaimana kita mendapatkan <b>dan <i>kembali (ya, itu sudah usang pada satu titik).
Stijn de Witt

Jawaban:

252

Versi awal JavaScript tidak mengizinkan ekspresi fungsi bernama, dan karena itu kami tidak dapat membuat ekspresi fungsi rekursif:

 // 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;
 });

Untuk menyiasatinya, arguments.calleeditambahkan agar kami dapat melakukan:

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

Namun ini sebenarnya solusi yang sangat buruk karena hal ini (dalam hubungannya dengan argumen lain, masalah callee, dan penelepon) membuat inlining dan rekursi ekor menjadi tidak mungkin dalam kasus umum (Anda dapat mencapainya dalam kasus tertentu melalui penelusuran dll, tetapi bahkan kode terbaik tidak optimal karena cek yang tidak diperlukan). Masalah utama lainnya adalah bahwa panggilan rekursif akan mendapatkan thisnilai yang berbeda , misalnya:

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();

Bagaimanapun, EcmaScript 3 menyelesaikan masalah ini dengan mengizinkan ekspresi fungsi bernama, misalnya:

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

Ini memiliki banyak manfaat:

  • Fungsi ini dapat dipanggil seperti yang lainnya dari dalam kode Anda.

  • Itu tidak mencemari namespace.

  • Nilai thistidak berubah.

  • Ini lebih performant (mengakses objek argumen mahal).

Aduh,

Baru menyadari bahwa selain segala sesuatu yang lain pertanyaannya adalah tentang arguments.callee.caller, atau lebih khusus Function.caller.

Kapan saja Anda dapat menemukan penelepon terdalam dari fungsi apa pun di stack, dan seperti yang saya katakan di atas, melihat stack panggilan memiliki satu efek utama: Membuat sejumlah besar optimasi tidak mungkin, atau jauh lebih sulit.

Misalnya. jika kami tidak dapat menjamin bahwa suatu fungsi ftidak akan memanggil fungsi yang tidak dikenal, maka itu tidak mungkin untuk sebaris f. Pada dasarnya itu berarti bahwa setiap situs panggilan yang mungkin tidak dapat ditelusuri secara sepele mengakumulasi sejumlah besar penjaga, ambil:

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

Jika juru bahasa js tidak dapat menjamin bahwa semua argumen yang disediakan adalah angka pada saat panggilan dilakukan, maka perlu memasukkan cek untuk semua argumen sebelum kode yang diuraikan, atau tidak dapat menguraikan fungsi.

Sekarang dalam kasus khusus ini penerjemah pintar harus dapat mengatur ulang cek agar lebih optimal dan tidak memeriksa nilai apa pun yang tidak akan digunakan. Namun dalam banyak kasus itu tidak mungkin dan karena itu menjadi tidak mungkin untuk sebaris.

olliej
sumber
12
Apakah Anda mengatakan ini hanya dihapus karena sulit untuk mengoptimalkan? Itu agak konyol.
Thomas Eding
11
Tidak, saya mencantumkan sejumlah alasan, selain membuatnya sulit untuk dioptimalkan (walaupun secara umum sejarah telah menunjukkan bahwa hal-hal yang sulit untuk dioptimalkan juga memiliki semantik yang sulit diikuti orang)
olliej
17
The ini argumen adalah sedikit palsu, nilainya dapat diatur oleh panggilan jika itu penting. Biasanya itu tidak digunakan (setidaknya, saya tidak pernah punya masalah dengan itu dalam fungsi rekursif). Memanggil fungsi dengan nama memiliki masalah yang sama dengan thisjadi saya pikir itu tidak relevan dalam hal apakah callee baik atau buruk. Juga, callee dan penelepon hanya "usang" dalam mode ketat (ECMAscript ed 5 Des 2009), tapi saya kira itu tidak diketahui pada 2008 ketika olliej diposting.
RobG
8
) Saya masih tidak melihat logikanya. Dalam bahasa apa pun dengan fungsi kelas satu, ada nilai yang jelas untuk dapat mendefinisikan fungsi tubuh yang dapat merujuk pada dirinya sendiri tanpa harus tahu saya
Mark Reed
8
RobG menunjukkan ini, tapi saya tidak berpikir itu semua jelas: berulang menggunakan fungsi bernama hanya akan mempertahankan nilai thisjika thisruang lingkup global. Dalam semua kasus lain, nilai this akan berubah setelah panggilan rekursif pertama, jadi saya pikir bagian dari jawaban Anda yang mengacu pada pelestarian thistidak benar-benar valid.
JLRishe
89

arguments.callee.calleradalah tidak usang, meskipun tidak membuat penggunaan properti. ( hanya akan memberi Anda referensi ke fungsi saat ini)Function.callerarguments.callee

  • Function.caller, meskipun tidak standar menurut ECMA3, diterapkan di semua browser utama saat ini .
  • arguments.caller sudah tidak digunakan lagi , dan tidak diterapkan di beberapa peramban utama saat ini (mis. Firefox 3).Function.caller

Jadi situasinya kurang dari ideal, tetapi jika Anda ingin mengakses fungsi panggilan dalam Javascript di semua browser utama, Anda dapat menggunakan properti, baik diakses secara langsung pada referensi fungsi bernama, atau dari dalam fungsi anonim melalui properti.Function.callerarguments.callee

James Wheare
sumber
5
Ini adalah penjelasan terbaik tentang apa yang sudah dan tidak usang, sangat berguna. Untuk contoh yang baik dari apa yang Function.caller tidak bisa lakukan (dapatkan jejak stack dari fungsi rekursif), lihat developer.mozilla.org/en/JavaScript/Reference/Global_Objects/…
Juan Mendes
1
Padahal, arguments.calleedilarang dalam mode ketat. Itu membuatku sedih juga, tapi lebih baik tidak menggunakannya lagi.
Gras Double
1
The arguments.callee hyperlink Anda harus MDN mengatakan bahwa itu dihapus dalam mode ketat. Apakah itu tidak sama dengan usang?
styfle
1
Catatan yang arguments.callee.callersudah usang dalam mode ketat ES5: "Fitur lain yang tidak digunakan lagi adalah arguments.callee.caller, atau lebih khusus Function.caller." ( Sumber )
thdoan
29

Lebih baik menggunakan fungsi bernama daripada arguments.callee:

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

lebih baik dari

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

Fungsi bernama akan memiliki akses ke pemanggil melalui properti pemanggil :

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

mana yang lebih baik dari

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

Penghentian ini disebabkan oleh prinsip-prinsip desain ECMAScript saat ini .

Zach
sumber
2
Bisakah Anda menjelaskan mengapa menggunakan fungsi bernama lebih baik. Apakah tidak pernah perlu menggunakan callee dalam fungsi anonim?
AnthonyWJones
27
Jika Anda menggunakan callee dalam fungsi anonim, maka Anda memiliki fungsi yang tidak boleh anonim.
Prestaul
3
terkadang cara termudah untuk melakukan debug adalah dengan .caller (). Dalam kasus seperti itu, fungsi yang dinamai tidak akan membantu - Anda mencoba mencari tahu fungsi mana yang melakukan panggilan.
SamGoody
6
Tentukan lebih baik. Sebagai contoh, IE6-8 telah menamai quirks fungsi sementara arguments.callee berfungsi.
cmc
1
Selain dari kebiasaan IE6-8, itu juga membuat kode erat. Jika nama-nama untuk objek dan / atau fungsi hardcoded, maka seperti ardsasd dan rsk82 menyebutkan ada bahaya refactoring utama, yang hanya meningkat dengan meningkatnya ukuran basis kode. Tes unit adalah garis pertahanan, dan saya menggunakannya, tetapi itu masih bukan jawaban yang benar-benar memuaskan saya secara pribadi mengenai masalah hardcoding ini.
Jasmine Hegman
0

Hanya perpanjangan. Nilai "ini" berubah selama rekursi. Dalam contoh berikut (dimodifikasi), faktorial mendapatkan objek {foo: true}.

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

faktorial disebut pertama kali mendapatkan objek, tetapi ini tidak benar untuk panggilan rekursif.

FERcsI
sumber
1
Karena Anda salah melakukannya. Jika thisperlu dipertahankan, tulis factorial.call(this, n-1)saya sebenarnya telah menemukan dalam menulis kode rekursif, yang biasanya tidak ada this, atau thismerujuk ke beberapa node di pohon dan sebenarnya itu baik bahwa itu berubah.
Stijn de Witt