javascript中常量(理想情况下是文字)圆形对象的表示

Representation of constant (ideally literal) circular objects in javascript

本文关键字:对象 表示 文字 常量 理想 情况下 javascript      更新时间:2023-09-26

我有一些相当循环结构的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原语/普通对象/数组组成的对象,我们应该让eval(print(x))在结构上(即深度上)等于x,但不完全相同。换句话说,eval(print(x))将是一种(愚蠢的)方式来对x进行深度复制(但尊重循环)

比起第二个选项,我更喜欢第一个选项。大概也可以实现一些漂亮的打印,但这是可选的。此外,我真的不太关心像使用void 0而不是undefined这样的小细节。

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

我认为,如果您能够将对象打印为尖锐的变量表示法,则可以相当容易地将其转换为如上所示的形式:

  1. 打印尖锐变量(不重复,例如#1=等)
  2. 计算使用的不同尖锐变量的数量。
  3. 为每一个Javascript变量命名,例如a1,也可以为根命名。
  4. 将所有#n#替换为undefined,跟踪它们在树中的位置
  5. 为每个#n#生成如下代码:an['path'][1]['to']['object'] = am
  6. 为root添加返回语句。

然而,比起直接打印代码,似乎更不可能存在一些库来打印带有尖锐变量的对象。

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

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
我知道这不是你真正想要的。

这里有一个答案:

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为路径创建一个链表可以减少一点内存使用。