Setiap pemimpin opini JS mengatakan bahwa memperluas objek asli adalah praktik yang buruk. Tapi kenapa? Apakah kita mendapatkan kinerja yang baik? Apakah mereka takut seseorang melakukan itu dengan "cara yang salah", dan menambahkan tipe yang dapat dihitung Object
, secara praktis menghancurkan semua loop pada objek apa pun?
Ambil TJ Holowaychuk 's should.js misalnya. Dia menambahkan getter sederhana untuk Object
dan semuanya bekerja dengan baik ( sumber ).
Object.defineProperty(Object.prototype, 'should', {
set: function(){},
get: function(){
return new Assertion(Object(this).valueOf());
},
configurable: true
});
Ini sangat masuk akal. Misalnya seseorang dapat memperpanjang Array
.
Array.defineProperty(Array.prototype, "remove", {
set: function(){},
get: function(){
return removeArrayElement.bind(this);
}
});
var arr = [0, 1, 2, 3, 4];
arr.remove(3);
Apakah ada argumen yang menentang perluasan tipe asli?
javascript
prototype
prototypal-inheritance
buschtoens
sumber
sumber
Jawaban:
Saat Anda memperluas objek, Anda mengubah perilakunya.
Mengubah perilaku objek yang hanya akan digunakan oleh kode Anda sendiri tidak masalah. Tetapi ketika Anda mengubah perilaku sesuatu yang juga digunakan oleh kode lain ada risiko Anda akan merusak kode lain itu.
Ketika datang menambahkan metode ke objek dan kelas array di javascript, risiko melanggar sesuatu sangat tinggi, karena cara kerja javascript. Pengalaman bertahun-tahun telah mengajarkan saya bahwa hal-hal semacam ini menyebabkan semua jenis bug yang mengerikan di javascript.
Jika Anda memerlukan perilaku khusus, jauh lebih baik untuk mendefinisikan kelas Anda sendiri (mungkin subkelas) daripada mengubah yang asli. Dengan begitu Anda tidak akan merusak apa pun.
Kemampuan untuk mengubah cara kerja kelas tanpa mensubclassing itu adalah fitur penting dari setiap bahasa pemrograman yang baik, tetapi itu adalah salah satu yang harus jarang digunakan dan dengan hati-hati.
sumber
.stgringify()
akan dianggap aman?stringify()
metode sendiri dengan perilaku yang berbeda? Ini benar-benar bukan sesuatu yang harus Anda lakukan dalam pemrograman sehari-hari ... tidak jika semua yang ingin Anda lakukan adalah menyimpan beberapa karakter kode di sana-sini. Lebih baik untuk mendefinisikan kelas atau fungsi Anda sendiri yang menerima input apa pun dan mengencangkannya. Sebagian besar browser menentukanJSON.stringify()
, yang terbaik adalah memeriksa apakah itu ada, dan jika tidak menentukannya sendiri.someError.stringify()
lebiherrors.stringify(someError)
. Sangat mudah dan sangat sesuai dengan konsep js. Saya melakukan sesuatu yang secara spesifik terikat pada ErrorObject tertentu.Tidak ada kelemahan yang terukur, seperti hit kinerja. Setidaknya tidak ada yang menyebutkan. Jadi ini adalah pertanyaan tentang preferensi dan pengalaman pribadi.
Argumen pro utama: Terlihat lebih baik dan lebih intuitif: sintaksis gula. Ini adalah fungsi spesifik tipe / instance, jadi harus secara spesifik terikat pada tipe / instance tersebut.
Argumen kontra utama: Kode dapat mengganggu. Jika lib A menambahkan fungsi, itu bisa menimpa fungsi lib B. Ini dapat memecahkan kode dengan sangat mudah.
Keduanya ada benarnya. Ketika Anda mengandalkan dua pustaka yang secara langsung mengubah jenis Anda, kemungkinan besar Anda akan berakhir dengan kode yang rusak karena fungsi yang diharapkan mungkin tidak sama. Saya setuju sepenuhnya tentang itu. Perpustakaan makro tidak boleh memanipulasi tipe asli. Kalau tidak, Anda sebagai pengembang tidak akan pernah tahu apa yang sebenarnya terjadi di balik layar.
Dan itulah alasan saya tidak suka lib seperti jQuery, garis bawah, dll. Jangan salah paham; mereka benar-benar diprogram dengan baik dan mereka bekerja seperti pesona, tetapi mereka besar . Anda hanya menggunakan 10% dari mereka, dan mengerti tentang 1%.
Itu sebabnya saya lebih suka pendekatan atomistik , di mana Anda hanya membutuhkan apa yang benar-benar Anda butuhkan. Dengan cara ini, Anda selalu tahu apa yang terjadi. Perpustakaan mikro hanya melakukan apa yang Anda inginkan, sehingga tidak akan mengganggu. Dalam konteks memiliki pengguna akhir yang mengetahui fitur mana yang ditambahkan, memperluas tipe asli dapat dianggap aman.
TL; DR Jika ragu, jangan berikan jenis asli. Hanya perluas jenis asli jika Anda 100% yakin, bahwa pengguna akhir akan mengetahui dan menginginkan perilaku itu. Dalam tidak ada kasus memanipulasi fungsi yang ada jenis pribumi, karena akan mematahkan antarmuka yang ada.
Jika Anda memutuskan untuk memperluas jenisnya, gunakan
Object.defineProperty(obj, prop, desc)
; jika tidak bisa , gunakan jenisnyaprototype
.Saya awalnya datang dengan pertanyaan ini karena saya ingin
Error
dapat dikirim melalui JSON. Jadi, saya perlu cara untuk mengikat mereka.error.stringify()
merasa jauh lebih baik daripadaerrorlib.stringify(error)
; seperti saran kedua, saya beroperasierrorlib
dan tidak denganerror
sendirinya.sumber
MyError extends Error
dapat memilikistringify
metode tanpa bertabrakan dengan subclass lainnya. Anda masih harus menangani kesalahan yang tidak dihasilkan oleh kode Anda sendiri, sehingga mungkin kurang bermanfaatError
dibandingkan dengan yang lain.Menurut pendapat saya, ini adalah praktik yang buruk. Alasan utamanya adalah integrasi. Mengutip dokumen should.js:
Nah, bagaimana penulis bisa tahu? Bagaimana jika kerangka kerja mengejek saya melakukan hal yang sama? Bagaimana jika janji saya melakukan hal yang sama?
Jika Anda melakukannya di proyek Anda sendiri maka tidak apa-apa. Tapi untuk perpustakaan, maka itu desain yang buruk. Underscore.js adalah contoh dari hal yang dilakukan dengan cara yang benar:
sumber
_(arr).flatten()
contoh sebenarnya meyakinkan saya untuk tidak memperpanjang objek asli. Alasan pribadi saya untuk melakukannya adalah murni sintaksis. Tapi ini memuaskan perasaan estetika saya :) Bahkan menggunakan beberapa nama fungsi yang lebih biasa inginfoo(native).coolStuff()
mengubahnya menjadi beberapa objek "extended" tampak hebat secara sintaksis. Jadi terima kasih untuk itu!Menimpa metode yang sudah dibuat menciptakan lebih banyak masalah daripada yang dipecahkan, itulah sebabnya mengapa hal itu dinyatakan secara umum, dalam banyak bahasa pemrograman, untuk menghindari praktik ini. Bagaimana Devs mengetahui fungsi telah diubah?
Dalam hal ini kami tidak menimpa metode JS core yang diketahui, tetapi kami memperluas String. Satu argumen dalam posting ini menyebutkan bagaimana pengembang baru mengetahui apakah metode ini adalah bagian dari inti JS, atau di mana menemukan dokumen? Apa yang akan terjadi jika objek JS String inti adalah untuk mendapatkan metode bernama capitalize ?
Bagaimana jika alih-alih menambahkan nama yang mungkin bertabrakan dengan perpustakaan lain, Anda menggunakan pengubah khusus perusahaan / aplikasi yang dapat dipahami semua devs?
Jika Anda terus memperluas objek lain, semua devs akan menemui mereka di alam liar dengan (dalam hal ini) kami , yang akan memberi tahu mereka bahwa itu adalah ekstensi khusus perusahaan / aplikasi.
Ini tidak menghilangkan tabrakan nama, tetapi mengurangi kemungkinan. Jika Anda menentukan bahwa memperluas objek JS inti adalah untuk Anda dan / atau tim Anda, mungkin ini untuk Anda.
sumber
Memperluas prototipe built-in memang ide yang buruk. Namun, ES2015 memperkenalkan teknik baru yang dapat digunakan untuk mendapatkan perilaku yang diinginkan:
Memanfaatkan
WeakMap
s untuk mengaitkan tipe dengan prototipe bawaanImplementasi berikut memperluas
Number
dan membuatArray
prototipe tanpa menyentuhnya sama sekali:Seperti yang Anda lihat, prototipe primitif pun dapat diperluas dengan teknik ini. Ini menggunakan struktur peta dan
Object
identitas untuk mengaitkan tipe dengan prototipe bawaan.Contoh saya mengaktifkan
reduce
fungsi yang hanya mengharapkanArray
argumen tunggal, karena dapat mengekstrak informasi cara membuat akumulator kosong dan cara menggabungkan elemen dengan akumulator ini dari elemen Array itu sendiri.Harap dicatat bahwa saya bisa menggunakan
Map
tipe normal , karena referensi yang lemah tidak masuk akal ketika mereka hanya mewakili prototipe built-in, yang tidak pernah mengumpulkan sampah. Namun, aWeakMap
tidak dapat diubah dan tidak dapat diperiksa kecuali Anda memiliki kunci yang tepat. Ini adalah fitur yang diinginkan, karena saya ingin menghindari segala bentuk refleksi.sumber
xs[0]
pada array kosong tho.type(undefined).monoid ...
Object.prototype
cara ini tidak dapat dipanggil pada aArray
), dan sintaks secara signifikan lebih lama / lebih buruk daripada jika Anda benar-benar memperpanjang prototipe. Hampir selalu lebih mudah untuk hanya membuat kelas utilitas dengan metode statis; satu-satunya keuntungan pendekatan ini adalah dukungan terbatas untuk polimorfisme.Satu lagi alasan mengapa Anda tidak perlu memperluas Objek asli:
Kami menggunakan Magento yang menggunakan prototype.js dan memperluas banyak hal pada Object asli. Ini berfungsi dengan baik sampai Anda memutuskan untuk mendapatkan fitur baru dan di situlah masalah besar dimulai.
Kami telah memperkenalkan komponen Web di salah satu halaman kami, jadi webcomponents-lite.js memutuskan untuk mengganti seluruh Objek Acara (asli) di IE (mengapa?). Ini tentu saja memecah prototype.js yang pada gilirannya memecah Magento. (sampai Anda menemukan masalah, Anda dapat menginvestasikan banyak jam untuk melacaknya kembali)
Jika Anda suka masalah, terus lakukan!
sumber
Saya dapat melihat tiga alasan untuk tidak melakukan ini (dari dalam aplikasi , setidaknya), hanya dua di antaranya yang dibahas dalam jawaban yang ada di sini:
Object.defineProperty
, yang menciptakan properti non-enumerable secara default.Poin 3 bisa dibilang yang paling penting. Anda dapat memastikan, melalui pengujian, bahwa ekstensi prototipe Anda tidak menyebabkan konflik dengan perpustakaan yang Anda gunakan, karena Anda memutuskan perpustakaan apa yang Anda gunakan. Hal yang sama tidak berlaku untuk objek asli, dengan asumsi kode Anda berjalan di browser. Jika Anda menentukan
Array.prototype.swizzle(foo, bar)
hari ini, dan besok Google menambahkanArray.prototype.swizzle(bar, foo)
ke Chrome, Anda mungkin berakhir dengan beberapa rekan bingung yang bertanya-tanya mengapa.swizzle
perilaku tampaknya tidak cocok dengan apa yang didokumentasikan di MDN.(Lihat juga kisah bagaimana mengutak-atik mootool dengan prototipe yang tidak mereka miliki memaksa metode ES6 diganti namanya untuk menghindari melanggar web .)
Ini dapat dihindari dengan menggunakan awalan khusus aplikasi untuk metode yang ditambahkan ke objek asli (misalnya mendefinisikan
Array.prototype.myappSwizzle
bukanArray.prototype.swizzle
), tapi itu agak jelek; itu hanya dapat dipecahkan dengan menggunakan fungsi utilitas mandiri alih-alih menambah prototipe.sumber
Perf juga merupakan alasan. Terkadang Anda mungkin perlu mengulang tombol. Ada beberapa cara untuk melakukan ini
Saya biasanya menggunakan
for of Object.keys()
karena melakukan hal yang benar dan relatif singkat, tidak perlu menambahkan cek.Tapi, ini jauh lebih lambat .
Hanya menebak alasannya
Object.keys
lambat sudah jelas,Object.keys()
harus membuat alokasi. Bahkan AFAIK harus mengalokasikan salinan semua kunci sejak itu.Mungkin saja mesin JS dapat menggunakan beberapa jenis struktur kunci yang tidak dapat diubah sehingga
Object.keys(object)
mengembalikan sesuatu referensi yang beralih pada kunci yang tidak dapat diubah dan yangobject.newProp
menciptakan objek kunci yang sama sekali tidak dapat diubah baru tapi apa pun, itu jelas lebih lambat hingga 15xBahkan memeriksa
hasOwnProperty
hingga 2x lebih lambat.Inti dari semua itu adalah bahwa jika Anda memiliki kode sensitif perf dan perlu untuk mengulang kunci maka Anda ingin dapat menggunakan
for in
tanpa harus meneleponhasOwnProperty
. Anda hanya dapat melakukan ini jika Anda belum mengubahObject.prototype
Perhatikan bahwa jika Anda menggunakan
Object.defineProperty
untuk memodifikasi prototipe jika hal-hal yang Anda tambahkan tidak dapat dihitung maka mereka tidak akan mempengaruhi perilaku JavaScript dalam kasus-kasus di atas. Sayangnya, setidaknya di Chrome 83, mereka mempengaruhi kinerja.Saya menambahkan 3000 properti non-enumerable hanya untuk mencoba memaksa masalah perf muncul. Dengan hanya 30 properti tes terlalu dekat untuk mengetahui apakah ada dampak perf.
https://jsperf.com/does-adding-non-enumerable-properties-affect-perf
Firefox 77 dan Safari 13.1 tidak menunjukkan perbedaan dalam perf antara kelas Augmented dan Unaugmented mungkin v8 akan diperbaiki di area ini dan Anda dapat mengabaikan masalah perf.
Tapi, izinkan saya juga menambahkan ada kisahnya
Array.prototype.smoosh
. Versi singkatnya adalah Mootools, perpustakaan populer, dibuat sendiriArray.prototype.flatten
. Ketika komite standar mencoba untuk menambahkan yang asliArray.prototype.flatten
mereka menemukan tidak bisa tanpa melanggar banyak situs. Para devs yang mengetahui tentang istirahat menyarankan menamai metode es5smoosh
sebagai lelucon tetapi orang-orang panik tidak mengerti itu adalah lelucon. Mereka memilihflat
bukannyaflatten
Moral dari cerita ini adalah Anda tidak harus memperluas objek asli. Jika Anda melakukannya, Anda bisa mengalami masalah yang sama tentang kerusakan barang-barang Anda dan kecuali perpustakaan khusus Anda menjadi sepopuler MooTools, vendor peramban tidak mungkin mengatasi masalah yang Anda sebabkan. Jika pustaka Anda menjadi sepopuler itu, itu akan menjadi semacam cara untuk memaksa orang lain mengatasi masalah yang Anda sebabkan. Jadi, tolong jangan Perluas Objek Asli
sumber
hasOwnProperty
memberi tahu Anda apakah properti ada di objek atau prototipe. Tetapifor in
loop hanya memberi Anda properti enumerable. Saya baru saja mencoba ini: Tambahkan properti non-enumerable ke prototipe Array dan itu tidak akan muncul dalam loop. Tidak perlu untuk tidak memodifikasi prototipe agar loop paling sederhana bekerja.