闭包不能吃...

为何要写深刻理解

笔者并非大神,只是一个在校的大三学生。开始写深刻理解系列是为了给js的一些重难点知识进行梳理,而不是每次面试以前都将这些知识从新理解一遍。有理解的不对的,请赐教!事不宜迟,咱们开始吧。javascript

什么是闭包

闭包是函数!(废话)闭包仍是一个能够访问函数中变量的函数。java

function who(){
  let name='clong';
  function print(){
    return name;
  }
  return print;
}
let boy=who();
let myName=boy();
console.log(myName);//'clong'

开始的定义可能会令你感受晦涩难懂,看了上面的例子咱们一块儿来理解一下。git

分析

下面咱们来分析一下上面的栗子。github

  • 首先咱们声明了一个函数,这个函数包含了一个变量name和一个print函数,执行到return print的时候进行返回。
  • 紧接着咱们在全局做用域下生命了一个变量boy,继续向该行后面看,有个who,接着咱们遇到(),这时咱们就会从新返回到上面查找who有没有声明。
  • 进入who体内,咱们声明了一个变量name并赋值为clong,而后申明了一个print函数,接着咱们返回print,此时print函数被销毁,而boy保存着print函数的引用。
  • 接着咱们遇到myName,基本流程与上面的boy是差很少一致的,只不过这时咱们的myName不是保存着一个函数,而是保存了name的值(注意:这里name已经被销毁了,不信你能够在全局做用域下打印name的值看看!)。

有了上面的栗子,咱们来看看下面这个栗子:面试

function createCounter() {
  let counter = 0
  const myFunction = function() {
      counter = counter + 1
      return counter
    }
    return myFunction
  }
  const increment = createCounter()
  const c1 = increment()
  const c2 = increment()
  const c3 = increment()
  console.log('example increment', c1, c2, c3)//1,2,3

思考一下,答案与你认为的同样吗?是否是觉得是1,1,1呢?先不要急,咱们再来看看下面这个栗子。闭包

var makeCounter = function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }  
};

var Counter1 = makeCounter();
var Counter2 = makeCounter();
console.log(Counter1===Counter2);
console.log(Counter1.value()); /* logs 0 */
Counter1.increment();
Counter1.increment();
console.log(Counter1.value()); /* logs 2 */
Counter1.decrement();
console.log(Counter1.value()); /* logs 1 */
console.log(Counter2.value()); /* logs 0 */

看看上面的栗子,你会不会疑惑?本质上都是调用的私有函数的方法,为何Counter1和Counter2的privateCounter就会彻底不同呢?函数

笔者查看了不少资料,别人总结的要么就是将mdn上的解释一贴,要么就是含糊其辞。性能

请注意两个计数器 counter1 和 counter2 是如何维护它们各自的独立性的。每一个闭包都是引用本身词法做用域内的变量 privateCounter 。每次调用其中一个计数器时,经过改变这个变量的值,会改变这个闭包的词法环境。然而在一个闭包内对变量的修改,不会影响到另一个闭包中的变量。————MDN

那么为何对一个闭包的变量的改变不会影响到另外一个闭包中的变量呢?我思考了好久,最后这样解释给本身听:this

前一个栗子中,咱们的increment保存的是myFunction的引用和他的闭包(important:函数在建立的时候就会造成本身的做用域链)。了解过闭包的应该都有保存在内存中这个概念,那么咱们这里没得操做都是直接对闭包的操做,价值做用域的执行顺序(闭包=>父级=>...),每次都是从闭包中获取,而且将修改的值保存在内存中,天然是1,2,3!code

那么后面一个栗子呢?我先姑且解释看看(有不对的但愿大牛能够指出),函数中的变量都是私有的(包括函数),第二个栗子返回的是一个对象,而后咱们后面的操做都是基于这个对象的属性的操做,间接操做了内部的私有方法并获取了内部的值。那么,回想一下new一个对象发生了什么?

  • 建立一个空对象
  • 将构造函数的做用域赋给新对象,即将this只想新对象
  • 讲原型中的属性添加到这个对象当中
  • 返回新对象

这个栗子不是正好跟上面的操做相似吗?若是仍是不明白,咱们假设makeCounter是一个Array对象,里面的private和changeBy是length和push内部实现原理,返回一个新对象,而且给了你一些接口,而这个接口正好有push,使你能够进行push操做,且仅限于该对象的私有属性的方式。那么实例与实例的私有属性或方法共有吗?固然不!神奇的利用闭包就实现了数据的私有和封装了

用处

通过了上面的分析,咱们大概能够了解到闭包的一些用处了吧。

  • 访问函数中的变量
  • 函数属性的私有化/封装(PS:这一块还涉及到原型链,下次再写)

劣势

这些也是耳熟能详了!

  • 性能问题,一直暴露在内存当中,没法被垃圾回收机制回收,颇有可能形成内存泄漏!

场景

  • 回调函数
  • 页面交互操做(同上!)
  • setTimeout(不也是回调函数嘛!)
  • 数据私有和封装

tips

  • 仍然能够访问外部函数的中定义的变量即便外部函数被返回了
  • 闭包存储对外部函数中变量的引用,而不是值
  • 闭包能够实现js的数据的封装和私有化

参考资料

MDN/JS/闭包
Understand JavaScript Closures With Ease
I never understood JavaScript closures

博客地址

相关文章
相关标签/搜索