Web前端之路

var app = app || {}

2016-05-05  本文已影响972人  NARUTO_86

看 TODOMVC 源代码的时候,看到如下一段代码:

var app = app || {};

https://github.com/tastejs/todomvc/blob/master/examples/backbone/js/models/todo.js#L2

很多次看到过类似的代码,也大概明白这是怎么回事,可是,为啥要这么写,这么写是怎么起作用的呢?

在 StackOverflow 上,也有带着类似困惑的小伙伴问了类似的问题,这里:
http://stackoverflow.com/questions/6439579/what-does-var-foo-foo-assign-a-variable-or-an-empty-object-to-that-va
“标准答案”是这么解释的:

Your guess as to the intent of || {} is pretty close.

This particular pattern when seen at the top of files is used to create a namespace, i.e. a named object under which functions and variables can be created without unduly polluting the global object.

The reason why it's used is so that if you have two (or more) files:

var MY_NAMESPACE = MY_NAMESPACE ||  {};
MY_NAMESPACE.func1 = {}

and

var MY_NAMESPACE = MY_NAMESPACE || {};
MY_NAMESPACE.func2 = {}

both of which share the same namespace it then doesn't matter in which order the two files are loaded, you still get func1 and func2 correctly defined within the MY_NAMESPACE object correctly.

The first file loaded will create the initial MY_NAMESPACE object, and any subsequently loaded file willaugment the object.

Usefully, this also allows asynchronous loading of scripts that share the same namespace which can improve page loading times. If the <script> tags have the defer attribute set you can't know in which order they'll be interpreted, so as described above this fixes that problem too.

不过这个答案给出的是为啥要这么用,至于这么写起作用的原理没有介绍很多。

单纯看一处代码:

var app = app || {};

这里涉及到变量的声明和赋值。而变量的声明涉及到“变量声明提前”的问题,比如在一个函数中,无论变量是不是在函数的最开始声明的,变量都可以使用,例如:

function foo() {
  x = 'ok';
  console.log(x);
  var x; 
}

当然,如果没有最后的 var x; 声明语句,变量 x 就成了全局变量了。
并且,在一个作用域下,多次声明同一变量,由于“变量声明提前”的缘故,其实仍然是同一个变量。
这一部分的具体介绍见:《JavaScript 权威指南》(第6版) 5.3.1 var。

回过头来琢磨最开始的问题,之所以 var app = app || {} 能够有效,应该是由于这些在不同文件中的语句,最终是在同一作用域下执行的。最终在该作用域(也就是全局作用域)下,只有一个app变量。而||的作用,就是在app变量已有值的情况下,直接返回已有的值,没有值的情况下返回右侧的空对象,从而确保app始终是非空的对象。
关于||的这种用法的具体介绍,请看:《JavaScript 权威指南》(第6版)4.10.2 逻辑或(||)。

那么,如果这些语句不在同一作用域下执行的话,会怎样呢?例如:

var app = app || {};
app.foo = 'foo';

(function () {
  var app = app || {};
  app.bar = 'bar';
})()

试验一下,就会发现,上述代码执行完之后,app 对象还是没有属性 bar 的。

为什么呢?
简单来说,因为函数内的 app 是个内部变量,与外部的 app 互不影响。

再把上面的例子改写下:

var app = app || {};
app.foo = 'foo';

(function () {
  app.bar = 'bar';
  var app = app || {};
})()

这样的话又如何呢?这次是不是代码执行后外部的 app 有了属性 bar 了呢?

执行一下的话会发现,居然报错了!

Uncaught TypeError: Cannot set property 'bar' of undefined

仔细琢磨下....其实还是“变量声明提前”在搞鬼。函数内部的变量 app 的声明提前了,但是赋值语句不会提前。没有赋值的变量的值是什么呢?是 undefined 的。这里的错误说清楚了吧,因为此时的 app 是函数内部的私有变量,还没有赋值。为啥不是外面的 app 呢,因为函数内部声明了同名的 app,而通过“变量声明提前”,函数内的 app 都是指向内部的变量的。

小结

其实像这里分析的代码,在日常工作中会看到很多,大致是怎么回事是清楚的,可是真的要去详细解释为啥这么做,这么做又是为啥会起作用,可能就懵了。

编程这件事好像特别看重细节,毕竟无论多么 NB 的应用,也都是一行行代码构建起来的。细节上不清楚,很可能会出现一些习以为常到反而看不出来的小 bug,这样的东西多了,问题也就大了。

上一篇下一篇

猜你喜欢

热点阅读