我正在为一些特定于应用程序的对象编写一个简单的序列化/反序列化框架。
请考虑以下事项:
"use strict";
function Dog(name) { this._name = name; };
Dog.prototype.constructor = Dog;
Dog.prototype.getName = function() { return this._name; }
var d1 = new Dog('fido');
var d2 = JSON.parse(JSON.stringify(d1)); // serialize / deserialize
> d1
Dog { _name: 'fido' }
> d1.getName()
'fido'
> d2
{ _name: 'fido' }
> d2.getName()
TypeError: d2.getName is not a function
此时,可以问“d1
缺少什么d2
?”
部分工作的一种方法是手动将d1的方法分配给d2:
> d2.constructor = d1.constructor
> d2.getName = d1.getName
> d2.getName()
'fido'
这有几个缺点。首先,我必须手动将d1的每个方法分配给d2。其次,d2获得自己的属性,并且不使用原型机制共享插槽:
> d2
Dog {
_name: 'fido',
constructor: [Function: Dog],
getName: [Function] }
所以我提出的问题是:给定一个对象(例如d2
),有没有办法将它与另一个对象的原型相关联(例如d1
),所以它继承了相同的行为? / p>
答案 0 :(得分:4)
Object.create()
和Object.getOwnPropertyDescriptors()
就是您所需要的。
const obj = JSON.parse(JSON.stringify(d1))
const d3 = Object.create(Dog.prototype, Object.getOwnPropertyDescriptors(obj))
这与OP的方法之间的区别在于此方法在原型上设置prototype
属性,而OP的方法直接在对象上设置属性。使用hasOwnProperty()
方法使用for-in循环遍历对象自己的属性时,可以看到这一点:
for (const i in d1) {
if (d3.hasOwnProperty(i)) {
console.log(i)
}
}
使用我的方法,它仅输出_name
,但使用OP的方法,它也输出getName
。
不幸的是,Object.getOwnPropertyDescriptors()
是ECMAScript 2017的一部分,目前仅在Firefox中受支持,因此您需要使用Babel。
或者,您可以使用Object.setPrototypeOf()
。它具有比Object.getOwnPropertyDescriptors()
更好的浏览器支持,但MDN不鼓励它,因为它很慢。
const d3 = JSON.parse(JSON.stringify(d1))
Object.setPrototypeOf(d3, Dog.prototype)
答案 1 :(得分:3)
在我写这篇文章时,我想到了创建一个使用反序列化的JSON来初始化对象的自定义构造函数:
Dog.createFromJSON = function(obj) {
var d = new Dog();
Object.keys(obj).forEach(function(key) {
d[key] = obj[key];
});
return d;
}
> d3 = Dog.createFromJSON(JSON.parse(JSON.serialize(d1)))
> d3
Dog { _name: 'fido' }
> d3.getName()
'fido'
正如@Louis指出的那样,@ Gothdo的回答要求你知道反序列化对象属于哪个类。如果您愿意将类名添加到序列化对象,则可以使用它来动态确定类。因此,例如,扩展OP的例子:
> var d1 = new Dog('fido');
> d1['_class'] = 'Dog';
> let jsonString = JSON.stringify(d1)
'{"_name":"fido","_class":"Dog"}'
使用deserialize JSON to JAVASCRIPT object中描述的技巧(但针对Node.js进行了调整),您可以使用字符串通过Node.js的global
对象获取类原型的句柄:
> global[d1['_class']].prototype
Dog { getName: [Function] }
现在您可以使用它来使用@ Gothdo的技术动态重建对象。把它们放在一起:
/**
* Dynamically create an object from a JSON string of properties.
* Assumes the presence of a _class meta-property that names the
* resulting class.
*/
function reconstitute(jsonString) {
let obj = JSON.parse(jsonString);
let cls = global[obj['_class']];
delete obj['_class']; // remove meta-property
return Object.setPrototypeOf(obj, cls.prototype);
}
> reconstitute('{"_name":"fido","_class":"Dog"}')
Dog { _name: 'fido' }
答案 2 :(得分:0)
简单方法:就地更改类
如果您确实需要就地更改对象的类 ,则此函数将在定义了Object.getPrototypeOf
和Object.setPrototypeOf
的任何系统上起作用:
// set class of object to `name`
function setObjectClassName(obj, name) {
let newObj = eval('new ' + name + '()');
let proto = Object.getPrototypeOf(newObj);
Object.setPrototypeOf(obj, proto);
return obj;
}
使用JSON.serialize()
和JSON.parse()
的示例:
class MyClass extends Object {}
let original = new MyClass();
original.foo = "bar";
console.log(original.constructor.name, original);
// MyClass { "foo": 'bar' }
let originalClassName = original.constructor.name;
let serialized = JSON.stringify(original);
console.log(serialized.constructor.name, serialized);
// String '{"foo":"bar"}'
let restored = JSON.parse(serialized);
console.log(restored.constructor.name, restored);
// Object { foo: 'bar' }
restored = setObjectClassName(restored, originalClassName);
console.log(restored.constructor.name, restored);
// MyClass { foo: 'bar' }
更好的方法:复制对象
Mozilla 警告不要更改现有对象的原型,因为它是:
每个浏览器和JavaScript引擎的操作速度都很慢 -Mozilla
如果您绝对不需要就地更改,则此函数将复制对象并更改复制的类:
function copyObjectAndChangeClass(obj, name) {
let newObj = eval('new ' + name + '()');
Object.assign(newObj, obj);
return newObj;
}