函数 进阶学习之四: 函数和函数表达式的区别 与 IIFE 的原理
一 :函数和函数表达体
1.前文 提到声明一个变量 和 定义一个变量,那么对于函数,也存在这个问题,这里 要区分 函数 和 函数表达体
function keqing(){ //函数 alert('Hi~'); } var keqing = function(){ //函数表达式 alert('Hi~') }
function foo() {...} // 这是定义,Declaration;定义只是让解释器知道其存在,但是不会运行。 foo(); // 这是语句,Statement;解释器遇到语句是会运行它的。
2.函数的函数声明有一个重要特征 —— 函数声明提升(即前文提到的Hoisting)
keqing(); // 正常执行 弹出Hi~ function keqing(){ alert('Hi~'); } 函数:在读取执行代码之前会先读取函数声明 keqing(); //报错 Uncaught TypeError: keqing is not a function var keqing = function(){ alert('Hi~'); } 函数表达式:没有函数声明提升,在执行前必须先赋值
二:IIFE 的真相
在Bootstrap源码(具体请看《Bootstrap源码解析》)和其他jQuery插件经常看到如下的写法:
+function ($) { }(window.jQuery);
这种写法就是 IIFE (Imdiately Invoked Function Expression 立即执行的函数表达式)
结论1:
函数表达式中的函数可以为匿名函数,也可以有函数名,但是该函数实际上不能直接使用,只能通过表达式左边的变量 a 来调用。
var a = function(){ alert(0); } var b = new a(); //可以弹出 0
直接 a(); 也可以执行 弹出0.个人觉着还是 直接执行a();比较直观,
结论2:
函数声明时必须有函数名。
三:使用
那么为什么要 IIFE?
1.传统的方法啰嗦,定义和执行分开写;
2.传统的方法直接污染全局命名空间(浏览器里的 global 对象,如 window)
于是,开发者们想找一个可以解决以上问题的写法:function foo(...){}();
但是:这样写是不行的
因为 function foo(...){} 这个部分只是一个声明,函数并没有执行,对于解释器来说,就好像你写了一个字符串 "function foo(...){}",它需要使用解析函数,比如 eval() 来执行它才可以。
所以把 () 直接放在声明后面是不会执行,这是错误的语法。
如何把它变得正确?说起来也简单,只要把 声明 变成 表达式(Expression) 就可以了,
最常见的办法是把函数声明用一对 () 包裹起来
例如
(function foo() {...}) // 这里是故意换行,实际上可以和下面的括号连起来 (); //这就等价于: var foo = function () {...}; // 这就不是定义,而是表达式了。 foo();
注意:function foo(...){}();
也可以直接用括号包起来,这也是一种等价的表达式:(function foo(){...}());
所以:以后见到(function foo(){...}());和 (function foo(){...})()是一样的。没啥区别!!
一般用(function (){}) 还有个作用,就是 避免全局变量
总计一下实现方法:
方法:通过一元操作符+变成了函数表达式。也可以使用 - ~ !等其他一元运算符或者括号,目的是为了引导解析器,指明运算符附近是一个表达式。以下是三种经典方式 :
+function () { }; (function () { }); void function() { };
然后,在末尾加个();函数表达式通过 末尾的() 来调用并运行,就不会报错了。就是一个IIFE。
+function () { }(); (funtion () { })();
四:使用IIFE的好处
4.1、减少作用域查找。使用IIFE的一个微小的性能优势是通过匿名函数的参数传递常用全局对象window、document、jQuery,在作用域内引用这些全局对象。JavaScript解释器首先在作用域内查找属性,然后一直沿着链向上查找,直到全局范围。将全局对象放在IIFE作用域内提升js解释器的查找速度和性能。
//传递全局对象到IIFE的一段代码示例 // Anonymous function that has three arguments function(window, document, $) { // You can now reference the window, document, and jQuery objects in a local scope }(window, document, window.jQuery); // The global window, document, and jQuery objects are passed into the anonymous function //
4.2、有利于压缩。另一个微小的优势是有利于代码压缩。既然通过参数传递了这些全局对象,压缩的时候可以将这些全局对象匿名为一个字符的变量名(只要这个字符没有被其他变量使用过)。如果上面的代码压缩后会变成这样:
// Anonymous function that has three arguments function(w, d, $) { // You can now reference the window, document, and jQuery objects in a local scope }(window, document, window.jQuery); // The global window, document, and jQuery objects are passed into the anonymous function
4.3、避免全局命名冲突。当使用jQuery的时候,全局的window.jQuery对象 作为一个参数传递给$,在匿名函数内部你再也不需要担心$和其他库或者模板申明冲突
4.4、通过传参的方式,可以灵活的加载第三方插件。
+function( KindEditor){ var editor if(KindEditor){ KindEditor.ready(function(K) { editor = K.create('textarea[data-name="kindeditor"]', { resizeType : 1 }) }) } }(KindEditor || undefined)
4.5结合IIFE的最佳实践,更好的写法是,立即执行document ready
+function ($) { $(function(){ }) }(window.jQuery)
4.6最佳实践
// IIFE - Immediately Invoked Function Expression +function(yourcode) { // The global jQuery object is passed as a parameter yourcode(window.jQuery, window, document); }(function($, window, document) { // The $ is now locally scoped // Listen for the jQuery ready event on the document $(function() { // The DOM is ready! }));
五:自执行匿名函数剖析
(function( window, undefined ) { // code })(window);
1.自执行匿名函数写法的好处:防止变量名冲突
2.为什么传入window:这样传入window可将其从全局变量变为局部变量,在函数作用域内可以直接访问到window,就不用将作用域链退回到顶层作用域了。
3.为什么增加参数undefined:由于undefined在一些情况下有可能会被重写,为确保在自执行匿名函数里的undefined是"真的undefined",就需要增加参数undefined。
总结:
1.虽然叫自执行匿名函数,不要让自执行迷惑了。认为是自己执行!比如我开始进行代码实验的时候写过下面的代码
//实验一 <script type="text/javascript"> (function(){ alert(1) })() </script> //实验二 <script type="text/javascript"> (function(){ function foo(){ alert(1) } })() </script>
实验二是不会弹出1的。。。。。
2.结合目前项目中的js,很少用到这种样式,只有在写插件的时候才这样使用。
所以,到目前为止,先了解了他的原理,如果需要可以回来反复仔细的理解上述的 方法!!