【问题标题】:Javascript object key value coding. Dynamically setting a nested valueJavascript 对象键值编码。动态设置嵌套值
【发布时间】:2010-01-14 00:14:36
【问题描述】:

我正在开发一个小库,让我可以对对象进行一些基本的键值编码。假设我有以下对象:

var data = { key1: "value1", key2: { nested1: 1, nested2: "wowza!" } };

我有以下 JavaScript 函数:

var setData = function(path, value) {
    eval("data." + path + "= value;");
};

并在使用中:

setData("key1", "updated value1"); // data.key1 == "updated value1"
setData("key2.nested1", 99); // data.key2.nested1 == 99

这可行,但是我想在不使用eval 的情况下完成上述操作。这可能吗,还是eval 是最好的选择?

编辑:

注意可以假设您设置的值存在,至少对于路径深度 - 1。我更关心设置现有对象的值。

【问题讨论】:

    标签: javascript object key-value-observing key-value-coding


    【解决方案1】:

    递归就是你所需要的:

    function setData(key,val,obj) {
      if (!obj) obj = data; //outside (non-recursive) call, use "data" as our base object
      var ka = key.split(/\./); //split the key by the dots
      if (ka.length < 2) { 
        obj[ka[0]] = val; //only one part (no dots) in key, just set value
      } else {
        if (!obj[ka[0]]) obj[ka[0]] = {}; //create our "new" base obj if it doesn't exist
        obj = obj[ka.shift()]; //remove the new "base" obj from string array, and hold actual object for recursive call
        setData(ka.join("."),val,obj); //join the remaining parts back up with dots, and recursively set data on our new "base" obj
      }    
    }
    
    setData("key1", "updated value1"); // data.key1 == "updated value1"
    setData("key2.nested1", 99); // data.key2.nested1 == 99
    

    【讨论】:

    • 我希望我能投票 100 次 - 这正是我想要的!谢谢!
    • 非常感谢!我无法告诉你这对我有多大帮助。
    • 这是一个很好的答案。我制作了一个没有obj 参数并使用ES6 语法的版本以提高可读性。 gist.github.com/sungwoncho/8caf7f3d9e56741c0631cda134e01832
    • 非常感谢。你拯救了我的一天!
    【解决方案2】:

    是的,这很容易。 obj.prop = valobj['prop'] = val 相同,因此您可以将 setData 重写为:

    var setData = function (path, value) {
        data[path] = value;
    };
    

    应该这样做。

    但是,我不确定您在第 3 级会取得多大的成功(如果这有意义的话。) obj.prop.prop 不能被 obj['prop.prop'] 引用,而是必须被 obj['prop']['prop'] 引用,因此必须重写 setData 以考虑到这一点。不过,我对此并不走运。

    编辑:我已经制作了一个(对我而言)将它设置为您想要的嵌套方式,但仅此而已。不幸的是,如果您的嵌套比您的示例更深,那么我认为没有真正的理由放弃eval。我没有做过任何基准测试,但在某些时候,计算的计算成本甚至比 eval 还要高(这很难完成。)

    这是我想出的,这将使它们至少设置 1 个“级别”深度;也就是说,它适用于key2.nested1,但不适用于key2.nested1.i_love_nesting,如果这有意义的话:

    var setData = function (path, value) {
        if (path.indexOf('.') != -1) {
            path = path.split('.');
            for (var i = 0, l = path.length; i < l; i++) {
                if (typeof(data[path[i]]) === 'object') {
                    continue;
                } else {
                    data[path[i - 1]][path[i]] = value;
                }
            }
        } else {
            data[path] = value;
        }
    };
    

    希望这会有所帮助。不过,我可能没有以最有效的方式写这个……

    【讨论】:

    • 不幸的是,这不适用于嵌套值。在您的示例中: setData("prop.nestedProp", "val");将导致以下数据json:{“prop.nestedProp”:“val”}我想要:{prop:{nestedProp:“val”}}
    • 是的,我在提交帖子后几秒钟就发现了这一点。我花了一点时间来获得一个不错的工作功能,但上面的那个应该可以。您的库的另一种选择可能是让他们直接访问“对象”——即让他们访问my_json_object.foo.bar
    • 如果值发生变化,完整的函数(为简单起见除外)将触发一个事件。 app.dataSet = function(path, value) { if (app.dataGet(path) == value) 返回值; eval("app._data." + path + "= value;"); app.trigger("数据。" + 路径);返回 app.dataGet(路径); }; 不幸的是,这需要在任意深度下工作。我希望这个问题可能有一个很好的解决方案,让 get 函数(必须测试它是否比使用 eval 更快)。感谢您的帮助。
    【解决方案3】:

    灵感来自@user1416920 我做了这个:

    function setData(key,val,obj)
    {
        keys = key.split(/\./);
        last = keys.pop();
    
        keys.forEach(function(key)
        {
          if(typeof obj[key] === "undefined")
            obj[key] = {}; 
          obj = obj[key]; 
        });
    
        obj[last] = val;
    }
    

    它的作用是不言自明的^^。

    【讨论】:

      【解决方案4】:
      function setData(key,val,obj){
          keys = key.split(/\./);
          to_set = keys.pop();
          keys.forEach(function(obj_name){ obj = obj[obj_name]; });
          obj[to_set] = val;
      }
      

      【讨论】:

        【解决方案5】:

        如果您可以灵活地指定路径,我认为这个问题会变得更容易、更自然地解决。如果您可以使用{key2:{nested1:{}}}(对象表示法)代替key2.nested1 之类的操作,则它变得相当简单:

        function setData(subtree, path){
            var nodeName = Object.keys(path);
        
            if(typeof path[nodeName] == 'object')
                setData(subtree[nodeName], path[nodeName]);
            else
                subtree[nodeName] = path[nodeName];
        }
        
        var data = { key1: "value1", key2: { nested1: 1, nested2: "wowza!" } };
        
        // For the initial call, 'subtree' is the full data tree
        setData(data, {key2:{nested1:99}});
        

        您只是递归到setData 调用的第二个参数,直到找到该值。每次递归时,都会在指定点下到data 树。

        这个函数也可以很容易地修改为接受您指定的点表示法,但对我来说这是一种更简洁、更自然的方法(另外,字符串操作很慢)。

        干杯

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-08-17
          • 2012-10-25
          • 2019-03-02
          • 2011-09-20
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多