javascript中常量(理想情况下为文字)循环对象的表示
我有一些非常循环结构的Javascript对象,它们的计算成本相对较高。我希望能够在源代码中将它们表示为(几乎)文本,这样它们就不需要重新计算。下面的几段文字中有一个库函数的例子 曾经有一些建议的符号,但它们只在旧版本的Firefox中得到支持 当然,如果对象有任何循环引用,那么它们就不能表示为文本,所以我希望将Javascript对象转换为一些代码,以便使用一个小函数来创建它。然后可以手动将其复制到要使用的源文件中 为了举例说明,我将调用我正在查找的函数javascript中常量(理想情况下为文字)循环对象的表示,javascript,Javascript,我有一些非常循环结构的Javascript对象,它们的计算成本相对较高。我希望能够在源代码中将它们表示为(几乎)文本,这样它们就不需要重新计算。下面的几段文字中有一个库函数的例子 曾经有一些建议的符号,但它们只在旧版本的Firefox中得到支持 当然,如果对象有任何循环引用,那么它们就不能表示为文本,所以我希望将Javascript对象转换为一些代码,以便使用一个小函数来创建它。然后可以手动将其复制到要使用的源文件中 为了举例说明,我将调用我正在查找的函数print。我希望行为大致如下: con
print
。我希望行为大致如下:
console.log(print({a:1,b:{c:2,d:3}})); // {a:1,b:{c:2,d:3}} (a string)
var obj = [null,null];
obj[0]=obj[1]=obj; //with sharp variables: obj = #1=[#1#,#1#]
console.log(print(obj));
// e.g. (function(){var u=void 0,a1=[u,u];a1[0]=a1;a1[1]=a1;return a1;})()
var obj = {a:1,
some:{b:2,
nested:{c:3,
cyclic:{d:4}}}};
obj.some.nested.cyclic.structure = obj;
//obj = #1={a:1,some:{b:2,nested:{c:3,cyclic:{d:4,structure:#1#}}}}
console.log(print(obj));
//e.g. (function(){var u=void 0,a1={a:1,some:{b:2,nested:{c:3,cyclic:{\
// d:4,structure:u};a1.some.nested.cyclic.structure=a1;return a1;})()
//OR e.g. (function(){var u=void 0,a1={a:1,some:u},a2={b:2,nested:u},...
// a1.some=a2;a2.nested=a3;a3.cyclic=a4;a4.structure=a1; return a1;})()
本质上,对于仅由JS/plain对象/数组构成的任何对象,我们应该使eval(print(x))
在结构上(即深度)等于x
,但不完全相同。换句话说,eval(print(x))
将是制作x
深度副本的一种(愚蠢的)方法(但不考虑周期)
我更喜欢第一种选择,而不是第二种。可能还可以实现一些漂亮的打印,但这是可选的。此外,我也不太关心小细节,比如使用void0
而不是未定义
我知道存在可以序列化循环对象的库,但它们在普通树结构的javascript对象中对一些自定义符号进行序列化,因此它们需要额外的代码进行反序列化。我不需要这个,所以我希望避免它
我假设,如果您能够将一个对象打印为sharp variables符号,那么您可以很容易地将其转换为上述形式,如下所示:
#1=
等)a1
,也可以为根设置一个名称#n#
替换为未定义,跟踪它们在树中的位置#n#
生成如下代码:一个['path'][1]['to']['object']=am
然而,与直接打印代码相比,似乎更不可能存在使用尖锐变量打印对象的库。无论如何,这里有一个答案:
function isPrimitive(x){
switch(typeof x){
case "number":
case "string":
case "boolean":
case "undefined":
return true;
}
return false;
}
function isSerialisable(x){
switch(typeof x){
case "function":
case "symbol":
return false;
case "number":
case "string":
case "object":
case "boolean":
case "undefined":
return true;
}
return false;
}
function isValidIdentifier(string){
//this is *really* stupid
//TODO: operate brain
if(/[] ,;'".+=-()[]!*/.test(string)) return false;
//don't want anything too stupid
try{
//this throws a syntax error if our identifier is invalid!
//note that whilst e.g. `var in = ...` is invalid, `foo.in` is valid
void new Function("x", "return x." + string + ";")
} catch(e) { return false;}
return true;
}
function serialise(object){
var seenObjects = new Map();
var places = [];
//this function traverses object in depth-first order,
//simultaneously finding recursive references and
//building up a tree-shaped near-deep-copy of object that can be
//JSON.stringified
function process(object, path){
if(!isSerialisable(object)) throw "Object is not serialisable";
if(isPrimitive(object)) return object;
//object really is an object now
if(seenObjects.has(object)){
places.push({path:path.slice(),from:seenObjects.get(object)});
return null; //so we don't have cycles.
//we use null so it is included by JSON.stringify so that the
//order of the keys is preserved.
} else {
//iterate over the own properties
var ret = Array.isArray(object) ? [] : {} //maybe Object.create(null); doesn't really matter
seenObjects.set(object, path.slice()); //so we can find it again
for(var prop in object){
if(Object.prototype.hasOwnProperty.call(object, prop)){
var p = +prop;
prop = (p == prop && prop !== "") ? p : prop;
path.push(prop);
ret[prop] = process(object[prop], path);
console.assert(prop == path.pop(), "Path stack not maintained");
}
}
return ret;
}
}
function dotPath(path){
return path.map(function(x){
if(isValidIdentifier(x)){
return "." + x;
} else {
return "[" + JSON.stringify(x) + "]";
}}).join("");
//we use JSON.stringify to properly escape strings
//therefore we hope that they do not contain the types of vertical space
//which JSON ignores.
}
var tree = process(object, []);
if(places.length == 0){
//object not cyclic
return JSON.stringify(tree);
}
//object is cyclic
var result = "(function(){x=" + JSON.stringify(tree) + ";"
return result + places.map(function(obj){
//obj = {path:..., from:...}
return "x" + dotPath(obj.path) + "=x" + dotPath(obj.from);
}).join(";") + ";return x;})()";
}
一些辅助函数特别令人敬畏,但如果大型对象的内存有点大,那么主要部分基本上是可以的。也许使用conses为路径创建一个链表会减少一点内存使用。无论如何,这里有一个答案:
function isPrimitive(x){
switch(typeof x){
case "number":
case "string":
case "boolean":
case "undefined":
return true;
}
return false;
}
function isSerialisable(x){
switch(typeof x){
case "function":
case "symbol":
return false;
case "number":
case "string":
case "object":
case "boolean":
case "undefined":
return true;
}
return false;
}
function isValidIdentifier(string){
//this is *really* stupid
//TODO: operate brain
if(/[] ,;'".+=-()[]!*/.test(string)) return false;
//don't want anything too stupid
try{
//this throws a syntax error if our identifier is invalid!
//note that whilst e.g. `var in = ...` is invalid, `foo.in` is valid
void new Function("x", "return x." + string + ";")
} catch(e) { return false;}
return true;
}
function serialise(object){
var seenObjects = new Map();
var places = [];
//this function traverses object in depth-first order,
//simultaneously finding recursive references and
//building up a tree-shaped near-deep-copy of object that can be
//JSON.stringified
function process(object, path){
if(!isSerialisable(object)) throw "Object is not serialisable";
if(isPrimitive(object)) return object;
//object really is an object now
if(seenObjects.has(object)){
places.push({path:path.slice(),from:seenObjects.get(object)});
return null; //so we don't have cycles.
//we use null so it is included by JSON.stringify so that the
//order of the keys is preserved.
} else {
//iterate over the own properties
var ret = Array.isArray(object) ? [] : {} //maybe Object.create(null); doesn't really matter
seenObjects.set(object, path.slice()); //so we can find it again
for(var prop in object){
if(Object.prototype.hasOwnProperty.call(object, prop)){
var p = +prop;
prop = (p == prop && prop !== "") ? p : prop;
path.push(prop);
ret[prop] = process(object[prop], path);
console.assert(prop == path.pop(), "Path stack not maintained");
}
}
return ret;
}
}
function dotPath(path){
return path.map(function(x){
if(isValidIdentifier(x)){
return "." + x;
} else {
return "[" + JSON.stringify(x) + "]";
}}).join("");
//we use JSON.stringify to properly escape strings
//therefore we hope that they do not contain the types of vertical space
//which JSON ignores.
}
var tree = process(object, []);
if(places.length == 0){
//object not cyclic
return JSON.stringify(tree);
}
//object is cyclic
var result = "(function(){x=" + JSON.stringify(tree) + ";"
return result + places.map(function(obj){
//obj = {path:..., from:...}
return "x" + dotPath(obj.path) + "=x" + dotPath(obj.from);
}).join(";") + ";return x;})()";
}
一些辅助函数特别令人敬畏,但如果大型对象的内存有点大,那么主要部分基本上是可以的。也许使用conses为路径创建一个链表会减少一点内存使用。我认为实现sharp变量很复杂,并且不会使计算更容易。我将通过以下功能实现类似的行为:
var sharps={};
Object.prototype.sharp=function(str){
sharps[str]=this;
}
function gs(str){
return function(){return sharps[str];};
}
所以你可以做:
var obj1={a:{b:gs("1");}
obj1.sharp("1");
alert(obj1.a.b());//returns obj1
我知道这不是你真正想要的。也许有人能找到更好的解决方案……我认为实现夏普变量很复杂,而且不会使计算更容易。我将通过以下功能实现类似的行为:
var sharps={};
Object.prototype.sharp=function(str){
sharps[str]=this;
}
function gs(str){
return function(){return sharps[str];};
}
所以你可以做:
var obj1={a:{b:gs("1");}
obj1.sharp("1");
alert(obj1.a.b());//returns obj1
我知道这不是你真正想要的。希望有人能找到更好的解决方案……好问题。但是为什么呢??我认为不再需要尖锐的变量了…@Jonasw,因为在某些情况下,圆形对象是有用的。在我的例子中,它们也是常数,但计算起来相对昂贵。在最一般的情况下,人们可能需要一个(循环)图来进行计算。好问题。但是为什么呢??我认为不再需要尖锐的变量了…@Jonasw,因为在某些情况下,圆形对象是有用的。在我的例子中,它们也是常数,但计算起来相对昂贵。在最一般的情况下,人们可能需要一个(循环)图来进行计算。重点不在于实现锐化——它们只是一个例子,而是序列化循环对象,但如果需要的话(关于内存消耗等),创建它们不是更好吗?仅用于构造对象的源代码比对象使用的内存更多。它将是一个常量,因此只需要创建一次。重点不在于实现锐化——它们只是一个示例,而是序列化循环对象,但如果需要它们(关于内存消耗等),只创建它们不是更好吗?仅用于构建对象的源代码就比对象使用更多内存。这将是一个常量,因此只需要创建一次。请注意,虽然这在理论上可行,但它会首先搜索深度(使用夏普变量的漂亮打印机(我的意思是lisp漂亮打印机)需要执行此操作),这可能会导致对非常自引用但不太深的对象进行非常深的嵌套(例如某些浅非循环有向图)因此需要大量的代码来引用这些严格的成员。广度优先的解决方案稍好一些,但对于我的用例来说,即使这样也会导致输出太大。我需要的是启发式地分离对象以输出类似上一个示例的内容。请注意,虽然这在理论上可行,但它会首先搜索深度(使用夏普变量的漂亮打印机(我指的是lisp漂亮打印机)需要这样做),这可能会导致对非常自引用但不太深的对象进行非常深的嵌套(例如某些浅无环有向图)因此需要大量代码来引用这些严格的成员。广度优先的解决方案稍微好一些,但对于我的用例来说,即使这样也会导致输出太大。什么