felix-cao / Blog

A little progress a day makes you a big success!
31 stars 4 forks source link

JavaScript 立即执行函数 IIFE #92

Open felix-cao opened 5 years ago

felix-cao commented 5 years ago

一、 概述

1.1、 概念

Imdiately Invoked Function Expression,简称IIFE,中文意思是立即执行函数,也叫自执行函数,声明一个匿名函数,马上调用这个匿名函数。也可以说成,立即执行函数是一种语法,可以让你的函数在定义以后立即被执行。

1.2、 用途

用于创建独立的作用域,封装一些临时变量或者局部变量,避免了污染全局变量。

这也是后来 CommonJSES6 都纷纷提出模块化的原因,请阅读:

二、写法

立即执行函数的写法分三步走:

《JavaScript 圆括号操作符和void操作符》一文中我们知道是:把函数放在()里会返回函数本身,如果圆括号紧跟函数后面表示调用函数,即对函数求值。

2.1、为什么这么写?

上面的第二步为什么要用圆括号括起来,这是为了兼容 JavaScript 定义的语法规则。 不能在函数的定义后面直接加圆括号,这会产生语法错误。

function() {console.log('匿名')} () // 报错

产生这个错误的原因,function 这个关键字既可以当做语句,也可以当做表达式。

// 语句
function fn() {};
// 表达式
var fn = function (){}

为了避免解析上的歧义,javascript 引擎规定,如果 function 关键字出现在行首,一律解释成语句。因此,javascript 引擎看到行首是 function 关键字之后,认为这一段都是函数的定义,不应该以圆括号结尾,所以就报错了。

解决方法就是不要让 function 出现在行首,让引擎将其理解成一个表达式。最简单的处理,就是将其放在一个圆括号里。

最常用的两种办法

(function(){ /* code */ }()); 
(function(){ /* code */ })(); 

其他写法

var i = function(){ return 10; }();
true && function(){ /* code */ }();
0, function(){ /* code */ }();

!function(){ /* code */ }();
~function(){ /* code */ }();
-function(){ /* code */ }();
+function(){ /* code */ }();

new function(){ /* code */ };
new function(){ /* code */ }(); 

2.2、块级作用域

JavaScript 中是没有块级作用域的(直到 ES6, 增加了 let, const),立即执行函数也可以看做是模仿了块级作用域的实现

(function(){  
    //这里是块级作用域  
})();  

三、用途分析

面向对象编程语言的对象有三大特性,分别是多态、继承、封装,封装的主要目的是隐藏数据。在类式编程语言中,一般都提供 private, protected, public关键词来修饰类的属性与方法,从而决定是否隐藏,但是 JavaScript 并没有类的概念,它的封装就是通过立即执行函数来实现的。

我们用一个案例来更直观地说明 IIFE 的用途:假设有一个需求,每次调用函数,都返回加1的一个数字(数字初始值为0),下面给出三种实现方式。

3.1、 利用全局变量

一般情况下,我们会使用全局变量来保存该数字状态

var a = 0;
function add(){
    return ++a;
}
console.log(add()); // 1
console.log(add()); // 2

3.2、 使用函数自定义属性

但上面的方法中,变量 a 实际上只和 add 函数相关,却声明为全局变量,不太合适。

将变量 a 更改为函数的自定义属性更为恰当

function add(){
    return ++add.count;
}
add.count = 0;
console.log(add()); // 1
console.log(add()); // 2

函数也是对象,对象可以动态添加属性和方法。

3.3、 使用立即执行函数

上面的方法还是有问题,有些代码可能会无意中将 add.count 重置。

使用 IIFE 把计数器变量 count 保存为私有变量更安全,同时也可以减少对全局空间的污染

var add = (function(){
    var count = 0; // private
    return function(){
        return ++count; 
    }
})();
console.log(add()); // 1
console.log(add()); // 2

需要注意的是这里有一个语法坑:

var a = function(){
    return 1;
}
(function(){
    console.log(a()); // 报错
})();

这是因为第三行没有加分号,浏览器将上面代码解释成如下所示

var a = function(){
    return 1;
}(function(){
    console.log(a()); // 报错
})();

如果加上分号,就不会出错了

var a = function(){
    return 1;
};
(function(){
    console.log(a()); // 1
})();

四、带参数的立即执行函数

立即执行函数也是函数,是函数就可以带参数。

var age = 18;
(function($){
   console.log('my age is: ',$)
})(age);
// my age is:  18

把全局作用域下声明的 age 作为匿名函数的参数,匿名函数用变量 $ 接收。

五、立即执行函数的返回值

3.3 就是利用了立即执行函数返回一个函数。

var age = 18;
var fn = (function($){
   return function() {
     console.log('my age is: ',$)
   }
})(age);

fn()
// my age is:  18