测试是否存在嵌套的JavaScript对象键

如果我有一个对象的引用:

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

但我想知道是否有更好的方法。

374037 次浏览

怎么样

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

如果您不想要TypeError,则必须逐步执行,因为如果其中一个成员是nullundefined,并且您尝试访问一个成员,则会引发异常。

您可以简单地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'); // truecheckNested(test, 'level1', 'level2', 'foo'); // false

ES6更新:

这是原始函数的较短版本,使用ES6特性和递归(它也是正确的尾部呼叫形式):

function checkNested(obj, level,  ...rest) {if (obj === undefined) return falseif (rest.length == 0 && obj.hasOwnProperty(level)) return truereturn checkNested(obj[level], ...rest)}

但是,如果你想获取嵌套属性的值,而不仅仅是检查它的存在,这里有一个简单的单行函数:

function getNested(obj, ...args) {return args.reduce((obj, level) => obj && obj[level], obj)}
const test = { level1:{ level2:{ level3:'level3'} } };console.log(getNested(test, 'level1', 'level2', 'level3')); // 'level3'console.log(getNested(test, 'level1', 'level2', 'level3', 'length')); // 6console.log(getNested(test, 'level1', 'level2', 'foo')); // undefinedconsole.log(getNested(test, 'a', 'b')); // undefined

上面的函数允许您获取嵌套属性的值,否则将返回undefined

更新2019-10-17:

可选链接提案ECMAScript委员会进程上达到了阶段3,这将允许您通过使用令牌?.,新的可选链接运算符安全地访问深度嵌套的属性:

const value = obj?.level1?.level2?.level3

如果访问的任何级别是nullundefined,则表达式将自行解析为undefined

该提案还允许您安全地处理方法调用:

obj?.level1?.method();

如果objobj.level1obj.level1.methodnullundefined,上面的表达式将产生undefined,否则它将调用该函数。

您可以使用可选链接插件开始使用Babel的此功能

巴别塔开始,默认支持ES2020

检查Babel REPL上的这个例子

🎉🎉更新:2019年12月🎉🎉

可选的链接提案最终在2019年12月的TC39委员会会议上获得第0号。这意味着此功能将成为ECMAScript 2020标准的一部分。

如果您像字符串一样处理名称,您可以在任何深度读取对象属性:'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

我把过程自动化了

if(isset(object,["prop1","prop2"])){// YES!
}
function isset(object, props){var dump;try {for(var x in props){if(x == 0) {dump = object[props[x]];return;}dump = dump[props[x]];}} catch(e) {return false;}
return true;}

这是一个模式I从Oliver Steele那拿到的

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

事实上,整篇文章都是关于如何在javascript中做到这一点的讨论。他决定使用上述语法(一旦你习惯了就不难阅读)作为习语。

一个简单的方法是这样的:

