资讯专栏INFORMATION COLUMN

理解js闭包

cnio / 3065人阅读

摘要:总结闭包的核心是属性,在函数解析过程中,如果函数引用了外层函数的变量,那么外层函数即使自身被销毁的活动对象带着对应变量将会被保留,并且记录在属性中,作为作用域链的第二层,如果还引用了外层函数的外层函数的变量,那么对应的活动对象与变量也会被保

总结:闭包的核心是[[scope]]属性,在函数解析过程中,如果函数引用了外层函数的变量,那么外层函数(即使自身被销毁)的活动对象带着对应变量将会被保留,并且记录在scope属性中,作为作用域链的第二层,如果还引用了外层函数的外层函数的变量,那么对应的活动对象与变量也会被保留,并记录,将会作为作用域链的第三层,依次类推...。当函数被调用时,所取到的外部变量的值将会是调用时各个变量的值。即当前值。

闭包调用时也是普通函数,只不过作用域链多了闭包Closure的成分

闭包的用途:1.模仿块级作用域,在立即调用函数中声明内部需要使用的变量;2.管理私有变量和方法;3.函数柯里化

来个极端的例子

function closure() {
    let result = [], count = 1
    setInterval(() => {count++}, 1000)
    function outer() {
        let doufu = "wu"
        return function inner() {
            let foo = "foo"
            return function closureCallback() {
                let bar = {bar: "bar"}, bar1 = "bar"
                result[i] = function () {
                    console.log(count)
                    let b = doufu
                    return bar.bar + i
                }
            }
        }
    }
    let inner = outer()
    let closureCallback = inner()
    for (var i=0; i<10; i++) {
        // [[scope]]包含closure{result, i}
        // 函数在这里立即调用
        // i为实时值0,1,2,3...
        closureCallback()
    }
    return result
}


从图中可以看到,最终返回结果分别引用了closureCallback, outer,closure中的变量,所以,在作用域链中会保存这三个函数的活动对象,不同时间调用,返回的count值不同,说明引用的是当前值。
以下是手工示意图

以下是我的学习过程
JavaScript里面的闭包,指的是函数与声明该函数的词法环境的组合。
一般在函数里面声明函数,并且引用外面函数的变量,就会产生闭包。定义在全局的时候,默认不产生闭包(所谓闭包,就是当内部函数引用了外部函数中的变量时,会在函数的作用域上面添加一层,这一层包含了函数所引用的外部函数的变量,存放在scope属性里面,在调用时,用于形成作用域链)
函数在执行时,会在内存中创建一个活动对象,该活动对象包含arguments以及一些参数。并通过复制[[scope]]属性中的对象构建起执行环境的作用域链。

var bar = "zoo"
function Foo() {
    this.bar = bar
}

function foo() {
    let bar = "zoo"
    function zoo() {
        let zoo = bar
    }
}
foo()

function closure() {
    let result = []
    for (var i=0; i<10; i++) {
        result[i] = function () {
            return i
        }
    }
    return result
}
let arr = closure()

主要体现在函数返回函数,函数A在调用函数B时被创建并从B函数的内部返回。当我们调用A函数的时候,B函数的作用域链已经从内存中销毁,但是我们仍然可以在A中访问B中存在的变量。因为B中的变量仍然保存在A的活动对象中(作用域链中[[scope]]对象里面)
此时,函数A与A的scope构成closure函数的闭包实例


从图中可以看到,ar[0](以下称为函数A)函数的作用域链最顶层为自身活动对象(arguments, caller, length, name等等构成)再往上则是由一个Closure对象实例构成,可以看到这一层里面只包含一个变量i,即创建A时外层函数里面声明的变量i。当我们调用A时,我们在第二层作用域链上面找到的这个i变量。
为什么arr数组每一项都返回的是10,而不是对应的下标值的原因就在这里:当我们调用数组项函数时,遇到变量i,并且在第二层作用域链读取到i,这里面保存的i就是closure函数里面定义的i。在A调用时,closure已经执完毕,在closure执行的过程中i的值从0变到了10。这个性质类似于把原本存在于closure函数中的变量,在closure函数执行完毕后(从内存中移除)我们可以在A自身的scope属性里访问到。
简单一点说就是我们在调用A函数的时候,访问到的i变量,是函数closure(虽然它不在了但是它的变量还在,仍然被scope属性引用。)的当前值(实时值)

所以要达到预期目标,我们只需要保证它的scope对象中保存的这个‘i’值是正确的就可以了。
这里面的思路就是,函数A被当做普通函数调用时,非闭包情况下,作用域就是自身(没有i变量)+ 全局作用域(也没有),所以这里还是需要借助闭包。即需要保证A上一层作用域的i是正确的值

