【问题标题】:Access object child properties using a dot notation string [duplicate]使用点符号字符串访问对象子属性[重复]
【发布时间】:2011-12-24 12:24:40
【问题描述】:

我暂时陷入了看似非常简单的 JavaScript 问题,但也许我只是缺少正确的搜索关键字!

假设我们有一个对象

var r = { a:1, b: {b1:11, b2: 99}};

访问99有几种方式:

r.b.b2
r['b']['b2']

我想要的是能够定义一个字符串

var s = "b.b2";

然后使用

访问 99
r.s or r[s] //(which of course won't work)

一种方法是为其编写一个函数,将字符串拆分为点,并可能递归/迭代地获取属性。但是有没有更简单/更有效的方法?这里的任何 jQuery API 有什么用处吗?

【问题讨论】:

  • 您可以随时构建一个字符串并在需要时eval() 它,但我认为没有人会认为这是一个好主意。按照您描述的方式解析字符串更安全。
  • @jrummell 在 struts2 应用程序中,我使用了 jqgrid,它在列格式化程序中获取 rowObject。 rowObject 对象结构遵循列数据模型,其中包含一些我需要在循环内以通用方式访问的嵌套属性。

标签: javascript jquery


【解决方案1】:

扩展@JohnB 的答案,我还添加了一个setter 值。查看 plunkr 在

http://plnkr.co/edit/lo0thC?p=preview

function getSetDescendantProp(obj, desc, value) {
  var arr = desc ? desc.split(".") : [];

  while (arr.length && obj) {
    var comp = arr.shift();
    var match = new RegExp("(.+)\\[([0-9]*)\\]").exec(comp);

    // handle arrays
    if ((match !== null) && (match.length == 3)) {
      var arrayData = {
        arrName: match[1],
        arrIndex: match[2]
      };
      if (obj[arrayData.arrName] !== undefined) {
        if (typeof value !== 'undefined' && arr.length === 0) {
          obj[arrayData.arrName][arrayData.arrIndex] = value;
        }
        obj = obj[arrayData.arrName][arrayData.arrIndex];
      } else {
        obj = undefined;
      }

      continue;
    }

    // handle regular things
    if (typeof value !== 'undefined') {
      if (obj[comp] === undefined) {
        obj[comp] = {};
      }

      if (arr.length === 0) {
        obj[comp] = value;
      }
    }

    obj = obj[comp];
  }

  return obj;
}

【讨论】:

  • 我试图调整 Andy E 的解决方案来获得一套,然后我向下滚动并找到了这个。插入此功能并观察我所有的测试都变绿了。谢谢。
  • 感谢您添加设置功能。
【解决方案2】:

splitreduce 同时将对象作为 initalValue 传递

var r = { a:1, b: {b1:11, b2: 99}};
var s = "b.b2";

var value = s.split('.').reduce(function(a, b) {
  return a[b];
}, r);

console.log(value);

更新 (感谢 TeChn4K 发表的评论)

使用 ES6 语法,它甚至更短

var r = { a:1, b: {b1:11, b2: 99}};
var s = "b.b2";

var value = s.split('.').reduce((a, b) => a[b], r);

console.log(value);

【讨论】:

  • 使用 ES6 语法更短:let value = s.split('.').reduce((a, b) => a[b], r)
  • @TeChn4K,感谢您的提示!更新了我的答案:-)
  • 很好的答案,谢谢(在没有学到新东西的情况下永远不要睡觉)
  • 对于您可能尝试查找对象的不存在子值的情况的小改进,即:c.c1。要抓住这一点,只需像这样对返回值添加一个检查:return (a != undefined) ? a[b] : a ;
  • @AmmarCSE 如果嵌套属性之一是数组,这会起作用吗?例如var key = "b.b2[0].c" ?
【解决方案3】:

Andy E、Jason More 和我自己的解决方案的性能测试可在http://jsperf.com/propertyaccessor 获得。请随时使用您自己的浏览器运行测试以添加到收集的数据中。

