在javascript中表示常量(理想文字)循环对象

时间:2016-09-30 15:57:20

标签: javascript

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

曾经有人为此提出了一些建议,sharp variables,但它们只在旧版本的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原型/普通对象/数组构成的对象,我们应该eval(print(x))在结构上(即深度)等于x但不相同。换句话说,eval(print(x))将是一种(愚蠢的)方式来制作x的深层副本(但尊重周期)

我更喜欢第一种选择而不是第二种选择。据推测,也可以实现一些漂亮的打印,但这是可选的。另外,我并不太关心使用void 0而不是未定义的小细节。

我知道存在可以序列化循环对象的库但是它们在普通的树结构javascript对象中执行一些自定义表示法,因此它们需要额外的代码来反序列化。我不需要这个,所以我希望避免它。

我想如果你能够将对象打印成尖锐的变量符号,你可以合理地将其转换为如上所示的形式:

  1. 使用尖锐变量打印(不重复,例如#1=等)
  2. 计算使用的不同尖锐变量的数量。
  3. 为每个人创建一个Javascript变量名称,例如a1也可能是根的名称。
  4. 将所有#n#替换为未定义,并跟踪它们在树中的位置
  5. 为每个#n#生成代码,例如:an['path'][1]['to']['object'] = am
  6. 为root添加return语句。
  7. 然而,似乎更不可能存在一些库来打印具有尖锐变量的对象而不是直接打印代码。

2 个答案:

答案 0 :(得分:0)

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

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为路径创建链接列表会减少内存使用量。

答案 1 :(得分:0)

我认为实施尖锐变量很复杂,并且不会使计算更容易。我会用函数实现这样的行为:

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

我知道这不是你真正想要的。愿有人找到更好的解决方案......