作用域闭包

再看闭包定义

闭包的概念看了几十回不止了,但是让我描述一遍,我应该还是说不出个什么东西。下面就再来看看闭包的概念:

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

下面配合一个例子来更好的理解

function foo() {
    var a = 2;
    function bar() {
        console.log(a) // 2
    }
    bar()
}
foo()
复制代码

看了这段代码,很容易有个疑惑,这就是闭包吗?这不应该是作用域的例子吗?对,这就是词法作用域的查找规则。但是,这些规则就是闭包的一部分。

通过这段代码,我们不能清晰的理解闭包的意义,却可以很容易的理解词法作用域。下面我们来稍加修改一下,来看看到底闭包应该什么样子,和词法作用域有什么联系?

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

再结合定义来看,就很容易理解了。

bar函数可以访问所在函数foo的词法作用域。这就产生了闭包。而bar函数本身作为返回值进行传递,并且赋值给了baz,在foo的词法作用域外,我们执行baz,依然可以执行。

下面我们来进一步的理解闭包。

依据JavaScript的垃圾回收机制,当var baz = foo()执行之后,理论上来说foo的内容不会再被使用,所以应该释放所在的内存空间,但是这时候却不会被回收,这就是闭包的神奇的地方。

bar()依然持有该作用域的引用。这个引用就是闭包。

所以当我们执行baz时,以然可以访问到a。

下面再来几个例子,加深我们对闭包的理解

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
}
function bar() {
    fn()
}
foo()
bar()
复制代码

循环和闭包

我第一次接触到闭包的这个概念是在这样一次使用之后:

for(var i = 0; i < 5;i++){
    setTimerout(function timer(){
        console.log(i)
    }, i * 1000)
}
复制代码

天真的我等待这每秒输出一个0-4的数字,等结果出现的时候,我直接蒙了,这是JavaScript出bug了啊。

现在看来但是的自己是多么的无知。。。

解决办法对现在的自己来说很简单了

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 );
}
复制代码

原因也是很简单的,

  1. 闭包
  2. 更多的词法作用域

块作用域

es6的出现让我们有了解决上面问题的更简单的方法。那就是利用块作用域。

for (let i=1; i<=5; i++) {
    setTimeout( function timer() {
    	console.log( i );
    }, i*1000 );
}
复制代码

模块

在大规模使用es6的模块机制的现在,我们对模块已经有了也许详细的认识。但是,具体的模块是怎么实现的呢?我们来看一下。

function Foo() {
    var something = "cool";
    var another = [1, 2, 3];
    function doSomething() {
    	console.log( something );
    }
    function doAnother() {
    	console.log( another.join( " ! " ) );
    }
    return {
        doSomething: doSomething,
		doAnother: doAnother
    }
}
var foo = Foo()
foo.doSomething()
foo.doAnother()
复制代码

上面的这个模式就是最基本的模块了,很显然,我们利用了闭包的知识。

Foo函数对外暴漏了一个对象,这个对象包含了doSomething和doAnother两个函数。

模块模式需要具备的两个必要条件:

  1. 必须有外部的封闭函数, 该函数必须至少被调用一次(每次调用都会创建一个新的模块 实例)。
  2. 封闭函数必须返回至少一个内部函数, 这样内部函数才能在私有作用域中形成闭包, 并 且可以访问或者修改私有的状态。

上面的模块我们可以调用很多次,每次都会创建一个新的模块实例。如果我们只需要使用一次时,我们可以这么做

var foo = (function CoolModule() {
    var something = "cool";
    var another = [1, 2, 3];
    function doSomething() {
    	console.log( something );
    }
    function doAnother() {
    	console.log( another.join( " ! " ) );
    }
    return {
        doSomething: doSomething,
        doAnother: doAnother
    };
})();
foo.doSomething(); // cool
foo.doAnother(); // 1 ! 2 ! 3
复制代码

看,闭包是不是无处不在。

当然,模块还可以接受参数,根据参数来设置一些模块内的内容,这都是很简单的应用,很容易理解。

es6的使用,使我们对模块的使用更加的方便。es6会将文件作为独立的模块来处理,每一个暴露的函数都可以用关键词export来导出。使用模块暴露出来的函数时,我们用import来引入。

通过这一次学习,对闭包有个更加深入的认识,我么再来看一下闭包的定义:

当函数可以记住并访问所在的词法作用域, 即使函数是在当前词法作用域之外执行, 这时 就产生了闭包

另一个重要的概念就是模块:

  1. 为创建内部作用域而调用了一个包装函数;
  2. 包装函数的返回值必须至少包括一个对内部函数的引用, 这样就会创建涵盖整个包装函数内部作用域闭包

闭包无处不在,以后的工作学习中能熟练的运用闭包,一眼认出来是闭包就是真正的掌握了

转载于:https://juejin.im/post/5be15446f265da6174644e36

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值