立即执行函数

定义

此类函数没有声明,在一次执行后即释放。是针对初始化功能的函数,适合做初始化工作。

立即执行函数和普通函数的唯一区别就在此:前者在一次执行后会释放,不能被反复调用,而普通函数会一直等待被执行。除此之外没有任何其他区别。

格式

第一种:

1
2
3
(function () {
// 函数体
})();

第二种:(W3C 建议使用)

1
2
3
(function () {
// 函数体
}());

如果需要返回值:

1
2
3
4
5
6
// num 用于接收返回值

var num = (function (形参) {
...
return ..;
}(实参));

用法

只有表达式才能被执行符号()执行。

1
2
3
4
5
6
// 函数声明:
function test(){
var d = 2 + 2;
console.log(d);
}();
// 报错:Uncaught SyntaxError: Unexpected token )
1
2
3
4
5
6
function test(){
var d = 2 + 2;
console.log(d);
}

test();// 正常执行,test 就是一个表达式

能被执行符号执行的表达式,函数名会被自动忽略。

1
2
3
4
5
var test = function () {
console.log('a'); // 输出 a
}();

console.log(test); // 输出 undefined

有一种情况,函数声明和执行符号在一起,执行符号内填写了实参,系统运行时不会报错,会将执行符号识别为逗号运算符和小括号一起组成的表达式,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function test(a, d, c, d){
console.log(a + b + c + d);
}(1, 2, 3, 4);

//系统会认为:
//这个是函数声明:
function test(a, b, c, d) {
console.log(a + b + c + d);
}

//这个是表达式(逗号运算符的表现形式):
(1, 2, 3, 4);

//最终,不会输出结果,也不会报错。

要在函数体后面加括号就能立即调用,则这个函数必须是函数表达式,不能是函数声明。(函数声明会被 js 解析时提升)

()、!、+、-、= 等运算符,都可以将函数声明转换成表达式,然后在后面加执行符号(),就可以变成立即执行函数。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// () 运算符(包含着 function 的最外面那个括号)
(function(a){
console.log(a); // 123
})(123);

// () 运算符(包含着 function 和执行符号的最外面那个括号)
(function(a){
console.log(a); // 1234
}(1234));

// ! 运算符
! function(a){
console.log(a); // 12345
}(12345);

// + 运算符
+ function(a){
console.log(a); // 123456
}(123456);

// - 运算符
- function(a){
console.log(a); // 1234567
}(1234567);

// = 运算符
var fn = function(a){
console.log(a); // 12345678
}(12345678);

闭包

概念

内部的函数被保存到外部,一定生成闭包。

该函数在外部执行时,一定能调用它原来的没被保存出去时的函数环境里的变量。

后果

当内部函数被保存到外部时,将会生成闭包。闭包会导致原有作用链不释放,造成内存泄漏 Memory leak(过多占用内存)。

例题

例一:

1
2
3
4
5
6
7
8
9
10
11
12
function a() {                           // a 定义并开始执行  
function b() { // b 定义
var bbb = 234;
console.log(aaa);
}
var aaa = 123;
return b; // b 作为 a 的结果被保存出去,此时 b 带着 a 的作用域链(b 只被定义,没被执行)
} // a 执行完成,它与自己的 AO 链接被砍断(引用地址销毁)

var glob = 100;
var demo = a(); // a 执行后的结果是 b,存到 demo 中
demo(); // 执行 b,执行结果为 123

例二:

1
2
3
4
5
6
7
8
9
10
11
12
function a() { 
var num = 100;
function b() {
num ++;
console.log(num);
}
return b;
}

var demo = a(); // 把 b 的地址引用赋值给 demo
demo(); // 101
demo() ;// 102
  • b 被保存出去的时候带着的是 a 的作用域链。
  • demo 第一次执行时,增加 b 的 AO 到作用域链第 0 位,然后依次向下去找 num。
  • num = 100 在 b 继承的 aAO 里面,所以将其中的 num 修改为 101。
  • 执行完成后,b 的 AO 被销毁,其他的作用域链 b 依然带着。
  • b 第二次执行时,是相同的操作,此时 num 的基础值是 101,执行 b 后 num ++ = 102

解决方法

实例

定义一个空数组 arr = [] (1*)

for 循环将十条数据添加到数组 arr 中,每条数据都是一个 function (2*)

  • arr[i]中的 i 会跟着 for 循环中的 i 变动
  • 而函数function (){console.log(i)}没有在当前执行,所以函数体内部的 i 不会随之变动,这个 i 在函数执行时才会赋值
  • arr[i] = 函数,数组位置会马上变现:arr[0],arr[1]...,函数function()(...)只是单纯赋值给数组,当前不会被执行: arr[0] = function(){console.log(i);} ……
  • i = 10 时,for 循环终止

将 arr 返回到外部,这==十个函数带着 test 的作用域链==被保存出去 (3*)

  • arr = [function(){console.log(i);}, function(){console.log(i);},..., function(){console.log(i);}]

test 函数执行完成,它与自己的 AO 链接被砍断(地址引用销毁),此时 i 为 10 (4*)

分别执行保存出去的十个函数 (5*)

这十个函数都与 test 函数形成了闭包,它们共用的是 test 的作用域链(变量arri),所以在外部访问 i 变量时,访问的是 test 里的 i (也就是同一个 i ),它们自己的函数内部没有变量。

