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

5
我希望将一个对象的属性/值与类实例合并。(我不确定JS中的正确术语是什么,但下面的示例应该可以说明问题)
我的尝试是使用展开语法。见下文。
我有一个文件实例:
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字段,即mergedObjectcontentObject相似。有趣的是,如果我这样做

const mergedObject = {
    ...file
}

mergedObject是一个文件实例。我假设扩展运算符在处理类实例时与处理对象的方式不同?我该如何实现合并的对象

更多非必要信息

  • FileReader在redux中间件中实现,并在读取完成后使用{ preview: '1234..ef', text: 'Lorem ipsum'}对象作为有效负载分派新操作。
  • 我正在使用preview字段将内容映射到文件,并希望以类似于以下方式返回合并的对象:“return files.map(file => file.preview !== payload.preview? file: {...file, text: payload.content}

你说你正在使用 FileReader.readAsText "获取内容,例如在对象中"。这不会返回一个字符串而不是一个对象吗?也许我误解了某个步骤。 - mccambridge
你说得没错,但它是异步的,所以我将其包装在一个对象中来处理回调。 - Thomas Fauskanger
我的例子有点简化,因为它是 Redux reducer 和 Middleware 的一部分。 - Thomas Fauskanger
4个回答

6

在 ES6 中,要将一个类实例和一个对象合并,可以使用 Object.assign() 来合并对象中的所有属性,并保留类实例的原型。而展开运算符只能合并所有的属性但不包括原型。

对于你的情况,请尝试以下代码:

const mergedObject = Object.assign(file, contentObject)

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


1
您可能需要做类似这样的操作...

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 有一个 pick 函数可以用于此操作。


我希望不必这么做,但似乎那是唯一的方法。谢谢。 - Thomas Fauskanger
1
@ThomasFauskanger,我更新了答案,并提供了一个可能的实用方法,可以让你更接近我认为你想要的东西。 - Doug Coburn
感谢您提供的额外解决方案。为了解决这个问题,我在我的解决方案中所做的是将File实例与文本一起嵌入到一个包装对象中,如const allInformation = {instance: file, text:content} - Thomas Fauskanger
1
请注意,这种技术不会为对象类可能扩展的父类中定义的属性创建引用。 - givanse

1

像循环一样的扩展语法遍历可枚举属性。如下面的代码所示,File对象的name属性不可枚举。因此,唯一的获取这些属性的方法是逐个获取。

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


感谢您解释为什么展开语法不能与文件实例一起使用。 - Thomas Fauskanger

0
你可以沿着原型链向上走,并创建一个包装类实例的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());


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接