资讯专栏INFORMATION COLUMN

闭包不能吃...

stonezhu / 287人阅读

摘要:什么是闭包闭包是函数废话闭包还是一个可以访问函数中变量的函数。每个闭包都是引用自己词法作用域内的变量。每次调用其中一个计数器时,通过改变这个变量的值,会改变这个闭包的词法环境。然而在一个闭包内对变量的修改,不会影响到另外一个闭包中的变量。

为什么要写深入理解

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

什么是闭包

闭包是函数!(废话)闭包还是一个可以访问函数中变量的函数。

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

开始的定义可能会令你感觉晦涩难懂,看了上面的例子我们一起来理解一下。

分析

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

首先我们声明了一个函数,这个函数包含了一个变量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

那么为什么对一个闭包的变量的改变不会影响到另一个闭包中的变量呢?我思考了很久,最后这样解释给自己听:

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

那么后面一个栗子呢?我先姑且解释看看(有不对的希望大牛可以指出),函数中的变量都是私有的(包括函数),第二个栗子返回的是一个对象,然后我们后面的操作都是基于这个对象的属性的操作,间接操作了内部的私有方法并获取了内部的值。那么,回想一下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

博客地址

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/95366.html

相关文章

  • JavaScript:面试频繁出现的几个易错点

    摘要:针对于面向对象编程的。因为面向对象就是针对对象例子中的守候来进行执行某些动作。这就是闭包的用途之一延续变量周期。把变量放在闭包里面和放在全局变量里面,影响是一致的。 1.前言 这段时间,金三银四,很多人面试,很多人分享面试题。在前段时间,我也临时担任面试官,为了大概了解面试者的水平,我也写了一份题目,面试了几个前端开发者。在这段时间里面,我在学,在写设计模式的一些知识,想不到的设计模式...

    VincentFF 评论0 收藏0
  • Javascript 面试中经常被问到的三个问题!

    摘要:相反,在讨论时,面试中通常会提到三件事。而认为最后一个参赛者说了算,只要还能吃的,就重新设定新的定时器。试想,如果用户的操作十分频繁他每次都不等设置的时间结束就进行下一次操作,于是每次都为该用户重新生成定时器,回调函数被延迟了不计其数次。本文不是讨论最新的 JavaScript 库、常见的开发实践或任何新的 ES6 函数。相反,在讨论 JavaScript 时,面试中通常会提到三件事。我自己...

    chnmagnus 评论0 收藏0
  • Javascript 面试中经常被问到的三个问题!

    摘要:相反,在讨论时,面试中通常会提到三件事。通过对事件对应的回调函数进行包裹以自由变量的形式缓存时间信息,最后用来控制事件的触发频率。而认为最后一个参赛者说了算,只要还能吃的,就重新设定新的定时器。 showImg(https://segmentfault.com/img/bVboH5x?w=1000&h=750); 想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你! 本...

    PrototypeZ 评论0 收藏0
  • JavaScript之闭包

    摘要:而闭包的妙处在于,当函数在执行完毕后它的活动对象不会被销毁,因为匿名函数的作用域链仍然在引用函数的活动对象它的作用域链会被销毁。 一、闭包 闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常用方式是,在一个函数内部创建另一个函数。 请看以下代码:我们在createComparisonFunction函数里创建了一个闭包 function createComparisonFun...

    Mr_houzi 评论0 收藏0
  • PHP闭包的理解与介绍

    摘要:闭包与函数真正的区别函数封装一次多处调用。闭包只限于本方法使用,耦合度低到忽略。 看过许多关于PHP中闭包的讲解,每个文档想要表达的意思大体相同,但是理解起来很费劲,我根据自身理解加以描述,有更好的理解请指出 众所周知,大家都知道PHP的闭包是function () use (){}; 本文分为3步1:讲解闭包的使用2:闭包实例3:闭包总结 1、讲解闭包的使用1:闭包中的use使用-上...

    gotham 评论0 收藏0

发表评论

0条评论

stonezhu

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<