可能被你忽略的 JavaScript 代码陷阱
下面这段代码,你知道有哪些错误吗:
var g_bar = "bar";
function foo(container, config) {
var container = container || document,
name = config.name || "无名氏",
isLive = config.isLive || true;
var g_bar = g_bar || "";
if(g_foo) {
/* your code */
}
}
foo(document, {isLive: false});
请仔细思考后再往下阅读。
—- 帮助你思考的刷屏线 开始 —-
—- 帮助你思考的刷屏线 结束 —-
1. isLive = config.isLive || true, 当传入的值有可能就是0, undefined, null, false, "", NaN这六个 falsy 值时,用 || 来设定默认值不妥当。更保险的做法是:
isLive = "isLive" in config ? config.isLive : true;
如果是独立变量,可以采用:
someVar = typeof someVar !== "undefined" ? someVar : defaultValue;
注意:大部分情况下,用 || 已经够用,比如:
container = container || document name = config.name || "无名氏"
一切皆权衡。
2. var g_bar = g_bar || "", 原意是取全局变量 g_bar 的值给内部变量 g_bar, 默认为空字符串。然而,实际情况等价为:
var g_bar; g_bar = g_bar || "";
很明显,|| 号左边的 g_bar 也是内部变量,并且为 undefined, 因此var g_bar = g_bar || ""实际上是var g_bar = "", 没有满足代码的原始意图。
思考:代码中的var container = container || document有无问题?为什么?
3. if(g_foo) { /* code */ }, 这段代码在执行时会报错。我们都知道在 JS 里,变量不定义就可以用。但一定要清楚,未定义的变量,仅仅是可写,但不可读。比如:
g_foo = 2; // 等价 window.g_foo = 2 var t = g_foo2; // 不等价为 var t = window.g_foo2, 会报错
具体原因可以参见 JavaScript 运行机制浅探:
未定义变量意味着在 scriptObject 的变量表中找不到,JS 引擎会沿着 scriptObject 的 upvalue 往上寻找,如果都没找到,对于写操作 i = 1; 最后就会等价为 window.i = 1; 给 window 对象新增了一个属性。对于读操作,如果一直追溯到全局执行环境的 scriptObject 上都找不到,就会产生运行期错误。
因此严谨的写法是:
if(window.g_foo) {
/* your code */
}
不要小看这些细微之处,有时会让人抓狂的。但这些细微之处又很容易被忽略或滥用。比如 YUI 2.8r4 里,有一个遗传了很久的 bug:
var NOTHING = [];
// ....
later: function(when, o, fn, data, periodic) {
when = when || 0;
o = o || {};
var m = fn, d = data, f, r;
// ...
if (d && !L.isArray(d)) {
d = [data];
}
f = function() {
m.apply(o, d || NOTHING);
};
// ...
}
当你的调用代码类似Lang.later(delay[0], o, "show", index)时,如果 index 不幸是 base-0 的,那么取 0 时,m.apply(o, d || NOTHING)会让你得到“惊喜”。更妥的做法是类似 YUI3 中的修正:
// ...
if (!L.isArray(d)) {
d = [data];
}?
f = function() {
m.apply(o, d);
};
//...
对于 || 和 && 的用法,很多 JS 书籍(无论中外),都用来片面强调 JS 的灵活性,包括 Douglas 的《JavaScript The Good Parts》中也存在误导。
最后,有感于 NCZ 今天写的 Writing Maintainable Code, 再举一例(和本文主题关系不明显,但的确又有关系,交给你去思考啰):
var isBoy = true; isBoy = typeof isGirl !== "undefined" ? !isGirl : true;
或者来个耍酷的代码:
var isBoy = true; (typeof isGirl !== "undefined") && (isBoy = !isGirl);
然而,以上两种写法,无论从代码长度还是性能上讲,都不如更直白的写法:
var isBoy = true; if(typeof isGirl !== "undefined") isBoy = !isGirl;
简单质朴,往往是最好的。

December 16th, 2009 on 23:17
第一个已经深受其害
December 16th, 2009 on 23:30
赞,又学习了一些细节问题。
前段时间有朋友让我整理一下”Javascript Hacks”,我觉得像这样的陷阱和一些代码技巧都可以收录起来。
December 17th, 2009 on 6:56
简单就是美,不能为了耍酷放弃代码的可读性
December 17th, 2009 on 8:22
Understandable
Intuitive
Adaptable
Extendable
Debuggable
Testable
December 17th, 2009 on 8:39
重剑无锋,大巧不工。
December 17th, 2009 on 9:15
不错,不过建议字符串也不要用||,用户传的是0,不是’0′,就惨了。
config.name = 0;
name = config.name || “无名氏”
December 17th, 2009 on 9:22
真的好恐怖
December 17th, 2009 on 9:28
那要怎么写才能实现var g_bar = g_bar || “”的原意呢?
December 17th, 2009 on 9:30
var g_bars = g_ar || “” ?
December 17th, 2009 on 9:52
config = config?config:{};
December 17th, 2009 on 10:21
思考:代码中的var container = container || document有无问题?为什么?
有问题啊,其实这个var的声明是没有必要的,function foo(container, config),container作为形参已经被确定为局部变量了.
不知道你说的是这个问题不。
December 17th, 2009 on 10:33
isLive = config.isLive || true, 当传入的值有可能就是0, undefined, null, false, “”, NaN这六个 falsy 值时,用 || 来设定默认值不妥当。更保险的做法是:
isLive = “isLive” in config ? config.isLive : true;
你这样写在不报错?如果config不是一个明确的对象(new String(“abc”),{}这些对象的话)可以用in操作?
December 17th, 2009 on 10:59
赞一个!
December 17th, 2009 on 20:19
@leoner: 这涉及到另一个问题:何时需要校验?何时抛出异常?对于文中的例子来说,当 config 不是对象时,直接报错是预期,没有问题的。
December 18th, 2009 on 12:02
雕兄好专业,牛人一个!不断学习中。
December 20th, 2009 on 18:24
你的Beyond挺好玩滴
December 31st, 2009 on 11:11
不推荐在js中设默认值为true,省很多麻烦;
December 31st, 2009 on 11:43
另外, var container = container || document 是没有问题的;
局部变量的声明不会覆盖参数变量,它们属于同一个作用域的同一名称,完全等价;
leave a reply