Tidak dapat memecahkan misteri fungsi dalam Javascript

16

Saya mencoba memahami di balik layar tirai Javascript dan agak terjebak dalam memahami penciptaan objek bawaan, khususnya Obyek dan Fungsi dan hubungan di antara mereka.

Ketika saya membaca bahwa semua objek bawaan seperti Array, String dll adalah ekstensi (diwarisi) dari Object, saya berasumsi bahwa Object adalah objek built-in pertama yang dibuat dan sisa objek yang diwarisi darinya. Tapi itu tidak masuk akal ketika Anda mengetahui bahwa Objek hanya dapat dibuat oleh fungsi tetapi kemudian fungsi juga tidak lain adalah objek Fungsi. Semacam itu mulai terdengar seperti dilema ayam dan ayam.

Hal lain yang sangat membingungkan adalah, jika saya console.log(Function.prototype)mencetak fungsi tetapi ketika saya mencetaknya console.log(Object.prototype)mencetak objek. Mengapa Function.prototypesuatu fungsi saat itu dimaksudkan untuk menjadi objek?

Juga, menurut dokumentasi Mozilla setiap javascript function adalah ekstensi Functionobjek tetapi ketika Anda console.log(Function.prototype.constructor)kembali berfungsi. Sekarang bagaimana Anda dapat menggunakan sesuatu untuk membuatnya sendiri (Mind = blown).

Hal terakhir, Function.prototypeadalah fungsi tetapi saya dapat mengakses constructorfungsi menggunakan Function.prototype.constructorapakah itu artinya Function.prototypeadalah fungsi yang mengembalikan prototypeobjek

Abid Abid
sumber
karena suatu fungsi adalah objek, itu berarti bahwa Function.prototypebisa menjadi fungsi dan memiliki bidang dalam. Jadi tidak, Anda tidak menjalankan fungsi prototipe ketika melalui struktur itu. Akhirnya ingat bahwa ada mesin yang menafsirkan Javascript, jadi Obyek dan Fungsi mungkin dibuat di dalam mesin dan bukan dari Javascript dan referensi khusus seperti Function.prototypedan Object.prototypemungkin hanya ditafsirkan dengan cara khusus oleh mesin.
Walfrat
1
Kecuali Anda ingin menerapkan kompiler JavaScript yang memenuhi standar, Anda benar-benar tidak perlu khawatir tentang hal ini. Jika Anda ingin menyelesaikan sesuatu yang bermanfaat, Anda tentu saja.
Jared Smith
5
FYI frasa biasa dalam bahasa Inggris untuk "dilema ayam dan ayam" adalah "masalah ayam dan telur", yaitu "mana yang lebih dulu, ayam atau telur?" (Tentu saja jawabannya adalah telur. Hewan-hewan Oviparous hidup sekitar jutaan tahun sebelum ayam.)
Eric Lippert

Jawaban:

32

Saya mencoba memahami di balik layar tirai Javascript dan agak terjebak dalam memahami penciptaan objek bawaan, khususnya Obyek dan Fungsi dan hubungan di antara mereka.

Ini rumit, mudah disalahpahami, dan banyak buku Javascript pemula yang salah, jadi jangan percaya semua yang Anda baca.

Saya adalah salah satu pelaksana mesin JS Microsoft pada 1990-an dan pada komite standardisasi, dan saya membuat sejumlah kesalahan dalam menyusun jawaban ini. (Meskipun karena saya belum mengerjakan ini selama lebih dari 15 tahun saya mungkin bisa dimaafkan.) Itu adalah hal yang rumit. Tapi begitu Anda memahami warisan prototipe, semuanya masuk akal.

Ketika saya membaca bahwa semua objek bawaan seperti Array, String dll adalah ekstensi (diwarisi) dari Object, saya berasumsi bahwa Object adalah objek built-in pertama yang dibuat dan sisa objek yang diwarisi darinya.

