闭包彻底学习
来源:互联网 发布:js循环while偶数 编辑:程序博客网 时间:2024/06/07 17:04
闭包的定义:
定义1(红宝书):有权访问另一个函数作用域的变量的函数。
定义2(你不知道的js):当函数能够记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
闭包的表现形式(来源:http://www.w3cschool.cn/javascript_prototype/3exmpozt.html):
1、函数作为返回值
2、函数作为参数被传递
函数嵌套有的时候并不是闭包,举例如下(来源:你不知道的js):
function foo(){ var a=2; function bar(){ console.log(a); }}foo();//输出2
其实这个不是闭包,只是正常的作用域链。
闭包的例子:
function foo(){ var a=2; function bar(){ console.log(a); }return bar;}var baz=foo();baz();//输出2这个例子印证了以上闭包定义,以及闭包的表现形式。
印证定义2:
bar()函数可以访问foo()的变量,这个根据作用域链可以知道对吧。
然后,通过执行foo()函数,bar()函数被返回,并赋值给baz,baz就等价于bar;
baz执行时实际调用了bar()函数,而此时bar()函数是在词法作用域之外执行的,而它可以访问foo()函数中的a变量(可以记住并访问bar()所在的词法作用域)。
当然bar访问foo,本身就印证了定义1。
印证闭包的表现形式:
这个例子中函数bar()是作为返回值的。
(你不知道的js)
function foo(){ var a=2; function baz(){ console.log(a); } bar(baz);}function bar(fn){ fn();}foo();//输出2
var fn;function foo(){ var a=2; function baz(){ console.log(a); } fn=baz;}function bar(){ fn();}foo();bar();//输出2这两个例子说明:无论使用何种方式对函数类型的值进行传递,当函数在别处被调用时都可以观察到闭包。
无论通过何种手段将内部函数传递到所在的词法作用域之外。它都会持有对原始定义作用域的引用,无论在何处执行这个函数都会使用闭包。
有这么一道面试题:实现一个暴露内部变量,并且可以在外部访问并修改参数的函数。
那也很简单,就把上述例子改一下就好了。
(你不知道的js)function foo(){ var a=2; function bar(){ a=3; console.log(a); }return bar;}var baz=foo();baz();//输出3
下面再举几个例子吧,都是比较容易出错的:
function wait(message){ setTimeout(function timer(){console.log(message)},1000;);}wait("hello,closure!");此时,是将函数timer()传递给setTimeout().wait函数执行1000ms后,它的内部作用域并不会消失,timer函数依然保有wait作用域的闭包,因此还保有对变量messge的引用。
循环和闭包:
for (var i=0;i<=5;i++){ setTimeout(function timer(){ console.log(i); },i*1000);}输出:
这段代码会在运行时,以每秒一次的频率输出5次6.
我们预期输出是分别输出1-5,每秒一个。而实际输出,却是以每秒一次的频率输出5次6.
解释:
这段代码到底有什么缺陷,导致它的行为同语义所暗示的不一致呢?
缺陷是:我们试图假设循环中的每个迭代在运行时都会给自己“捕获”一个i的副本。但是根据作用域的工作原理,实际情况是尽管循环中的5个函数是在各个迭代中分别定义的,但是他们都被封装在一个共享的全局作用域中,因此实际上只有一个i。
这样说的话,当然所有函数都共享一个i的引用。如果将延迟函数的回调重复定义5次,完全不使用循环,那它同这段代码是完全等价的。
那么缺陷到底是什么?我们需要更多的闭包作用域,特别是在循环的过程中每个迭代都需要一个闭包作用域。
而通过声明并立即执行一个函数可以创建一个作用域。
因此,要使上述代码符合我们的预期,可以额外加一个立即执行函数。
我们预期输出是分别输出1-5,每秒一个。而实际输出,却是以每秒一次的频率输出5次6.
解释:
这段代码到底有什么缺陷,导致它的行为同语义所暗示的不一致呢?
缺陷是:我们试图假设循环中的每个迭代在运行时都会给自己“捕获”一个i的副本。但是根据作用域的工作原理,实际情况是尽管循环中的5个函数是在各个迭代中分别定义的,但是他们都被封装在一个共享的全局作用域中,因此实际上只有一个i。
这样说的话,当然所有函数都共享一个i的引用。如果将延迟函数的回调重复定义5次,完全不使用循环,那它同这段代码是完全等价的。
那么缺陷到底是什么?我们需要更多的闭包作用域,特别是在循环的过程中每个迭代都需要一个闭包作用域。
而通过声明并立即执行一个函数可以创建一个作用域。
因此,要使上述代码符合我们的预期,可以额外加一个立即执行函数。
for (var i=0;i<=5;i++){ (function(){ var j=i; setTimeout(function timer(){ console.log(j); },j*1000); })();}
还可以进一步简化,
for (var i=0;i<=5;i++){ (function(j){ setTimeout(function timer(){ console.log(j); },j*1000); })(i);}这样实际上是生成了一个额外的作用域来储存每次的变量i(此时变量i不是所有函数共享的)。
还可以使用块级作用域,
for(var i=1;i<=5;i++){ let j=i; setTimeout(function timer(){console.log(j);},j*1000);}或者
for(let i=1;i<=5;i++){ setTimeout(function timer(){console.log(i);},i*1000);}
0 0
- 闭包彻底学习
- 彻底理解Javascript闭包
- 彻底弄懂Javascript闭包
- JavaScript中的闭包之彻底领悟
- 彻底理解js中的闭包
- java.io包的学习,彻底搞透!
- 彻底明白IO包
- 彻底征服闭包定义,特点,价值,用法!
- 彻底学习smarty
- 彻底学习Java IO
- c#多线程彻底学习
- 彻底学习sqlite
- 闭包学习笔记
- 学习Javascript闭包
- 学习javascript闭包
- python学习~闭包
- Lua闭包学习
- 闭包学习
- Eclipse中最常用的快捷键组合
- 面试小知识
- 有关熵的几个概念 及 最大似然和交叉熵的一致性
- 深入java final关键字 基本用法、注意点和优点
- 通过git describe --tags 查询当前branch是从那个tag建立的
- 闭包彻底学习
- vue 数据更新 视图不刷新
- Intellij IDEA怎么像Eclipse一样对Class成员进行排序
- spark提交任务端口占用异常
- 使用org.apache.commons.lang.StringUtils方法containsAny误区 看看你们遇到过没有
- C#里partial关键字的作用(转摘)
- Java的高级编程
- import caffe ImportError: No module named caffe
- RxSwift(3.4.1)