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

How to merge JavaScript class instance with object?

我想将对象的 properties/values 与 class 实例合并。 (我不确定 JS 中的正确术语是什么,但这个例子应该很清楚)

我尝试使用传播语法。见下文。

我有一个 File-实例:

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 是一个文件实例。我假设扩展运算符对 class 实例的工作方式与对对象的工作方式不同?如何实现合并的 object?

更多非必要信息

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

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 有一个可以用于此的选择功能。

循环遍历可枚举属性的传播语法。正如您所看到的,下面的代码表明 File 对象的 name 属性 不可枚举。因此,获得这些属性的唯一方法是一项一项。

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

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

在你的情况下,尝试:

const mergedObject = Object.assign(file, contentObject)

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

您可以沿着原型链向上走,创建一个环绕 class 实例的 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());