var app = app || {}

看 TODOMVC 源代碼的時(shí)候,看到如下一段代碼:

var app = app || {};

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

很多次看到過(guò)類似的代碼,也大概明白這是怎么回事,可是,為啥要這么寫(xiě),這么寫(xiě)是怎么起作用的呢?

在 StackOverflow 上,也有帶著類似困惑的小伙伴問(wèn)了類似的問(wèn)題,這里:
http://stackoverflow.com/questions/6439579/what-does-var-foo-foo-assign-a-variable-or-an-empty-object-to-that-va
“標(biāo)準(zhǔn)答案”是這么解釋的:

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.

不過(guò)這個(gè)答案給出的是為啥要這么用,至于這么寫(xiě)起作用的原理沒(méi)有介紹很多。

單純看一處代碼:

var app = app || {};

這里涉及到變量的聲明和賦值。而變量的聲明涉及到“變量聲明提前”的問(wèn)題,比如在一個(gè)函數(shù)中,無(wú)論變量是不是在函數(shù)的最開(kāi)始聲明的,變量都可以使用,例如:

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

當(dāng)然,如果沒(méi)有最后的 var x; 聲明語(yǔ)句,變量 x 就成了全局變量了。
并且,在一個(gè)作用域下,多次聲明同一變量,由于“變量聲明提前”的緣故,其實(shí)仍然是同一個(gè)變量。
這一部分的具體介紹見(jiàn):《JavaScript 權(quán)威指南》(第6版) 5.3.1 var。

回過(guò)頭來(lái)琢磨最開(kāi)始的問(wèn)題,之所以 var app = app || {} 能夠有效,應(yīng)該是由于這些在不同文件中的語(yǔ)句,最終是在同一作用域下執(zhí)行的。最終在該作用域(也就是全局作用域)下,只有一個(gè)app變量。而||的作用,就是在app變量已有值的情況下,直接返回已有的值,沒(méi)有值的情況下返回右側(cè)的空對(duì)象,從而確保app始終是非空的對(duì)象。
關(guān)于||的這種用法的具體介紹,請(qǐng)看:《JavaScript 權(quán)威指南》(第6版)4.10.2 邏輯或(||)。

那么,如果這些語(yǔ)句不在同一作用域下執(zhí)行的話,會(huì)怎樣呢?例如:

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

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

試驗(yàn)一下,就會(huì)發(fā)現(xiàn),上述代碼執(zhí)行完之后,app 對(duì)象還是沒(méi)有屬性 bar 的。

為什么呢?
簡(jiǎn)單來(lái)說(shuō),因?yàn)楹瘮?shù)內(nèi)的 app 是個(gè)內(nèi)部變量,與外部的 app 互不影響。

再把上面的例子改寫(xiě)下:

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

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

這樣的話又如何呢?這次是不是代碼執(zhí)行后外部的 app 有了屬性 bar 了呢?

執(zhí)行一下的話會(huì)發(fā)現(xiàn),居然報(bào)錯(cuò)了!

Uncaught TypeError: Cannot set property 'bar' of undefined

仔細(xì)琢磨下....其實(shí)還是“變量聲明提前”在搞鬼。函數(shù)內(nèi)部的變量 app 的聲明提前了,但是賦值語(yǔ)句不會(huì)提前。沒(méi)有賦值的變量的值是什么呢?是 undefined 的。這里的錯(cuò)誤說(shuō)清楚了吧,因?yàn)榇藭r(shí)的 app 是函數(shù)內(nèi)部的私有變量,還沒(méi)有賦值。為啥不是外面的 app 呢,因?yàn)楹瘮?shù)內(nèi)部聲明了同名的 app,而通過(guò)“變量聲明提前”,函數(shù)內(nèi)的 app 都是指向內(nèi)部的變量的。

小結(jié)

其實(shí)像這里分析的代碼,在日常工作中會(huì)看到很多,大致是怎么回事是清楚的,可是真的要去詳細(xì)解釋為啥這么做,這么做又是為啥會(huì)起作用,可能就懵了。

編程這件事好像特別看重細(xì)節(jié),畢竟無(wú)論多么 NB 的應(yīng)用,也都是一行行代碼構(gòu)建起來(lái)的。細(xì)節(jié)上不清楚,很可能會(huì)出現(xiàn)一些習(xí)以為常到反而看不出來(lái)的小 bug,這樣的東西多了,問(wèn)題也就大了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容