一、IIFE解釋 全拼Imdiately Invoked Function Expression,立即執行的函數表達式。 像如下的代碼所示,就是一個匿名立即執行函數: 二、括弧的意義 2.1 包住 的括弧的意義 這個括弧的目的,是為了把 轉化為表達式。像一些庫的源碼,喜歡用如下方式代替: 或者這種方 ...
一、IIFE解釋
全拼Imdiately Invoked Function Expression,立即執行的函數表達式。
像如下的代碼所示,就是一個匿名立即執行函數:
(function(window, undefined){
// 代碼...
})(window);
二、括弧的意義
2.1 包住function(){}
的括弧的意義
這個括弧的目的,是為了把function(){}
轉化為表達式。像一些庫的源碼,喜歡用如下方式代替:
~function(){
// 代碼...
}();
或者這種方式:
+function(){
// 代碼...
}();
其實,作用都一樣,都是把function(){}
轉化成一個可執行的表達式,方便執行。
如果去掉該括弧,則會報錯。因為單純的function(){}
不是可執行的表達式,會直接報錯。如下圖:
2.1 第二個括弧的意義
理解了第一個括弧的意義,第二個括弧就很簡單了,就是執行表達式了。
三、參數的意義
以這段代碼為例子,講解參數
var wall = {};
(function(window, WALL, undefined){
})(window, wall);
參數分為形參和實參。
function(window, WALL, undefined)
三個參數為形參,第二個括弧(window, wall)
的兩個參數為實參。
也即可以理解為 window == window
,wall == WALL
。
2.1 普通形參
普通形參是指由window
和wall
這樣的實際變數傳入指定,可以為任何類型的變數。一個形參就對應一個實參
2.2 特殊形參undefined
為什麼形參要多寫一個undefined,這是一個很有趣的話題。
可以知道這個示例,實參只有兩個,而形參有三個。所以在函數執行的時候,形參undefined
會預設賦值為undefined。
形參undefined
的作用如下:
2.2.1 防止特殊值undefined被惡意代碼篡改。
IE6等低版本瀏覽器,undefined是支持被修改的。而這個特殊值被修改後,像以下這種判斷就失效了。
if(wall == undefined){
// 代碼...
}
所以,這裡多加一個形參的目的就是為了防止這種情況發生。只要在這個IIFE作用域內,undefined就能夠正常獲取到。
2.2.2 壓縮代碼可以壓縮undefined
因為undefined作為形參,像YUI compressor這種類型的代碼壓縮工具,可以將其相關的值進行壓縮,減小文件的體積。
四、寫法解析
4.1 普通寫法
var wall = {}; // 聲明定義一個命名空間wall
// 定義方法
(function(window, WALL, undefined){
// 給wall命名空間綁定方法say
WALL.say = function(){
console.log('hello');
};
})(window, wall);
(function(window, WALL, undefined){
// 給wall命名空間綁定方法 whoIam
WALL.whoIam = function(){
console.log('wall');
};
})(window, wall);
// 調用
wall.say();
wall.whoIam();
先定義一個命名空間,然後再給這個命名空間加東西。這是最普遍的寫法,也是最好理解的。
不足的地方就是必須先聲明一個命名空間,然後才能執行相關的綁定代碼。存在順序載入的問題。
4.2 放大模式
var wall = (function(window, WALL, undefined){
if(typeof WALL == 'undefined'){
WALL = {};
}
// 給wall命名空間綁定方法say
WALL.say = function(){
console.log('hello');
}
return WALL; // 返回引用
})(window, wall);
var wall = (function(window, WALL, undefined){
if(typeof WALL == 'undefined'){
WALL = {};
}
// 給wall命名空間綁定方法 whoIam
WALL.whoIam = function(){
console.log('wall');
}
return WALL; // 返回引用
})(window, wall);
// 調用
wall.say();
wall.whoIam();
放大模式的好處就是,可以不用考慮代碼載入的先後順序。
因為js允許wall
變數進行重覆var聲明,所以這段代碼是可以執行的。
我可以把IIFE函數拆分成多個文件進行載入,而不會出現普通寫法需要註意的問題。
需要註意的點:
1.IIFE的頭部,都要先進行檢查命名空間是否已經實例化,如果還沒實例化,則進行實例化。
2.IIFE的尾部,都要return命名空間的引用,使後續代碼能夠得到最新的wall
命名空間內容。
4.3 寬放大模式
(function(window, WALL, undefined){
// 給wall命名空間綁定方法say
WALL.say = function(){
console.log('hello');
}
})(window, window.wall || (window.wall = {}));
(function(window, WALL, undefined){
// 給wall命名空間綁定方法 whoIam
WALL.whoIam = function(){
console.log('wall');
}
})(window, window.wall || (window.wall = {}));
// 調用
wall.say();
wall.whoIam();
寬放大模式的重點註意的地方:就是在實參部分的window.wall || (window.wall = {})
。
用||運算符進行取巧。
如果window.wall
是已經實例化的,非not defined。則直接返回window.wall
的引用,賦值給形參WALL
。不會執行||運算符後面的內容。
如果window.wall
還未實例化,則進行實例化。這裡要註意的點是實例化是一個賦值操作,需要用括弧包起來,變成表達式去執行,才不會報錯。
表達式(window.wall = {})
執行完畢後,會返回新對象window.wall
的引用。
寬放大模式的好處:是可以切割成多個文件進行載入,而不必考慮文件載入的先後順序,不存在強耦合關係。
當然,如果IIFE裡面的方法互相引用,還是存在載入依賴的問題。這個問題可以用載入器Require.js等工具解決,這裡就不討論了。
五、分文件載入IIFE要註意的點
;(function(window, WALL, undefined){
// 給wall命名空間綁定方法say
WALL.say = function(){
console.log('hello');
}
})(window, window.wall || (window.wall = {}));
眼尖的已經看出區別了,就是文件開始的地方,先寫上分號;
。
這樣,多個文件合併的時候,才不會出現收尾相接,代碼出現錯亂的問題。比如下麵這種情況:
// a.js 文件
wall.log()
// b.js 文件
(function(window, WALL, undefined){
// 給wall命名空間綁定方法say
WALL.say = function(){
console.log('hello');
}
})(window, window.wall || (window.wall = {}));
由於a.js文件的wall.log()
少寫了分號,跟b.js文件合併後,js就會認為‘wall.log()(...)’是需要這麼執行的,結果代碼就報錯了。
覺得不錯的,可以關註模塊化
這個系列的文章,容我後續碼字,敬請期待!
閱讀原文:http://www.jianshu.com/p/4dbf4a4c8ebb