你不知道的JavaScript笔记-闭包

闭包

当函数可以记住访问所在的词法作用域时,就产生了闭包(不一定是完整的闭包)。即使函数是在当前词法作用域之外执行的。

function foo () {
    var a = 2;
    function bar () {
        console.log(a);
    }
    bar();
}
foo();

函数 bar( ) 具有一个涵盖 foo ( ) 作用域的闭包,但不是完整的闭包,向下面把 bar return 出来的才算是完整的闭包。也就是在自己定义的词法作用域外执行

function foo () {
    var a = 2;
    function bar () {
        console.log(a);
    }
    return bar;
}
var baz = foo();
baz();          // 2 这就是闭包

下面这两个也是闭包。

function foo () {
    var a = 2;
    function baz () {
        console.log(a);
    }
    bar(baz);
}
function bar (fn) {
    fn();       // 这也是闭包
}

var fn;
function foo () {
    var a = 2;
    function baz () {
        console.log(a);
    }
    fn = baz;   // 将 baz 赋值给全局变量
}
function bar () {
    fn();       // 这也是闭包
}
foo();
bar();          // 2

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

循环和闭包

来看一段代码:

for (var i = 1; i <= 5; i++) {
    setTimeout(function timer () {
        console.log(i);
    }, i * 1000)
}

没错,结果是以每秒一次的频率输出5次6。

分析一下,以上代码试图在循环中的每隔迭代都捕获一个 i 的副本,尽管循环中5个函数是在各自的迭代中分别定义的,但是它们都被封闭在一个共享的全局作用域中,因此实际上只有一个 i 。

所以,我们需要再循环的每次迭代中使用一个闭包作用域。

for (var i = 1; i <= 5; i++) {
    (function () {
        var j = i;
        setTimeout(function timer () {
            console.log(j);
        }, j * 1000)
    })()
}

改进一下:

for (var i = 1; i <= 5; i++) {
    (function (j) {
        setTimeout(function timer () {
            console.log(j);
        }, j * 1000)
    })(i)
}

使用 IIFE 在每次迭代的时候都创建了一个新的作用域,其实就是每次迭代只需要一个块作用域。因此,可以通过 let 实现。

for (var i = 1; i <= 5; i++) {
    let j = i;      // 块作用域
    setTimeout(function timer () {
        console.log(j);
    }, j * 1000)
}
// var i 只声明了一次,后面是给 i 赋值

for 循环头部的 let 声明还会有一个特殊的行为:变量(i)在循环中每次迭代都会被声明,使用上一个迭代的结果来初始化这个变量。

for (let i = 1; i <= 5; i++) {
    setTimeout(function timer () {
        console.log(i);
    }, i * 1000)
}
/**
 * 相当于
 * let i = 1;
 * let i = 2;
 * let i = 3;
 * let i = 4;
 * let i = 5;
 */

模块

闭包的另一个用法就是模块

function Module () {
    var a = 'cool';
    var b = 'awesome';
    function bar () {
        console.log(a);
    }
    function foo () {
        console.log(b);
    }
    return { bar, foo }
}

模块总结为两点:

  1. 外部封闭函数,该函数必须至少被调用一次
  2. 封闭函数返回至少一个内部函数,形成闭包,且内部函数可以访问和修改私有变量

模块另一个简单但是强大的用法,命名返回的对象

var foo = (function Module (id) {
    function change () {
        // 修改公共的 API
        publicAPI.identify = identify2;
    } 
    function identify1 () {
        console.log(id);
    }
    function identify2 () {
        console.log(id.toUpperCase());
    }
    var publicAPI = {
        change,
        identify: identify1
    }
})('foo');
foo.identify();         // 'foo'
foo.change();           
foo.identify();         // 'FOO'

将要返回的对象(publicAPI)赋值给一个变量,在模块实例内部就可以保留对 publicAPI 的引用,就可以从内部对模块实例进行修改。

简单的介绍一个模块实现

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值