使用扩展运算符的ES6对象克隆也在修改输入

Joh*_*ing 3 javascript typescript ecmascript-6

我有一个相当深的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
  }
Run Code Online (Sandbox Code Playgroud)

这个接口的大多数成员本身就是接口,一般架构遵循这种使用枚举,字符串,数组和更多接口的模式.所有代码都是作为TypeScript编写的,编译成JS并作为JS上传到AWS.(节点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 };
};
Run Code Online (Sandbox Code Playgroud)

定义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);
    }
  }
Run Code Online (Sandbox Code Playgroud)

问题是在调用之后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;
  };
Run Code Online (Sandbox Code Playgroud)

(我在这里使用lodash库)

线路市场'LINE TWO'也在改变from功能参数,即使在'LINE ONE'我正在做我认为是深刻克隆的东西from.

我知道情况就是这样,因为如果我将'LINE ONE'更改为:

// super hard core deep cloning
let job: Types.Job = JSON.parse(JSON.stringify(from));
Run Code Online (Sandbox Code Playgroud)

......一切都按预期工作.from不被突变,得到的JobUpdatedMessage是如预期,和StartPipelineevent参数不具有一束从除去性能event.Job.

我在这方面花费了数小时的努力,包括重新学习我认为我使用扩展运算符在Es6中克隆对象的所有知识.

为什么'LINE ONE'也会改变输入?

Cod*_*iac 5

Spread运算符执行浅层克隆 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)
Run Code Online (Sandbox Code Playgroud)

使用spread operator对象是shallow cloned这样所有的第一级属性将成为副本,而所有更深层次的属性仍将保留references.

因此,您在示例中看到c属性不会影响原始对象,因为它是一个第一级深度,另一方面,b属性更改会影响父属性,因为它处于深层次并且仍然是引用.

  • @DohnDibling-一个浅层克隆*看起来就像一个深层克隆,直到你改变一个深层属性.也就是说,你看不到它们都解析为相同值的值和引用之间的区别. (2认同)