预后很明显,Andy E的解决方案是迄今为止最快的!

对于任何感兴趣的人,这是我对原始问题的解决方案的代码。

function propertyAccessor(object, keys, array) {
    /*
    Retrieve an object property with a dot notation string.
    @param  {Object}  object   Object to access.
    @param  {String}  keys     Property to access using 0 or more dots for notation.
    @param  {Object}  [array]  Optional array of non-dot notation strings to use instead of keys.
    @return  {*}
    */
    array = array || keys.split('.')

    if (array.length > 1) {
        // recurse by calling self
        return propertyAccessor(object[array.shift()], null, array)
    } else {
        return object[array]
    }
}

【讨论】:

  • 好崩溃!
【解决方案4】:

这是我前段时间写的一个简单的函数,但它适用于基本的对象属性:

function getDescendantProp(obj, desc) {
    var arr = desc.split(".");
    while(arr.length && (obj = obj[arr.shift()]));
    return obj;
}

console.log(getDescendantProp(r, "b.b2"));
//-> 99

虽然有一些答案将其扩展到“允许”数组索引访问,但这并不是真正必要的,因为您可以使用这种方法使用点表示法指定数字索引:

getDescendantProp({ a: [ 1, 2, 3 ] }, 'a.2');
//-> 3

【讨论】:

  • jshint 告诉我重写:while(arr.length) { obj = obj[arr.shift()]; }
  • @user1912899:这不太一样,但可能更健壮一点,因为它会抛出错误(而我的只会返回undefined)。这取决于你的喜好,我想。 JSHint 只是抱怨循环条件中的赋值,可以使用 boss 选项禁用。
  • 你可以做while(arr.length && obj) { obj = obj[arr.shift()]; }
  • a[1] 不起作用 :(
  • @JuanDavid:不,这不像 eval 语句,它只是一个简单的拆分和循环。你可以使用a.1,除非你有一些带有'.'的属性。在名字里。如果是这种情况,您将需要更复杂的解决方案。
【解决方案5】:

您可以使用lodash get() and set() methods

获取

var object = { 'a': [{ 'b': { 'c': 3 } }] };

_.get(object, 'a[0].b.c');
// → 3

设置

var object = { 'a': [{ 'b': { 'c': 3 } }] };

_.set(object, 'a[0].b.c', 4);
console.log(object.a[0].b.c);
// → 4

【讨论】:

  • 不错,lodash 真的很简单
【解决方案6】:

这是 Andy E 代码的扩展,它递归到数组中并返回所有值:

function GetDescendantProps(target, pathString) {
    var arr = pathString.split(".");
    while(arr.length && (target = target[arr.shift()])){
        if (arr.length && target.length && target.forEach) { // handle arrays
            var remainder = arr.join('.');
            var results = [];
            for (var i = 0; i < target.length; i++){
                var x = this.GetDescendantProps(target[i], remainder);
                if (x) results = results.concat(x);
            }
            return results;
        }
    }
    return (target) ? [target] : undefined; //single result, wrap in array for consistency
}

所以给定这个target

var t = 
{a:
    {b: [
            {'c':'x'},
            {'not me':'y'},
            {'c':'z'}
        ]
    }
};

我们得到:

GetDescendantProps(t, "a.b.c") === ["x", "z"]; // true

【讨论】:

    【解决方案7】:

    这里有一个比@andy's 更好的方法,其中obj(上下文)是可选,如果没有提供,它会退回到window..

    function getDescendantProp(desc, obj) {
        obj = obj || window;
        var arr = desc.split(".");
        while (arr.length && (obj = obj[arr.shift()]));
        return obj;
    };
    

    【讨论】:

      【解决方案8】:

      我已经扩展了 Andy E 的答案,这样它也可以处理数组:

      function getDescendantProp(obj, desc) {
          var arr = desc.split(".");
      
          //while (arr.length && (obj = obj[arr.shift()]));
      
          while (arr.length && obj) {
              var comp = arr.shift();
              var match = new RegExp("(.+)\\[([0-9]*)\\]").exec(comp);
              if ((match !== null) && (match.length == 3)) {
                  var arrayData = { arrName: match[1], arrIndex: match[2] };
                  if (obj[arrayData.arrName] != undefined) {
                      obj = obj[arrayData.arrName][arrayData.arrIndex];
                  } else {
                      obj = undefined;
                  }
              } else {
                  obj = obj[comp]
              }
          }
      
          return obj;
      }
      

      可能有更有效的方法来执行正则表达式,但它很紧凑。

      您现在可以执行以下操作:

      var model = {
          "m1": {
              "Id": "22345",
              "People": [
                  { "Name": "John", "Numbers": ["07263", "17236", "1223"] },
                  { "Name": "Jenny", "Numbers": ["2", "3", "6"] },
                  { "Name": "Bob", "Numbers": ["12", "3333", "4444"] }
               ]
          }
      }
      
      // Should give you "6"
      var x = getDescendantProp(model, "m1.People[1].Numbers[2]");
      

      【讨论】:

      • 您可以将正则表达式传递给split。只需使用desc..split(/[\.\[\]]+/); 并向下循环属性。
      【解决方案9】:

      这是我能做的最简单的:

      var accessProperties = function(object, string){
         var explodedString = string.split('.');
         for (i = 0, l = explodedString.length; i<l; i++){
            object = object[explodedString[i]];
         }
         return object;
      }
      var r = { a:1, b: {b1:11, b2: 99}};
      
      var s = "b.b2";
      var o = accessProperties(r, s);
      alert(o);//99
      

      【讨论】:

      • +1,这与我的解决方案非常相似,但有一个显着差异。如果其中一个属性(最后一个除外)不存在,您的将引发错误。我的将返回undefined。两种解决方案都适用于不同的场景。
      【解决方案10】:

      简短回答:不,没有您想要的原生 .access 函数。正如您正确提到的,您必须定义自己的函数来拆分字符串并对其部分进行循环/检查。

      当然,你总是可以做的(即使它被认为是不好的做法)是使用eval()

      喜欢

      var s = 'b.b2';
      
      eval('r.' + s); // 99
      

      【讨论】:

        【解决方案11】:

        你也可以

        var s = "['b'].b2";
        var num = eval('r'+s);
        

        【讨论】:

          【解决方案12】:

          我不知道支持的 jQuery API 函数,但我有这个函数:

              var ret = data; // Your object
              var childexpr = "b.b2"; // Your expression
          
              if (childexpr != '') {
                  var childs = childexpr.split('.');
                  var i;
                  for (i = 0; i < childs.length && ret != undefined; i++) {
                      ret = ret[childs[i]];
                  }
              }
          
              return ret;
          

          【讨论】:

            【解决方案13】:

            如果在您的场景中可以将您所关注的整个数组变量放入一个字符串中,您可以使用eval() 函数。

            var r = { a:1, b: {b1:11, b2: 99}};
            var s = "r.b.b2";
            alert(eval(s)); // 99
            

            我能感觉到人们在恐惧中挣扎

            【讨论】:

            • +1 期待我的晕车。
            • reel 问题,不幸的是使用eval 会阻止编译器进行某些词法优化。这意味着,不仅eval 本身很慢,而且还会减慢它周围的代码。哦……双关语。
            • 哦,我知道eval() 的陷阱。事实上,我要去洗个钢丝绒淋浴,因为即使推荐它,我也觉得很脏。安迪,我已经为你的答案 +1 了,因为它很容易成为这里最优雅的答案。
            • 感谢这非常简洁 - 但鉴于 Andy 的 cmets,我无法承受性能下降,因为脚本做了很多事情。
            • var getObjectValue = function getter(object, key) { var value; if (typeof object === 'object' && typeof key === 'string') { value = eval('object' + '.' + key); } 返回值; }
            猜你喜欢
            • 1970-01-01
            • 2013-01-23
            • 1970-01-01
            • 1970-01-01
            • 2016-01-09
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多