Objek biasanya dibuat untuk mewakili entitas dunia nyata, seperti pengguna, pesanan, dan sebagainya:
biarkan pengguna = { nama: "Yohanes", usia: 30 };
Dan, di dunia nyata, pengguna dapat bertindak : memilih sesuatu dari keranjang belanja, login, logout, dll.
Tindakan direpresentasikan dalam JavaScript berdasarkan fungsi di properti.
Sebagai permulaan, mari ajari user
untuk menyapa:
biarkan pengguna = { nama: "Yohanes", usia: 30 }; pengguna.sayHi = fungsi() { peringatan("Halo!"); }; pengguna.sayHi(); // Halo!
Di sini kita baru saja menggunakan Ekspresi Fungsi untuk membuat fungsi dan menugaskannya ke properti user.sayHi
objek tersebut.
Lalu kita bisa menyebutnya sebagai user.sayHi()
. Pengguna sekarang dapat berbicara!
Fungsi yang merupakan properti suatu objek disebut metodenya .
Jadi, di sini kita punya metode sayHi
dari objek user
.
Tentu saja, kita dapat menggunakan fungsi yang telah dideklarasikan sebelumnya sebagai sebuah metode, seperti ini:
biarkan pengguna = { // ... }; // pertama, deklarasikan fungsi ucapkan Hai() { peringatan("Halo!"); } // lalu tambahkan sebagai metode pengguna.sayHi = sayHi; pengguna.sayHi(); // Halo!
Pemrograman berorientasi objek
Saat kita menulis kode menggunakan objek untuk mewakili entitas, itu disebut pemrograman berorientasi objek, singkatnya: “OOP”.
OOP adalah hal besar, ilmu tersendiri yang menarik. Bagaimana cara memilih entitas yang tepat? Bagaimana cara mengatur interaksi di antara mereka? Itulah arsitektur, dan ada banyak buku bagus tentang topik itu, seperti “Pola Desain: Elemen Perangkat Lunak Berorientasi Objek yang Dapat Digunakan Kembali” oleh E. Gamma, R. Helm, R. Johnson, J. Vissides atau “Analisis dan Desain Berorientasi Objek dengan Aplikasi” oleh G. Booch, dan banyak lagi.
Terdapat sintaks yang lebih pendek untuk metode dalam objek literal:
// objek-objek ini melakukan hal yang sama pengguna = { sapa: fungsi() { peringatan("Halo"); } }; // singkatan metode terlihat lebih baik, bukan? pengguna = { sayHi() { // sama dengan "sayHi: function(){...}" peringatan("Halo"); } };
Seperti yang ditunjukkan, kita dapat menghilangkan "function"
dan cukup menulis sayHi()
.
Sejujurnya, notasi tersebut tidak sepenuhnya identik. Terdapat perbedaan halus terkait dengan pewarisan objek (akan dibahas nanti), namun untuk saat ini hal tersebut tidak menjadi masalah. Di hampir semua kasus, sintaksis yang lebih pendek lebih disukai.
Merupakan hal yang umum bahwa metode objek perlu mengakses informasi yang disimpan dalam objek untuk melakukan tugasnya.
Misalnya, kode di dalam user.sayHi()
mungkin memerlukan nama user
.
Untuk mengakses objek, suatu metode dapat menggunakan kata kunci this
.
Nilai this
adalah objek “sebelum titik”, yang digunakan untuk memanggil metode.
Misalnya:
biarkan pengguna = { nama: "Yohanes", usia: 30, ucapkan Hai() { // "ini" adalah "objek saat ini" alert(ini.nama); } }; pengguna.sayHi(); // Yohanes
Di sini selama eksekusi user.sayHi()
, this
adalah user
.
Secara teknis, objek juga dapat diakses tanpa this
, dengan mereferensikannya melalui variabel luar:
biarkan pengguna = { nama: "Yohanes", usia: 30, ucapkan Hai() { alert(nama pengguna); // "pengguna" bukannya "ini" } };
…Tetapi kode seperti itu tidak dapat diandalkan. Jika kita memutuskan untuk menyalin user
ke variabel lain, misalnya admin = user
dan menimpa user
dengan yang lain, maka ia akan mengakses objek yang salah.
Itu ditunjukkan di bawah ini:
biarkan pengguna = { nama: "Yohanes", usia: 30, ucapkan Hai() { alert( nama pengguna ); // menyebabkan kesalahan } }; biarkan admin = pengguna; pengguna = nol; // timpa untuk memperjelas admin.sayHi(); // TypeError: Tidak dapat membaca 'nama' properti null
Jika kita menggunakan this.name
alih-alih user.name
di dalam alert
, maka kodenya akan berfungsi.
Dalam JavaScript, kata kunci this
berperilaku tidak seperti kebanyakan bahasa pemrograman lainnya. Ini dapat digunakan dalam fungsi apa pun, meskipun itu bukan metode suatu objek.
Tidak ada kesalahan sintaksis dalam contoh berikut:
fungsi ucapkan Hai() { alert( ini.nama ); }
Nilai this
dievaluasi selama run-time, bergantung pada konteksnya.
Misalnya, di sini fungsi yang sama ditetapkan ke dua objek berbeda dan memiliki “ini” yang berbeda dalam pemanggilannya:
biarkan pengguna = { nama: "John" }; biarkan admin = { nama: "Admin" }; fungsi ucapkan Hai() { alert( ini.nama ); } // menggunakan fungsi yang sama pada dua objek pengguna.f = sayHai; admin.f = sayHai; // panggilan-panggilan ini mempunyai this yang berbeda // "ini" di dalam fungsi adalah objek "sebelum titik" pengguna.f(); // John (ini == pengguna) admin.f(); // Admin (ini == admin) admin['f'](); // Admin (titik atau tanda kurung siku mengakses metode – tidak masalah)
Aturannya sederhana: jika obj.f()
dipanggil, maka this
adalah obj
selama pemanggilan f
. Jadi, user
atau admin
pada contoh di atas.
Memanggil tanpa objek: this == undefined
Kita bahkan dapat memanggil fungsi tersebut tanpa objek sama sekali:
fungsi ucapkan Hai() { waspada(ini); } sayHai(); // belum diartikan
Dalam hal ini this
undefined
dalam mode ketat. Jika kita mencoba mengakses this.name
, akan terjadi error.
Dalam mode non-ketat, nilai this
dalam hal ini akan menjadi objek global ( window
di browser, kita akan membahasnya nanti di bab Objek global). Ini adalah perilaku historis yang "use strict"
.
Biasanya panggilan seperti itu merupakan kesalahan pemrograman. Jika ada this
di dalam suatu fungsi, ia diharapkan dipanggil dalam konteks objek.
Konsekuensi dari tidak terikatnya this
Jika Anda berasal dari bahasa pemrograman lain, Anda mungkin terbiasa dengan gagasan "terikat this
", di mana metode yang didefinisikan dalam suatu objek selalu memiliki this
yang merujuk pada objek tersebut.
Dalam JavaScript this
adalah “gratis”, nilainya dievaluasi pada waktu panggilan dan tidak bergantung pada di mana metode dideklarasikan, melainkan pada objek apa yang “sebelum titik”.
Konsep run-time yang dievaluasi this
mempunyai kelebihan dan kekurangan. Di satu sisi, suatu fungsi dapat digunakan kembali untuk objek yang berbeda. Di sisi lain, fleksibilitas yang lebih besar menciptakan lebih banyak kemungkinan terjadinya kesalahan.
Di sini posisi kami bukan untuk menilai apakah keputusan desain bahasa ini baik atau buruk. Kita akan memahami cara menyiasatinya, cara memperoleh manfaat, dan menghindari masalah.
Fungsi panah itu istimewa: mereka tidak memiliki this
"sendiri". Jika kita this
dari fungsi tersebut, maka diambil dari fungsi “normal” terluar.
Misalnya, di sini arrow()
menggunakan this
dari metode user.sayHi()
luar:
biarkan pengguna = { Nama Depan: "Ilya", ucapkan Hai() { biarkan panah = () => alert(ini.NamaDepan); anak panah(); } }; pengguna.sayHi(); // Elia
Itu adalah fitur khusus dari fungsi panah, berguna ketika kita sebenarnya tidak ingin memisahkan this
, melainkan mengambilnya dari konteks luar. Nanti di bab ini meninjau kembali fungsi panah, kita akan membahas fungsi panah lebih dalam.
Fungsi yang disimpan dalam properti objek disebut “metode”.
Metode memungkinkan objek untuk "bertindak" seperti object.doSomething()
.
Metode dapat mereferensikan objek seperti this
.
Nilai this
ditentukan pada saat run-time.
Ketika suatu fungsi dideklarasikan, ia dapat menggunakan this
, tetapi this
tidak memiliki nilai sampai fungsi tersebut dipanggil.
Suatu fungsi dapat disalin antar objek.
Ketika suatu fungsi dipanggil dalam sintaks “metode”: object.method()
, nilai this
selama pemanggilan adalah object
.
Harap perhatikan bahwa fungsi panah itu istimewa: tidak ada this
. Ketika this
diakses di dalam fungsi panah, ini diambil dari luar.
pentingnya: 5
Di sini fungsi makeUser
mengembalikan sebuah objek.
Apa hasil dari mengakses ref
? Mengapa?
fungsi makeUser() { kembali { nama: "Yohanes", referensi: ini }; } biarkan pengguna = makeUser(); peringatan( pengguna.ref.nama ); // Apa hasilnya?
Jawaban: kesalahan.
Cobalah:
fungsi makeUser() { kembali { nama: "Yohanes", referensi: ini }; } biarkan pengguna = makeUser(); peringatan( pengguna.ref.nama ); // Kesalahan: Tidak dapat membaca 'nama' properti yang tidak ditentukan
Itu karena aturan yang mengatur this
tidak melihat definisi objek. Hanya momen panggilan yang penting.
Di sini nilai this
di dalam makeUser()
adalah undefined
, karena disebut sebagai fungsi, bukan sebagai metode dengan sintaks “titik”.
Nilai this
adalah satu untuk seluruh fungsi, blok kode dan literal objek tidak memengaruhinya.
Jadi ref: this
sebenarnya mengambil fungsi this
saat ini.
Kita dapat menulis ulang fungsinya dan mengembalikan this
yang sama dengan nilai undefined
:
fungsi makeUser(){ kembalikan ini; // kali ini tidak ada objek literal } peringatan( makeUser().nama ); // Kesalahan: Tidak dapat membaca 'nama' properti yang tidak ditentukan
Seperti yang Anda lihat, hasil alert( makeUser().name )
sama dengan hasil alert( user.ref.name )
dari contoh sebelumnya.
Inilah kasus sebaliknya:
fungsi makeUser() { kembali { nama: "Yohanes", referensi() { kembalikan ini; } }; } biarkan pengguna = makeUser(); peringatan( pengguna.ref().nama ); // Yohanes
Sekarang berfungsi, karena user.ref()
adalah sebuah metode. Dan this
disetel ke objek sebelum titik .
.
pentingnya: 5
Buat calculator
objek dengan tiga metode:
read()
meminta dua nilai dan menyimpannya sebagai properti objek dengan nama a
dan b
masing-masing.
sum()
mengembalikan jumlah nilai yang disimpan.
mul()
mengalikan nilai yang disimpan dan mengembalikan hasilnya.
misalkan kalkulator = { // ... kode Anda ... }; kalkulator.read(); waspada( kalkulator.jumlah() ); waspada( kalkulator.mul() );
Jalankan demonya
Buka kotak pasir dengan tes.
misalkan kalkulator = { jumlah() { kembalikan ini.a + ini.b; }, banyak() { kembalikan ini.a * ini.b; }, membaca() { ini.a = +prompt('a?', 0); ini.b = +prompt('b?', 0); } }; kalkulator.read(); waspada( kalkulator.jumlah() ); waspada( kalkulator.mul() );
Buka solusi dengan pengujian di kotak pasir.
pentingnya: 2
Ada objek ladder
yang memungkinkan Anda naik dan turun:
misalkan tangga = { langkah: 0, ke atas() { ini.langkah++; }, turun() { ini.langkah--; }, showStep: function() { // menampilkan langkah saat ini alert( this.langkah ); } };
Sekarang, jika kita perlu melakukan beberapa panggilan secara berurutan, kita bisa melakukannya seperti ini:
tangga.atas(); tangga.atas(); tangga.bawah(); tangga.showStep(); // 1 tangga.bawah(); tangga.showStep(); // 0
Ubah kode up
, down
, dan showStep
agar panggilan dapat dirantai, seperti ini:
tangga.atas().atas().bawah().showStep().down().showStep(); // menampilkan 1 lalu 0
Pendekatan seperti ini banyak digunakan di perpustakaan JavaScript.
Buka kotak pasir dengan tes.
Solusinya adalah mengembalikan objek itu sendiri dari setiap panggilan.
misalkan tangga = { langkah: 0, ke atas() { ini.langkah++; kembalikan ini; }, turun() { ini.langkah--; kembalikan ini; }, tampilkanLangkah() { alert( this.langkah ); kembalikan ini; } }; tangga.atas().atas().bawah().showStep().down().showStep(); // menampilkan 1 lalu 0
Kami juga dapat menulis satu panggilan per baris. Untuk rantai panjang lebih mudah dibaca:
tangga .ke atas() .ke atas() .turun() .showStep() // 1 .turun() .showStep(); // 0
Buka solusi dengan pengujian di kotak pasir.