Mulailah dengan membuang semua yang Anda ketahui tentang warisan berbasis kelas. JS menggunakan pewarisan berbasis prototipe.

Selanjutnya, pastikan Anda memiliki definisi yang sangat jelas di kepala Anda tentang apa arti "warisan". Orang yang terbiasa dengan bahasa OO seperti C # atau Java atau C ++ berpikir bahwa pewarisan berarti subtipe, tetapi pewarisan tidak berarti subtipe. Warisan berarti bahwa anggota dari satu hal juga anggota dari hal lain . Itu tidak selalu berarti bahwa ada hubungan subtyping antara hal-hal itu! Begitu banyak kesalahpahaman dalam teori jenis adalah hasil dari orang tidak menyadari bahwa ada perbedaan.

Tapi itu tidak masuk akal ketika Anda mengetahui bahwa Objek hanya dapat dibuat oleh fungsi tetapi kemudian fungsi juga tidak lain adalah objek Fungsi.

Ini benar-benar salah. Beberapa objek tidak dibuat dengan memanggil new Fbeberapa fungsi F. Beberapa objek dibuat oleh runtime JS dari ketiadaan sama sekali. Ada telur yang tidak diletakkan oleh ayam apa pun . Mereka baru saja dibuat oleh runtime saat dijalankan.

Katakanlah apa aturannya dan mungkin itu akan membantu.

  • Setiap instance objek memiliki objek prototipe.
  • Dalam beberapa kasus, prototipe dapat dibuat null .
  • Jika Anda mengakses anggota pada instance objek, dan objek tidak memiliki anggota tersebut, maka objek tersebut akan menolak prototipenya, atau berhenti jika prototipenya nol.
  • The prototypeanggota dari suatu objek biasanya tidak prototipe dari objek.
  • Sebaliknya, prototypeanggota objek fungsi F adalah objek yang akan menjadi prototipe objek yang dibuat olehnew F() .
  • Dalam beberapa implementasi, contoh mendapatkan __proto__anggota yang benar-benar memberikan prototipe mereka. (Ini sekarang sudah usang. Jangan mengandalkan itu.)
  • Objek fungsi mendapatkan objek default baru yang ditetapkan prototypesaat dibuat.
  • Prototipe objek fungsi tentu saja Function.prototype.

Mari kita simpulkan.

  • Prototipe dari ObjectadalahFunction.prototype
  • Object.prototype adalah objek prototipe objek.
  • Prototipe dari Object.prototypeadalahnull
  • Prototipe Functionis Function.prototype- ini adalah salah satu situasi langka di mana Function.prototypesebenarnya adalah prototipe Function!
  • Function.prototype adalah objek prototipe fungsi.
  • Prototipe dari Function.prototypeadalahObject.prototype

Misalkan kita membuat fungsi Foo.

  • Prototipe dari Fooadalah Function.prototype.
  • Foo.prototype adalah objek prototipe Foo.
  • Prototipe dari Foo.prototypeadalah Object.prototype.

Anggap saja kita katakan new Foo()

  • Prototipe objek baru tersebut adalah Foo.prototype

Pastikan itu masuk akal. Mari kita menggambarnya. Oval adalah instance objek. Tepi bisa __proto__berarti "prototipe", atau prototypeberarti " prototypeproperti".

masukkan deskripsi gambar di sini

Yang harus dilakukan runtime adalah membuat semua objek itu dan menetapkan berbagai propertinya sesuai. Saya yakin Anda bisa melihat bagaimana itu akan dilakukan.

Sekarang mari kita lihat contoh yang menguji pengetahuan Anda.

function Car(){ }
var honda = new Car();
print(honda instanceof Car);
print(honda.constructor == Car);

Apa yang dicetak ini?

Nah, apa instanceofartinya? honda instanceof Carberarti " Car.prototypesama dengan benda apa pun hondadi rantai prototipe?"

Ya itu. hondaPrototipenya sudah Car.prototype, jadi kita selesai. Ini mencetak benar.

