Проверка существования вложенного ключа объекта JavaScript - PullRequest
553 голосов
/ 13 апреля 2010

Если у меня есть ссылка на объект:

var test = {};

, который потенциально (но не сразу) будет иметь вложенные объекты, что-то вроде:

{level1: {level2: {level3: "level3"}}};

Как лучше всегопроверить наличие свойства в глубоко вложенных объектах?

alert(test.level1); дает undefined, но alert(test.level1.level2.level3); не удается.

В настоящее время я делаю что-то вроде этого:

if(test.level1 && test.level1.level2 && test.level1.level2.level3) {
    alert(test.level1.level2.level3);
}

но мне было интересно, есть ли лучший способ.

Ответы [ 53 ]

343 голосов
/ 13 апреля 2010

Вы должны делать это шаг за шагом, если вы не хотите TypeError, потому что, если один из членов - null или undefined, и вы пытаетесь получить доступ к члену, будет выдано исключение.

Вы можете просто catch сделать исключение или создать функцию для проверки существования нескольких уровней, что-то вроде этого:

function checkNested(obj /*, level1, level2, ... levelN*/) {
  var args = Array.prototype.slice.call(arguments, 1);

  for (var i = 0; i < args.length; i++) {
    if (!obj || !obj.hasOwnProperty(args[i])) {
      return false;
    }
    obj = obj[args[i]];
  }
  return true;
}

var test = {level1:{level2:{level3:'level3'}} };

checkNested(test, 'level1', 'level2', 'level3'); // true
checkNested(test, 'level1', 'level2', 'foo'); // false

ОБНОВЛЕНИЕ 2019-05-16:

Вот более короткая версия, использующая функции ES6 и рекурсию (она также находится в правильном хвостовом вызове форма):

function checkNested(obj, level,  ...rest) {
  if (obj === undefined) return false
  if (rest.length == 0 && obj.hasOwnProperty(level)) return true
  return checkNested(obj[level], ...rest)
}
333 голосов
/ 27 октября 2010

Вот образец, который я взял у Оливера Стила :

var level3 = (((test || {}).level1 || {}).level2 || {}).level3;
alert( level3 );

На самом деле вся эта статья - обсуждение того, как вы можете сделать это в javascript. Он останавливается на использовании приведенного выше синтаксиса (который не так уж сложно прочитать, как только вы к нему привыкнете) в качестве идиомы.

236 голосов
/ 04 июня 2014

Обновление

Похоже, что lodash добавил _.get для всех ваших потребностей получения вложенного свойства.

_.get(countries, 'greece.sparta.playwright')

https://lodash.com/docs#get


Предыдущий ответ

lodash пользователи могут пользоваться lodash.contrib , в котором есть пара методов, которые решают эту проблему .

GetPath

Подпись: _.getPath(obj:Object, ks:String|Array)

Получает значение на любой глубине вложенного объекта на основе пути, описанного ключи даны. Ключи могут быть заданы в виде массива или в виде строки, разделенной точками. Возвращает undefined, если путь не может быть достигнут.

var countries = {
        greece: {
            athens: {
                playwright:  "Sophocles"
            }
        }
    }
};

_.getPath(countries, "greece.athens.playwright");
// => "Sophocles"

_.getPath(countries, "greece.sparta.playwright");
// => undefined

_.getPath(countries, ["greece", "athens", "playwright"]);
// => "Sophocles"

_.getPath(countries, ["greece", "sparta", "playwright"]);
// => undefined
175 голосов
/ 08 января 2017

Я провел тесты производительности (спасибо cdMinix за добавление lodash) для некоторых предложений, предложенных по этому вопросу, с результатами, перечисленными ниже.

Отказ от ответственности # 1 Превращение строк в ссылки - это ненужное метапрограммирование и, вероятно, его лучше избегать. Не забывайте свои ссылки для начала. Узнайте больше из этого ответа на похожий вопрос .

Отказ от ответственности # 2 Мы говорим о миллионах операций в миллисекунду здесь. Маловероятно, что какой-либо из них будет иметь большое значение в большинстве случаев использования. Выберите тот, который имеет больше смысла, зная ограничения каждого из них. Для меня я бы пошел с чем-то вроде reduce из-за удобства.

Обтекание объекта (Оливер Стил) - 34% - самый быстрый

var r1 = (((test || {}).level1 || {}).level2 || {}).level3;
var r2 = (((test || {}).level1 || {}).level2 || {}).foo;

Исходное решение (предлагается в вопросе) - 45%

var r1 = test.level1 && test.level1.level2 && test.level1.level2.level3;
var r2 = test.level1 && test.level1.level2 && test.level1.level2.foo;

