一、闭包预备知识
1. 举个栗子分析执行上下文
let a = 3
function addTwo(x) {
let ret = x + 2
return ret
}
let b = addTwo(a)
console.log(b); // 5
为了理解JavaScript是如何工作的,让我们详细分析一下:
- 在第1行,我们在全局执行上下文中声明了一个新变量a,并将其赋值为3
- 接下来,就变得棘手了,第2行到第5行实际上是在一起的,这里发生了什么?
- 我们在全局执行上下文中声明了一个名为addTwo的新变量,我们给它分配了什么?–> 一个函数定义。
- 现在我们在第6行。
- 它看起来很简单,但是这里有很多东西需要拆开分析。首先,我们在全局执行上下文中声明一个新变量,并将其标记为 b ,变量一经声明,其值即为undefined。
- 接下来,仍然在第6行,我们看到一个赋值操作符。我们准备给变量b赋一个新值,接下来,我们看到一个函数被调用。当您看到一个变量后面跟着一个圆括号 (…) 时,这就是调用函数的信号,接着,每个函数都返回一些东西(值、对象或 undefined),无论从函数返回什么,都将赋值为变量b。
- 但是首先,我们需要调用标记为 addTwo 的函数。JS 将在其全局执行上下文内存中查找名为addTwo 的变量。噢,它找到了,它是在【步骤2(或第2-5行)中定义】的。变量addTwo 包含一个函数定义。
- 注意:变量 a 作为参数传递给函数。
- JS 在全局执行上下文内存中搜索变量 a ,找到它,发现它的值是3,并将数字 3 作为参数传递给函数,准备好执行函数。
- 现在执行上下文将切换,创建了一个新的本地执行上下文,我们将其命名为 addTwo 执行上下文,执行上下文被推送到调用堆栈上。在addTwo执行上下文中,我们要做的第一件事是什么?
- 你可能会说,“在addTwo执行上下文中声明了一个新的变量ret”,这是不对的。
- 正确的答案是:我们需要看函数的参数。在addTwo执行上下文中声明一个新的变量 x,因为值 3 是作为参数传递的,因此变量被赋值为3。
- 下一步,才是在addTwo 执行上下文中声明一个新的变量ret。它的值被设置为undefined(第三行)。
- 仍然是第3行,需要执行一个相加操作。
- 首先我们需要 x 的值,js 会寻找一个变量 x , 它会首先在addTwo执行上下文中查找,找到了一个值为3,。第二个操作数是数字2。两个相加结果为5,就被分配给变量ret。
- 第4行,我们返回变量ret的内容,在addTwo 执行上下文中查找,找到值为5,返回,函数结束。
- 第4 - 5行,函数结束。
- addTwo执行上下文被销毁,变量 x 和 ret 被消去了,它们已经不存在了。addTwo 执行上下文从调用堆栈中弹出,返回值返回给调用上下文,在这种情况下,调用上下文是全局执行上下文,因为函数addTwo 是从全局执行上下文中调用的。
- 现在我们继续第4步的内容,返回值 5 被分配给变量b,此时实际上程序仍在第6行。
- 在第7行,b的值 5 被打印到控制台了。
对于一个非常简单的程序,这是一个非常冗长的解释,我们甚至还没有涉及到闭包。
2. 举个栗子分析词法作用域
这里想说明,我们在函数执行上下文中有变量,在全局执行上下文有变量。JS的复杂之处在于它如何查找变量,
如果在函数执行上下文中找不到变量,它将在调用上下文中寻找它,如果在它的调用上下文中没有找到,就一直往上一级找,直到它在全局执行上下文中国找到为止。(如果最后找不到,它就是undefined)。
1: let val1 = 2
2: function multiplyThis(n) {
3: let ret = n * val1
4: return ret
5: }
6: let multiplied = multiplyThis(6)
7: console.log('example of scope:', multiplied)
下面列个步骤来解释一下:(如果已经熟悉了,请跳过)
- 在全局执行上下文中声明一个新的变量val1,并将其赋值为2。
- 行2 - 5,声明一个新的变量multiplyThis,并给它分配一个函数定义。
- 第6行,声明一个在全局执行上下文multiplied 的新变量。
- 从全局执行上下文内存中查找变量multiplyThis ,并将其作为函数执行,传递数字6作为参数。
- 新函数调用(创建新执行上下文),创建一个新的multiplyThis 函数执行上下文。
- 在multiplyThis 执行上下文中,声明一个变量 n 并将其赋值为6,–> 声明后才会进入函数体内部执行。
- 执行函数回到第3行
- 在multiplyThis 执行上下文中,声明一个变量ret。
- 继续第3行。对两个操作数n和val1进行乘法运算,在multiplyThis执行上下文中查找变量n。
- 我们在步骤6中声明了它,它的内容是数字6。在multiplyThis 执行上下文中查找变量val1。
- multiplyThis 执行上下文没有一个标记为val1的变量, 我们向调用上下文查找,调用上下文是全局执行上下文,在全局执行上下文中寻找【val1】。它在步骤1中定义了,数值是2。
- 继续第3行。将两个操作数相乘并将值赋给ret变量,6 * 2 = 12,ret 现在值是12。
- 返回ret变量,销毁multiplyThis执行上下文及其变量ret和n。变量val1没有被销毁,因为它是全局执行上下文的一部分。
- 回到第6行。在调用上下文中,数字12赋值给multiplied的变量。
- 最后在第7行,我们在控制台中打印multiplied变量的值。
在这个例子中,我们需要记住一个函数可以访问在它的调用上下文中定义的变量,这个就是词法作用域。
3. 返回函数的函数(高阶函数)
在第一个例子中,函数addTwo返回一个数字。请记住,函数可以返回任何东西。让我们看一个返回函数的函数示例,因为这对于下方理解闭包非常重要。
看栗子:
1: let val = 7
2: function createAdder() {
3: function addNumbers(a,) {
4: let ret = a + b
5: return ret
6: }
7: return addNumbers
8: }
9: let adder = createAdder()
10: let sum = adder(val, 8)
11: console.log('example of function returning a function: ', sum)
让我们回到分布分解:
- 第一行,我们在全局执行上下文中声明一个变量val并赋值为7。
- 行2-8行。我们在全局执行上下文中声明一个名为createAdder 的变量,并为其分配了一个函数定义
- 内部的第3-7行描述了上述函数定义,和以前一样,在这一点上,我们没有直接讨论这个函数。我们只是将函数定义存储到createAdder变量中。
- 第9行。
- 我们在全局执行上下文中声明了一个名为adder的新变量,暂时,值为undefined。
- 我们看到括号(),我们需要执行或调用一个函数,查找全局执行上下文的内存并查找名为createAdder的变量,它是在步骤2中创建的,我们调用它。
- 调用函数时,执行到第2行。
- 创建一个新的createAdder执行上下文。我们可以在createAdder的执行上下文中创建自有变量。js引擎将createAdder的上下文添加到调用堆栈。这个函数没有参数,让我们直接跳到它的主题部分。
- 第3 - 6 行,执行到主体函数中。
- 我们有一个新的函数声明,我们在createAdder 执行上下文中创建一个变量addNumber。这很重要,addNumber 只存在于createAdder 执行上下文中。我们将函数定义存储在名为addNumbers 的自有变量中。
- 在第7行,我们返回变量addNumbers 的定义。第4行和第5行括号之间的内容构成该函数定义。
- [return addNumbers] 时,createAdder 执行上下文将被销毁。addNumbers 变量不再存在。但addNumbers 函数定义仍然存在,因为它返回并赋值给了adder 变量。
- 此处很重要!!!! 此时的【adder=createAdder()】实际上它的值是【addNumbers】的函数定义而不是【createAdder】了,adder 现在是一个匿名函数。
- 第10行。我们在全局执行上下文中定义了一个新的变量sum,先赋值为undefined;
- 接下来,我们需要执行一个函数。
- 是名为adder 变量中定义的函数。我们在全局执行上下文中查找它,果然找到了它,这个函数有两个参数。
- 让我们查找这两个参数,第一个是我们在步骤1中定义的变量val,它表示数字7,第二个是数字8。
- 现在我们要执行这个函数,函数定义概述在第3 - 5行,因为这个函数是匿名,为了方便理解,我们暂且叫它adder 吧。这时创建一个adder函数执行上下文,在adder执行上下文中创建了两个新变量a 和 b。它们分别被赋值为7和8,因为这些是我们在上一步传递给函数的参数。
- 执行回到第4行。
- 在adder执行上下文中声明了一个名为ret的新变量。
- 将变量a的内容和变量b的内容相加得15并赋值给ret变量。
- ret 变量从该函数返回。这个匿名函数执行上下文被销毁,从调用堆栈中删除,变量a、b和ret 不再存在。
- 返回值被分配给我们在步骤9中定义的sum变量。
- 我们将sum的值打印到控制台。
如预期,控制台将打印 15。
注意:
① 函数定义可以存储在变量中,【函数定义】在程序调用之前是不可见的;
② 每次调用函数时,都会临时创建一个本地执行上下文。当函数完成时,执行上下文将消失。函数在遇到return 或右括号 } 时执行完成。
高阶函数
高阶函数是什么?
所谓高阶函数,就是一个函数可以接收另一个函数作为参数,或者返回一个函数 —> 常见的高阶函数有:map,reduce,filter,sort等。
var ADD = function add(a) {
return function(b) {
return a+b
}
}
ADD(2)(3)
// 调用ADD(2)(3)即可获得结果
- map
// map接受一个函数作为参数,不改变原来的数组,只是返回一个全新的数组
var arr = [1,2,3,4,5]
var arr1 = arr.map(item => item = 2)
//arr 输出[1,2,3,4,5]
//arr1 输出[2,2,2,2,2]
- reduce
// reduce也是返回一个全新的数组。reduce接受一个函数作为参数,这个函数要有两个形参,代表数组中的前两项,reduce会将这个函数的结果与数组中的第三项再次组成这个函数的两个形参以此类推进行累积操作
var arr = [1,2,3,4,5]
var arr2 = arr.reduce((a,b)=> a+b)
console.log(arr2) // 15
- filter
// filter返回过滤后的数组。filter也接收一个函数作为参数,这个函数将作用于数组中的每个元素,根据该函数每次执行后返回的布尔值来保留结果,如果是true就保留,如果是false就过滤掉(这点与map要区分)
var arr = [1,2,3,4,5]
var arr3 = arr.filter(item => item % 2 == 0)
console.log(arr3)// [2,4]
二、闭包
一个函数和对其周围状态(词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包。
也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在JS中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。
1. 引出闭包概念
① 错误场景
需求:点击某个按钮,提示“点击的是第n个按钮”
<button>测试1</button>
<button>测试2</button>
<button>测试3</button>
<!--
需求: 点击某个按钮, 提示"点击的是第n个按钮"
-->
<script type="text/javascript">var btns = document.getElementsByTagName('button')
//注意[btns]不是一个数组,它是一个伪数组
//每次获取[btns.length]其实都是需要进行计算的(因为它是伪数组)
//所以为了性能更好,在此处赋值,就只需要计算一次
for (var i = 0,length=btns.length; i < length; i++) {
var btn = btns[i]
btn.onclick = function () { //遍历加监听
alert('第'+(i+1)+'个') //结果 全是[4]
}
}</script>
此处错误是:直接修改并使用全局变量【i】,导致for循环结束后,所有点击按钮绑定的弹窗值都是【i+1】
随后调用时,都会找到【i】这个变量,但是此时 i == 3 ,所以所有结果都是4。
② 将变量挂载到自身来解决
解决方式:将btn所对应的下标保存在btn上
<button>测试1</button>
<button>测试2</button>
<button>测试3</button>
<!--
需求: 点击某个按钮, 提示"点击的是第n个按钮"
-->
<script type="text/javascript">var btns = document.getElementsByTagName('button')
for (var i = 0,length=btns.length; i < length; i++) {
var btn = btns[i]
//将btn所对应的下标保存在btn上
btn.index = i
btn.onclick = function () { //遍历加监听
alert('第'+(i+1)+'个') //结果 全是[4]
}
}</script>
将其放在自己的身上,需要时自己找自己拿,这样就能解决。
③ 利用闭包
利用闭包解决
<body>
<button>测试1</button>
<button>测试2</button>
<button>测试3</button>
<script type="text/javascript">//利用闭包
for (var i = 0,length=btns.length; i < length; i++) {
//此处的j是局部的,它将传入的[i]存入局部的[j]中,这样就能实现效果
(function (j) {
var btn = btns[j]
btn.onclick = function () {
alert('第'+(j+1)+'个')
}
})(i)
}</script>
</body>
2. 举个栗子分析理解
① 按照正常逻辑理解
先说,此部分不是按照闭包机制进行理解,所以中途发觉不对(肯定有地方不对劲)请勿钻牛角尖,主要用作后方对照
1: function createCounter() {
2: let counter = 0
3: const myFunction = function() {
4: counter = counter + 1
5: return counter
6: }
7: return myFunction
8: }
9: const increment = createCounter()
10: const c1 = increment()
11: const c2 = increment()
12: const c3 = increment()
13: console.log(‘example increment’, c1, c2, c3)
现在,我们已经从前几个示例中掌握了它的诀窍,让我们按照预期的方式快速执行它: (错误的流程理解,故意按照正常的逻辑流程走,做印证)
- 行 1 - 8。我们在全局执行上下文中创建了一个新的变量createCounter,并赋值了一个的函数定义。
- 第 9 行。
- 我们在全局执行上下文中声明了一个名为increment的新变量。
- 我们需要调用createCounter函数并将其返回值赋给increment变量。
- 返回执行 行 1 - 8。调用函数,创建新的本地执行上下文。
- 第 2 行。在本地执行上下文中,声明一个名为counter的新变量并赋值为 0;
- 行 3 - 6。声明一个名为myFunction的新变量,变量在本地执行上下文中声明,变量的内容是为第 4 行和第 5 行所定义。
- 第 7 行。返回myFunction变量的内容,删除本地执行上下文。变量myFunction和counter不再存在。此时控制权回到了调用上下文。
- 再次回到 第 9 行
- 在调用上下文(全局执行上下文)中,createCounter返回的值赋给了increment,变量increment现在包含一个函数定义内容为createCounter返回的函数。
- 它不再标记为myFunction,但它的定义是相同的。在全局上下文中,它是的标记为 labeledincrement。
- 第 10 行。声明一个新变量(c1)。
- 继续第 10 行。查找increment变量,它是一个函数并调用它。它包含前面返回的函数定义,如第 4-5 行所定义的。
- 创建一个新的执行上下文。没有参数。开始执行函数。
- 回到 第 4 行。
- counter=counter + 1。在本地执行上下文中查找counter变量。
- 我们只是创建了那个上下文,从来没有声明任何局部变量。让我们看看全局执行上下文。这里也没有counter变量。
- Javascript 会将其计算为 counter = undefined + 1,声明一个标记为counter的新局部变量,并将其赋值为 number 1,因为 undefined 被当作值为 0。 -->此处是错误的哦,别钻牛角尖,正确的理解在下方,此处是做错误对比
- 第 5 行。我们变量counter的值(1),我们销毁本地执行上下文和counter变量。
- 回到第 10 行。返回值(1)被赋给 c1。
- 第 11 行。重复步骤 10-14,c2 也被赋值为 1。
- 第 12 行。重复步骤 10-14,c3 也被赋值为 1。
- 第 13 行。我们打印变量 c1 c2 和 c3 的内容。
你自己试试,看看会发生什么。你会将注意到,它并不像从我上面的解释中所期望的那样记录 1,1,1。而是记录 1,2,3。这个是为什么?
② 正确的理解
不知怎么滴,increment函数记住了那个cunter的值。这是怎么回事?
- counter是全局执行上下文的一部分吗?
- 尝试 console.log(counter),得到undefined的结果,显然不是这样的。
- 也许,当你调用increment时,它会以某种方式返回它创建的函数(createCounter)?
- 这怎么可能呢?变量increment包含函数定义,而不是函数的来源,显然也不是这样的。
- 所以一定有另一种机制。闭包,我们终于找到了,丢失的那块。
- 它是这样工作的,无论何时声明新函数并将其赋值给变量,都要存储函数定义和闭包。闭包包含在函数创建时作用域中的所有变量,它类似于背包。函数定义附带一个小背包,它的包中存储了函数定义创建时作用域中的所有变量。
所以我们上面的解释都是错的,让我们再试一次,但是这次是正确的。
1: function createCounter() {
2: let counter = 0
3: const myFunction = function() {
4: counter = counter + 1
5: return counter
6: }
7: return myFunction
8: }
9: const increment = createCounter()
10: const c1 = increment()
11: const c2 = increment()
12: const c3 = increment()
13: console.log('example increment', c1, c2, c3)
- 同上,第1-8行。我们在全局执行上下文中创建了一个新的变量createCounter,它得到了指定的函数定义。
- 同上,第9行。
- 我们在全局执行上下文中声明了一个名为increment的新变量。
- 我们需要调用createCounter函数并将其返回值赋给increment变量。
- 同上,第1-8行。调用函数,创建新的本地执行上下文。
- 第2行。在本地执行上下文中,声明一个名为counter的新变量并赋值为 0 。
- 第3-6行。声明一个名为myFunction的新变量,变量在本地执行上下文中声明,变量的内容是另一个函数定义。如第4行和第5行所定义,现在我们还创建了一个闭包,并将其作为函数定义的一部分。闭包包含作用域中的变量,在本例中是变量counter(值为0)。
- 第7行。返回myFunction变量的内容,删除本地执行上下文。myFunction和counter不再存在。控制权交给了调用上下文,我们返回函数定义和它的闭包,闭包中包含了创建它时在作用域内的变量。
- 回到第9行。
- 在调用上下文(全局执行上下文)中,createCounter返回的值被指定为increment
- 变量increment现在包含一个函数定义(和闭包),由createCounter返回的函数定义,它不再标记为myFunction,但它的定义是相同的,在全局上下文中,称为increment。
- 第10行。声明一个新变量c1。
- 继续第10行。查找变量increment,它是一个函数,调用它。它包含前面返回的函数定义,如第4-5行所定义的。(它还有一个带有变量的闭包)。
- 创建一个新的执行上下文,没有参数,开始执行函数。
- 第4行。[counter = counter + 1],寻找变量 [counter],在查找本地或全局执行上下文之前,让我们检查一下闭包,瞧,闭包包含一个名为[counter]的变量,其值为0。在第4行表达式之后,它的值被设置为1。它再次被储存在闭包里,闭包现在包含值为1的变量 [counter]。
- 第5行。我们返回counter的值,销毁本地执行上下文。
- 回到第10行。返回值1被赋给变量c1。
- 第11行。我们重复步骤10-14。这一次,在闭包中此时变量counter的值是1。它在第12行设置的,它的值被递增并以2的形式存储在递增函数的闭包中,c2被赋值为2。
- 第12行。重复步骤10-14行,c3被赋值为3。
- 第13行。我们打印变量c1 c2和c3的值。
你此时可能会问,是否有任何函数具有闭包,甚至是在全局范围内创建的函数?
答案是肯定的。在全局作用域中创建的函数创建闭包,但是由于这些函数是在全局作用域中创建的,所以它们可以访问全局作用域中的所有变量,闭包的概念并不重要。
但当函数返回函数时,闭包的概念就变得更加重要了。返回的函数可以访问不属于全局作用域的变量,但它们仅存在于其闭包中。
3. 常见的闭包
① 将函数作为另外一个函数的返回值
// 1. 将函数作为另一个函数的返回值
function fn1() {
var a = 2
function fn2() {
a++
console.log(a)
}
return fn2
}
var f = fn1()
f() // 3
f() // 4
② 将函数作为实参传递给另一个函数调用
// 2. 将函数作为实参传递给另一个函数调用
function showDelay(msg,) {
setTimeout(function () {
alert(msg)
}, time)
}
showDelay('atguigu', 2000)
③ 高阶函数与柯里化
- 从ES6高阶箭头函数理解函数柯里化(运用到闭包)
// 1. 例子:
let add1 = a => b => a + b
// 2.以上是一个很简单的相加函数,把它转化为es5的写法如下:
function add2(a) {
return function(b) {
return a + b
}
}
var add3 = add2(3) // add3 表示一个指向函数的变量 可以当成函数调用名来用
add3(4) === 3 + 4 // true
// 3. 再简化一下,可以写成如下形式:
let add = function(a) {
var param = a;
var innerFun = function(b) {
return param + b;
}
return innerFun;
}
// 4. 虽然好像没什么意义,但是很显然上述使用了闭包,而且该函数返回值是一个函数。
// 其实,这就是高阶函数的定义:以函数为参数或者返回值是函数的函数。
- 柯里化
关键就是理解柯里化,其实可以把它理解成,柯里化后,将第一个参数变量存在函数里面了(闭包),然后本来需要n个参数的函数可以变成只需要剩下的参数(n - 1个)就可以调用,比如:
let add = x => y => x + y
let add2 = add(2)
-*----------------------------------
本来完成 add 这个操作,应该是这样调用
let add = (x,) => x + y
add(2,3)
----------------------------------
1. 而现在 add2 函数完成同样操作只需要一个参数,这在函数式编程中广泛应用。
let add = x => y => x + y
let add2 = add(2)
2.详细解释一下,就是 add2 函数 等价于 有了 x 这个闭包变量的 y => x + y 函数,并且此时 x = 2,所以此时调用
add2(3) === 2 + 3
- 总结
- 如果是a => b => c => {xxx}这种多次柯里化的,如何理解?
理解:前n - 1次调用,其实是提前将参数传递进去,并没有调用最内层函数体,最后一次调用才会调用最内层函数体,并返回最内层函数体的返回值 - 结合上文可知,这里的多个连续箭头(无论俩个箭头函数三个及以上)函数连在一起 就是在柯里化。所以连续箭头函数就是多次柯里化函数的 es6 写法。
- 调用特点:let test = a => b => c => {xxx}
比如对于上面的 test 函数,它有 3 个箭头, 这个函数要被调用 3 次 test(a)(b)©,前两次调用只是在传递参数,只有最后一次调用才会返回 {xxx} 代码段的返回值,并且在 {xxx} 代码段中可以调用 a,b,c
4. 闭包的作用
- 使用函数内部的变量在函数执行完后, 仍然存活在内存中**(延长了局部变量的生命周期)**
- 让函数外部可以操作(读写)到函数内部的数据(变量/函数)
问题:
1.函数执行完后, 函数内部声明的局部变量是否还存在?
- 一般是不存在, 存在于闭包中的变量才可能存在
2.在函数外部能直接访问函数内部的局部变量吗? - 不能, 但我们可以通过闭包让外部操作它
5. 闭包的生命周期
- 产生: 在嵌套内部函数定义执行完时就产生了(不是在调用)
- 死亡: 在嵌套的内部函数成为垃圾对象时
- 即没有人指向它时死亡,通常置为[null],当然指向其他也行,但不安全(容易污染变量)
//闭包的生命周期
function fn1() {
//此时闭包就已经产生了(函数提升,实际上[fn2]提升到了第一行, 内部函数对象已经创建了)
var a = 2
function fn2 () { //如果时[let fn2=function(){}],那么在这行才会产生闭包
a++
console.log(a)
}
return fn2
}
var f = fn1()
f() // 3
f() // 4
f = null //闭包死亡(包含闭包的函数对象成为垃圾对象)
6. 闭包的应用
闭包的应用 : 定义JS模块
- 具有特定功能的js文件
- 将所有的数据和功能都封装在一个函数内部(私有的)
- 只向外暴露一个包信n个方法的对象或函数
- 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能
- 模块定义:
//myModule.js
function myModule() {
//私有数据
var msg = 'My atguigu'
//操作数据的函数
function doSomething() {
console.log('doSomething() '+msg.toUpperCase())
}
function doOtherthing () {
console.log('doOtherthing() '+msg.toLowerCase())
}
//向外暴露对象(给外部使用的方法)
return {
doSomething: doSomething,
doOtherthing: doOtherthing
}
}
-----------------------------------------------------------------
// myModule2.js
(function () {
//私有数据
var msg = 'My atguigu'
//操作数据的函数
function doSomething() {
console.log('doSomething() '+msg.toUpperCase())
}
function doOtherthing () {
console.log('doOtherthing() '+msg.toLowerCase())
}
//向外暴露对象(给外部使用的方法)
window.myModule2 = {
doSomething: doSomething,
doOtherthing: doOtherthing
}
})()
- 模块调用
//调用示例
------------ 模块调用1 --------------------------------------------
<script type="text/javascript" src="myModule.js"></script>
<script type="text/javascript">
var module = myModule()
module.doSomething()
module.doOtherthing()
</script>
------------ 模块调用2 --------------------------------------------
<script type="text/javascript" src="myModule2.js"></script>
<script type="text/javascript">
myModule2.doSomething()
myModule2.doOtherthing()
</script>
7.闭包的缺点及解决
- 缺点 :
- 函数执行完后, 函数内的局部变量没有释放, 占用内存时间会变长
- 容易造成内存泄露
- 解决:
- 能不用闭包就不用
- 及时释放
function fn1() {
var arr = new Array(100000)
function fn2() {
console.log(arr.length)
}
return fn2
}
var f = fn1()
f()
f = null //让内部函数成为垃圾对象-->回收闭包
还有一个解决方式,调用时直接fn1()()直接运行调用即可–>匿名函数,用完自动就销毁了.
8. 内存溢出与内存泄露
- 内存溢出
- 一种程序运行出现的错误
- 当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误
- 内存泄露
- 占用的内存没有及时释放
- 内存泄露积累多了就容易导致内存溢出
- 常见的内存泄露:
- 意外的全局变量
- 没有及时清理的计时器或回调函数
- 闭包
<script type="text/javascript">
// 1. 内存溢出
var obj = {}
for (var i = 0; i < 10000; i++) {
obj[i] = new Array(10000000)
console.log('-----')
}
// 2. 内存泄露
// 意外的全局变量
function fn() {
a = new Array(10000000) //不使用var let const去承接
console.log(a)
}
fn()
// 没有及时清理的计时器或回调函数
var intervalId = setInterval(function () { //启动循环定时器后不清理
console.log('----')
}, 1000)
// clearInterval(intervalId)
// 闭包
function fn1() {
var a = 4
function fn2() {
console.log(++a)
}
return fn2
}
var f = fn1()
f()
// f = null
</script>
不使用let const var等去声明,实际上是挂载到[window]上的,所以导致内存泄露
9. 相关面试题1
//代码片段一 -->没有产生闭包:因为内部函数没有调用外部变量
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()()); //? the window
//函数体的this是window
//代码片段二
var name2 = "The Window";
var object2 = {
name2 : "My Object",
getNameFunc : function(){
//此处的this指向是[getNameFunc],他是对象中的属性,所以this指向就是object
var that = this;
return function(){
//此处用的是保存的 that
return that.name2;
};
}
};
alert(object2.getNameFunc()()); //? my object
- 代码片段一:
- 函数体的this指向是[window]
- 没有产生闭包:因为内部函数没有调用外部变量
- 代码片段二为何指向是对象?
- this指向是调用它的[getNameFunc],他是对象中的属性,所以this指向就是object
- 产生了闭包
10.相关面试题2
function fun(n,o) {
console.log(o)
return {
fun:function(m){
return fun(m,n)
}
}
}
var a = fun(0) //undefined
a.fun(1) //0
a.fun(2) //0
a.fun(3) //0
var b = fun(0).fun(1).fun(2).fun(3) //undefined 0 1 2
var c = fun(0).fun(1) //undefined 0
c.fun(2)//1 -->经过上方定义后 n固定为1
c.fun(3)//1 -->此处是陷阱!!! 一直没有改到n,所以一直是1