Bagaimana dengan yang kedua?

honda.constructortidak ada jadi kami berkonsultasi dengan prototipe, yaitu Car.prototype. Ketika Car.prototypeobjek itu dibuat secara otomatis diberi properti constructorsama dengan Car, jadi ini benar.

Sekarang bagaimana dengan ini?

var Animal = new Object();
function Reptile(){ }
Reptile.prototype = Animal;
var lizard = new Reptile();
print(lizard instanceof Reptile);
print(lizard.constructor == Reptile);

Apa yang dicetak oleh program ini?

Sekali lagi, lizard instanceof Reptileberarti " Reptile.prototypesama dengan objek apa pun dilizard di rantai prototipe?"

Ya itu. lizardprototipe adalahReptile.prototype, jadi kita selesai. Ini mencetak benar.

Sekarang bagaimana

print(lizard.constructor == Reptile);

Anda mungkin berpikir bahwa ini juga mencetak benar, karena lizarddibangun dengan new Reptiletetapi Anda akan salah. Alasan itu keluar.

  • Apakah lizardpunyaconstructor properti? Tidak. Oleh karena itu kami melihat prototipe.
  • Prototipe dari lizardadalah Reptile.prototype, yaituAnimal .
  • Apakah Animalpunyaconstructor properti? Tidak. Jadi kita melihat prototipe itu.
  • Prototipe dari Animaladalah Object.prototype, dan Object.prototype.constructordibuat oleh runtime dan sama dengan Object.
  • Jadi ini mencetak salah.

Kita seharusnya mengatakan Reptile.prototype.constructor = Reptile;pada suatu titik di sana, tetapi kita tidak ingat untuk melakukannya!

Pastikan semua masuk akal bagi Anda. Gambarlah beberapa kotak dan panah jika masih membingungkan.

Hal lain yang sangat membingungkan adalah, jika saya console.log(Function.prototype)mencetak fungsi tetapi ketika saya mencetaknya console.log(Object.prototype)mencetak objek. Mengapa Function.prototypesuatu fungsi saat itu dimaksudkan untuk menjadi objek?

Prototipe fungsi didefinisikan sebagai fungsi yang, ketika dipanggil, kembali undefined. Kita sudah tahu bahwa Function.prototypeadalah Functionprototipe, anehnya. Jadi karena Function.prototype()itu legal, dan ketika Anda melakukannya, Anda undefinedkembali. Jadi itu sebuah fungsi.

The Objectprototipe tidak memiliki properti ini; itu tidak bisa dipanggil. Itu hanya sebuah objek.

ketika Anda console.log(Function.prototype.constructor)lagi fungsi.

Function.prototype.constructorhanya Function, jelas. Dan Functionmerupakan fungsi.

Sekarang bagaimana Anda dapat menggunakan sesuatu untuk membuatnya sendiri (Mind = blown).

Anda terlalu memikirkan ini . Semua yang diperlukan adalah bahwa runtime membuat banyak objek saat dijalankan. Objek hanyalah tabel pencarian yang mengaitkan string dengan objek. Ketika runtime dijalankan, itu harus lakukan adalah membuat beberapa objek lusin kosong, dan kemudian mulai menempatkan prototype, __proto__, constructor, dan sebagainya properti dari setiap objek sampai mereka membuat grafik yang mereka butuhkan untuk membuat.

Akan sangat membantu jika Anda mengambil diagram yang saya berikan di atas dan menambahkan constructorpinggirannya. Anda akan segera melihat bahwa ini adalah grafik objek yang sangat sederhana dan runtime tidak akan bermasalah saat membuatnya.

Latihan yang baik adalah melakukannya sendiri. Di sini, saya akan memulai Anda. Kami akan menggunakan my__proto__berarti "objek prototipe" dan myprototypeberarti "properti prototipe".

var myobjectprototype = new Object();
var myfunctionprototype = new Object();
myfunctionprototype.my__proto__ = myobjectprototype;
var myobject = new Object();
myobject.myprototype = myobjectprototype;

