资讯专栏INFORMATION COLUMN

JS进击之路:闭包

tolerious / 2500人阅读

摘要:常见问题说到闭包相关的问题,最典型的就是变量和指向这两类问题。如果有错误或不严谨的地方,欢迎批评指正,如果喜欢,欢迎点赞。

引言

闭包这个词对很多前端开发人员来说既熟悉又陌生,熟悉是因为很多人都用过闭包,但是用的时候不知道闭包,陌生是因为并不理解闭包,接下来这篇文章将会从多方面介绍闭包

定义

闭包是怎么定义的呢?当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数在当前词法作用域之外执行。来看一个具体例子:

function foo () {
  var a = 2
  function bar () {
    console.log(a)
  }
  return bar
}
var baz = foo()
baz() //2

函数bar的词法作用域可以访问foo的内部作用域,并且bar在被作为返回值赋值给baz执行时,bar函数在定义时的词法作用域以外的地方被调用,依然可以访问foo函数的内部作用域变量a,这就是闭包

分析

现在让我们来看为什么闭包可以在定义的词法作用域外记住并且访问定义时的词法作用域的变量,想要一探究竟,先来看一个简单的例子来函数的执行过程:

function foo (a) {
  console.log(a)
}
foo (a)


上面是一个简单的函数调用,以及在执行时的上下文环境,重点看执行时上下文环境,在创建foo函数时,会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部的[[Scope]]属性中,当调用foo()函数时,会为函数创建一个执行环境,然后通过复制函数的[[Scope]属性中的对象构建起执行环境的作用域链。此后,又有一个活动对象(包含this、arguments、a)被创建并被推入执行环境作用域链的前端,对于foo函数来说,其作用域链包含两个变量对象,一个时全局的变量对象,一个是局部的活动变量对象,一般来说当函数执行完成后,局部的活动变量对象会被销毁,只留全局的,但是闭包执行过程有所不同,来看具体例子:

function foo () {
  var a = 2
  function bar (b) {
    console.log(a + b)
  }
  return bar
}
var baz = foo()
baz(3) //5


接下来来分析下上面闭包的执行上下环境,在一个函数内部定义的函数会将包含函数的活动对象添加到它的作用域链中,因此,bar函数的作用域链中会包含foo函数的活动对象,在bar函数从foo中被返回后,它的作用域链条被初始化为全局变量和foo中活动对象,因此,bar函数可以访问foo函数中定义的所有变量,同时foo函数在执行完毕后,其活动对象也不会被销毁,因为bar函数的作用域链仍然在引用这个活动对象。

常见问题

说到闭包相关的问题,最典型的就是变量和this指向这两类问题。

变量
function test () {
  var result = new Array()
  for (var i = 0; i < 6; i++) {
    result[i] = function () {
      return i
    }
  }
  return result
}


上面的代码展示就是面试题里面经常会碰到,result的结果从上面截图能看到,作用域中保存的i都是6,这是为什么呢?因为闭包保存的是函数中的活动对象,因此它们引用的都是同一个变量,并且是变量的最后一个值,因此都是6,那这个问题怎么解决呢?最常见的最简单肯定是将var换成let,也可以像下面这样:

function test () {
  var result = new Array()
  for (var i = 0; i < 6; i++) {
    result[i] = (function () {
      return i
    })()
  }
  return result
}

将闭包直接改成一个自执行函数,自执行函数本身是没有变量作用域的,因此会使用外层函数的变量作用域,这样也能达到我们想要的效果

this指向
var name = "window"
var obj = {
  name: "object",
  getName: function () {
    return function () {
      return this.name
    }
  }
}
console.log(obj.getName()())

上面这段js代码的this.name的返回值是window,这是为什么呢?按照上面写到的,此匿名函数在执行过程中,它的作用域会包含三部分:自身的活动对象、getName函数的活动对象和全局的变量对象,同时每个活动对象自动取得两个特殊的变量:this和arguments,但是内部函数在查找this时是无法直接访问外部函数的this变量,因此会沿着作用域链去查找全局变量中继续查找,如果想要取外部函数中的this取值也很简单,只需要向下面代码这样:

var name = "window"
var obj = {
  name: "object",
  getName: function () {
    var that = this
    return function () {
      return that.name
    }
  }
}
console.log(obj.getName()())

将this赋值给一个变量,内部函数是可以访问外部函数变量的,这样就解决了

总结

闭包是一个容易混淆不清的概念,这篇文章对闭包的定义、执行、常见问题做了简单的介绍,希望通过这篇能对大家理解和使用闭包有所帮助。如果有错误或不严谨的地方,欢迎批评指正,如果喜欢,欢迎点赞。

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

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

相关文章

  • 进击的 JavaScript(五) 之 立即执行函数与闭包

    摘要:匿名函数是不能单独写的,所以就提不上立即执行了。六立即执行函数在闭包中的应用立即执行函数能配合闭包保存状态。来看下上节内容中闭包的例子现在,我们来利用立即执行函数来简化它第一个匿名函数执行完毕后,返回了第二个匿名函数。 前面的闭包中,提到与闭包相似的立即执行函数,感觉两者还是比较容易弄混吧,严格来说(因为犀牛书和高程对闭包的定义不同),立即执行函数并不属于闭包,它不满足闭包的三个条件。...

    vincent_xyb 评论0 收藏0
  • 进击JavaScript之(三)玩转闭包

    摘要:为了更好的理解,在阅读此文之前建议先阅读上一篇进击之词法作用域与作用域链什么是闭包闭包的含义就是闭合,包起来,简单的来说,就是一个具有封闭功能与包裹功能的结构。在中函数构成闭包。 为了更好的理解,在阅读此文之前建议先阅读上一篇《进击JavaScript之词法作用域与作用域链》 1.什么是闭包 闭包的含义就是闭合,包起来,简单的来说,就是一个具有封闭功能与包裹功能的结构。所谓的闭包就是...

    cyixlq 评论0 收藏0
  • JS进击之路:作用域

    摘要:如果是,编译器会忽略该声明,继续进行编译否则它会要求作用域在当前作用域的集合中声明一个新的变量接下来编译器会为引擎生成运行时所需的代码,这些代码被用来处理这个赋值操作。引擎运行时会首先询问作用域,在当前的作用域集合中是否存在一个叫做的变量。 引言 几乎所有的编程语言都有作用域的概念,那作用域到底指的是什么呢?作用域就是编程语言在定义变量时,变量如何存储、变量如何访问的一套规则,不同的编...

    el09xccxy 评论0 收藏0
  • 进击的 JavaScript(四) 之 闭包

    摘要:此时产生了闭包。导致,函数的活动对象没有被销毁。是不是跟你想的不一样其实,这个例子重点就在函数上,这个函数的第一个参数接受一个函数作为回调函数,这个回调函数并不会立即执行,它会在当前代码执行完,并在给定的时间后执行。 上一节说了执行上下文,这节咱们就乘胜追击来搞搞闭包!头疼的东西让你不再头疼! 一、函数也是引用类型的。 function f(){ console.log(not cha...

    Anleb 评论0 收藏0
  • 我的前端进阶之路

    摘要:事件模型事件捕获阶段。事件到达目标元素触发目标元素的监听函数。的状态值与状态码的状态值未初始化还没有调用方法。载入完成已经执行完成,已经接收到全部的响应内容。 前言 总括: 包含这三个月来碰到的一些觉得比较好的面试题,三个月没怎么写博客着实有些手痒,哈哈哈。7000余字,不成敬意2333 原文地址:我的前端进阶之路 知乎专栏&&简书专题:前端进击者(知乎)&&前端进击者(简书) 博主...

    cloud 评论0 收藏0

发表评论

0条评论

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