ECMAScript笔记(四)闭包
立即执行函数
定义
此类函数没有声明,在一次执行后即释放。是针对初始化功能的函数,适合做初始化工作。
立即执行函数和普通函数的唯一区别就在此:前者在一次执行后会释放,不能被反复调用,而普通函数会一直等待被执行。除此之外没有任何其他区别。
格式
第一种:
1 | (function () { |
第二种:(W3C 建议使用)
1 | (function () { |
如果需要返回值:
1 | // num 用于接收返回值 |
用法
只有表达式才能被执行符号()
执行。
1 | // 函数声明: |
1 | function test(){ |
能被执行符号执行的表达式,函数名会被自动忽略。
1 | var test = function () { |
有一种情况,函数声明和执行符号在一起,执行符号内填写了实参,系统运行时不会报错,会将执行符号识别为逗号运算符和小括号一起组成的表达式,如:
1 | function test(a, d, c, d){ |
要在函数体后面加括号就能立即调用,则这个函数必须是函数表达式,不能是函数声明。(函数声明会被 js 解析时提升)
()、!、+、-、= 等运算符,都可以将函数声明转换成表达式,然后在后面加执行符号()
,就可以变成立即执行函数。如:
1 | // () 运算符(包含着 function 的最外面那个括号) |
闭包
概念
内部的函数被保存到外部,一定生成闭包。
该函数在外部执行时,一定能调用它原来的没被保存出去时的函数环境里的变量。
后果
当内部函数被保存到外部时,将会生成闭包。闭包会导致原有作用链不释放,造成内存泄漏 Memory leak(过多占用内存)。
例题
例一:
1 | function a() { // a 定义并开始执行 |
例二:
1 | function a() { |
- 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 的作用域链(变量arr
、i
),所以在外部访问 i 变量时,访问的是 test 里的 i (也就是同一个 i ),它们自己的函数内部没有变量。
此时的 i 变量,是在 test 函数执行完成后的 i ,即 i = 10
arr 中的十个函数都是为了输出 i,在外部去访问 i 时,i 为 10,所以会输出十个 10
1 | function test() { |
解决:立即执行函数
引入立即执行函数(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));
的作用域链(变量j
、arr
、i
),i
和arr
在 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 | function add() { |
做缓存(存储结构)
函数 a 和 b 被保存出去的时候,带着的是同一套作用域链(都是 test 的,在同一个房间,同一个地址引用)
a 执行 时 num 被修改为 101,那么 b 作用域链中的 num 也会变为 101
1 | function test() { |
被保存出去的函数内部没有定义变量,修改的都是 test 函数里的变量 food
1 | function eater() { |
实现封装,属性私有化
隐式对象this
被保存出去,它包含的两个方法(函数)changeUseCard
以及sayUseCard
带着同一套作用域链出去(函数BankCard
的)。
变量useCard
在函数外部是无法访问到的,但是函数内的方法可以直接使用它。
它成为对象和原有函数形成闭包后存在的一个私有变量。
1 | function BankCard(name, card){ |
模块化开发
实现模块化开发,防止污染全局变量。(详见命名空间)