Dan seterusnya. Bisakah Anda mengisi sisa program untuk membangun satu set objek yang memiliki topologi yang sama dengan objek bawaan Javascript "asli"? Jika Anda melakukannya, Anda akan menemukannya sangat mudah.

Objek dalam JavaScript hanyalah tabel pencarian yang mengaitkan string dengan objek lain . Itu dia! Tidak ada keajaiban di sini. Anda mendapatkan diri Anda terikat di simpul karena Anda membayangkan kendala yang sebenarnya tidak ada, seperti setiap objek harus dibuat oleh konstruktor.

Fungsi hanyalah objek yang memiliki kemampuan tambahan: untuk dipanggil. Jadi, ikuti program simulasi kecil Anda dan tambahkan .mycallableproperti ke setiap objek yang menunjukkan apakah itu dapat dipanggil atau tidak. Sesederhana itu.

Eric Lippert
sumber
9
Akhirnya, penjelasan singkat tentang JavaScript yang ringkas, ringkas, mudah dipahami! Luar biasa! Bagaimana mungkin ada di antara kita yang bingung? :) Meskipun dalam semua keseriusan, bit terakhir tentang objek menjadi tabel pencarian adalah kuncinya. Ada metode untuk kegilaan --- tetapi masih kegilaan ...
Greg Burghardt
4
@GregBurghardt: Saya setuju itu terlihat rumit pada awalnya, tetapi kompleksitasnya adalah konsekuensi dari aturan sederhana. Setiap objek memiliki a __proto__. The __proto__prototipe objek adalah null. The __proto__dari new X()adalah X.prototype. Semua objek fungsi memiliki prototipe fungsi untuk __proto__kecuali untuk prototipe fungsi itu sendiri. Objectdan Functionprototipe fungsi adalah fungsi. Aturan-aturan itu semuanya mudah, dan mereka menentukan topologi grafik objek awal.
Eric Lippert
6

Anda sudah memiliki banyak jawaban bagus, tetapi saya hanya ingin memberikan jawaban singkat dan jelas untuk jawaban Anda tentang bagaimana semua ini bekerja, dan jawaban itu adalah:

SIHIR!!!

Sungguh, itu dia.

Orang-orang yang melaksanakan ECMAScript mesin eksekusi harus menerapkan aturan ECMAScript, tetapi tidak mematuhi oleh mereka dalam pelaksanaannya.

Spesifikasi Naskah ECMAS mengatakan bahwa A mewarisi dari B tetapi B adalah turunan dari A? Tidak masalah! Buat A pertama dengan pointer prototipe NULL, buat B sebagai instance dari A, lalu perbaiki pointer prototipe A untuk menunjuk ke B sesudahnya. Peasy mudah.

Anda bilang, tapi tunggu, tidak ada cara untuk mengubah pointer prototipe di ECMAScript! Tapi, ini masalahnya: kode ini tidak berjalan di mesin ECMAScript, kode ini adalah mesin ECMAScript. Itu memang memiliki akses ke objek internal yang kode ECMAScript berjalan pada mesin tidak memiliki. Singkatnya: ia dapat melakukan apa pun yang diinginkannya.

Omong-omong, jika Anda benar-benar ingin, Anda hanya perlu melakukan ini satu kali: setelah itu, Anda dapat, misalnya, membuang memori internal Anda dan memuat dump ini setiap kali Anda memulai mesin ECMAScript Anda.

Perhatikan bahwa semua ini masih berlaku, bahkan jika mesin ECMAScript sendiri ditulis dalam ECMAScript (seperti halnya Mozilla Narcissus, misalnya). Bahkan kemudian, kode ECMAScript yang mengimplementasikan mesin masih memiliki akses penuh ke mesin yang sedang diimplementasikan , meskipun tentu saja tidak memiliki akses ke mesin yang sedang berjalan .

