- 什么是「闭包」。
- 「闭包」的作用是什么。
- 「闭包」的缺点是什么
闭包是什么
let a = 1
let b = function(){
console.log(a)
}
函数b因为捕获了外部作用域(环境)中的变量a,因此形成了闭包。「函数」和「函数内部能访问到的变量」(也叫环境)的总和,就是一个闭包。
执行上下文
- 全局执行上下文: 这是默认的、最基础的执行上下文。不在任何函数中的代码都位于全局执行上下文中。它做了两件事:1. 创建一个全局对象,在浏览器中这个全局对象就是 window 对象。2. 将 this指针指向这个全局对象。一个程序中只能存在一个全局执行上下文。
- 函数执行上下文: 每次调用函数时,都会为该函数创建一个新的执行上下文。每个函数都拥有自己的执行上下文,但是只有在函数被调用的时候才会被创建。一个程序中可以存在任意数量的函数执行上下文。
函数执行过程
- JavaScript创建一个新的执行上下文,我们叫作本地执行上下文。
- 这个本地执行上下文将有它自己的一组变量,这些变量将是这个执行上下文的本地变量。
- 新的执行上下文被推到到执行堆栈中。可以将执行堆栈看作是一种保存程序在其执行中的位置的容器。
- 当它遇到一个return语句或一个结束括号}。
函数结束
- 这个本地执行上下文从执行堆栈中弹出。
- 函数将返回值返回调用上下文。调用上下文是调用这个本地的执行上下文,它可以是全局执行上下文,也可以是另外一个本地的执行上下文。这取决于调用执行上下文来处理此时的返回值,返回的值可以是一个对象、一个数组、一个函数、一个布尔值等等,如果函数没有return语句,则返回undefined。
- 这个本地执行上下文被销毁,销毁是很重要,这个本地执行上下文中声明的所有变量都将被删除,不在有变量,这个就是为什么 称为本地执行上下文中自有的变量。
函数声明
function addTwo(x) {
let ret = x + 2
return ret
}
我们在全局执行上下文中声明了一个名为addTwo的新变量,我们给它分配一个函数定义。两个括号{ } 之间的任何内容都被分配给addTwo,函数内部的代码没有被求值,没有被执行,只是存储在一个变量中以备将来使用。
返回函数的函数
1: let val = 7
2: function createAdder() {
3: function addNumbers(a, b) {
4: let ret = a + b
5: return ret
6: }
7: return addNumbers
8:}
9: let adder = createAdder()
10: let sum = adder(val, 8)
createAdder执行上下文将被销毁。addNumbers变量不再存在。但addNumbers函数定义仍然存在,因为它返回并赋值给了adder 变量。adder里面就包含了addNumbers的局部变量
闭包
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()
- 定义函数时,会同时存储定义语句所处执行上下文的变量(称为闭包);执行该函数进行变量查找,但是直接定义成全局变量会被污染,所以通常包裹在另一个函数里
- 我们的目的是调用myfunction,createCounter的目的是创建闭包(counter),所以它返回myfunction,createCounter()返回一个无名函数,
const increment = createCounter()给一个名字
闭包的作用
闭包常常用来「间接访问一个变量」。换句话说,「隐藏一个变量」。
假设我们在做一个游戏,在写其中关于「还剩几条命」的代码。
如果不用闭包,你可以直接用一个全局变量:
window.lives = 30 // 还有三十条命
这样看起来很不妥。万一不小心把这个值改成-1了怎么办。所以我们不能让别人「直接访问」这个变量。
怎么办呢?
用局部变量。
但是用局部变量别人又访问不到,怎么办呢?
暴露一个访问器(函数),让别人可以「间接访问」
闭包的缺点:
(1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
(2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
总结
- 定义函数时,会同时存储定义语句所处执行上下文的变量(称为闭包);执行该函数进行变量查找,但是直接定义成全局变量会被污染,所以通常包裹在另一个函数里
- 如果直接定义在里面的函数那么每次调用会重置,定义在闭包里不会重置
写代码时遇到的典型的用法
function creator() {
let id = 0;
function creatId() {
id++;
return id
}
return creatId()
}
creator() //暴露
--------------------------------
let id = parseInt(window.localStorag
e.getItem('idMax')|| '0')
function createId() {
id++;
return id
}
export default createId(); //暴露
上面的两个暴露方法时一样的,一个是模块化,一个是闭包