checkNested - 50%

function checkNested(obj) {
  for (var i = 1; i < arguments.length; i++) {
    if (!obj.hasOwnProperty(arguments[i])) {
      return false;
    }
    obj = obj[arguments[i]];
  }
  return true;
}

get_if_exist - 52%

function get_if_exist(str) {
    try { return eval(str) }
    catch(e) { return undefined }
}

validChain - 54%

function validChain( object, ...keys ) {
    return keys.reduce( ( a, b ) => ( a || { } )[ b ], object ) !== undefined;
}

objHasKeys - 63%

function objHasKeys(obj, keys) {
  var next = keys.shift();
  return obj[next] && (! keys.length || objHasKeys(obj[next], keys));
}

nestedPropertyExists - 69%

function nestedPropertyExists(obj, props) {
    var prop = props.shift();
    return prop === undefined ? true : obj.hasOwnProperty(prop) ? nestedPropertyExists(obj[prop], props) : false;
}

_. Get - 72%

самый глубокий - 86%

function deeptest(target, s){
    s= s.split('.')
    var obj= target[s.shift()];
    while(obj && s.length) obj= obj[s.shift()];
    return obj;
}

грустные клоуны - 100% - самые медленные

var o = function(obj) { return obj || {} };

var r1 = o(o(o(o(test).level1).level2).level3);
var r2 = o(o(o(o(test).level1).level2).foo);
43 голосов
/ 13 апреля 2010

Вы можете прочитать свойство объекта на любой глубине, если вы обрабатываете имя как строку: 't.level1.level2.level3'.

window.t={level1:{level2:{level3: 'level3'}}};

function deeptest(s){
    s= s.split('.')
    var obj= window[s.shift()];
    while(obj && s.length) obj= obj[s.shift()];
    return obj;
}

alert(deeptest('t.level1.level2.level3') || 'Undefined');

Возвращает undefined, если любой из сегментов равен undefined.

26 голосов
/ 18 ноября 2014
var a;

a = {
    b: {
        c: 'd'
    }
};

function isset (fn) {
    var value;
    try {
        value = fn();
    } catch (e) {
        value = undefined;
    } finally {
        return value !== undefined;
    }
};

// ES5
console.log(
    isset(function () { return a.b.c; }),
    isset(function () { return a.b.c.d.e.f; })
);

Если вы кодируете в среде ES6 (или используете 6to5 ), вы можете воспользоваться синтаксисом функции стрелки :

// ES6 using the arrow function
console.log(
    isset(() => a.b.c),
    isset(() => a.b.c.d.e.f)
);

Что касается производительности, штраф за производительность при использовании блока try..catch не устанавливается, если установлено свойство. Если свойство не установлено, это влияет на производительность.

Рассмотрим просто использование _.has:

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

_.has(object, 'a');
// → true

_.has(object, 'a.b.c');
// → true

_.has(object, ['a', 'b', 'c']);
// → true
19 голосов
/ 13 апреля 2010

как насчет

try {
   alert(test.level1.level2.level3)
} catch(e) {
 ...whatever

}
16 голосов
/ 31 мая 2018

ES6 ответ, тщательно протестирован :)

const propExists = (obj, path) => {
    return !!path.split('.').reduce((obj, prop) => {
        return obj && obj[prop] ? obj[prop] : undefined;
    }, obj)
}

→ см. Codepen с полным тестовым покрытием

12 голосов
/ 14 июня 2018

Вы также можете использовать дополнительное предложение о связывании по tc39 вместе с babel 7 - tc39-предложение-добавление по цепочке

Код будет выглядеть так:

  const test = test?.level1?.level2?.level3;
  if (test) alert(test);
9 голосов
/ 08 ноября 2013

Я попробовал рекурсивный подход:

function objHasKeys(obj, keys) {
  var next = keys.shift();
  return obj[next] && (! keys.length || objHasKeys(obj[next], keys));
}

! keys.length || выбрасывает рекурсию, поэтому она не запускает функцию без клавиш для тестирования. Тесты:

obj = {
  path: {
    to: {
      the: {
        goodKey: "hello"
      }
    }
  }
}

console.log(objHasKeys(obj, ['path', 'to', 'the', 'goodKey'])); // true
console.log(objHasKeys(obj, ['path', 'to', 'the', 'badKey']));  // undefined

Я использую его для печати дружественного HTML-представления группы объектов с неизвестным ключом / значениями, например ::10000

var biosName = objHasKeys(myObj, 'MachineInfo:BiosInfo:Name'.split(':'))
             ? myObj.MachineInfo.BiosInfo.Name
             : 'unknown';
...