جاوااسکریپت - کلون کردن شیء در ES6 با استفاده از عملگر اسپرد نیز ورودی را تغییر میدهد
من یک interface نسبتاً عمیق اعلام شده دارم که چیزی شبیه به این به نظر میرسد:
export interface Job {
JobId: JobId; // type JobId = string
UserId: UserId; // type UserId = string
JobName: string;
AudioFile: JobAudioFile; // this is an interface
Status: JobStatus; // this is an enum
Tracks: JobTracks[]; // 'JobTracks' is an enum
Results: JobResults; // this is an interface
Timestamps: JobTimestamps // interface
}
بسیاری از اعضای این رابط خود رابط هستند، با معماری کلی از این الگوی استفاده از enums، رشته ها، آرایه ها و رابط های بیشتر پیروی می کند. همه کدها به صورت TypeScript نوشته می شوند، به JS منتقل می شوند و به صورت JS در AWS آپلود می شوند. (Node 8.10 روی AWS اجرا می شود)
در یک نقطه از کد، من نیاز دارم یک نسخه عمیق از نمونهسازی Job که به عنوان پارامتر تابع ارسال شده است، بسازم:
export const StartPipeline: Handler = async (
event: PipelineEvent
): Promise<PipelineEvent> => {
console.log('StartPipeline Event: %o', event);
const newBucket = await copyToJobsBucket$(event.Job);
await deleteFromOriginalBucket$(event.Job);
console.log(`Job [${event.Job.JobId}] moved to Jobs bucket: ${newBucket}`);
event.Job.AudioFile.Bucket = newBucket;
event.Job.Status = Types.JobStatus.Processing;
// update the job status
// VVV PROBLEM OCCURS HERE VVV
const msg: Types.JobUpdatedMessage = new Types.JobUpdatedMessage({ Job: Object.assign({}, event.Job) });
await Send.to$(event.Job.UserId, msg);
return { ...event };
};
تعریف JobUpdatedMessage:
export class JobUpdatedMessage extends BaseMessage {
constructor(payload: { Job: Types.Job }) {
console.log('Incoming: %o', payload);
const copy: object = { ...payload.Job };
// VVV PROBLEM ON NEXT LINE VVV
const filtered = JobUtils.FilterJobProperties(copy as Types.Job);
super(MessageTypes.JobUpdated, filtered);
}
}
مشکل این است که پس از فراخوانی JobUtils.FilterJobProperties، payload.Job نیز بهطور نامطلوب و غیرمنتظره تغییر پیدا کرده است.
در اینجا پیادهسازی JobUtils.FilterJobProperties آمده است:
export const FilterJobProperties = (from: Types.Job): Types.Job => {
const fieldsToRemove: string[] = [
'Transcripts.GSTT',
'Transcripts.WSTT',
'Transcripts.ASTT',
'TranscriptTracks',
'Transcripts.Stream.File',
'Transcripts.Stream.State',
'AudioFile.Bucket',
'AudioFile.S3Key',
];
let job: Types.Job = { ...from }; // LINE ONE
fieldsToRemove.forEach(field => _.unset(job, field)); // LINE TWO
return job;
};
(من در اینجا از کتابخانه lodash استفاده میکنم)
خط بازار 'LINE TWO' نیز پارامتر تابع from را تغییر میدهد، حتی با اینکه در 'LINE ONE' کاری که فکر میکنم یک کلون عمیق از from است را انجام میدهم.
من میدانم که این چنین است زیرا اگر 'LINE ONE' را به این تغییر دهم:
// super hard core deep cloning
let job: Types.Job = JSON.parse(JSON.stringify(from));
... همه چیز همانطور که انتظار میرود کار میکند. from تغییر نمیکند، JobUpdatedMessage حاصل همانطور که انتظار میرود است، و پارامتر event در StartPipeline مجموعهای از ویژگیها که از event.Job حذف شدهاند را ندارد.
من ساعتها با این موضوع دست و پنجه نرم کردم، از جمله دوباره یاد گرفتن تمام چیزهایی که فکر میکردم دربارهی کلون کردن اشیاء در ES6 با استفاده از عملگر انتشار میدانم.
چرا 'LINE ONE' هم ورودی را تغییر میداد؟
پاسخ شماره ۱
عملگر اسپرد همانند Object.assign() کلون سطحی انجام میدهد
کلونسازی سطحی (به جز پروتوتایپ) یا ادغام اشیاء اکنون با استفاده از نگارش کوتاهتری نسبت به Object.assign() ممکن است.
عملگر انتشار
یک مثال برای درک عملگر اسپرد و کلونسازی سطحی.
let obj = { 'a': { 'b' : 1 },'c': 2}
let copy = {...obj}
copy.c = 'changes only in copy' //shallow-cloned
copy.a.b = 'changed' // still reference
console.log('original\n',obj)
console.log('\ncopy',copy)با استفاده از اپراتور اسپرد، شیء به صورت کلون سطحی کپی میشود، بنابراین تمام ویژگیهای سطح اول یک کپی خواهند شد در حالی که تمام ویژگیهای سطح عمیقتر هنوز ارجاعها باقی میمانند.
همانطور که در مثال میبینید، ویژگی c بر روی شیء اصلی تأثیری ندارد چون در سطح اول قرار دارد، از طرف دیگر تغییرات ویژگی b بر روی ویژگیهای والد تأثیر میگذارد زیرا در سطح عمیقتری قرار دارد و هنوز ارجاع است.
