【问题标题】:default values for nested javascript hashes嵌套 javascript 哈希的默认值
【发布时间】:2024-01-20 19:40:01
【问题描述】:

在 JavaScript 中,我想执行以下操作:

var pi = {}; pi[0]['*']['*'] = 1;

当然,这会引发“无法读取未定义的属性 '*'”错误。显然我可以定义 p[0] = {},但这有点痛苦,因为我将在不同的属性中粘贴许多不同的值,例如

pi[2]['O']['I-GENE'] = 1;

等等。散列的第一个键只是一个整数,所以我想我可以在顶层使用一个数组而不是散列,然后像这篇文章一样默认初始化:

default array values

但这并不能满足我对其他哈希默认初始化的需求。

看起来我正在尝试做的是违反 ECMAScript 规范,该规范表明对象的未定义属性(它是 JavaScript 中的哈希)应该返回未定义,如此处所述:

Set default value of javascript object attributes

有趣的是,其中包含一项危险的工作。

在我尝试使用这样的嵌套哈希的其他地方,我发现自己正在编写长长的丑陋代码,如下所示:

function incrementThreeGramCount(three_grams,category_minus_two,category_minus_one,category){
    if(three_grams[category_minus_two] === undefined){
      three_grams[category_minus_two] = {};
    }
    if(three_grams[category_minus_two][category_minus_one] === undefined){
      three_grams[category_minus_two][category_minus_one] = {};
    }
    if(three_grams[category_minus_two][category_minus_one][category] === undefined){
      three_grams[category_minus_two][category_minus_one][category] = 0;
    }
    three_grams[category_minus_two][category_minus_one][category]++;
}

我真的很想避免在这里,或者至少找到一些通过原型方法添加到哈希功能的好方法。然而似乎因为 JavaScript 中的 Hash 和 Object 是一回事,我们不能在不影响很多其他东西的情况下真正使用 JavaScript 中的默认哈希行为......

也许我应该编写自己的 Hash 类...或使用原型:

http://prototypejs.org/doc/latest/language/Hash/

或其他人:

http://www.daveperrett.com/articles/2007/07/25/javascript-hash-class/

或mootools:

http://mootools.net/docs/more/Types/Hash

啊,这么多选择 - 希望我知道这里的最佳做法......

【问题讨论】:

标签: javascript jquery hash prototypejs mootools


【解决方案1】:

这可以使用ES6 proxies 来完成。您将使用get 处理程序在对象上定义代理。当对具有undefined 值或对象没有自己属性的键执行get 时,您将其设置为使用相同get 处理程序的新代理并返回该新代理。

另外,这不需要括号语法:

var obj = ...;
obj.a.b.c = 3;

不幸的是,作为 ES6 功能,它们的支持仅限于 Firefox,并且可以在带有实验标志的 Chrome 中启用。

【讨论】:

  • 不幸的是,代理对象目前仅受 firefox 支持。也许你应该把它写成一个笔记
  • 它们在 Chrome stable 中受到支持,并启用了 Enable Experimental JavaScript 标志,因为它们在 V8 中已经存在很长时间了。我确信当规范最终确定时,它们将在下一次稳定推送中启用。
  • 很好,谢谢,我不知道 chrome 支持代理作为实验性 +1
【解决方案2】:

我会这样做:

Object.prototype.get = function(v){ 
    if(!this[v]){
        this[v] = {} 
    } 

    return this[v];  
}

然后,改为 object["x"]["y"].z = 666 使用 object.get("x").get("y").z = 666

【讨论】:

  • 这看起来很酷,但它对可能依赖返回“未定义”的默认获取行为的其他库没有危险的副作用吗?我喜欢这个 mod,但仅限于本地代码 - 因此 Hash 类似乎更安全,不是吗?
  • 好吧,有可能一些 lib 也定义了 get 方法,所以请尝试使用更独特的名称,如 get_or_initialize... 或考虑不那么冗长的名称,如 _ :)
  • 啊,我明白了 - 抱歉,我错过了你建议将语法更改为 .get("x") - 是的,希望我能坚持使用 ['x'] 不那么冗长 - 我猜想 ._('x') 也不错。而且我制作的任何自定义哈希类也必须更改语法 - 我想我不能轻易覆盖 [] 运算符,如 Object.prototype.[] = function(v){ ...
【解决方案3】:

您可以编写一个简单的助手来执行此操作。 例如

function setWDef() {
    var args = [].slice.call(arguments);
    var obj = args.shift();
    var _obj = obj;
    var val = args.pop();
    if (typeof obj !== "object") throw new TypeError("Expected first argument to be of type object");
    for (var i = 0, j = args.length - 1; i < j; i++) {
        var curr = args[i];
        if (!_obj[curr]) _obj[curr] = {};
        _obj = _obj[curr];
    }
    _obj[args.pop()] = val;
    return obj;
}   

现在只需使用函数设置值

var pi ={}
console.log (setWDef(pi,"0","*","a*",1)) //{"0": {"*": {"a*": 1}}}

这是JSBin上的演示

【讨论】:

    【解决方案4】:

    使用drafted logical OR assignment operator ||= 您可以执行以下操作:

     const pi = [];
     ((((pi[0]||={})['*'])||={})['*'] = 1);
     console.log(JSON.stringify({pi}));

    输出:

    {"pi":[{"*":{"*":1}}]}
    

    您还可以将某些级别初始化为数组或定义默认属性值:

    const pi = [];
    ((((((pi[0]||={})['*'])||=[])[3])||={'#':2})['*'] = 1);
    console.log(JSON.stringify({pi}));

    输出:

    {"pi":[{"*":[null,null,null,{"#":2,"*":1}]}]}
    

    【讨论】: