INTERESTING FACTS BLOG

Painting and Sketches

JavaScriptda chuqur nusxalash

Asl maqola - Deep-copying in JavaScript

Maqola Muallifi(lari) - Surma

Manba: https://dassur.ma/things/deep-copy/

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.

Chrome 63 da ishlash

Firefox 58 da ishlash

Edge 16da ishlash

Xulosa

Xo'sh, biz undan nimani olib tashlaymiz?

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.