Доступ к дочерним свойствам объекта с помощью строки точечной нотации [дубликат]


этот вопрос уже есть ответ здесь:

  • Доступ к вложенным объектам JavaScript с помощью строкового ключа 28 ответов

Я временно застрял с тем, что кажется очень простой проблемой 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)

один из способов-написать для него функцию, которая разбивает строку на точку и, возможно, рекурсивно/итеративно получает свойство. Но есть ли более простой / эффективный способ? Что-нибудь полезное в любом из API jQuery здесь?

13 94

13 ответов:

вот наивная функция, которую я написал некоторое время назад, но она работает для основных свойств объекта:

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

сплит и уменьшить при передаче объекта как 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);

можно использовать lodash get() и set () методы.

начало

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

Если в вашем сценарии возможно, что вы можете поместить всю переменную массива, которую вы ищете, в строку, вы можете использовать

расширяя ответ @JohnB, я также добавил значение сеттера. Проверьте plunkr на

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

enter image description here

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

Это самое простое, что я мог сделать:

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

вы также можете сделать

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

Я не знаю поддерживаемой функции API jQuery, но у меня есть эта функция:

    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;

я расширил ответ Энди Э, так что он также может обрабатывать массивы:

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]");

здесь является расширением кода Энди Е, что повторяется в массивы и возвращает все значения:

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

тесты производительности для Энди Э, Джейсона Мора и моего собственного решения доступны по адресу http://jsperf.com/propertyaccessor. пожалуйста, не стесняйтесь запускать тесты с помощью собственного браузера, чтобы добавить к собранным данным.

прогноз ясен, решение Энди Э является самым быстрым на сегодняшний день!

для всех, кто заинтересован, вот код для моего решения исходного вопроса.

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

короткий ответ: Нет, уроженки .access функции, как вы хотите. Как вы правильно упомянули, вам нужно будет определить свою собственную функцию, которая разбивает строку и циклы/проверки по ее частям.

конечно, то, что вы всегда можете сделать (даже если это считается плохой практикой), это использовать eval().

как

var s = 'b.b2';

eval('r.' + s); // 99

вот немного лучший способ, чем @Энди!--5--> ответ, где obj (контекст) составляет дополнительно, он возвращается к window если не предусмотрено..

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