1.创建另外一个闭包,每个i的值都会创建一个闭包

function closureCallback(i) {
    // 返回函数里面的i变量就是closureCallback函数的参数i
    return function() {
        return i
    }
}

function closure() {
    let result = [], b = "closure"
    for (var i=0; i<10; i++) {
                // 这里的closureCallback作为普通函数调用
                // 且没有引用closure函数的变量,
                // 函数作用域内的变量,无法直接在函数外取得
                // 所以作用域链不包含closure
                // 所以在closureCallback函数[[scope]]中不会有closure函数
        result[i] = closureCallback(i)
    }
    return result
}

2.使用匿名闭包

function closure() {
    let result = [], b = "closure"
    for (var i=0; i<10; i++) {
        result[i] = (function (i) {
            return () => i
        })(i)
    }
    return result
}
let arr = closure()

3.使用let,减少闭包,let具有块级作用域的效果

function closure() {
    let result = [], b = "closure"
    for (var i=0; i<10; i++) {
        let j = i
        result[i] = function () {
            return j
        }
    }
    return result
}
let arr = closure()

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

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

相关文章

  • 谈谈我所理解闭包js、php、golang里的closure

    摘要:当初看这个解释有点懵逼,理解成闭包就是函数中的函数了。里的闭包最近不满足于只干前端的活,开始用起了。里的闭包最近在学习语言,让我们来看一下语言里的闭包。在中,闭包特指将函数作为值返回的情况,被返回的函数引用了生成它的母函数中的变量。 本人开始接触编程是从js开始的,当时网上很多人说闭包是难点,各种地方对闭包的解释也是千奇百怪。如今开始接触js以外的各种编程语言,发现不光是js,php、...

    betacat 评论0 收藏0
  • 谈谈我所理解闭包js、php、golang里的closure

    摘要:当初看这个解释有点懵逼,理解成闭包就是函数中的函数了。里的闭包最近不满足于只干前端的活,开始用起了。里的闭包最近在学习语言,让我们来看一下语言里的闭包。在中,闭包特指将函数作为值返回的情况,被返回的函数引用了生成它的母函数中的变量。 本人开始接触编程是从js开始的,当时网上很多人说闭包是难点,各种地方对闭包的解释也是千奇百怪。如今开始接触js以外的各种编程语言,发现不光是js,php、...

    zhoutao 评论0 收藏0
  • js闭包理解

    摘要:一般来讲,函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域,但是闭包的情况有所不同理解闭包的前提先理解另外两个内容作用域链垃圾回收作用域链当代码在执行过程中,会创建变量对象的一个作用域链。 闭包是javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包来实现。个人的理解是:函数中嵌套函数。 闭包的定义及其优缺点 闭包是指有权访问另一个函数作用域中的变量的...

    EasonTyler 评论0 收藏0
  • 理解 JavaScript 闭包

    摘要:如何在初学就理解闭包你需要接着读下去。这样定义闭包是函数和声明该函数的词法环境的组合。小结闭包在中随处可见。闭包是中的精华部分,理解它需要具备一定的作用域执行栈的知识。 这是本系列的第 4 篇文章。 作为 JS 初学者,第一次接触闭包的概念是因为写出了类似下面的代码: for (var i = 0; i < helpText.length; i++) { var item = he...

    宠来也 评论0 收藏0
  • JavaScript中的闭包

    摘要:闭包引起的内存泄漏总结从理论的角度将由于作用域链的特性中所有函数都是闭包但是从应用的角度来说只有当函数以返回值返回或者当函数以参数形式使用或者当函数中自由变量在函数外被引用时才能成为明确意义上的闭包。 文章同步到github js的闭包概念几乎是任何面试官都会问的问题,最近把闭包这块的概念梳理了一下,记录成以下文章。 什么是闭包 我先列出一些官方及经典书籍等书中给出的概念,这些概念虽然...

    HmyBmny 评论0 收藏0
  • js 变量提升和闭包理解

    摘要:变量的作用域无非就是两种全局变量和局部变量。其中内部函数中可以访问外部函数的变量,是因为内部函数的作用域链中包含了外部函数的作用域也可以理解为内部函数的作用范围辐射到了外部函数的作用范围另一方面,在函数外部自然无法读取函数内的局部变量。 以前学习的时候,了解过变量提升和闭包,但是没有深入了解,网上查了资料,这里记录下,只供参考。部分内容引用: https://www.cnblogs.c...

    luoyibu 评论0 收藏0

发表评论

0条评论

cnio

|高级讲师

TA的文章

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