立即執行函式(IIFE)的理解與運用
作為JavaScript的常用語法,立即執行函式IIFE(Immediately-Invoked Function Expression)是值得我們認真去學習探究的。
一、建立函式的兩種方式
我們先從基礎講起,要建立一個JS函式,有兩種方式。
(一)函式定義(Function Declaration)
function Identifier ( Parameters ){ FunctionBody }
函式定義中,引數(Parameters)識別符號(Identifier )是必不可少的。如果遺漏,會報提示如下錯誤:Expected identifier
(二)函式表示式(Function Expression)
function Identifier(Parameters){ FunctionBody }
函式表示式中,引數和識別符號都是可選的。
那麼我們是不是可以通過有無識別符號來判斷,建立函式的是一個函式定義,還是一個函式表示式呢?很遺憾,不行!
我們剛剛說過,函式表示式中識別符號是可選的。那如果函式表示式有識別符號,又該如何判斷?
其實,"function Identifier(Parameters){ FunctionBody }"並不是一個完整的函式表示式,完整的函式的表示式,需要一個賦值操作。
比如: var name=function Identifier(Parameters){ FunctionBody }
這裡的Identifier常常被省略,至於為什麼,由於該主題內容涉及面較廣,在此文中不作討論。
好了。兩種建立函式的方式例項如下:
//function expressionvar myExpression = function () { return window.location }//function declarationfunction myDeclaration() { return window.location.hostname }
二、立即執行函式
顧名思義,立即執行函式可以讓你的函式在建立後立即執行。
(一)基本結構
這樣的函式有多常用呢,我們可以看看下面的程式碼:
(function( window, undefined ) {//……})(window);
這段程式碼,大家一定不會陌生。是的,它就是我們"Write less, do more"的jQuery。
jQuery整個檔案就是一個立即執行函式。
(function(){})(); 是立即執行函式常用的表現形式之一。
另一種也很常用:
(function(){}());
以上兩種是比較常用的寫法,但立即執行函式的寫法因人而異。記住以下兩點就可以了。
如果是函式表示式,可直接在其後加"()"立即執行。
如果是函式宣告,可以通過"()"、"+"、"-"、"void"、"new"等運算子將其轉換為函式表示式,然後再加"()"立即執行。
比如,下面的寫法也是沒有問題的。
void function(){}(alert("ok"));
在執行前,可以在最後呼叫的"()"傳入我們需要的引數,比如jQuery就把window物件作為實參傳入了立即函式內部。
(二)使用時機
什麼時候需要用到立即執行函式呢?
1.當我們需要寫一個js檔案,並且複用率很高的時候,建議使用。
2.如果宣告的函式只需要呼叫一次,建議使用。
3.獨立模組,這個和第一點差不多。單獨提出來,是想強調一下立即執行函式的好處,開發時,它能做到各模組的低耦合,減少對全域性作用域的汙染。
(三)例項及好處
無例項,無真相。找什麼例項好呢?還是我們的jQuery吧。
1 /*! 2 * jQuery JavaScript Library v1.4.4 3 * http://jquery.com/ 4 * 5 * Copyright 2010, John Resig 6 * Dual licensed under the MIT or GPL Version 2 licenses. 7 * http://jquery.org/license 8 * 9 * Includes Sizzle.js 10 * http://sizzlejs.com/ 11 * Copyright 2010, The Dojo Foundation 12 * Released under the MIT, BSD, and GPL Licenses. 13 * 14 * Date: Thu Nov 11 19:04:53 2010 -0500 15 */ 16 (function( window, undefined ) { 17 18 // Use the correct document accordingly with window argument (sandbox) 19 var document = window.document; 20 var jQuery = (function() { 21 22 // Define a local copy of jQuery 23 var jQuery = function( selector, context ) { 24 // The jQuery object is actually just the init constructor 'enhanced' 25 return new jQuery.fn.init( selector, context ); 26 }, 27 28 // Map over jQuery in case of overwrite 29 _jQuery = window.jQuery, 30 31 // Map over the $ in case of overwrite 32 _$ = window.$, 33 34 // A central reference to the root jQuery(document) 35 rootjQuery, 36 37 // A simple way to check for HTML strings or ID strings 38 // (both of which we optimize for) 39 quickExpr = /^(?:[^<]*(<[/w/W]+>)[^>]*$|#([/w/-]+)$)/, 40 41 // Is it a simple selector 42 isSimple = /^.[^:#/[/.,]*$/, 43 44 // Check if a string has a non-whitespace character in it 45 rnotwhite = //S/, 46 rwhite = //s/, 47 48 // Used for trimming whitespace 49 trimLeft = /^/s+/, 50 trimRight = //s+$/, 51 52 // Check for non-word characters 53 rnonword = //W/, 54 55 // Check for digits 56 rdigit = //d/, 57 58 // Match a standalone tag 59 rsingleTag = /^<(/w+)/s*//?>(?:<///1>)?$/, 60 61 // JSON RegExp 62 rvalidchars = /^[/],:{}/s]*$/, 63 rvalidescape = ///(?:["////bfnrt]|u[0-9a-fA-F]{4})/g, 64 rvalidtokens = /"[^"///n/r]*"|true|false|null|-?/d+(?:/./d*)?(?:[eE][+/-]?/d+)?/g, 65 rvalidbraces = /(?:^|:|,)(?:/s*/[)+/g, 66 67 // Useragent RegExp 68 rwebkit = /(webkit)[ //]([/w.]+)/, 69 ropera = /(opera)(?:.*version)?[ //]([/w.]+)/, 70 rmsie = /(msie) ([/w.]+)/, 71 rmozilla = /(mozilla)(?:.*? rv:([/w.]+))?/, 72 73 // Keep a UserAgent string for use with jQuery.browser 74 userAgent = navigator.userAgent, 75 76 // For matching the engine and version of the browser 77 browserMatch, 78 79 // Has the ready events already been bound? 80 readyBound = false, 81 82 // The functions to execute on DOM ready 83 readyList = [], 84 85 // The ready event handler 86 DOMContentLoaded, 87 88 // Save a reference to some core methods 89 toString = Object.prototype.toString, 90 hasOwn = Object.prototype.hasOwnProperty, 91 push = Array.prototype.push, 92 slice = Array.prototype.slice, 93 trim = String.prototype.trim, 94 indexOf = Array.prototype.indexOf, 95 96 // [[Class]] -> type pairs 97 class2type = {}; 98 99 jQuery.fn = jQuery.prototype = { 100 init: function( selector, context ) { 101 var match, elem, ret, doc; 102 103 // Handle $(""), $(null), or $(undefined) 104 if ( !selector ) { 105 return this; 106 } 107 108 // Handle $(DOMElement) 109 if ( selector.nodeType ) { 110 this.context = this[0] = selector; 111 this.length = 1; 112 return this; 113 } 114 115 // The body element only exists once, optimize finding it 116 if ( selector === "body" && !context && document.body ) { 117 this.context = document; 118 this[0] = document.body; 119 this.selector = "body"; 120 this.length = 1; 121 return this; 122 } 123 124 // Handle HTML strings 125 if ( typeof selector === "string" ) { 126 // Are we dealing with HTML string or an ID? 127 match = quickExpr.exec( selector ); 128 129 // Verify a match, and that no context was specified for #id 130 if ( match && (match[1] || !context) ) { 131 132 // HANDLE: $(html) -> $(array) 133 if ( match[1] ) { 134 doc = (context ? context.ownerDocument || context : document); 135 136 // If a single string is passed in and it's a single tag 137 // just do a createElement and skip the rest 138 ret = rsingleTag.exec( selector ); 139 140 if ( ret ) { 141 if ( jQuery.isPlainObject( context ) ) { 142 selector = [ document.createElement( ret[1] ) ]; 143 jQuery.fn.attr.call( selector, context, true ); 144 145 } else { 146 selector = [ doc.createElement( ret[1] ) ]; 147 } 148 149 } else { 150 ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); 151 selector = (ret.cacheable ? ret.fragment.cloneNode(true) : ret.fragment).childNodes; 152 } 153 154 return jQuery.merge( this, selector ); 155 156 // HANDLE: $("#id") 157 } else { 158 elem = document.getElementById( match[2] ); 159 160 // Check parentNode to catch when Blackberry 4.6 returns 161 // nodes that are no longer in the document #6963 162 if ( elem && elem.parentNode ) { 163 // Handle the case where IE and Opera return items 164 // by name instead of ID 165 if ( elem.id !== match[2] ) { 166 return rootjQuery.find( selector ); 167 } 168 169 // Otherwise, we inject the element directly into the jQuery object 170 this.length = 1; 171 this[0] = elem; 172 } 173 174 this.context = document; 175 this.selector = selector; 176 return this; 177 } 178 179 // HANDLE: $("TAG") 180 } else if ( !context && !rnonword.test( selector ) ) { 181 this.selector = selector; 182 this.context = document; 183 selector = document.getElementsByTagName( selector ); 184 return jQuery.merge( this, selector ); 185 186 // HANDLE: $(expr, $(...)) 187 } else if ( !context || context.jquery ) { 188 return (context || rootjQuery).find( selector ); 189 190 // HANDLE: $(expr, context) 191 // (which is just equivalent to: $(context).find(expr) 192 } else { 193 return jQuery( context ).find( selector ); 194 } 195 196 // HANDLE: $(function) 197 // Shortcut for document ready 198 } else if ( jQuery.isFunction( selector ) ) { 199 return rootjQuery.ready( selector ); 200 } 201 202 if (selector.selector !== undefined) { 203 this.selector = selector.selector; 204 this.context = selector.context; 205 } 206 207 return jQuery.makeArray( selector, this ); 208 }, 209 210 // Start with an empty selector 211 selector: "", 212 213 // The current version of jQuery being used 214 jquery: "1.4.4", 215 216 // The default length of a jQuery object is 0 217 length: 0, 218 219 // The number of elements contained in the matched element set 220 size: function() { 221 return this.length; 222 }, 223 224 toArray: function() { 225 return slice.call( this, 0 ); 226 }, 227 228 // Get the Nth element in the matched element set OR 229 // Get the whole matched element set as a clean array 230 get: function( num ) { 231 return num == null ? 232 233 // Return a 'clean' array 234 this.toArray() : 235 236 // Return just the object 237 ( num < 0 ? this.slice(num)[ 0 ] : this[ num ] ); 238 }, 239 240 // Take an array of elements and push it onto the stack 241 // (returning the new matched element set) 242 pushStack: function( elems, name, selector ) { 243 // Build a new jQuery matched element set 244 var ret = jQuery(); 245 246 if ( jQuery.isArray( elems ) ) { 247 push.apply( ret, elems ); 248 249 } else { 250 jQuery.merge( ret, elems ); 251 } 252 253 // Add the old object onto the stack (as a reference) 254 ret.prevObject = this; 255 256 ret.context = this.context; 257 258 if ( name === "find" ) { 259 ret.selector = this.selector + (this.selector ? " " : "") + selector; 260 } else if ( name ) { 261 ret.selector = this.selector + "." + name + "(" + selector + ")"; 262 } 263 264 // Return the newly-formed element set 265 return ret; 266 }, 267 268 // Execute a callback for every element in the matched set. 269 // (You can seed the arguments with an array of args, but this is 270 // only used internally.) 271 each: function( callback, args ) { 272 return jQuery.each( this, callback, args ); 273 }, 274 275 ready: function( fn ) { 276 // Attach the listeners 277 jQuery.bindReady(); 278 279 // If the DOM is already ready 280 if ( jQuery.isReady ) { 281 // Execute the function immediately 282 fn.call( document, jQuery ); 283 284 // Otherwise, remember the function for later 285 } else if ( readyList ) { 286 // Add the function to the wait list 287 readyList.push( fn ); 288 } 289 290 return this; 291 }, 292 293 eq: function( i ) { 294 return i === -1 ? 295 this.slice( i ) : 296 this.slice( i, +i + 1 ); 297 }, 298 299 first: function() { 300 return this.eq( 0 ); 301 }, 302 303 last: function() { 304 return this.eq( -1 ); 305 }, 306 307 slice: function() { 308 return this.pushStack( slice.apply( this, arguments ), 309 "slice", slice.call(arguments).join(",") ); 310 }, 311 312 map: function( callback ) { 313 return this.pushStack( jQuery.map(this, function( elem, i ) { 314 return callback.call( elem, i, elem ); 315 })); 316 }, 317 318 end: function() { 319 return this.prevObject || jQuery(null); 320 }, 321 322 // For internal use only. 323 // Behaves like an Array's method, not like a jQuery method. 324 push: push, 325 sort: [].sort, 326 splice: [].splice 327 }; 328 329 // Give the init function the jQuery prototype for later instantiation 330 jQuery.fn.init.prototype = jQuery.fn; 331 332 jQuery.extend = jQuery.fn.extend = function() { 333 var options, name, src, copy, copyIsArray, clone, 334 target = arguments[0] || {}, 335 i = 1, 336 length = arguments.length, 337 deep = false; 338 339 // Handle a deep copy situation 340 if ( typeof target === "boolean" ) { 341 deep = target; 342 target = arguments[1] || {}; 343 // skip the boolean and the target 344 i = 2; 345 } 346 347 // Handle case when target is a string or something (possible in deep copy) 348 if ( typeof target !== "object" && !jQuery.isFunction(target) ) { 349 target = {}; 350 } 351 352 // extend jQuery itself if only one argument is passed 353 if ( length === i ) { 354 target = this; 355 --i; 356 } 357 358 for ( ; i < length; i++ ) { 359 // Only deal with non-null/undefined values 360 if ( (options = arguments[ i ]) != null ) { 361 // Extend the base object 362 for ( name in options ) { 363 src = target[ name ]; 364 copy = options[ name ]; 365 366 // Prevent never-ending loop 367 if ( target === copy ) { 368 continue; 369 } 370 371 // Recurse if we're merging plain objects or arrays 372 if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { 373 if ( copyIsArray ) { 374 copyIsArray = false; 375 clone = src && jQuery.isArray(src) ? src : []; 376 377 } else { 378 clone = src && jQuery.isPlainObject(src) ? src : {}; 379 } 380 381 // Never move original objects, clone them 382 target[ name ] = jQuery.extend( deep, clone, copy ); 383 384 // Don't bring in undefined values 385 } else if ( copy !== undefined ) { 386 target[ name ] = copy; 387 } 388 } 389 } 390 } 391 392 // Return the modified object 393 return target; 394 }; 395 396 jQuery.extend({ 397 noConflict: function( deep ) { 398 window.$ = _$; 399 400 if ( deep ) { 401 window.jQuery = _jQuery; 402 } 403 404 return jQuery; 405 }, 406 407 // Is the DOM ready to be used? Set to true once it occurs. 408 isReady: false, 409 410 // A counter to track how many items to wait for before 411