使用传播运算符的ES6对象克隆也正在修改输入

时间:2019-02-05 20:19:15

标签: 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
  }

该接口的大多数成员本身都是接口,通用体系结构遵循使用枚举,字符串,数组和更多接口的这种模式。所有代码均以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 };
};

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 ONE”上,我正在做的是from的深层克隆,线路市场“ LINE TWO”也正在改变from函数参数。

我知道是这种情况,因为如果我将“ LINE ONE”更改为:

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

...一切正常。 from未突变,得到的JobUpdatedMessage符合预期,并且StartPipeline的{​​{1}}参数没有从event中删除一堆属性。

我为此付出了数小时的努力,其中包括重新学习我相信使用散布运算符在Es6中克隆对象的所有知识。

为什么“ LINE ONE”也会同时改变输入内容?

1 个答案:

答案 0 :(得分:5)

Spread运算符与Object.assign()一样进行浅层克隆

  

现在是对象的浅克隆(不包括原型)或合并   可能使用比Object.assign()短的语法。

Spread operator

了解传播算子和浅克隆的示例。

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)

使用 spread operator 对象是shallow cloned,因此所有 第一级 属性将成为副本,而所有 更深级别 属性仍将保留references

因此,如您在示例中所见,c属性不会影响原始对象,因为它是一个第一级深度,另一方面,b属性更改会影响父属性,因为它的深度很深级别,仍在参考。