Jörg W Mittag
sumber
3

Dari ECMA spec 1

ECMAScript tidak mengandung kelas yang tepat seperti yang ada di C ++, Smalltalk, atau Java, melainkan mendukung konstruktor yang membuat objek dengan mengeksekusi kode yang mengalokasikan penyimpanan untuk objek dan menginisialisasi semua atau sebagian dari mereka dengan menetapkan nilai awal ke properti mereka. Semua fungsi termasuk konstruktor adalah objek, tetapi tidak semua objek adalah konstruktor.

Saya tidak melihat bagaimana ini bisa menjadi lebih jelas !!! </sarcasm>

Lebih jauh ke bawah kita melihat:

Prototipe Prototipe adalah objek yang digunakan untuk menerapkan struktur, status, dan perilaku warisan dalam ECMAScript. Ketika konstruktor membuat objek, objek yang secara implisit referensi prototipe terkait konstruktor untuk tujuan menyelesaikan referensi properti. Prototipe terkait konstruktor dapat dirujuk oleh ekspresi program constructor.protype, dan properti yang ditambahkan ke prototipe objek dibagi, melalui pewarisan, oleh semua objek yang berbagi prototipe.

Jadi kita dapat melihat bahwa prototipe adalah objek, tetapi tidak harus merupakan objek fungsi.

Juga, kami memiliki titbit yang menarik ini

http://www.ecma-international.org/ecma-262/8.0/index.html#sec-object-objects

Konstruktor objek adalah objek intrinsik% Object% dan nilai awal properti Object objek global.

dan

Konstruktor fungsi adalah objek intrinsik% Function% dan nilai awal properti Function dari objek global.

Ewan
sumber
Sekarang sudah. ECMA6 memungkinkan Anda untuk membuat kelas dan membuat objek dari mereka.
ncmathsadist
2
@ncmathsadist ES6 kelas hanya gula sintaksis, semantiknya sama.
Hamza Fatmi
1
sarcasmSebaliknya, moniker Anda , teks ini benar-benar buram bagi pemula.
Robert Harvey
benar, tambah sakit lagi nanti, perlu dilakukan beberapa penggalian
Ewan
1
erm? untuk menunjukkan bahwa itu tidak jelas dari dokumen
Ewan
1

Jenis-jenis berikut mencakup setiap nilai dalam JavaScript:

  • boolean
  • number
  • undefined(yang termasuk nilai tunggal undefined)
  • string
  • symbol ("hal-hal" unik abstrak yang dibandingkan dengan referensi)
  • object

Setiap objek (yaitu semuanya) dalam JavaScript memiliki prototipe, yang merupakan jenis objek.

Prototipe berisi fungsi, yang juga merupakan semacam objek 1 .

Objek juga memiliki konstruktor, yang merupakan fungsi, dan karena itu semacam objek.

bersarang

Semuanya rekursif, tetapi implementasinya dapat melakukannya secara otomatis karena, tidak seperti kode JavaScript, ia dapat membuat objek tanpa harus memanggil fungsi JavaScript (karena objek hanyalah memori yang dikendalikan oleh implementasi).

Sebagian besar sistem objek dalam banyak bahasa yang diketik secara dinamis berbentuk lingkaran 2 seperti ini. Sebagai contoh, dalam Python, kelas adalah objek, dan kelas kelas adalah type, typekarena itu merupakan turunan dari dirinya sendiri.

Ide terbaik adalah dengan hanya menggunakan alat yang disediakan bahasa, dan tidak terlalu memikirkan bagaimana mereka sampai di sana.

1 Fungsi agak istimewa karena mereka dapat dipanggil, dan mereka adalah satu-satunya nilai yang dapat berisi data buram (tubuh mereka dan mungkin penutup).

2 Ini sebenarnya lebih merupakan pita yang tersiksa dan bercabang yang tertekuk ke belakang dengan sendirinya, tetapi "bundar" cukup dekat.

Penantang5
sumber