2016-09-30 81 views
1

我有一些相當循環結構的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 primatives /平的對象/數組,我們應該有一個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. 添加return語句的根。

然而,它似乎不太可能存在某些庫來打印具有尖銳變量的對象,而不是直接打印代碼。

+0

不錯的問題。但爲什麼??我沒有看到任何需要尖銳的變量... –

+0

@Jonasw,因爲有些情況下,圓形對象是有用的。在我的情況下,它們也是恆定的,但計算起來相對昂貴。在最一般的情況下,可能需要一個(循環)圖來進行計算。 –

回答

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;})()"; 
} 

少數的輔助功能尤爲aweful但主要部分主要是OK,如果一個小的內存沉重大對象。也許使用conses來創建路徑的鏈接列表可能會減少內存使用量。

+0

請注意,雖然這在理論上有效,但它會先搜索深度優先(使用尖銳變量的漂亮打印機(我真的認爲這是一個lisp漂亮打印機)需要這樣做),這會導致一些非常深的嵌套對於非常自我指涉但不那麼深的對象(例如某些淺的非循環有向圖),因此需要大量代碼來引用那些嚴格的成員。廣度優先解決方案稍好一些,但對於我的使用情況,即使這樣也會導致產量過大。我認爲需要的是將啓發式對象與最後一個例子類似地輸出。 –

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 

我知道這是不是你真正想要的東西。可能有人會找到更好的解決方案...

+0

重點不在於實現銳利 - 它們僅僅是一個例子,而是爲了連續化圓形對象 –

+0

但是,如果需要創建它們(關於內存消耗等),不是更好嗎? –

+0

構建對象的源代碼僅使用比對象更多的內存。這將是一個常數,因此只需創建一次。 –