JavaScript嵌套哈希的默认值

3
在JavaScript中,我想要做以下操作:
``` var pi = {}; pi[0]['*']['*'] = 1; ```
当然,这会抛出“Cannot read property '*' of undefined”错误。显然,我可以定义`p[0] = {}`,但这有点麻烦,因为我将在不同的属性中插入许多不同的值,例如:
``` pi[2]['O']['I-GENE'] = 1; ```
等等。哈希表中的第一个键只是一个整数,所以我想我可以在顶层使用数组,然后像这篇文章中那样进行默认初始化: default array values 但这不能处理我需要默认初始化其他哈希表的需求。
似乎我尝试做的事情正在遭遇ECMAScript规范,该规范指示对象(在JavaScript中是哈希表)的未定义属性应返回undefined,如此处所述: 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中的哈希和对象实际上是相同的东西,所以我们不能真正地在不影响其他很多东西的情况下玩弄默认哈希行为......

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

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

啊,太多选择了 - 希望我知道最佳实践...


嘿 - 这让我想起了去年关于mootools的一个有争议的PR:https://github.com/mootools/mootools-core/pull/2191 - Object.get/set方法(不是.prototype)可以自动完成。但是没有被采纳,Daniel也因此停止了贡献 :D - 他的实现相当合理 - https://github.com/csuwldcat/mootools-core/blob/master/Source/Types/Object.js#L23-L43 - 可以轻松地在没有mootools的情况下工作。 - Dimitar Christoff
4个回答

2
这可以使用ES6代理来完成。您可以在对象上定义一个带有get处理程序的代理。当对具有undefined值或对象没有自己的属性的键执行get操作时,您将其设置为使用相同get处理程序的新代理,并返回该新代理。
此外,这也可以不需要使用方括号语法:
var obj = ...;
obj.a.b.c = 3;

不幸的是,由于它们是ES6功能,它们的支持仅限于Firefox,并且可以在Chrome中通过实验性标志启用。

1
不幸的是,代理对象目前只有 Firefox 支持。也许你可以将此作为提示添加进去。 - Moritz Roessler
当启用了“启用实验性JavaScript”标志时,它们在Chrome稳定版中得到支持,因为它们已经在V8中存在很长时间了。我相信当规范最终确定后,它们将在下一个稳定版本中得到启用。 - Justin Summerlin
不错,谢谢。我不知道Chrome支持代理作为实验性功能。+1 - Moritz Roessler

1
我会这样做:

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

    return this[v];  
}

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


这看起来很酷,但它对可能依赖默认的返回“undefined”的get行为的其他库是否具有危险副作用呢?我喜欢的是这种修改,但限于本地代码似乎更安全,因此一个哈希类会更好,不是吗? - Sam Joseph
好的,有可能某些库也定义了 get 方法,所以尝试使用更独特的名称,比如 get_or_initialize ... 或者考虑使用不那么冗长的名称,比如 _ :) - IProblemFactory
啊,我明白了 - 很抱歉我没有注意到你建议将语法改成.get("x") - 是的,希望我可以坚持使用['x'],这样会更简洁 - 我想._('x')也不错。如果我创建任何自定义哈希类,语法也必须更改 - 我猜我不能像Object.prototype那样轻松地重写[]运算符,例如:Object.prototype.[] = function(v){ ...} - Sam Joseph

0

使用drafted 逻辑或赋值运算符 ||=,您可以执行以下操作:

 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}]}]}

0
你可以编写一个简单的辅助函数来实现这个功能。 例如:
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的演示。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接