JavaScript表达式'a = a || function() {...}'的含义是什么?

17

我不确定这个结构的含义,但我已经看到过几次。下面的例子来自另一个Stack Overflow问题。我不确定如何解释初始的"or"结构:

Object.keys = Object.keys || (function () {
  var hasOwnProperty = Object.prototype.hasOwnProperty,
      hasDontEnumBug = !{toString:null}.propertyIsEnumerable("toString"),
      DontEnums = [ 
          'toString', 'toLocaleString', 'valueOf', 'hasOwnProperty',
          'isPrototypeOf', 'propertyIsEnumerable', 'constructor'
      ],
      DontEnumsLength = DontEnums.length;
  //etc...
});

1
我给这个问题点赞是因为(在我看来)每一个 JavaScript 程序员都应该深入理解这个习语。实际上,是否使用它取决于你自己,但我认为它能让很多代码看起来更加优雅。 - Michael Lorton
1
对于这种情况,我更喜欢使用a||(a=b)而不是a=a||b,它们看起来很相似且做的事情一样,但前者不会在a已经为truthy时重新分配给自己,这样稍微快一些。参见http://jsperf.com/a-a-b(我正在手机上编写和运行测试,因此仅在Android上进行了基准测试-但我认为您应该在其他浏览器中看到类似的结果,除非它们通过某种方式优化并知道无需运行`a=a`)。 - shesek
该死,我刚发完评论手机就崩溃了,而且在手机上打字还花了好长时间。我拿出笔记本电脑重新写了一遍,但幸运的是看起来评论已经提交成功了。 - shesek
4个回答

21

a = a || function(){...}是Javascript中非常常见的习语。它依赖于两个概念,虽然不是Javascript独有的,但你可能还不熟悉。

1. 运算符短路

运算符短路[维基百科]是一种编译器优化,旨在防止不必要的计算。

为了证明这一点,假设我们想确定一个人是否是青少年:也就是说,一个人的年龄在13岁到19岁之间。

var isTeenager = person.age >= 13 && person.age <= 19;

现在,假设代码执行后发现这个人年龄小于13岁。第一个条件将被评估并返回false。由于程序现在知道&&操作符左侧为false,并且&&要求两侧都为true才能评估为true,因此它知道评估右侧是毫无意义的。
换句话说,程序已经知道这个人的年龄不大于13岁,已经知道他不是青少年,不管他是否小于19岁都无所谓。
同样的原理也适用于||运算符。假设我们想知道一个人是否可以免费乘坐公交车:即该人是否超过70岁或残疾。
var canRideFree = person.age >= 70 || isHandicapped(person);

如果这个人超过70岁,程序已经知道他可以免费乘车。此时,程序不关心他是否残疾,因此不评估对isHandicapped函数的调用。另一方面,如果这个人年龄小于70岁,那么canRideFree将被设置为isHandicapped返回的任何值。
2.真值和假值[some random person's blog]是对象的布尔运算结果。在Javascript中,每个对象都将计算出"真值"或"假值"。
如果表达式的值是以下任何一种情况,则表达式为"假值":
false, null, undefined, 0, "", NaN

其他所有值都是真值。

人们利用 null 或 undefined 变量的值为 false 这个事实。这意味着你可以很容易地检查变量是否存在:

if (a) { /* a exists and is not a falsy value */ }

将我们所知道的结合起来

|| 运算符会短路并返回它评估的最后一个表达式的值。这个原则与真值性相结合,体现在以下单个语句中:

Object.keys = Object.keys || function() {...}

如果Object.keys为真值,它将被评估并分配给自身。否则,Object.keys将被分配给该函数。这是Javascript中非常常见的习语,用于检查一个值是否已经存在,如果不存在,则将其分配给其他内容。
一些其他的语言,如C#,没有真值,它们有一个类似的null-coalescing operator[MSDN],具有类似的目的。
object Value = PossiblyNullValue ?? ValueIfNull;

在这段代码中,如果Value不为null,则将其赋值给PossiblyNullValue,否则将其赋值给ValueIfNull

简而言之 [维基百科]

如果你没有仔细阅读我上面说的任何内容,你只需要知道a = a || function() {...}基本上做了这段代码所做的事情:

if (exists(Object.keys)) {
  Object.keys = Object.keys;
} else { 
  Object.keys = function() {...};
}

function exists(obj) {
  return typeof obj !== "undefined" && 
         obj !== null && 
         obj !== false &&
         obj !== 0 &&
         obj !== "" &&
         !isNaN(obj);
}

[] 不是假值:!![] === true - Domenic
4
从现在开始,我将彻底抄袭你的上标括号用于链接引用,真不敢相信这里还没有更多人使用它。 - Joel Coehoorn
非常好的解释,但也应该更具体地解释一下,当表达式计算为真时,带有 ||&& 运算符的表达式返回它们找到的第一个真值。 - shesek
@shesek 感谢您的反馈。我现在明确提到了它,但它可能仍然不像它应该的那样重要。 - Peter Olson

4
这段内容似乎不完整,但它似乎是Object.keys的替代品。基本上,如果属性不存在(例如在非标准兼容的浏览器中),我们会自己实现它。
“或”运算符仅在第一个操作数为假时评估第二个操作数。因此,
alert(false || "Hello, world");

会弹出 "Hello, world"。 在这种情况下,Object.keys将是 undefined,它会被评估为 false


3
< p > || 基本上是这样的:如果 Object.keys 未定义,则使用 || 后面的表达式来定义它。

这种行为基于 JavaScript 的特性,即任何未定义的变量都会被评估为 false。如果变量为 true,则不需要评估第二个表达式;如果为 false,则需要评估第二个表达式。


2
据我所知,该代码尝试定义函数Object.keys,如果它还没有被定义(或者为false)。在||左侧的函数将成为函数Object.keys
我之所以说“据我所知”,是因为您没有发布整个代码片段。请注意,在||后面的代码中读取(function(){而不仅仅是function(){。作者可能已经设置了自调用函数。
如果在函数定义之后,您看到})(),那么函数的返回值将存储在Object.keys中。如果没有,则函数本身将被存储在那里。

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