JavaScript中检查深度嵌套对象属性存在的最简单方法是什么?

26

我需要检查深度嵌套的对象属性,例如YAHOO.Foo.Bar.xyz

我目前使用的代码是

if (YAHOO && YAHOO.Foo && YAHOO.Foo.Bar && YAHOO.Foo.Bar.xyz) {
    // operate on YAHOO.Foo.Bar.xyz
}

这样做虽然可行,但看起来有些笨拙。

有没有更好的方法来检查这种深嵌套的属性?


3
你能不能只写成 if (YAHOO.Foo.Bar.xyz) - Michael Berkowski
1
http://enterprise-js.com/34 <-- 这只是个笑话。 - Joseph Marikle
6
@Michael,你不能只写 if (YAHOO.Foo.Bar.xyz),因为如果 Foo 或 Bar 不存在,那么 if (YAHOO.Foo.Bar.xyz) 将会抛出异常,因为中间的值未定义。 - jfriend00
1
这可能是检查的最佳方式;使用 try...catch 会影响性能 - 当抛出异常时,代码将变慢。jsPerf测试 - Digital Plane
1
@Digital Plane - 但是,如果通常情况是存在的话,那么在Chrome中,try/catch要比多重if测试快19倍。 - jfriend00
@jfriend00 - 所以,你可以根据你认为属性在代码运行时是否存在来选择更快的方法。 - Digital Plane
7个回答

