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

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

该接口(interface)的大部分成员本身就是接口(interface),一般架构遵循使用枚举、字符串、数组和更多接口(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 TWO”也在变异 from函数参数,即使在“LINE ONE”上我正在做我认为是 from 的深度克隆.

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

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

...一切都按预期进行。 from没有突变,结果 JobUpdatedMessage符合预期,StartPipelineevent参数没有从 event.Job 中删除一堆属性.

我为此苦苦挣扎了几个小时,包括重新学习我认为我知道的关于使用扩展运算符在 Es6 中克隆对象的所有知识。

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

最佳答案

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

Shallow-cloning (excluding prototype) or merging of objects is now possible using a shorter syntax than 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)

使用展开运算符对象是浅克隆所以所有第一级 属性将成为副本,而所有更深层次 属性仍将保留为引用

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

关于javascript - 使用扩展运算符的 ES6 对象克隆也在修改输入,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54542400/

相关文章:

javascript - Angular4 提示 HTML 中的中心元素是未定义的函数

javascript - 如何在 for 循环中访问 json 数组的字段名称?

javascript - Babel React 变换 : Property value expected type of string but got null

javascript - javascript中可迭代的格式是什么

javascript - 如何在 jQuery 集合中获取被点击的项目索引?不是通过索引()

javascript - DataTables 设置默认排序列并设置不可排序列

某种类型的 Typescript keyof

javascript - JS 如何检查对象是否具有接口(interface)中未包含的属性?

javascript - 忽略掉毛和格式化的行 - VSCODE EsLint + Prettier

javascript - 将另一个类作为类的静态属性