如何将 JavaScript 类实例与对象合并?

Posted

技术标签:

【中文标题】如何将 JavaScript 类实例与对象合并?【英文标题】:How to merge JavaScript class instance with object? 【发布时间】:2018-12-18 14:31:39 【问题描述】:

我想将对象的属性/值与类实例合并。 (我不确定 JS 中的正确术语是什么,但示例应该澄清)

我的尝试是使用扩展语法。见下文。

我有一个File-instance:

const files = listOfFilesFromDragNdrop();
let file = files[0];

console.log(file)

输出类似:

File(2398)
lastModified: 1530519711960
lastModifiedDate: Mon Jul 02 2018 10:21:51 GMT+0200
name: "my_file.txt"
preview: "blob:http://localhost:8080/6157f5d5-925a-4e5d-a466-24576ba1bf7c"
size: 2398
type: "text/plain"
webkitRelativePath: ""

添加后,我使用FileReader.readAsText() 获取内容,并将其包装在如下对象中:

contentObject = getFileContentFromFile()
console.log(contentObject)

会输出如下内容:

 
    preview: "blob:http://localhost:8080/6157f5d5-925a-4e5d-a466-24576ba1bf7c",
    content: "Lorem ipsum some text here." 

我想最终得到一个合并的对象,例如:

 
    // "preview" is the key used to map files and content
    preview: "blob:http://localhost:8080/6157f5d5-925a-4e5d-a466-24576ba1bf7c",

    // "text" is the new field with the content from contentObject
    text: "Lorem ipsum some text here." 

    // The other fields are from the File instance
    name: "my_file.txt",
    size: 2398,
    type: "text/plain",
    lastModified: 1530519711960,
    // ...        

我首先尝试的是:

const mergedObject = 
    ...file,
    text: contentObject.content

同样(知道text 键会变成content)我试过了

const mergedObject = 
    ...file,
    ...contentObject

但是,然后我只得到contentObject 字段,即mergedObject 类似于contentObject。有趣的是,如果我这样做了

const mergedObject = 
    ...file

mergedObject 是一个 File 实例。我假设扩展运算符对类实例的工作方式与对对象的工作方式不同?如何实现合并的对象

更多不必要的信息

FileReader 在 redux 中间件中实现,并在完成读取后以 preview: '1234..ef', text: 'Lorem ipsum' 对象作为负载分派一个新操作。 我正在使用preview-字段将内容映射到文件,并希望在“文件”-reducer 中返回合并的对象,例如:return files.map(file => file.preview !== payload.preview? file: ...file, text: payload.content

【问题讨论】:

您说您正在使用FileReader.readAsText“获取内容,例如在对象中”。这不是要返回一个字符串,而不是一个对象吗?也许我误会了一步。 你是对的,但它是异步的,所以我将它包装在一个对象中来处理回调。 我的例子有点简化,因为它是 Redux reducer 和 Middleware 的一部分 【参考方案1】:

要合并一个类实例和一个对象,在 ES6 中,您可以使用 Object.assign() 来合并对象中的所有属性,并保持类实例的原始原型。扩展运算符只合并所有属性,但不合并原型。

在你的情况下,尝试:

const mergedObject = Object.assign(file, contentObject)

请记住,在这种情况下,您的原始文件对象将被更改。

【讨论】:

【参考方案2】:

你可能只需要做这样的事情......

const mergedObject = 
  lastModified: file.lastModified,
  lastModifiedDate: file.lastModifiedDate,
  name: file.name,
  size: file.size,
  type: file.type,
  webkitRelativePath: file.webkitRelativePath,
  text: contentObject.content,
  preview: contentObject.preview,

您可以编写一个实用函数来从文件实例中提取伪属性:

// Error is a like File with a pseudo property named message
let error = new Error('my error message')
error.status = 404;

const pick = (objectLike, properties) =>
    properties.reduce(
        (acc, key) => 
            acc[key] = objectLike[key];
            return acc;
        ,
        
    );

const contentObject = 
    content: 'content text',
    preview: 'http://url.io',
;

const mergedObject = 
  ...pick(error, Object.getOwnPropertyNames(error)),
  ...contentObject,

console.log(JSON.stringify(mergedObject));

Lodash 有一个你可以用来做这个的选择函数。

【讨论】:

我希望我不必这样做,但似乎这是唯一的方法。谢谢。 @ThomasFauskanger,我用一种可能的实用方法更新了答案,你可以编写它以更接近我认为你想要的。 感谢添加的建议解决方案。 (在我的解决方案中,我实际上为解决这个问题所做的是将 File 实例与文本一起嵌入到像 const allInformation = instance: file, text: content) 这样的包装对象中。) 请注意,此技术不会为对象类可能扩展的父类中定义的属性创建引用。【参考方案3】:

像循环这样的传播语法会迭代可枚举的属性。正如您所看到的,下面的代码显示 File 对象的 name 属性不可枚举。因此,获得这些属性的唯一方法是一个一个。

document.querySelector('input').addEventListener('change', e => 
  const file = e.target.files[0];
  console.log(file.propertyIsEnumerable('name'));
);
<input type="file">

【讨论】:

感谢您解释为什么传播语法不适用于 File 实例。【参考方案4】:

您可以沿着原型链向上走并创建一个包装类实例的 POJO。一旦你有了合并是微不足道的。

// just a sample object hierarchy

class Plant 
  constructor() 
    this._thorns = false;
  
  hasThorns() 
    return this._thorns;
  


class Fruit extends Plant 
  constructor(flavour) 
    super();
    this._flavour = flavour;
  
  flavour() 
    return this._flavour;
  


// this is the mechanism

function findProtoNames(i) 
  let names = [];
  let c = i.constructor;
  do 
    const n = Object.getOwnPropertyNames(c.prototype);
    names = names.concat(n.filter(s => s !== "constructor"));
    c = c.__proto__;
   while (c.prototype);

  return names;


function wrapProto(i) 
  const names = findProtoNames(i);
  const o = ;
  for (const name of names) 
    o[name] = function() 
      return i[name].apply(i, arguments);
    
  

  return o;


function assignProperties(a, b) 
  
  for (const propName of Object.keys(b)) 
    if (a.hasOwnProperty(propName)) 
      const msg = `Error merging $a and $b. Both have a property named $propName.`;
      throw new Error(msg);
    

    Object.defineProperty(a, propName, 
      get: function() 
        return b[propName];
      ,
      set: function(value) 
        b[propName] = value;
      
    );
  

  return a;


function merge(a, b) 
  if (b.constructor.name === "Object") 
    return Object.assign(a, b);
   else 
    const wrapper = wrapProto(b);
    a = assignProperties(a, b);
    return assignProperties(a, wrapper);
  


// testing it out

const obj = 
  a: 1,
  b: 2
;
const f = new Fruit('chicken');
const r = merge(obj, f);
console.log(r);
console.log('thorns:', r.hasThorns());
console.log('flavour:', r.flavour());

【讨论】:

以上是关于如何将 JavaScript 类实例与对象合并?的主要内容,如果未能解决你的问题,请参考以下文章

将javascript普通对象转换为模型类实例

将javascript普通对象转换为模型类实例

如何将两个java对象合并为一个?

如何将同一属性上的数组和对象“合并/合并”到平面数组中(使用Javascript)?

Java类的定义与类的实例化

我将如何减少这个数组,以便每个对象中的对象被合并 javascript 但不知道键值名称