JavaScriptda chuqur nusxalash
Asl maqola - Deep-copying in JavaScript
Maqola Muallifi(lari) - Surma
Ob'ektni JavaScriptda qanday qilib nusxa ko'chiraman? Bu oddiy savol, biroq oddiy bo'lmagan javobga ega.
Call by reference
JavaScriptni har bir narsani mos yozuvlar yo'li bilan uzatadi. Bu nimani anglatishini bilmasangiz, mana bu misol:
function mutate(obj) {
obj.a = true;
}
const obj = {a: false};
mutate(obj)
console.log(obj.a); // prints true
Funktsiya mutate
ob'ektni parametr sifatida o'zgartiradi. "Chaqiruv by call" muhitida funktsiya qiymatdan o'tadi - shuning uchun nusxa - funktsiya bilan ishlashi mumkin.Funktsiya ob'ektga tegishli har qanday o'zgarish ushbu funktsiyadan tashqarida ko'rinmaydi. Ammo "call by reference" javascript kabi funksiya funktsiyani oladi - Siz buni taxmin qildingiz va haqiqiy obyektni o'zi mutatsiyaga soladi. Shunday qilib, oxir console.log
true
chop etiladi.
Ba'zida, asl nusxangizni saqlash va boshqa funktsiyalar uchun nusxasini yaratishingiz mumkin.
Sayoz nusxa ko'chirish: Object.assign ()
Ob'ektni nusxalashning bir usuli Object.assign(target, sources...)
dan foydalanish. U o'zboshimchalik bilan ko'plab manba obyektlarini oladi, ularning barcha xususiyatlarini numaralandırır va ularni target
tayinlaydi. Agar biz target
sifatida yangi, bo'sh ob'ektdan foydalansak, asosan nusxa ko'chiramiz.
const obj = /* ... */;
const copy = Object.assign({}, obj);
Biroq, bu faqat sayoz nusxa. Bizning ob'ekt ob'ektlar bo'lsa, ular biz birgalikda arizalar bo'lib qoladi, bu biz xohlamagan narsa emas:
function mutateDeepObject(obj) {
obj.a.thing = true;
}
const obj = {a: {thing: false}};
const copy = Object.assign({}, obj);
mutateDeepObject(copy)
console.log(obj.a.thing); // prints true
Potentsial sayohat qilish uchun yana bir narsa Object.assign()
oddiy narsalarga aylantiradi.
Xo'sh, endi nima bo'ldi? Chiqib ketadi, ob'ektning chuqur nusxasini yaratishning bir necha yo'li mavjud.
Eslatma: Ba'zi odamlar ob'ektni tarqatish operatori haqida so'rashdi. Ob'ektni tarqalishi ham sayoz nusxasini yaratadi.
JSON.parse
Ob'ekt nusxasini yaratishning eng qadimiy usullaridan biri ob'ektni JSON mag'lubiyatga namoyishiga aylantirish va keyin uni ob'ektga qaytarishdir. Bir oz og'ir qo'lni his qiladi, lekin u ishlaydi:
const obj = /* ... */;
const copy = JSON.parse(JSON.stringify(obj));
Bu yerdagi salbiy holat - bu sizni vaqtinchalik, potentsial jihatdan katta mag'lubiyatga aylantirmoq, shunchaki uni ayrıştırıcıya qaytarish. Yana bir salbiy tomoni shundaki, bu yondashuv tsiklik ob'ektlar bilan ishlamaydi. Va nima deb o'ylashingiz mumkinligiga qaramasdan, ular juda oson kechishi mumkin. Misol uchun, agar tugun ota-onaga zikr qiladigan daraxtga o'xshash ma'lumotlar tuzilmasini qurayotganingizda va ota-ona o'z farzandlariga murojaat qilsa.
const x = {};
const y = {x};
x.y = y; // Cycle: x.y.x.y.x.y.x.y.x...
const copy = JSON.parse(JSON.stringify(x)); // throws!
Bundan tashqari, Xaritalar, Sets, RegExps, Dates, ArrayBuffers va boshqa o'rnatilgan narsalar kabi narsalar ketma-ketlashtirishda yo'qoladi.
Strukturalangan klon
Strukturali klonlash - bu qadriyatlarni bir mintaqadan boshqasiga o'tkazish uchun foydalaniladigan mavjud algoritm. Misol uchun, bu xabarni boshqa oynaga yoki WebWorker- ga yuborish uchun postMessage
deb postMessage
ishlatiladi. Strukturali klonlash haqida yaxshi narsalar - bu murakkab narsalarni boshqaradi va keng ko'lamli ichki o'rnatilgan turlarni qo'llab-quvvatlaydi . Muammo shundaki, algoritm yozish vaqtida bevosita boshqa API-larning bir qismi sifatida ta'sir qilmaydi. O'ylaymanki, o'sha paytlarga qarashimiz kerak, biz ...
MessageChannel
Men aytganimdek, postMessage
deb postMessage
tuzilgan klon algoritmi ishlatiladi. MessageChannel yaratish va o'zimizga xabar yuborishimiz mumkin. Qabul qilish oxirida xabar asl ma'lumotlar ob'ektining strukturaviy klonini o'z ichiga oladi.
function structuralClone(obj) {
return new Promise(resolve => {
const {port1, port2} = new MessageChannel();
port2.onmessage = ev => resolve(ev.data);
port1.postMessage(obj);
});
}
const obj = /* ... */;
const clone = await structuralClone(obj);
Ushbu yondashuvning salbiy tomoni shundaki, u asenkron hisoblanadi. Bu juda katta shart emas, lekin ba'zida siz ob'ektni chuqur ko'chirishning sinxron usulidan foydalanish kerak.
API Tarixi
Agar SPA ni qurish uchun history.pushState()
dan foydalangan bo'lsangiz, URL bilan birga saqlash uchun davlat obyekti berishingiz mumkinligini bilasiz. Shunday qilib, ushbu davlat ob'ekti tizimli ravishda klonlanadi - sinxronlashtiriladi. Biz davlat ob'ektidan foydalanishimiz mumkin bo'lgan har qanday dastur mantig'i bilan chalkashmasligimiz kerak, shuning uchun biz klonlashni tugatgandan so'ng asl holatni tiklashimiz kerak. Har qanday hodisalarni o'chirishga yo'l qo'ymaslik uchun history.replaceState()
ning history.pushState()
foydalaning.
function structuralClone(obj) {
const oldState = history.state;
history.replaceState(obj, document.title);
const copy = history.state;
history.replaceState(oldState, document.title);
return copy;
}
const obj = /* ... */;
const clone = structuralClone(obj);
Yana bir bor, ob'ektni nusxalash uchun brauzerning dvigateliga bir oz teginish hissi paydo bo'ladi, ammo nima qilish kerak. Bundan tashqari, Safari 30 soniyadan iborat oynada 100 AQSh replaceState
bo'lgan chaqiruv miqdorini cheklaydi.
API Bildirishnomasi
Twitterdagi bu safar haqida tweet-shov-shuvlardan so'ng, Jeremy Banklar menga tizimli klonlashni ochishning 3-yo'li borligini ko'rsatdi: The Notification API.Bildirishnomalar klonlash bilan bog'liq bo'lgan ma'lumotlar obyektiga ega.
function structuralClone(obj) {
return new Notification('', {data: obj, silent: true}).data;
}
const obj = /* ... */;
const clone = structuralClone(obj);
Mavzu, qisqacha. Menga yoqdi! Biroq, asosan, brauzerda ruxsat olish texnikasini ishga tushiradi, shuning uchun u juda sekin bo'lishiga shubha qildim. Safari, ma'lum sabablarga ko'ra, doimo ma'lumotlar obyekti uchun undefined
.
Extravaganza unumdorligi
Bu yo'llardan qaysi birini eng samarali deb bilmoqchi edim. Mening birinchi (naif) urinishimda kichik JSON ob'ektini oldim va ob'ektni ming marta klonlashning turli usullari orqali uni quvib qo'ydim. Yaxshiyamki, Mathias Bynens menga V8-da kesh borligi obyektga xossalarni qo'shganingizda aytdi. Men keshni boshqa har qanday narsadan ko'ra sinab ko'rdim. Men hech qachon keshni urmasligimni ta'minlash uchun tasodifiy kalit nomlaridan foydalanib, berilgan chuqurlik va kenglik ob'ektlarini yaratadigan va testni qayta bajaradigan funksiya yozdim.
Tasvirlar!
Chrome, Firefox va Edge-da turli usullar qanday bajarilgani. Pastroq bo'lsa yaxshi.
Xulosa
Xo'sh, biz undan nimani olib tashlaymiz?
- Tik davriy ob'ektlarni
JSON.parse(JSON.stringify())
yordamida barcha brauzerlarda eng tezkor klonni qo'lgaJSON.parse(JSON.stringify())
, bu juda hayratlanarli bo'ldi. - Agar to'g'ri tuzilgan klonni istasangiz,
MessageChannel
sizning ishonchli o'zaro faoliyat brauzeringizning tanlovidir.
Platformada funksiya sifatida faqat structuredClone()
ni tuzgan bo'lsak yaxshi bo'larmidi? Men, albatta, shunday deb o'ylayman va bu yondashuvni qayta ko'rib chiqish uchun HTML spesifikatsiyasidagi eski masalani qayta tikladim.