读《你不知道的JavaScript(上卷)》后感-作用域闭包(二)
一、 序言
最近我在读一本书:《你不知道的JavaScript》,这书分为上中卷,内容非常丰富,认真细读,能学到非常多JavaScript的知识点,希望广大的前端同胞们,也入手看看这本书,受益匪浅。
《你不知道的JavaScript上卷》
现在我读完这本书的一些心得与总结:
一、作用域闭包
先来一段代码:
function foo(){
var a = 10;
function bar(){
console.log(a); // 10
}
bar()
}
foo();
console.log(a); // ReferenceError: a is not defined
这段代码看起来和嵌套作用域中的示例代码很相似。基于词法作用域的查找规则,函数 bar() 可以访问外部作用域中的变量 a(这个例子中的是一个 RHS 引用查询)。
当调用foo()时候,进入了foo函数的作用域里面,一直读,读到输出a的时候,foo里面的生命周期就结束了,当然我们清晰知道,外面作用域是访问不了内部的作用域的变量的,则最外层的输出a ReferenceError: a is not defined
我们还记得return的语法么?
小复习:
1.
(function foo(){
return function(){
console.log(2);
};
}())
这里我定义了一个自调用的函数,第一步是什么都没有输出的,这里是执行了foo函数,那也是重要的一部,当执行完了foo函数,里面的return到底发生了什么的作用?
我简单概括rerun有2个作用是:
1. return; 等于 return false;在这个作用域的后面的任何代码都会停止运行,一定记得加逗号;结束。
2. retrun ... ; 后面跟着一个函数就返回一个函数,后面跟着一个值就会返回什么的值。
所以:上面执行了foo()函数,return 返回了 bar函数
在执行了bar()函数,则输出了2
有了上面的基础,我们来优化第一次的代码:
function foo(){
var a = 10;
return function bar(){
console.log(a);
}
}
foo()(); // 10
以上代码,我的理解是:
当执行foo()函数时候,return 返回 bar函数,
在次执行foo()() => 实际是执行了bar()函数,
所以输出了 2
在 foo() 执行后,通常会期待 foo() 的整个内部作用域都被销毁,因为我们知道引擎有垃 圾回收器用来释放不再使用的内存空间。由于看上去 foo() 的内容不会再被使用,所以很 自然地会考虑对其进行回收。
而闭包的“神奇”之处正是可以阻止这件事情的发生。事实上内部作用域依然存在,因此 没有被回收。谁在使用这个内部作用域?原来是 bar() 本身在使用。
闭包的形式非常多,例如:
1.
function foo() {
var a = 2;
function baz() {
console.log( a ); // 2
}
bar( baz );
}
function bar(fn) {
fn(); // 妈妈快看呀,这就是闭包!
}
把内部函数 baz 传递给 bar,当调用这个内部函数时(现在叫作 fn),它涵盖的 foo() 内部
作用域的闭包就可以观察到了,因为它能够访问 a
传递函数当然也可以是间接的
var fn;
function foo() {
var a = 2;
function baz() {
console.log( a );
}
fn = baz; // 将 baz 分配给全局变量
}
function bar() {
fn(); // 妈妈快看呀,这就是闭包!
}
foo();
bar(); // 2
看了上面的代码,我按照第一章的作用域的读取顺序能搞明白,但这里的原理是什么??
我继续读下去发现一段代码:
function wait(message) {
setTimeout( function timer() {
console.log( message );
}, 1000 );
}
wait( "Hello, closure!" );
书里的解释是:
将一个内部函数(名为 timer)传递给 setTimeout(..)。timer 具有涵盖 wait(..) 作用域
的闭包,因此还保有对变量 message 的引用。
wait(..) 执行 1000 毫秒后,它的内部作用域并不会消失,timer 函数依然保有 wait(..)
作用域的闭包。
深入到引擎的内部原理中,内置的工具函数 setTimeout(..) 持有对一个参数的引用,这个 参数也许叫作 fn 或者 func,或者其他类似的名字。引擎会调用这个函数,在例子中就是 内部的 timer 函数,而词法作用域在这个过程中保持完整。
这就是闭包。
这里代码我看了几次,终于发现之前理解以为这里只是简单的函数调用:
我的理解是:
function wait(message) {
setTimeout( params , 1000 );
}
wait( "Hello, closure!" );
传进去的params返回来的是params,或许也叫fn比较适合
function timer() {
console.log( message );
}
引擎会调用这个函数,就有了所谓的定时器。
书中更是引入了一段jq的源代码:
function setupBot(name, selector) {
$( selector ).click( function activator() {
console.log( "Activating: " + name );
} );
}
setupBot( "Closure Bot 1", "#bot_1" );
setupBot( "Closure Bot 2", "#bot_2" );
原来这里也是个闭包啊!
本质上无论何时何地,如果将函数(访问它们各自的词法作用域)当作第一 级的值类型并到处传递,你就会看到闭包在这些函数中的应用。在定时器、事件监听器、 Ajax 请求、跨窗口通信、Web Workers 或者任何其他的异步(或者同步)任务中,只要使 用了回调函数,实际上就是在使用闭包!
我记得之前老师教过我三个经典的闭包函数:
for (var i=1; i<=5; i++) {
setTimeout( function timer() {
console.log( i ); // 6,6,6,6,6
}, i*1000 );
}
这段代码,根据作用域的关系,输出:5个6
为啥呢?
我们来加一行代码:
for (var i=1; i<=5; i++) {
console.log(i) // 1,2,3,4,5
setTimeout( function timer() {
console.log( i ); // 6,6,6,6,6
}, i*1000 );
}
由以上可知道,
setTimeout( function timer() {
console.log( i ); // 6,6,6,6,6
}, i*1000 );
这段代码已经暴露出外面的作用域了,
我们再来修改一下代码
我想,如果他已经暴露出了外面的作用域,那么的每次来自调用它看下可不可以呢?
for (var i=1; i<=5; i++) {
(function(){
setTimeout( function timer() {
console.log( i ); // 6,6,6,6,6
}, i*1000 );
})();
}
其实这里给多了一个作用域,实质是,和第一次没什么分别
实际是这样:
var i;
for(i = 0;i < 5; i++){
}
(function (){
setTimeout(function(){
console.log(i)
},i * 1000)
})()
i始终是5
那么既然setTimeout还是在for循环这个作用域里,i还是能在的,我想尝试一下一下代码
for (var i=1; i<=5; i++) {
(function(i){
setTimeout( function timer() {
console.log( i );
}, i*1000 );
})(i);
}
我把for循环里的i一个一个的穿进去,这不是通了么
执行一下代码:
1
2
3
4
5
太棒了!可以了。
这里要不得不提一下es6里面的let
for(let i = 0;i < 5; i++){
setTimeout(function(){
console.log(i)
},i * 1000)
}
一样能到达同样的效果,分别输出1,2,3...5
因为let的作用域起了作用
实际是这样:
1.第一种.
for(let i = 0;i < 3; i++){
}
(function (){
setTimeout(function(){
console.log(i)
},i * 1000)
})()
// 输出 ReferenceError: i is not defined
2.第二种.
for(var i = 0;i < 5; i++){
}
(function (){
setTimeout(function(){
console.log(i)
},i * 1000)
})()
// 输出 5
3.第三种.
let i
for(i = 0;i < 5; i++){
}
(function (){
setTimeout(function(){
console.log(i)
},i * 1000)
})()
// 输出 5
这里我的理解是: 块级作用域的问题:
let 在for循环括号里有效
刚才:
for (var i=1; i<=5; i++) {
(function(i){
setTimeout( function timer() {
console.log( i );
}, i*1000 );
})(i);
}
这里把i穿进去,其实是闭包的作用,传进去返回出来
很酷是吧?块作用域和闭包联手便可天下无敌
读《你不知道的JavaScript(上卷)》后感-作用域闭包(二)的更多相关文章
- 你不知道的javaScript上卷(第一章 作用域是什么)
在写这篇博客时这本书我已经是看过一遍了,为了加深印象和深入学习于是打算做这系列的前端经典书籍导读博文,大家如果觉得这本书讲的好可以自己买来看看,我是比较喜欢看纸质版书的,因为这样才有读书的那种感觉. ...
- 读《你不知道的JavaScript(上卷)》后感-浅谈JavaScript作用域(一)
原文 一. 序言 最近我在读一本书:<你不知道的JavaScript>,这书分为上中卷,内容非常丰富,认真细读,能学到非常多JavaScript的知识点,希望广大的前端同胞们,也入手看看这 ...
- 《你不知道的 JavaScript 上卷》 学习笔记
第一部分: 作用域和闭包 一.作用域 1. 作用域:存储变量并且查找变量的规则 2. 源代码在执行之前(编译)会经历三个步骤: 分词/此法分析:将代码字符串分解成有意义的代码块(词法单元) 解析/语法 ...
- 你不知道的JavaScript上卷笔记
你不知道的JavaScript上卷笔记 前言 You don't know JavaScript是github上一个系列文章 初看到这一标题的时候,感觉怎么老外也搞标题党,用这种冲突性比较强的题目 ...
- 为JavaScript正名--读你不知道的JavaScript(持续更新..)
你不知道的JavaScript上卷 JavaScript和Java的关系就像Carnival和Car的关系一样,八竿子打不着. JavaScript易上手,但由于其本身的特殊性,相比其他语言能真正掌握 ...
- 【你不知道的javaScript 上卷 笔记3】javaScript中的声明提升表现
console.log( a ); var a = 2; 执行输出undefined a = 2; var a; console.log( a ); 执行输出2 说明:javaScript 运行时在编 ...
- JS闭包—你不知道的JavaScript上卷读书笔记(二)
关于闭包,初学者会被绕的晕头转向,在学习的路上也付出了很多精力来理解. 让我们一起来揭开闭包神秘的面纱. 闭包晦涩的定义 看过很多关于闭包的定义,很多讲的云里雾里,晦涩难懂.让不少人以为闭包是多么玄乎 ...
- JavaScript词法作用域—你不知道的JavaScript上卷读书笔记(一)
前段时间在每天往返的地铁上抽空将 <你不知道的JavaScript(上卷)>读了一遍,这本书很多部分写的很是精妙,对于接触前端时间不太久的人来说,就好像是叩开了JavaScript的另一扇 ...
- 你不知道的JavaScript(上)作用域与闭包
第一部分 作用域与闭包 第一章 作用域是什么 1.作用域 变量赋值操作会执行两个动作:首先编译器会在当前作用域中声明一个变量(如果之前没有声明过), 然后会在运行时引擎会在作用域中查找该变量,找到就会 ...
随机推荐
- 蓝色巨人IBM
1911年IBM的前身CRT建立,在中华民国时期就与中国有很多商业合作,中国中央银行,中国银行,黄埔造船厂,建国后直到中美建交,IBM与中国的关系越来越紧密,今晚看了一遍关于蓝色巨人的视频,收益匪浅. ...
- Java中增强for循环的用法
此方法在jdk1.5之后才出现. 1:遍历数组 语法: for (Type value : array) { expression value; } 例子: void Sum() { int[] ar ...
- TCP/IP中你不得不知的十大秘密
这段时间 有一点心很浮躁,不过希望自己马上要矫正过来.好好学习编程!这段时间我想好好地研究一下TCP/IP协议和网络传输这块!加油 一.TCP/IP模型 TCP/IP协议模型(Transmission ...
- 关于DbContext能不能单次请求内唯一?DbContex需不需要主动释放?欢迎各路大侠来“参战”!
基于前篇文章<HiBlogs>重写笔记[1]--从DbContext到依赖注入再到自动注入园友@Flaming丶淡蓝@ 吴瑞祥 提出了讨论和质疑,吓得我连夜查询资料(玩笑~). 本来文章的 ...
- angularjs——路由篇
路由 路由功能是由 routeProvider服务 和 ng-view 搭配实现,ng-view相当于提供了页面模板的挂载点,当切换URL进行跳转时,不同的页面模板会放在ng-view所在的位置; 然 ...
- 深入理解计算机系统chapter2
---恢复内容开始--- 整数表示: 反码和原码都会有正零和负零 有符号整数和无符号整数之间的转换 反之 扩展一个数字的位级表示 截断操作 无符号加法的益处 补码的加法 规格化的值:E=e-bias ...
- hdu1754线段树的单点更新区间查询
I Hate It Time Limit: 9000/3000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total ...
- Google赛马分析
原题 想必田忌赛马的故事,大家都耳熟能详.但是,大家知道Goolge的童鞋们是怎么赛马的么?不过,首先,大家要先尝试一下:有25匹马,每次只能五匹一起跑,那么最少跑几次,才能确定前三甲呢? 分析 这样 ...
- pongo英雄会-幸运数题解
显然我们只要知道1~x范围有多少幸运数(用f(x)表示),lucky(x,y)=f(y)-f(x-1). 解法1. 计算排列数 由于y<=1000000000这个规模,我们不能暴力验证每个数是否 ...
- open() close()
open() 方法可以查找一个已经存在或者新建的浏览器窗口. 语法: window.open([URL], [窗口名称], [参数字符串]) 每个参数必须用引号 参数说明: URL:可选参数,在窗口中 ...