几年之后再看JavaScript----闭包与递归

1. 闭包

闭包就是一个函数捕获作用域内外部绑定

  • 函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学文献中称为“闭包” ----《JavaScript权威指南》

  • 闭包是指有权访问另一个函数作用域中的变量的函数。 ---- 《Javascript高级程序设计》

  • 当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用 域之外执行。— 《你不知道的JavaScript(上)》

2. 常见闭包两种形式

2.1 函数作为返回值
    function connectTest() {
        var a = 'OK'

        function childFun(childParam) {
            return "haha~~~" + a + childParam
        }
        return childFun
    }
    var report = connectTest()
    console.log(report(''))

函数childFun()的词法作用域能够 访问 \color{#FF0000}{访问} 访问connectTest()的内部作用域。然后将 函数 c h i l d F u n 当做一个值类型 \color{#FF0000}{函数childFun当做一个值类型} 函数childFun当做一个值类型进行传递。

在函数connectTest执行后,其返回值(函数childFun)赋值给report 对象。

当打印执行report()的时候,实际是通过不同的标识符引用调用了内部函数childFun 函数 c h i l d F u n 在自己定义的词法作用域以外的地方执行 \color{#FF0000}{函数childFun在自己定义的词法作用域以外的地方执行} 函数childFun在自己定义的词法作用域以外的地方执行

在代码最下方延时一下还会打印么?

    setTimeout(() => {
        console.log(report('!!!'))
    }, 3000)

js中有垃圾回收器,函数等执行完之后会释放不再使用的内存空间。当执行完connectTest(),按道理会被销毁,但是由于函数childFun所在,使得该作用域能够一直存活,以供childFun之后任何时间进行引用。 相当于把局部变量驻留在内存中,避免使用了全局变量 \color{#FF0000}{相当于把局部变量驻留在内存中,避免使用了全局变量} 相当于把局部变量驻留在内存中,避免使用了全局变量。但每一个新的闭包都会捕获不一样的值!

2.2 函数作为参数传递
    function connectTest() {
        var a = 'OK'

        function childFun(childParam) {
            return "haha~~~" + a + childParam
        }
        return childFun
    }
    var report = connectTest()

    function test(fn) {
        var a = 'test'
        fn('')
        console.log(fn('!!!test'))  // haha~~~OK!!!test
    }
    test(report)

3. 闭包优缺点

优点:

  • 避免全局变量的污染【阻止父作用域被销毁,没有被垃圾回收器释放内存】
  • 变量尽量私有化

缺点:

  • 使用不佳内存泄露 ,降低程序的性能【驻留在内存中,增大内存使用率】
  • 解决方案将保存内层函数对象的变量赋值为null

4. 常见闭包的场景

4.1 回调函数

在定时器、事件监听器、Ajax 请求、跨窗口通信、Web Workers 或者任何其他的异步(或者同步)任务中,只要使用了回调函数,实际上就是在使用闭包!

4.2 模块化

为了最大限度减少暴露捕获变量的风险,JS经常用下面这种模式

    var dateYear = (function () {
        var year = 2019;
        return {
            lastYear: function (n) {
                return year -= n;
            },
            futureYear: function (n) {
                return year += n;
            }
        }
    })()
    var num = 3;
    console.log(dateYear.year) // undefined
    console.log('2019的前面' + num + '年是:' + dateYear.lastYear(num))
    // console.log('2019的后面' + num + '年是:' + dateYear.futureYear(num))

此时year是私有变量,只有这两个函数可以调用,其他任何手段都是无法进行访问,闭包模式提过强大的访问保护。

4.3 循环中的问题
    for (var i = 0; i < 5; i++) {
        setTimeout(() => {
            console.log(i)
        }, 1000);
    };

输出的结果是// 5 5 5 5 5.
解释一下为什么5个5,延迟函数的会调会在循环结束时才执行,此时循环已经在5了。

如何需要修改

    for (var i = 0; i < 5; i++) {
        (function () {
            var j = i;
            setTimeout(function timer() {
                console.log(j);
            }, j * 1000);
        })();
        // 修改完善一下
        // (function (j) {
        //     setTimeout(function timer() {
        //         console.log(j);
        //     }, j * 1000);
        // })(i);
    }

此时打印// 0 1 2 3 4
方案二,当然将var换成let,就不存在这回事儿了,【所以说让let代替var】

    for (let i = 0; i < 5; i++) {
        setTimeout(() => {
            console.log(i)
        }, 1000);
    };

到此差不多对闭包已经认识了,可以在自己开发的项目中看看有那些闭包~~~
总结:只要希望给给一个函数保存一个即可反复使用,又不会被外界污染的专属局部变量时,就用闭包

  • 闭包也是一个对象
  • 闭包就是每次调用外层函数时,临时创建的函数作用域对象。
  • 为什么外层函数作用域对象能留下来?因为被内层函数对象的作用域链引用着,
    无法释放。

5 递归

5.1 递归的定义

递归通常涉及函数调用自身。

递归常见的两种方式

a. 自身直接调用自身的方法或函数

function searchSelf(someProperty) {
 searchSelf(someProperty)
}

b. 间接调用自身的函数

function searchSelf(someProperty) {
 myself(someProperty)
}
function myself(someProperty) {
 searchSelf(someProperty)
}

每个递归函数都必须要有边界条件,即一个停止的条件,以防止无限递归(同时浏览器
会抛出栈溢出错误 stack overflow error)

递归并不比普通版本更快,反而更慢。但递归容易理解,而且它所需的代面量少。

ES6中使用了尾掉用优化,让递归不会更慢。
(相关尾掉用的参考)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值