Օբյեկտների մակերեսային պատճենման (shallow copy) ի՞նչ եղանակներ կան: Ո՞ր եղանակն է ապահովում անթերի մակերեսային պատճենում: Նաև շատ էլեգանտ տարբերակ՝ spread օպերատորի օգնությամբ:
Օգտագործելով վերագրման օպերատորը՝ մենք չենք կարող ստեղծել օբյեկտային, կամ ինչպես հաճախ անվանում են հղումային տիպին պատկանող արժեքների (Object, Array, Function, Map, Set) անկախ պատճեններ, ինչպես որ պրիմիտիվ արժեքների դեպքում է։ Օրինակ՝
const user = { name: "Roman" };
const visitor = user;
visitor.name = "Artur";
alert(user.name); // "Artur"
Երբ մենք հայտարարում ենք փոփոխական և նրան որպես արժեք վերագրում ենք օբյեկտ, ապա այդ օբյեկտը փոփոխականի մեջ ուղիղ կերպով չի պահվում, ինչպես որ պրիմիտիվ տիպերի դեպքում էր։ Այն պահվում է հիշողության մեջ ինչ-որ բոլորովին ուրիշ տեղում, իսկ փոփոխականի մեջ ընդամենը պահվում է հիշողության այն բջիջների հասցեն, որտեղ օբյեկտը գտնվում է։ Այլ կերպ ասած մենք փոփոխականի մեջ պահում ենք հղումը, թե հիշողության մեջ կոնկրետ որտեղ է գտնվում այդ օբյեկտը։ Դրա համար այս տիպերը կոչվում են նաև հղումային։
Վերևի օրինակի մեջ user փոփոխականի մեջ պահվում է {name: "Roman"}
օբյեկտի հասցեն, և visitor փոփոխականին վերագրելով user-ի արժեքը, մենք իրականում նրա մեջ պատճենում ենք {name: "Roman"}
օբյեկտի հասցեն, ոչ թե հենց օբյեկտը։ Հետևաբար թե՛ user և թե՛ visitor փոփոխականներն այժմ հղվում են հիշողության նույն հատվածին, որտեղ գտնվում է {name: "Roman"}
օբյեկտը, և նրանց արժեքները իրականում բացարձակապես նույն օբյեկտն է։ Դրա մեջ կարող ենք համոզվել, կատարելով հետևյալ ստուգումը՝
alert(user === visitor); // true
Իսկ ինչպե՞ս ստեղծել տրված օբյեկտի լրիվ անկախ պատճենը։ Նախ նկատենք, որ օբյեկտների պատճենումը լինում է երկու տիպի՝ մակերեսային պատճենում (shallow copy) և խորը պատճենում (deep copy): Տարբերությունը կայանում է նրանում, որ խորը պատճենման դեպքում հաշվի է առնվում նաև այն, որ հատկության արժեքը նույնպես կարող է պատկանել օբյեկտային տիպի, և հետևաբար պետք է խորությամբ անցնելով ստեղծել նաև այդ օբյեկտային արժեքների անկախ պատճենները։
Առօրյա խնդիրների մեծամասնության դեպքում՝ օբյեկտների մակերեսային պատճենումը լիովին բավարար է, և մենք կդիտարկենք այդպիսի պատճենման մի քանի եղանակներ։
Օբյեկտների խորը պատճենման անհրաժեշտություն համեմատաբար ավելի քիչ է լինում, իսկ լինելու դեպքում հիմնականում օգտագործվում են կողմնակի JavaScript գրադարաններ, օրինակ lodash ֆունկցիոնալ գրադարանը, որի _.cloneDeep(obj) մեթոդը հաջողությամբ լուծում է այդ խնդիրը։ HTML Living Standard-ի մեջ սկսած 2022 թվականից ավելացվել է structuredClone մեթոդը, որը թույլ է տալիս խորը պատճենում կատարել։ Մեթոդին մանրամասն կարող եք ծանոթանալ այստեղ: Նաև քանի-որ մեթոդը համեմատաբար նոր է, այն սպասարկվում է ոչ բոլոր դիտարկիչների կողմից: Այստեղ կարող եք ծանոթանալ թե որ դիտարկիչների որ տարբերակներն են մեթոդը սպասարկում։
Օբյեկտների պարզագույն մակերեսային պատճենում կարելի է անել for in ցիկլի օգնությամբ։ for in ցիկլին նվիրված գրառմանս մեջ այդ մասին խոսվում է, կարող եք նայել այս հղումով:
Հաջորդ տարածված տարբերակը՝ Object.assign մեթոդի օգտագործումն է։ Այն ունի հետևյալ գրելաձևը՝
Object.assign(target, ...sources)
Որտեղ target-ը թիրախային օբյեկտն է, որի մեջ ուզում ենք ստեղծել ուրիշ օբյեկտների պատճենը, իսկ ․․․sources-ը մեկ կամ մի քանի օբյեկտներն են, որոնց պատճենը ուզում ենք ստեղծել։ Վերևում բերված մեր օրինակը այժմ փորձենք ճիշտ պատճենում անել այս մեթոդի օգնությամբ՝
let user = { name: "Roman" };
let visitor = Object.assign({}, user);
console.log(user); // {name: "Roman"}
consle.log(visitor); // {name: "Roman"}
console.log(user === visitor); // false
Մեթոդին որպես առաջին արգումենտ տալիս ենք դատարկ օբյեկտ, որպես երկրորդ արգումենտ՝ user օբյեկտը, որի պատճենը պատրաստվում ենք ստեղծել։ Մեթոդի վերադարձրած արժեքը, որը user օբյեկտի մակերեսային պատճենն է, վերագրում ենք visitor փոփոխականին։ Կատարելով երկու օբյեկտների նույնականության ստուգում, ստանում ենք false, որն ապացուցում է, որ նրանք իրարից լիովին անկախ օբյեկտներ են, և այժմ մի օբյեկտի դաշտում կատարված փոփոխությունն ամենևին չի անդրադառնում մյուս օբյեկտի վրա։
Իսկ հիմա դիտարկենք օբյեկտների մակերեսային պատճենման ամենաէլեգանտ տարբերակը՝ spread օպերատորի օգնությամբ (...), որը հասանելի է սկսած ES6 ստանդարտից։
let user = { name: "Roman" };
let visitor = { ...user };
console.log(user); // {name: "Roman"}
console.log(visitor); // {name: "Roman"}
console.log(user === visitor); // false
Այս բոլոր տարբերակները՝ for in ցիկլի, Object,assign մեթոդի և spread օպերատորի օգնությամբ, հիմնականում բավարարում են օբյեկտների պատճենման առօրյա մեզ անհրաժեշտ չափանիշներին։
Սակայն երբեմն անհրաժեշտ է լինում ստանալ օբյեկտի բացարձակ նման պատճեն, իսկ վերը նշված եղանակներով դա հնարավոր չէ, քանի-որ դրանք հաշվի չեն առնում հատկությունների ֆլագները, այսինքն արդյո՞ք կարելի է փոխել այդ հատկությունը, թե այն միայն կարդալու համար է, կամ արդյո՞ք այդ հատկությունը պետք է տեսանելի լինի ցիկլի համար, թե ոչ։ Բացի դրանից for in ցիկլի օգնությամբ օբյեկտի պատճենման ժամանակ ցիկլը անտեսում է բոլոր այն հատկությունները, որոնց բանալիները պատկանում են ES6 ստանդարտում ավելացված Symbol տիպին։
Ստորև դիտարկվող եղանակը կատարում է օբյեկտների լավագույն մակերեսային պատճենումը՝ հաշվի առնելով, թե՛ հատկության բանալիի Symbol տիպին պատկանելիությունը, և թե՛ հատկությունների ֆլագները։ Գրելաձևը հետևյալն է՝
let clone = Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj));
Իրականացնելով պատճենման այս եղանակը մեր օրինակում բերված օբյեկտի վրա, կստանանք՝
let user = { name: "Roman" };
let visitor = Object.defineProperties(
{},
Object.getOwnPropertyDescriptors(user)
);
console.log(user); // {name: "Roman"}
consle.log(visitor); // {name: "Roman"}
console.log(user === visitor); // false
Այս 4 եղանակներն ամենաշատ կիրառվողներն են, ո՞րն ընտրել, կախված է առաջադրանքի բնույթից և անձնական նախընտրությունից: