javascript--闭包看完你就明白了

127 阅读5分钟
  1. 什么是「闭包」。
  2. 「闭包」的作用是什么。
  3. 「闭包」的缺点是什么

闭包是什么

let a = 1
let b = function(){
    console.log(a)
}

函数b因为捕获了外部作用域(环境)中的变量a,因此形成了闭包。「函数」和「函数内部能访问到的变量」(也叫环境)的总和,就是一个闭包。

执行上下文

  • 全局执行上下文: 这是默认的、最基础的执行上下文。不在任何函数中的代码都位于全局执行上下文中。它做了两件事:1. 创建一个全局对象,在浏览器中这个全局对象就是 window 对象。2. 将 this指针指向这个全局对象。一个程序中只能存在一个全局执行上下文。
  • 函数执行上下文: 每次调用函数时,都会为该函数创建一个新的执行上下文。每个函数都拥有自己的执行上下文,但是只有在函数被调用的时候才会被创建。一个程序中可以存在任意数量的函数执行上下文。

函数执行过程

  1. JavaScript创建一个新的执行上下文,我们叫作本地执行上下文。
  2. 这个本地执行上下文将有它自己的一组变量,这些变量将是这个执行上下文的本地变量。
  3. 新的执行上下文被推到到执行堆栈中。可以将执行堆栈看作是一种保存程序在其执行中的位置的容器。
  4. 当它遇到一个return语句或一个结束括号}。

函数结束

  1. 这个本地执行上下文从执行堆栈中弹出。
  2. 函数将返回值返回调用上下文。调用上下文是调用这个本地的执行上下文,它可以是全局执行上下文,也可以是另外一个本地的执行上下文。这取决于调用执行上下文来处理此时的返回值,返回的值可以是一个对象、一个数组、一个函数、一个布尔值等等,如果函数没有return语句,则返回undefined。
  3. 这个本地执行上下文被销毁,销毁是很重要,这个本地执行上下文中声明的所有变量都将被删除,不在有变量,这个就是为什么 称为本地执行上下文中自有的变量。

函数声明

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(); //暴露

上面的两个暴露方法时一样的,一个是模块化,一个是闭包