我有一个应用程序,允许用户生成对象,并将它们(在MySQL表中,作为字符串)存储供以后使用.对象可能是:
function Obj() {
this.label = "new object";
}
Obj.prototype.setLabel = function(newLabel) {
this.label = newLabel;
}
Run Code Online (Sandbox Code Playgroud)
如果我在这个对象上使用JSON.stringify,我将只获取有关信息Obj.label(字符串化对象将是一个字符串{label: "new object"}.如果我存储此字符串,并希望以后允许我的用户检索该对象,该setLabel方法将丢失.
所以我的问题是:如何重新实例化对象,以便通过JSON.stringify保存属性,还可以获取应该属于其原型的不同方法.你会怎么做?我在考虑"创建一个空白对象"并"将其与存储的属性合并",但我无法使其工作.
T.J*_*der 20
要做到这一点,你需要在解析JSON字符串时使用"reviver"函数(以及toJSON创建它时构造函数原型上的"replacer"函数或函数).参见第15.12.2和15.12.3的规范.如果您的环境尚不支持本机JSON解析,您可以使用Crockford的解析器之一(Crockford是JSON的发明者),它也支持"reviver"函数.
这是一个简单的定制示例,适用于符合ES5标准的浏览器(或模拟ES5行为的库)(实时复制,在Chrome或Firefox或类似版本中运行),但请查看示例以获得更通用的解决方案.
// Our constructor function
function Foo(val) {
this.value = val;
}
Foo.prototype.nifty = "I'm the nifty inherited property.";
Foo.prototype.toJSON = function() {
return "/Foo(" + this.value + ")/";
};
// An object with a property, `foo`, referencing an instance
// created by that constructor function, and another `bar`
// which is just a string
var obj = {
foo: new Foo(42),
bar: "I'm bar"
};
// Use it
display("obj.foo.value = " + obj.foo.value);
display("obj.foo.nifty = " + obj.foo.nifty);
display("obj.bar = " + obj.bar);
// Stringify it with a replacer:
var str = JSON.stringify(obj);
// Show that
display("The string: " + str);
// Re-create it with use of a "reviver" function
var obj2 = JSON.parse(str, function(key, value) {
if (typeof value === "string" &&
value.substring(0, 5) === "/Foo(" &&
value.substr(-2) == ")/"
) {
return new Foo(value.substring(5, value.length - 2));
}
return value;
});
// Use the result
display("obj2.foo.value = " + obj2.foo.value);
display("obj2.foo.nifty = " + obj2.foo.nifty);
display("obj2.bar = " + obj2.bar);
Run Code Online (Sandbox Code Playgroud)
注意toJSONon Foo.prototype和我们传入的函数JSON.parse.
但问题是,reviver与Foo构造函数紧密耦合.相反,您可以在代码中采用通用框架,其中任何构造函数都可以支持fromJSON(或类似)函数,并且您只能使用一个通用的reviver.
这是一个广义的reviver的例子,它查找ctor属性和data属性,ctor.fromJSON如果找到则调用,传入它收到的完整值(实例):
// A generic "smart reviver" function.
// Looks for object values with a `ctor` property and
// a `data` property. If it finds them, and finds a matching
// constructor that has a `fromJSON` property on it, it hands
// off to that `fromJSON` fuunction, passing in the value.
function Reviver(key, value) {
var ctor;
if (typeof value === "object" &&
typeof value.ctor === "string" &&
typeof value.data !== "undefined") {
ctor = Reviver.constructors[value.ctor] || window[value.ctor];
if (typeof ctor === "function" &&
typeof ctor.fromJSON === "function") {
return ctor.fromJSON(value);
}
}
return value;
}
Reviver.constructors = {}; // A list of constructors the smart reviver should know about
Run Code Online (Sandbox Code Playgroud)
为避免在函数toJSON和fromJSON函数中重复使用通用逻辑,您可以使用通用版本:
// A generic "toJSON" function that creates the data expected
// by Reviver.
// `ctorName` The name of the constructor to use to revive it
// `obj` The object being serialized
// `keys` (Optional) Array of the properties to serialize,
// if not given then all of the objects "own" properties
// that don't have function values will be serialized.
// (Note: If you list a property in `keys`, it will be serialized
// regardless of whether it's an "own" property.)
// Returns: The structure (which will then be turned into a string
// as part of the JSON.stringify algorithm)
function Generic_toJSON(ctorName, obj, keys) {
var data, index, key;
if (!keys) {
keys = Object.keys(obj); // Only "own" properties are included
}
data = {};
for (index = 0; index < keys.length; ++index) {
key = keys[index];
data[key] = obj[key];
}
return {ctor: ctorName, data: data};
}
// A generic "fromJSON" function for use with Reviver: Just calls the
// constructor function with no arguments, then applies all of the
// key/value pairs from the raw data to the instance. Only useful for
// constructors that can be reasonably called without arguments!
// `ctor` The constructor to call
// `data` The data to apply
// Returns: The object
function Generic_fromJSON(ctor, data) {
var obj, name;
obj = new ctor();
for (name in data) {
obj[name] = data[name];
}
return obj;
}
Run Code Online (Sandbox Code Playgroud)
这里的优点是,您可以按顺序执行特定的"类型"(缺少更好的术语),以便进行序列化和反序列化.所以你可能有一个只使用泛型的"类型":
// `Foo` is a constructor function that integrates with Reviver
// but doesn't need anything but the generic handling.
function Foo() {
}
Foo.prototype.nifty = "I'm the nifty inherited property.";
Foo.prototype.spiffy = "I'm the spiffy inherited property.";
Foo.prototype.toJSON = function() {
return Generic_toJSON("Foo", this);
};
Foo.fromJSON = function(value) {
return Generic_fromJSON(Foo, value.data);
};
Reviver.constructors.Foo = Foo;
Run Code Online (Sandbox Code Playgroud)
...或者,无论出于何种原因,必须做更多自定义的事情:
// `Bar` is a constructor function that integrates with Reviver
// but has its own custom JSON handling for whatever reason.
function Bar(value, count) {
this.value = value;
this.count = count;
}
Bar.prototype.nifty = "I'm the nifty inherited property.";
Bar.prototype.spiffy = "I'm the spiffy inherited property.";
Bar.prototype.toJSON = function() {
// Bar's custom handling *only* serializes the `value` property
// and the `spiffy` or `nifty` props if necessary.
var rv = {
ctor: "Bar",
data: {
value: this.value,
count: this.count
}
};
if (this.hasOwnProperty("nifty")) {
rv.data.nifty = this.nifty;
}
if (this.hasOwnProperty("spiffy")) {
rv.data.spiffy = this.spiffy;
}
return rv;
};
Bar.fromJSON = function(value) {
// Again custom handling, for whatever reason Bar doesn't
// want to serialize/deserialize properties it doesn't know
// about.
var d = value.data;
b = new Bar(d.value, d.count);
if (d.spiffy) {
b.spiffy = d.spiffy;
}
if (d.nifty) {
b.nifty = d.nifty;
}
return b;
};
Reviver.constructors.Bar = Bar;
Run Code Online (Sandbox Code Playgroud)
下面是我们如何测试Foo和Bar按预期工作(现场复印件):
// An object with `foo` and `bar` properties:
var before = {
foo: new Foo(),
bar: new Bar("testing", 42)
};
before.foo.custom = "I'm a custom property";
before.foo.nifty = "Updated nifty";
before.bar.custom = "I'm a custom property"; // Won't get serialized!
before.bar.spiffy = "Updated spiffy";
// Use it
display("before.foo.nifty = " + before.foo.nifty);
display("before.foo.spiffy = " + before.foo.spiffy);
display("before.foo.custom = " + before.foo.custom + " (" + typeof before.foo.custom + ")");
display("before.bar.value = " + before.bar.value + " (" + typeof before.bar.value + ")");
display("before.bar.count = " + before.bar.count + " (" + typeof before.bar.count + ")");
display("before.bar.nifty = " + before.bar.nifty);
display("before.bar.spiffy = " + before.bar.spiffy);
display("before.bar.custom = " + before.bar.custom + " (" + typeof before.bar.custom + ")");
// Stringify it with a replacer:
var str = JSON.stringify(before);
// Show that
display("The string: " + str);
// Re-create it with use of a "reviver" function
var after = JSON.parse(str, Reviver);
// Use the result
display("after.foo.nifty = " + after.foo.nifty);
display("after.foo.spiffy = " + after.foo.spiffy);
display("after.foo.custom = " + after.foo.custom + " (" + typeof after.foo.custom + ")");
display("after.bar.value = " + after.bar.value + " (" + typeof after.bar.value + ")");
display("after.bar.count = " + after.bar.count + " (" + typeof after.bar.count + ")");
display("after.bar.nifty = " + after.bar.nifty);
display("after.bar.spiffy = " + after.bar.spiffy);
display("after.bar.custom = " + after.bar.custom + " (" + typeof after.bar.custom + ")");
display("(Note that after.bar.custom is undefined because <code>Bar</code> specifically leaves it out.)");
Run Code Online (Sandbox Code Playgroud)
您确实可以创建一个空实例,然后将该实例与数据合并.我建议使用库函数以便于使用(如jQuery.extend).
但是你有一些错误(function ... = function(...)和JSON需要键被包围").
var data = '{"label": "new object"}'; // JSON
var inst = new Obj; // empty instance
jQuery.extend(inst, JSON.parse(data)); // merge
Run Code Online (Sandbox Code Playgroud)
请注意,像这样合并会直接设置属性,所以如果setLabel正在做一些检查,那么就不会这样做.