try {alert(test.level1.level2.level3);} catch(e) {alert("undefined");    // this is optional to put any output here}

try/catch捕获未定义任何更高级别对象(如test、test.level1、test.level1.level2)的情况。

一个更短的ES5版本的@CMS的优秀答案:

// Check the obj has the keys in the order mentioned. Used for checking JSON results.var checkObjHasKeys = function(obj, keys) {var success = true;keys.forEach( function(key) {if ( ! obj.hasOwnProperty(key)) {success = false;}obj = obj[key];})return success;}

类似的测试:

var test = { level1:{level2:{level3:'result'}}};utils.checkObjHasKeys(test, ['level1', 'level2', 'level3']); // trueutils.checkObjHasKeys(test, ['level1', 'level2', 'foo']); // false

ES5解决方案:

function hasProperties(object, properties) {return !properties.some(function(property){if (!object.hasOwnProperty(property)) {return true;}object = object[property];return false;});}

今天刚刚写了这个函数,它对嵌套对象中的属性进行深度搜索,如果找到,则返回属性处的值。

/*** Performs a deep search looking for the existence of a property in a* nested object. Supports namespaced search: Passing a string with* a parent sub-object where the property key may exist speeds up* search, for instance: Say you have a nested object and you know for* certain the property/literal you're looking for is within a certain* sub-object, you can speed the search up by passing "level2Obj.targetProp"* @param {object} obj Object to search* @param {object} key Key to search for* @return {*} Returns the value (if any) located at the key*/var getPropByKey = function( obj, key ) {var ret = false, ns = key.split("."),args = arguments,alen = args.length;
// Search starting with provided namespaceif ( ns.length > 1 ) {obj = (libName).getPropByKey( obj, ns[0] );key = ns[1];}
// Look for a property in the objectif ( key in obj ) {return obj[key];} else {for ( var o in obj ) {if ( (libName).isPlainObject( obj[o] ) ) {ret = (libName).getPropByKey( obj[o], key );if ( ret === 0 || ret === undefined || ret ) {return ret;}}}}
return false;}

我的解决方案,我用了很长时间(使用字符串不幸,找不到更好的)

function get_if_exist(str){try{return eval(str)}catch(e){return undefined}}
// way to useif(get_if_exist('test.level1.level2.level3')) {alert(test.level1.level2.level3);}
// or simplyalert(get_if_exist('test.level1.level2.level3'));

编辑:仅当对象“test”具有全局范围/范围时才起作用。你可以这样做:

// i think it's the most beautiful code I have ever write :pfunction get_if_exist(obj){return arguments.length==1 || (obj[arguments[1]] && get_if_exist.apply(this,[obj[arguments[1]]].concat([].slice.call(arguments,2))));}
alert(get_if_exist(test,'level1','level2','level3'));

编辑最终版本以允许2种调用方法:

function get_if_exist(obj){var a=arguments, b=a.callee; // replace a.callee by the function name you choose because callee is depreceate, in this case : get_if_exist// version 1 calling the version 2if(a[1] && ~a[1].indexOf('.'))return b.apply(this,[obj].concat(a[1].split('.')));// version 2return a.length==1 ? a[0] : (obj[a[1]] && b.apply(this,[obj[a[1]]].concat([].slice.call(a,2))));}
// method 1get_if_exist(test,'level1.level2.level3');

// method 2get_if_exist(test,'level1','level2','level3');

有一个函数在这里的ecodeabode(安全阅读)将以安全的方式执行此操作……即

safeRead(test, 'level1', 'level2', 'level3');

如果任何属性为空或未定义,则返回一个空字符串

CMS给出的答案可以正常工作,对空检查进行以下修改

function checkNested(obj /*, level1, level2, ... levelN*/){var args = Array.prototype.slice.call(arguments),obj = args.shift();
for (var i = 0; i < args.length; i++){if (obj == null || !obj.hasOwnProperty(args[i]) ){return false;}obj = obj[args[i]];}return true;}

基于先前的评论,这是另一个无法定义主对象的版本:

// Supposing that our property is at first.second.third.property:var property = (((typeof first !== 'undefined' ? first : {}).second || {}).third || {}).property;

另一个选项(接近这个答案):

function resolve(root, path){try {return (new Function('root', 'return root.' + path + ';'))(root);} catch (e) {}}
var tree = { level1: [{ key: 'value' }] };resolve(tree, 'level1[0].key'); // "value"resolve(tree, 'level1[1].key'); // undefined

更多关于这个:https://stackoverflow.com/a/18381564/1636522

这个答案开始阐述了以下选项。两者的树相同:

var o = { a: { b: { c: 1 } } };

未定义时停止搜索

var u = undefined;o.a ? o.a.b ? o.a.b.c : u : u // 1o.x ? o.x.y ? o.x.y.z : u : u // undefined(o = o.a) ? (o = o.b) ? o.c : u : u // 1

确保每个级别一个接一个

var $ = function (empty) {return function (node) {return node || empty;};}({});
$($(o.a).b).c // 1$($(o.x).y).z // undefined

我尝试了一种递归方法:

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'])); // trueconsole.log(objHasKeys(obj, ['path', 'to', 'the', 'badKey']));  // undefined

我正在使用它来打印一堆具有未知键/值的对象的友好html视图,例如:

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

更新

看起来像洛达什已经增加_.get为您所有的嵌套财产得到需要。

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

https://lodash.com/docs#get


以前的答复

豆沙用户可以享受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

另一个版本:

function nestedPropertyExists(obj, props) {var prop = props.shift();return prop === undefined? true: obj.hasOwnProperty(prop) ? nestedPropertyExists(obj[prop], props) : false;}
nestedPropertyExists({a:{b:{c:1}}}, ['a','b','c']); // returns truenestedPropertyExists({a:{b:{c:1}}}, ['a','b','c','d']); // returns false
var a;
a = {b: {c: 'd'}};
function isset (fn) {var value;try {value = fn();} catch (e) {value = undefined;} finally {return value !== undefined;}};
// ES5console.log(isset(function () { return a.b.c; }),isset(function () { return a.b.c.d.e.f; }));

如果您在ES6环境中编码(或使用6to5),那么您可以利用箭头函数语法:

// ES6 using the arrow functionconsole.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

我写了一个名为l33teral的库来帮助测试嵌套属性。你可以这样使用它:

var myObj = {/*...*/};var hasNestedProperties = leet(myObj).probe('prop1.prop2.prop3');

我也喜欢这里的ES5/6解决方案。

function isIn(string, object){var arr = string.split(".");var notFound = true;var length = arr.length;for (var i = 0; i < length; i++){var key = arr[i];if (!object.hasOwnProperty(key)){notFound = false;break;}if ((i + length) <= length){object = object[key];}}return notFound;}var musicCollection = {hasslehoff: {greatestHits : true}};console.log(isIn("hasslehoff.greatestHits", musicCollection));console.log(isIn("hasslehoff.worseHits", musicCollection));

这是我的基于字符串的分隔符版本。

我编写了自己的函数,它采用所需的路径,并具有好的和坏的回调函数。

function checkForPathInObject(object, path, callbackGood, callbackBad){var pathParts = path.split(".");var currentObjectPath = object;
// Test every step to see if it exists in objectfor(var i=0; i<(pathParts.length); i++){var currentPathPart = pathParts[i];if(!currentObjectPath.hasOwnProperty(pathParts[i])){if(callbackBad){callbackBad();}return false;} else {currentObjectPath = currentObjectPath[pathParts[i]];}}
// call full path in callbackcallbackGood();}

用法:

var testObject = {level1:{level2:{level3:{}}}};

checkForPathInObject(testObject, "level1.level2.level3", function(){alert("good!")}, function(){alert("bad!")}); // good
checkForPathInObject(testObject, "level1.level2.level3.levelNotThere", function(){alert("good!")}, function(){alert("bad!")}); //bad

我知道这个问题很老,但我想通过将其添加到所有对象来提供扩展。我知道人们倾向于不赞成使用Object原型来扩展对象功能,但我发现没有比这样做更容易的了。另外,现在允许使用Object.define房地产方法。

Object.defineProperty( Object.prototype, "has", { value: function( needle ) {var obj = this;var needles = needle.split( "." );for( var i = 0; i<needles.length; i++ ) {if( !obj.hasOwnProperty(needles[i])) {return false;}obj = obj[needles[i]];}return true;}});

现在,为了测试任何对象中的任何属性,您可以简单地执行以下操作:

if( obj.has("some.deep.nested.object.somewhere") )

这是一个jsfiddle来测试它,特别是它包含一些jQuery,如果您直接修改Object.prototype,因为属性变得可枚举。这应该适用于第三方库。

//Just in case is not supported or not included by your framework//***************************************************Array.prototype.some = function(fn, thisObj) {var scope = thisObj || window;for ( var i=0, j=this.length; i < j; ++i ) {if ( fn.call(scope, this[i], i, this) ) {return true;}}return false;};//****************************************************
function isSet (object, string) {if (!object) return false;var childs = string.split('.');if (childs.length > 0 ) {return !childs.some(function (item) {if (item in object) {object = object[item];return false;} else return true;});} else if (string in object) {return true;} else return false;}
var object = {data: {item: {sub_item: {bla: {here : {iam: true}}}}}};
console.log(isSet(object,'data.item')); // trueconsole.log(isSet(object,'x')); // falseconsole.log(isSet(object,'data.sub_item')); // falseconsole.log(isSet(object,'data.item')); // trueconsole.log(isSet(object,'data.item.sub_item.bla.here.iam')); // true

我认为这是一个轻微的改进(成为一个1-line):

   alert( test.level1 && test.level1.level2 && test.level1.level2.level3 )

这是有效的,因为&&运算符返回它评估的最终操作数(并且它短路)。

我正在寻找如果属性存在要返回的值,所以我修改了上面CMS的答案。这是我想出来的:

function getNestedProperty(obj, key) {// Get property array from key stringvar properties = key.split(".");
// Iterate through properties, returning undefined if object is null or property doesn't existfor (var i = 0; i < properties.length; i++) {if (!obj || !obj.hasOwnProperty(properties[i])) {return;}obj = obj[properties[i]];}
// Nested property found, so return the valuereturn obj;}

Usage:
getNestedProperty(test, "level1.level2.level3") // "level3"getNestedProperty(test, "level1.level2.foo") // undefined

这是我的看法-大多数这些解决方案忽略了嵌套数组的情况,如:

    obj = {"l1":"something","l2":[{k:0},{k:1}],"l3":{"subL":"hello"}}

我可能要检查obj.l2[0].k

使用下面的函数,您可以执行deeptest('l2[0].k',obj)

如果对象存在,函数将返回true,否则返回false

function deeptest(keyPath, testObj) {var obj;
keyPath = keyPath.split('.')var cKey = keyPath.shift();
function get(pObj, pKey) {var bracketStart, bracketEnd, o;
bracketStart = pKey.indexOf("[");if (bracketStart > -1) { //check for nested arraysbracketEnd = pKey.indexOf("]");var arrIndex = pKey.substr(bracketStart + 1, bracketEnd - bracketStart - 1);pKey = pKey.substr(0, bracketStart);var n = pObj[pKey];o = n? n[arrIndex] : undefined;
} else {o = pObj[pKey];}return o;}
obj = get(testObj, cKey);while (obj && keyPath.length) {obj = get(obj, keyPath.shift());}return typeof(obj) !== 'undefined';}
var obj = {"l1":"level1","arr1":[{"k":0},{"k":1},{"k":2}],"sub": {"a":"letter A","b":"letter B"}};console.log("l1: " + deeptest("l1",obj));console.log("arr1[0]: " + deeptest("arr1[0]",obj));console.log("arr1[1].k: " + deeptest("arr1[1].k",obj));console.log("arr1[1].j: " + deeptest("arr1[1].j",obj));console.log("arr1[3]: " + deeptest("arr1[3]",obj));console.log("arr2: " + deeptest("arr2",obj));

这适用于所有对象和数组:)

例如:

if( obj._has( "something.['deep']['under'][1][0].item" ) ) {//do something}

这是我对Brian答案的改进版本

我使用_has作为属性名,因为它可能与现有的has属性(例如:map)冲突

Object.defineProperty( Object.prototype, "_has", { value: function( needle ) {var obj = this;var needles = needle.split( "." );var needles_full=[];var needles_square;for( var i = 0; i<needles.length; i++ ) {needles_square = needles[i].split( "[" );if(needles_square.length>1){for( var j = 0; j<needles_square.length; j++ ) {if(needles_square[j].length){needles_full.push(needles_square[j]);}}}else{needles_full.push(needles[i]);}}for( var i = 0; i<needles_full.length; i++ ) {var res = needles_full[i].match(/^((\d+)|"(.+)"|'(.+)')\]$/);if (res != null) {for (var j = 0; j < res.length; j++) {if (res[j] != undefined) {needles_full[i] = res[j];}}}
if( typeof obj[needles_full[i]]=='undefined') {return false;}obj = obj[needles_full[i]];}return true;}});

这里是小提琴

我认为以下脚本提供了更具可读性的表示。

声明一个函数:

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

然后像这样使用它:

if (o(o(o(o(test).level1).level2).level3){
}

我称之为悲伤小丑技巧,因为它使用的是符号o(


编辑:

这是一个版本TypeScript

它在编译时提供类型检查(如果您使用Visual Studio等工具,还可以提供智能感知)

export function o<T>(someObject: T, defaultValue: T = {} as T) : T {if (typeof someObject === 'undefined' || someObject === null)return defaultValue;elsereturn someObject;}

用法是一样的:

o(o(o(o(test).level1).level2).level3

但这次智能感知奏效了!

另外,您可以设置默认值:

o(o(o(o(o(test).level1).level2).level3, "none")

基于@Stephane LaFlèche的回答,我想出了脚本的替代版本。

JSFiddle上的演示

var obj = {"a":{"b":{"c":"Hello World"}},"resTest":"potato","success":"This path exists"};checkForPathInObject = function(object,path,value) {var pathParts   = path.split("."),result      = false;// Check if required parameters are set; if not, return falseif(!object || typeof object == 'undefined' || !path || typeof path != 'string')return false;/* Loop through object keys to find a way to the path or check for value* If the property does not exist, set result to false* If the property is an object, update @object* Otherwise, update result */for(var i=0;i<pathParts.length;i++){var currentPathPart = pathParts[i];if(!object.hasOwnProperty( currentPathPart )) {result = false;} else if (object[ currentPathPart ] && path == pathParts[i]) {result = pathParts[i];break;} else if(typeof object[ currentPathPart ] == 'object') {object = object[ currentPathPart ];} else {result = object[ currentPathPart ];}}/* */if(typeof value != 'undefined' && value == result)return true;return result;};// Uncomment the lines below to test the script// alert( checkForPathInObject(obj,'a.b.c') ); // Results "Hello World"// alert( checkForPathInObject(obj,'a.success') ); // Returns false// alert( checkForPathInObject(obj,'resTest', 'potato') ); // Returns true

基于这个答案,我使用ES2015提出了这个通用函数,它可以解决问题

function validChain( object, ...keys ) {return keys.reduce( ( a, b ) => ( a || { } )[ b ], object ) !== undefined;}
var test = {first: {second: {third: "This is not the key your are looking for"}}}
if ( validChain( test, "first", "second", "third" ) ) {console.log( test.first.second.third );}

我遇到了同样的问题,想看看我是否可以提出自己的解决方案。这接受您要检查的路径作为字符串。

function checkPathForTruthy(obj, path) {if (/\[[a-zA-Z_]/.test(path)) {console.log("Cannot resolve variables in property accessors");return false;}
path = path.replace(/\[/g, ".");path = path.replace(/]|'|"/g, "");path = path.split(".");
var steps = 0;var lastRef = obj;var exists = path.every(key => {var currentItem = lastRef[path[steps]];if (currentItem) {lastRef = currentItem;steps++;return true;} else {return false;}});
return exists;}

这是一个包含一些日志记录和测试用例的片段:

console.clear();var testCases = [["data.Messages[0].Code", true],["data.Messages[1].Code", true],["data.Messages[0]['Code']", true],['data.Messages[0]["Code"]', true],["data[Messages][0]['Code']", false],["data['Messages'][0]['Code']", true]];var path = "data.Messages[0].Code";var obj = {data: {Messages: [{Code: "0"}, {Code: "1"}]}}
function checkPathForTruthy(obj, path) {if (/\[[a-zA-Z_]/.test(path)) {console.log("Cannot resolve variables in property accessors");return false;}
path = path.replace(/\[/g, ".");path = path.replace(/]|'|"/g, "");path = path.split(".");
var steps = 0;var lastRef = obj;var logOutput = [];var exists = path.every(key => {var currentItem = lastRef[path[steps]];if (currentItem) {logOutput.push(currentItem);lastRef = currentItem;steps++;return true;} else {return false;}});console.log(exists, logOutput);return exists;}
testCases.forEach(testCase => {if (checkPathForTruthy(obj, testCase[0]) === testCase[1]) {console.log("Passed: " + testCase[0]);} else {console.log("Failed: " + testCase[0] + " expected " + testCase[1]);}});

我已经做了性能测试(谢谢你cdMinix添加了黄豆)对这个问题提出的一些建议,结果如下。

免责声明阅读更多关于类似问题的回答将字符串转换为引用是不必要的元编程,最好避免。首先不要失去对引用的跟踪。阅读更多关于类似问题的回答

免责声明#2我们在这里谈论的是每毫秒数百万次的操作。在大多数用例中,这些操作都不太可能产生太大的影响。选择最有意义的,知道每个操作的局限性。对我来说,出于方便,我会选择reduce这样的东西。

对象包装(Oliver Steele)-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;

check嵌套-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;}

对象HasKeys-63%

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

嵌套属性存在

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

我以以下方式使用函数。

var a = {};a.b = {};a.b.c = {};a.b.c.d = "abcdabcd";
function isDefined(objectChainString) {try {var properties = objectChainString.split('.');var currentLevel = properties[0];if (currentLevel in window) {var consolidatedLevel = window[currentLevel];for (var i in properties) {if (i == 0) {continue;} else {consolidatedLevel = consolidatedLevel[properties[i]];}}if (typeof consolidatedLevel != 'undefined') {return true;} else {return false;}} else {return false;}} catch (e) {return false;}}
// definedconsole.log(checkUndefined("a.b.x.d"));//undefinedconsole.log(checkUndefined("a.b.c.x"));console.log(checkUndefined("a.b.x.d"));console.log(checkUndefined("x.b.c.d"));

现在我们也可以使用reduce遍历嵌套键:

// @params o<object>// @params path<string> expects 'obj.prop1.prop2.prop3'// returns: obj[path] value or 'false' if prop doesn't exist
const objPropIfExists = o => path => {const levels = path.split('.');const res = (levels.length > 0)? levels.reduce((a, c) => a[c] || 0, o): o[path];return (!!res) ? res : false}
const obj = {name: 'Name',sys: { country: 'AU' },main: { temp: '34', temp_min: '13' },visibility: '35%'}
const exists = objPropIfExists(obj)('main.temp')const doesntExist = objPropIfExists(obj)('main.temp.foo.bar.baz')
console.log(exists, doesntExist)

我没有看到有人使用代理的任何示例

我想到了我自己的。它的好处是你不必插值字符串。你实际上可以返回一个可链接的对象函数并用它做一些神奇的事情。你甚至可以调用函数并获取数组索引来检查深层对象

function resolve(target) {var noop = () => {} // We us a noop function so we can call methods alsoreturn new Proxy(noop, {get(noop, key) {// return end result if key is _resultreturn key === '_result'? target: resolve( // resolve with target value or undefinedtarget === undefined ? undefined : target[key])},
// if we want to test a function then we can do so alos thanks to using noop// instead of using target in our proxyapply(noop, that, args) {return resolve(typeof target === 'function' ? target.apply(that, args) : undefined)},})}
// some modified examples from the accepted answervar test = {level1: {level2:() => ({level3:'level3'})}}var test1 = {key1: {key2: ['item0']}}
// You need to get _result in the end to get the final result
console.log(resolve(test).level1.level2().level3._result)console.log(resolve(test).level1.level2().level3.level4.level5._result)console.log(resolve(test1).key1.key2[0]._result)console.log(resolve(test1)[0].key._result) // don't exist

上面的代码适用于同步的东西。但是你如何测试像这个ajax调用这样的异步东西呢?如何测试?

fetch('https://httpbin.org/get').then(function(response) {return response.json()}).then(function(json) {console.log(json.headers['User-Agent'])})

当然,你可以使用async/wait来摆脱一些回调。但是,如果你可以更神奇地做到这一点呢?看起来像这样:

fetch('https://httpbin.org/get').json().headers['User-Agent']

你可能想知道所有的Proxy和.then链在哪里……这可能会阻止你所知道的一切……但是使用与Proxy相同的代理技术,你实际上可以测试它存在的深度嵌套的复杂路径,而无需编写单个函数

function resolve(target) {return new Proxy(() => {}, {get(noop, key) {return key === 'then' ? target.then.bind(target) : resolve(Promise.resolve(target).then(target => {if (typeof target[key] === 'function') return target[key].bind(target)return target[key]}))},
apply(noop, that, args) {return resolve(target.then(result => {return result.apply(that, args)}))},})}
// this feels very much synchronous but are still non blocking :)resolve(window) // this will chain a noop function until you call then().fetch('https://httpbin.org/get').json().headers['User-Agent'].then(console.log, console.warn) // you get a warning if it doesn't exist  
// You could use this method also for the first test object// also, but it would have to call .then() in the end


// Another exampleresolve(window).fetch('https://httpbin.org/get?items=4&items=2').json().args.items// nice that you can map an array item without even having it ready.map(n => ~~n * 4).then(console.log, console.warn) // you get a warning if it doesn't exist

最好和最简单的答案是:

var isDefinedPath = function (path) {
var items = path.split ('.');
if (!items || items.length < 1 || !(items[0] in window)) { return false; }
var buffer = [items[0]];for (var i = 1, e = items.length; i < e; i ++) {buffer.push (items[i]);if (eval ('typeof(' + buffer.join ('.') + ') == "undefined"')) {return false;}}
return true;
}

测试:isDefedPath('level1.level2.level3');

第一层不能是数组,其他可以

稍微编辑到这个答案以允许路径中的嵌套数组

var has = function (obj, key) {return key.split(".").every(function (x) {if (typeof obj != "object" || obj === null || !x in obj)return false;if (obj.constructor === Array)obj = obj[0];obj = obj[x];return true;});}

检查链接的答案的用法:)

CMS解决方案效果很好,但使用/语法可以更方便。建议关注

var checkNested = function(obj, structure) {
var args = structure.split(".");
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'); // truecheckNested(test, 'level1.level2.foo'); // false

我想我会添加我今天想出的另一个。我为这个解决方案感到自豪的原因是它避免了许多解决方案中使用的嵌套括号,例如对象包装(Oliver Steele)

(在这个例子中,我使用下划线作为占位符变量,但任何变量名称都可以)

//the 'test' objectvar test = {level1: {level2: {level3: 'level3'}}};
let _ = test;
if ((_=_.level1) && (_=_.level2) && (_=_.level3)) {
let level3 = _;//do stuff with level3
}

//you could also use 'stacked' if statements. This helps if your object goes very deep.//(formatted without nesting or curly braces except the last one)
let _ = test;
if (_=_.level1)if (_=_.level2)if (_=_.level3) {
let level3 = _;//do stuff with level3}

//or you can indent:if (_=_.level1)if (_=_.level2)if (_=_.level3) {
let level3 = _;//do stuff with level3}

另一种解决这个问题的方法是,例如,具有以下对象:

var x = {a: {b: 3}};

然后,我所做的是向这个对象添加以下函数:

x.getKey = function(k){var r ;try {r = eval('typeof this.'+k+' !== "undefined"');}catch(e){r = false;}if(r !== false){return eval('this.'+k);}else{console.error('Missing key: \''+k+'\'');return '';}};

然后你可以测试:

x.getKey('a.b');

如果未定义,则函数返回“”(空字符串),否则返回现有值。

还请考虑其他更复杂的解决方案检查链接:JS对象具有属性深度检查

Object.prototype.hasOwnNestedProperty = function(propertyPath){if(!propertyPath)return false;
var properties = propertyPath.split('.');var obj = this;
for (var i = 0; i < properties.length; i++) {var prop = properties[i];
if(!obj || !obj.hasOwnProperty(prop)){return false;} else {obj = obj[prop];}}
return true;};
// Usage:var obj = {innerObject:{deepObject:{value:'Here am I'}}}
obj.hasOwnNestedProperty('innerObject.deepObject.value');

P. S.:还有一个递归版本。

您可以使用递归函数来执行此操作。即使您不知道所有嵌套的Object键名称,这也可以工作。

function FetchKeys(obj) {let objKeys = [];let keyValues = Object.entries(obj);for (let i in keyValues) {objKeys.push(keyValues[i][0]);if (typeof keyValues[i][1] == "object") {var keys = FetchKeys(keyValues[i][1])objKeys = objKeys.concat(keys);}}return objKeys;}
let test = { level1: { level2: { level3: "level3" } } };let keyToCheck = "level2";let keys = FetchKeys(test); //Will return an array of Keys
if (keys.indexOf(keyToCheck) != -1) {//Key Exists logic;}else {//Key Not Found logic;}

ES6答案,经过彻底测试:)

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

→见具有完整测试覆盖的CoDepen

您还可以将tc39可选链接建议与Babel 7-TC 39-提案-可选-链接一起使用

代码看起来像这样:

  const test = test?.level1?.level2?.level3;if (test) alert(test);

我创建了一个小函数来安全地获取嵌套对象属性。

function getValue(object, path, fallback, fallbackOnFalsy) {if (!object || !path) {return fallback;}
// Reduces object properties to the deepest property in the path argument.return path.split('.').reduce((object, property) => {if (object && typeof object !== 'string' && object.hasOwnProperty(property)) {// The property is found but it may be falsy.// If fallback is active for falsy values, the fallback is returned, otherwise the property value.return !object[property] && fallbackOnFalsy ? fallback : object[property];} else {// Returns the fallback if current chain link does not exist or it does not contain the property.return fallback;}}, object);}

或者更简单但略微不可读的版本:

function getValue(o, path, fb, fbFalsy) {if(!o || !path) return fb;return path.split('.').reduce((o, p) => o && typeof o !== 'string' && o.hasOwnProperty(p) ? !o[p] && fbFalsy ? fb : o[p] : fb, o);}

或者更短,但没有回退在false sy标志上:

function getValue(o, path, fb) {if(!o || !path) return fb;return path.split('.').reduce((o, p) => o && typeof o !== 'string' && o.hasOwnProperty(p) ? o[p] : fb, o);}

我有测试:

const obj = {c: {a: 2,b: {c: [1, 2, 3, {a: 15, b: 10}, 15]},c: undefined,d: null},d: ''}

以下是一些测试:

// nullconsole.log(getValue(obj, 'c.d', 'fallback'));
// arrayconsole.log(getValue(obj, 'c.b.c', 'fallback'));
// array index 2console.log(getValue(obj, 'c.b.c.2', 'fallback'));
// no index => fallbackconsole.log(getValue(obj, 'c.b.c.10', 'fallback'));

要查看所有带留档的代码和我尝试过的测试,您可以查看我的github要点:https://gist.github.com/vsambor/3df9ad75ff3de489bbcb7b8c60beebf4#file-javascriptgetnestedvalues-js

好吧,在html模板中使用单行没有真正好的答案,所以我使用ES6代理做了一个。您只需将一个对象或值传递给“traverse”函数,并执行尽可能多的嵌套调用,使用将返回值或回退值的函数调用来关闭它们。使用:

const testObject = {deep: {nested: {obj: {closure: () => { return "closure" },number: 9,boolean: true,array: [1, 2, { foo: { bar: true } }]}}}}
traverse(testObject).deep()// {nested: {…}}
traverse(testObject).non.existent()// undefined
traverse(testObject).deep.nested.obj.closure()()// closure
traverse(testObject).deep.nested.obj.array[5]('fallback')// fallback
traverse(testObject).deep.nested.obj.array[2]()// {foo: {…}}
traverse(testObject).deep.nested.obj.array[2].foo.bar()// true
traverse(testObject).deep.nested.obj.array[2].foo.bar[4]('fallback')// fallback
traverse(testObject).completely.wrong[3].call().WILL_THROW()// Uncaught TypeError: Cannot read property 'WILL_THROW' of undefined

功能本身:

const traverse = (input) => {// unique empty objectconst unset = new Object();// we need wrapper to ensure we have access to the same unique empty objectconst closure = (input) => {// wrap each input into thisconst handler = new Function();handler.input = input;// return wrappers proxyreturn new Proxy(handler, {// keep traversingget: (target, name) => {// if undefined supplied as initial inputif (!target.input) {return closure(unset);}// otherwiseif (target.input[name] !== undefined) {// input has that propertyreturn closure(target.input[name]);} else {return closure(unset);}},// result with fallbackapply: (target, context, args) => {return handler.input === unset ?args[0] : handler.input;}})}return closure(input);}

您可以使用“”分隔路径对象和路径。

function checkPathExist(obj, path) {var pathArray =path.split(".")for (var i of pathArray) {if (Reflect.get(obj, i)) {obj = obj[i];		
}else{return false;}}return true;}
var test = {level1:{level2:{level3:'level3'}} };
console.log('level1.level2.level3 => ',checkPathExist(test, 'level1.level2.level3')); // trueconsole.log( 'level1.level2.foo => ',checkPathExist(test, 'level1.level2.foo')); // false

这是我使用的一个小助手函数,对我来说,非常简单明了。希望它对一些人有帮助:)。

static issetFromIndices(param, indices, throwException = false) {var temp = param;
try {if (!param) {throw "Parameter is null.";}
if(!Array.isArray(indices)) {throw "Indices parameter must be an array.";}
for (var i = 0; i < indices.length; i++) {var index = indices[i];if (typeof temp[index] === "undefined") {throw "'" + index + "' index is undefined.";}

temp = temp[index];}} catch (e) {if (throwException) {throw new Error(e);} else {return false;}}
return temp;}
var person = {hobbies: {guitar: {type: "electric"}}};
var indices = ["hobbies", "guitar", "type"];var throwException = true;
try {var hobbyGuitarType = issetFromIndices(person, indices, throwException);console.log("Yay, found index: " + hobbyGuitarType);} catch(e) {console.log(e);}

在typeScript中,你可以这样做:

 if (object.prop1 && object.prop1.prop2 && object.prop1.prop2.prop3) {const items = object.prop1.prop2.prop3console.log(items);}
getValue (o, key1, key2, key3, key4, key5) {try {return o[key1][key2][key3][key4][key5]} catch (e) {return null}}

这有一个小模式,但有时会让人不知所措。我建议你一次使用两个或三个嵌套。

if (!(foo.bar || {}).weep) return;// Return if there isn't a 'foo.bar' or 'foo.bar.weep'.

正如我可能忘记提到的,你也可以进一步扩展它。下面的示例显示了对嵌套foo.bar.weep.woop的检查,或者如果没有可用的,它将返回。

if (!((foo.bar || {}).weep || {}).woop) return;// So, return if there isn't a 'foo.bar', 'foo.bar.weep', or 'foo.bar.weep.woop'.// More than this would be overwhelming.

如果你碰巧使用AngularJs,你可以使用$parse服务来检查是否存在深度对象属性,如下所示:

if( $parse('model.data.items')(vm) ) {vm.model.data.items.push('whatever');}

为了避免这样的陈述:

if(vm.model && vm.model.data && vm.model.data.items) {....}

不要忘记将$parse服务注入您的控制器

更多信息:https://glebbahmutov.com/blog/angularjs-parse-hacks/

还有一个非常紧凑的:

function ifSet(object, path) {return path.split('.').reduce((obj, part) => obj && obj[part], object)}

称为:

let a = {b:{c:{d:{e:'found!'}}}}ifSet(a, 'b.c.d.e') == 'found!'ifSet(a, 'a.a.a.a.a.a') == undefined

它不会表现出色,因为它拆分了一个字符串(但增加了调用的易读性)并遍历所有内容,即使已经很明显什么都找不到(但增加了函数本身的易读性)。

至少比_.gethttp://jsben.ch/aAtmc

/*** @method getValue* @description simplifies checking for existance and getting a deeply nested value within a ceratin context* @argument {string} s       string representation of the full path to the requested property* @argument {object} context optional - the context to check defaults to window* @returns the value if valid and set, returns undefined if invalid / not available etc.*/var getValue = function( s, context ){var fn = function(){try{return eval(s);}catch(e){return undefined;}}return fn.call(context||window,s);}

和用法:

if( getValue('a[0].b[0].b[0].d') == 2 ) // true

有很多答案,但仍然:为什么不更简单?

获取值的es5版本将是:

function value(obj, keys) {if (obj === undefined) return obj;if (keys.length === 1 && obj.hasOwnProperty(keys[0])) return obj[keys[0]];return value(obj[keys.shift()], keys);}
if (value(test, ['level1', 'level2', 'level3'])) {// do something}

您也可以将其与value(config, ['applet', i, 'height']) || 42一起使用

感谢CMS的ES6解决方案给了我这个想法。

创建一个全局function并在整个项目中使用

试试这个

function isExist(arg){try{return arg();}catch(e){return false;}}
let obj={a:5,b:{c:5}};
console.log(isExist(()=>obj.b.c))console.log(isExist(()=>obj.b.foo))console.log(isExist(()=>obj.test.foo))

如果条件

if(isExist(()=>obj.test.foo)){....}
function propsExists(arg) {try {const result = arg()  
if (typeof result !== 'undefined') {return true}
return false} catch (e) {return false;}}

此函数还将测试0null。如果它们存在,它也将返回true

示例:

function propsExists(arg) {try {const result = arg()  
if (typeof result !== 'undefined') {return true}
return false} catch (e) {return false;}}
let obj = {test: {a: null,b: 0,c: undefined,d: 4,e: 'Hey',f: () => {},g: 5.4,h: false,i: true,j: {},k: [],l: {a: 1,}}};

console.log('obj.test.a', propsExists(() => obj.test.a))console.log('obj.test.b', propsExists(() => obj.test.b))console.log('obj.test.c', propsExists(() => obj.test.c))console.log('obj.test.d', propsExists(() => obj.test.d))console.log('obj.test.e', propsExists(() => obj.test.e))console.log('obj.test.f', propsExists(() => obj.test.f))console.log('obj.test.g', propsExists(() => obj.test.g))console.log('obj.test.h', propsExists(() => obj.test.h))console.log('obj.test.i', propsExists(() => obj.test.i))console.log('obj.test.j', propsExists(() => obj.test.j))console.log('obj.test.k', propsExists(() => obj.test.k))console.log('obj.test.l', propsExists(() => obj.test.l))

我已经使用这个函数来访问深度嵌套对象的属性,它为我工作…

这是功能

/*** get property of object* @param obj object* @param path e.g user.name*/getProperty(obj, path, defaultValue = '-') {const value = path.split('.').reduce((o, p) => o && o[p], obj);
return value ? value : defaultValue;}

这是我访问深度嵌套对象属性的方式

\{\{ getProperty(object, 'passengerDetails.data.driverInfo.currentVehicle.vehicleType') }}

另一种方式:

/*** This API will return particular object value from JSON Object hierarchy.** @param jsonData : json type : JSON data from which we want to get particular object* @param objHierarchy : string type : Hierarchical representation of object we want to get,*                       For example, 'jsonData.Envelope.Body["return"].patient' OR 'jsonData.Envelope.return.patient'*                       Minimal Requirements : 'X.Y' required.* @returns evaluated value of objHierarchy from jsonData passed.*/function evalJSONData(jsonData, objHierarchy){    
if(!jsonData || !objHierarchy){return null;}    
if(objHierarchy.indexOf('["return"]') !== -1){objHierarchy = objHierarchy.replace('["return"]','.return');}    
let objArray = objHierarchy.split(".");if(objArray.length === 2){return jsonData[objArray[1]];}return evalJSONData(jsonData[objArray[1]], objHierarchy.substring(objHierarchy.indexOf(".")+1));}

只需使用https://www.npmjs.com/package/js-aid包来检查嵌套对象。

function getValue(base, strValue) {
if(base == null) return;    
let currentKey = base;    
const keys = strValue.split(".");    
let parts;    
for(let i=1; i < keys.length; i++) {parts = keys[i].split("[");if(parts == null || parts[0] == null) return;let idx;if(parts.length > 1) { // if arrayidx = parseInt(parts[1].split("]")[0]);currentKey = currentKey[parts[0]][idx];} else {currentKey = currentKey[parts[0]];}if(currentKey == null) return;}return currentKey;}

如果结果在嵌套或值本身的任何地方失败,调用该函数将返回未定义

const a = {b: {c: [{d: 25}]}}console.log(getValue(a, 'a.b.c[1].d'))// output25

这个函数怎么样?它不需要单独列出每个嵌套属性,而是维护“点”语法(尽管是字符串),使其更具可读性。如果未找到属性,则返回undefined或指定的默认值,如果找到则返回属性的值。

val(obj, element, default_value)// Recursively checks whether a property of an object exists. Supports multiple-level nested properties separated with '.' characters.// obj = the object to test// element = (string or array) the name of the element to test for.  To test for a multi-level nested property, separate properties with '.' characters or pass as array)// default_value = optional default value to return if the item is not found. Returns undefined if no default_value is specified.// Returns the element if it exists, or undefined or optional default_value if not found.// Examples: val(obj1, 'prop1.subprop1.subsubprop2');// val(obj2, 'p.r.o.p', 'default_value');{
// If no element is being requested, return obj. (ends recursion - exists)if (!element || element.length == 0) { return obj; }
// if the element isn't an object, then it can't have properties. (ends recursion - does not exist)if (typeof obj != 'object') { return default_value; }
// Convert element to array.if (typeof element == 'string') { element = element.split('.') };   // Split on dot (.)
// Recurse into the list of nested properties:let first = element.shift();return val(obj[first], element, default_value);
}

这个问题很老。今天您可以使用可选链接(?.)

let value = test?.level1?.level2?.level3;

来源:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining

您可以尝试Optional chaining(但要注意浏览器兼容性)。

let test = {level1: {level2: {level3: 'level3'}}};
let level3 = test?.level1?.level2?.level3;console.log(level3); // level3
level3 = test?.level0?.level1?.level2?.level3;console.log(level3); // undefined

有一个巴贝尔插件(@babel/plugin-proposal-optional-chaining)用于选择改变。所以,如果有必要,请升级您的巴贝尔。