此时的 i 变量,是在 test 函数执行完成后的 i ,即 i = 10

arr 中的十个函数都是为了输出 i,在外部去访问 i 时,i 为 10,所以会输出十个 10

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function test() {
var arr = []; // 1*
for(var i = 0; i < 10; i++){ // 2*
arr[i] = function () {
console.log(i);
}
}
return arr; // 3*
} // 4*

var myArr = test();
for(var j = 0; j < 10; j++) {
myArr[j](); // 5*
}

// 结果:10 10 10 10 10 10 10 10 10 10

解决:立即执行函数

引入立即执行函数(function (j){...}(i)); (1*)

for 循环执行,i = 0 时,立即执行函数中的实参 i 为 0,赋值给形参 j,此时 j = 0,依此类推,每次 i 的值都会被赋到相应的 j 上面 (2*)

  • for 循环执行 10 次,会产生 10 个独立的不同的立即执行函数
  • arr[0] = function(){console.log(j);}arr[1] = function(){console.log(j);}……
  • 同样,立即执行函数内的函数function (){...}没有在当前执行
  • i = 10 时,for 循环终止

将 arr 返回到外部,十个函数带着各自的立即执行函数的作用域链被保存出去 (3*)

  • arr = [function(){console.log(j);}, function(){console.log(j);},..., function(){console.log(j);}]

test 函数执行完成,它与自己的 AO 链接被砍断(地址引用销毁),此时 i 为 10 (4*)

分别执行这十个函数 (5*)

被保存出去的十个函数都与各自的立即执行函数形成了闭包,它们带出去的是各自的立即执行函数(function(j){...}(i));的作用域链(变量jarri),iarr在 test 函数的 AO 中,j在立即执行函数的 AO 中。所以在外部访问 i 变量时,访问的依然是 test 函数的 i ,而 j 变量则是各自立即执行函数里的 j

i = 0 时,j = 0,即第一个立即执行函数中的 j 为 0,执行完成后销毁。i = 1 时,又是一个全新的立即执行函数,此时 j = 1。依此类推,被保存出去的函数,对应的是不同的 j ,相同的 i

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function test() {
var arr = [];
for(var i = 0; i < 10; i++) {
(function (j) { // 1*
arr[j] = function () {
console.log(j);
}
}(i)); // 2*
}
return arr; // 3*
} // 4*

var myArr = test();
for(var x = 0; x < 10; x++){
myArr[x](); // 5*
}

// 结果:0 1 2 3 4 5 6 7 8 9

关键点:将每一次 i 的值立马套现到 j 里面,然后让每个函数带着不同的 j 走

作用

实现共有变量

如:函数累加器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function add() {
var count = 0;
function demo() {
count ++; // demo 函数中没有变量,这个是针对 add 函数里的 count 变量的操作
console.log(count);
}
return demo;
}

var counter = add();
counter();// 1
counter();// 2
counter();// 3
counter();// 4

做缓存(存储结构)

函数 a 和 b 被保存出去的时候,带着的是同一套作用域链(都是 test 的,在同一个房间,同一个地址引用)

a 执行 时 num 被修改为 101,那么 b 作用域链中的 num 也会变为 101

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function test() {
var num = 100;
function a () {
// a defined a.[[scope]] 0: testAO
// 1: GO
num ++;
console.log(num);
}
function b () {
// b defined b.[[scope]] 0: testAO
// 1: GO
num --;
console.log(num);
}
return [a,b];
}

var myArr = test();

myArr[0](); // a 函数执行 -> 结果 101

// a doing a.[[scope]] 0: aAO
// 1: testAO *
// 2: GO

myArr[1](); // b 函数执行 -> 结果 100

// b doing b.[[scope]] 0: bAO
// 1: testAO *
// 2: GO

// * b 使用的 testAO 是 a 执行后修改的 testAO

被保存出去的函数内部没有定义变量,修改的都是 test 函数里的变量 food

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function eater() {
var food = 'apple';
var obj = {
eatFood : function () {
if(food != ''){
console.log('I am eating ' + food);
food = '';
}else{
console.log('Nothing!');
}
},
pushFood : function (myFood) {
food = myFood;
}
}
return obj;
}

var person = eater();

person.eatFood(); // I am eating apple

person.eatFood(); // Nothing!

person.pushFood('banana');
person.eatFood(); // I am eating banana

person.eatFood(); // Nothing!

实现封装,属性私有化

隐式对象this被保存出去,它包含的两个方法(函数)changeUseCard以及sayUseCard带着同一套作用域链出去(函数BankCard的)。

变量useCard在函数外部是无法访问到的,但是函数内的方法可以直接使用它。

它成为对象和原有函数形成闭包后存在的一个私有变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function BankCard(name, card){

var useCard = 'Visa'; // 构造函数内声明并赋值的变量

this.name = name;
this.card = card;

this.changeUseCard = function(target) {
useCard = target;
}

this.sayUseCard = function() {
console.log(useCard);
}
}

var dou = new BankCard('dou', 'Master');

console.log(dou); // {name: 'dou', card: 'Master', changeUseCard: f, sayUseCard: f}

console.log(dou.useCard); // undefined

dou.sayUseCard(); // Visa

dou.changeUseCard('Apple Card');

dou.sayUseCard(); // Apple Card

模块化开发

实现模块化开发,防止污染全局变量。(详见命名空间)