20
如果你期望YAHOO.Foo.Bar是一个有效的对象,但是想要让你的代码防弹以防万一它不是,那么最干净的方法就是在它周围加上try catch,并让一个错误处理程序捕获任何缺失的部分。然后,你只需要使用一个if条件语句,而不是四个,来检测终端属性是否存在,以及一个catch处理程序来捕获中间对象不存在的情况:
try {
    if (YAHOO.Foo.Bar.xyz) {
        // operate on YAHOO.Foo.Bar.xyz
} catch(e) {
    // handle error here
}

或者,根据您的代码工作方式,它甚至可能只是这样:

try {
    // operate on YAHOO.Foo.Bar.xyz
} catch(e) {
    // do whatever you want to do when YAHOO.Foo.Bar.xyz doesn't exist
}

我特别喜欢在处理外来输入时使用这些技巧,因为外来输入可能不符合特定的格式,而我希望自己能够捕获并处理无效的输入,而不是让异常向上传播。

一般来说,一些JavaScript开发者很少使用try/catch。我发现有时我可以用一个大函数块周围的单个try/catch替换5-10个检查输入的if语句,并同时使代码更简单易读。当然,这取决于特定的代码,但一定值得考虑。

如果通常操作是不使用try/catch抛出异常,则比一堆if语句快得多。


如果您不想使用异常处理程序,您可以创建一个函数来为您测试任意路径:

function checkPath(base, path) {
    var current = base;
    var components = path.split(".");
    for (var i = 0; i < components.length; i++) {
        if ((typeof current !== "object") || (!current.hasOwnProperty(components[i]))) {
            return false;
        }
        current = current[components[i]];
    }
    return true;
}

使用示例:

var a = {b: {c: {d: 5}}};
if (checkPath(a, "b.c.d")) {
    // a.b.c.d exists and can be safely accessed
}

12
不要滥用try catch。天啊,我的眼睛。甚至还有一个空的catch块。如果我曾经看到过这种代码味道,那么这就是一种代码异味。 - Raynos
7
为什么使用 try/catch 是滥用呢?对我来说,它比多个嵌套的 if 语句更有意义地检查所有中间级别。一个空的 catch 只是表示如果中间级别不存在,则有意不执行任何操作(这是一个合理的设计选择)。这是完全正常的代码。你真的因为喜欢其他方法而给它点了踩吗?像这样使用 try/catch 没有任何问题。 - jfriend00
2
异常的发生是指特殊情况,它会改变程序执行的正常流程。我认为声称属性不存在是一种“改变程序执行正常流程的特殊情况”是一种牵强附会。我认为你使用了错误的工具来完成这项工作。 - Raynos
2
当你正常期望中间属性存在时,它不存在,这很可能是“改变程序执行正常流程的特殊条件”,似乎是异常的完美使用。你似乎认为异常只应该用于非凡的事情,但它们只是另一种错误返回/传播机制,在许多情况下可以比多个if语句更有效地使用,特别是当你处理不一定控制并且测试每个操作可能非常昂贵的外部输入时。 - jfriend00
1
我在操作不受我控制的外部HTML(HTML不符合特定格式,可能存在错误)时经常使用异常。当开始有大量的琐碎工作(解析结构、获取数据等等...),最终结果在结束时应用时,这种方法尤其有用。我用try/catch包围整个块,如果任何结构不如预期,它会抛出一个错误,我可以非常高效地捕获所有错误,而不是使用数十个if语句。这也使代码更易读。 - jfriend00
显示剩余6条评论

6
var _ = {};

var x = ((YAHOO.Foo || _).Bar || _).xyz;

6
本周最不清晰的话,点赞! - Jonathan

6

考虑这个实用函数:

function defined(ref, strNames) {
    var name;
    var arrNames = strNames.split('.');

    while (name = arrNames.shift()) {        
        if (!ref.hasOwnProperty(name)) return false;
        ref = ref[name];
    } 

    return true;
}

使用方法:

if (defined(YAHOO, 'Foo.Bar.xyz')) {
    // operate on YAHOO.Foo.Bar.xyz
}

实时演示: http://jsfiddle.net/DWefK/5/


我认为你需要在while循环测试中显式检查undefined,而不仅仅是真值测试。目前defined({a:''}, 'a.b') === true。不过这是一个不错的方法,我喜欢它! - codebox
@codebox 我对我的代码不满意,所以我进行了重写。你觉得怎么样? - Šime Vidas
我希望在underscore或lodash中有类似这样的东西。 - Jonathan
哦,lodash实际上有它。_.get(),太棒了!甚至还有一个默认参数。 - Jonathan
这是一个非常好的解决方案。但是如果引用对象未定义,代码将会出错。我认为检查引用对象是否已定义将解决问题。您的解决方案更新后的解决方案 - Shahid

3
如果您需要检查路径的正确性,而不是"YAHOO.Foo.Bar"对象上"xyz"成员的存在,则最好将调用包装在try catch中:
var xyz;
try {
    xyz = YAHOO.Foo.Bar.xyz;
} catch (e) {
    // fail;
};

另外,您也可以运用一些字符串技巧魔法TM

function checkExists (key, obj) {
    obj = obj || window;
    key = key.split(".");

    if (typeof obj !== "object") {
        return false;
    }

    while (key.length && (obj = obj[key.shift()]) && typeof obj == "object" && obj !== null) ;

    return (!key.length && typeof obj !== "undefined");
}

使用方法如下:
if (checkExists("YAHOO.Foo.Bar.xyz")) {
    // Woo!
};

我只是不明白 typeof obj !== "object" 测试的意义所在。为什么要这么限制呢? - hugomg
@jfriend00:字符串方法允许您找到事情崩溃的点(如果您也想要)。通过字符串传递对象描述在某些情况下也是有意义的,例如自定义模块系统。 - hugomg
1
@jfriend00:如果你有很多地方需要检查对象路径的存在,那么try/catch会有点繁琐。 - Matt
@Matt - 好的,但是try/catch比分割数组和循环快得多,并且在一些中间引用也是变量时易于使用。我发现在JS中try/catch被低估了。我经常可以用一个包围的try/catch替换函数中的一堆if语句,不仅可以保护我的代码免受我手动检查的错误,还可以保护其他错误。显然,它的最佳使用取决于你的代码正在做什么,但大多数JS程序员应该更多地使用它。 - jfriend00
@jfriend:相对而言,我同意。然而在现实世界中,今天的计算机,+-ms 的差异并不等同于很多。 - Matt
显示剩余2条评论

1
这个问题可以通过 coffeescript 很漂亮地解决(它编译成 javascript):
if YAHOO.Foo?.Bar?.xyz
  // operate on YAHOO.Foo.Bar.xyz

0

0

使用 try catch

a={
  b:{}
};

//a.b.c.d?true:false; Errors and stops the program.

try{
  a.b.c.d;
}
catch(e){
  console.log(e);//Log the error
  console.log(a.b);//This will run
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接