javascript中常量(理想情况下为文字)循环对象的表示

javascript中常量(理想情况下为文字)循环对象的表示,javascript,Javascript,我有一些非常循环结构的Javascript对象,它们的计算成本相对较高。我希望能够在源代码中将它们表示为(几乎)文本,这样它们就不需要重新计算。下面的几段文字中有一个库函数的例子 曾经有一些建议的符号,但它们只在旧版本的Firefox中得到支持 当然,如果对象有任何循环引用,那么它们就不能表示为文本,所以我希望将Javascript对象转换为一些代码,以便使用一个小函数来创建它。然后可以手动将其复制到要使用的源文件中 为了举例说明,我将调用我正在查找的函数print。我希望行为大致如下: con

我有一些非常循环结构的Javascript对象,它们的计算成本相对较高。我希望能够在源代码中将它们表示为(几乎)文本,这样它们就不需要重新计算。下面的几段文字中有一个库函数的例子

曾经有一些建议的符号,但它们只在旧版本的Firefox中得到支持

当然,如果对象有任何循环引用,那么它们就不能表示为文本,所以我希望将Javascript对象转换为一些代码,以便使用一个小函数来创建它。然后可以手动将其复制到要使用的源文件中

为了举例说明,我将调用我正在查找的函数
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=
    等)
  • 计算使用的不同夏普变量的数量
  • 为每一个设置一个Javascript变量名,例如
    a1
    ,也可以为根设置一个名称
  • 将所有
    #n#
    替换为未定义,跟踪它们在树中的位置
  • 对于每个
    #n#
    生成如下代码:
    一个['path'][1]['to']['object']=am
  • 为root添加return语句

  • 然而,与直接打印代码相比,似乎更不可能存在使用尖锐变量打印对象的库。

    无论如何,这里有一个答案:

    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漂亮打印机)需要这样做),这可能会导致对非常自引用但不太深的对象进行非常深的嵌套(例如某些浅无环有向图)因此需要大量代码来引用这些严格的成员。广度优先的解决方案稍微好一些,但对于我的用例来说,即使这样也会导致输出太大。什么