大白话解释作用域和闭包是个啥
作用域的分类
常见的变量作用域就是 静态作用域(词法作用域) 与 动态作用域 。词法作用域注重的是 write-time ,即 编程时的上下文 ,而 动态作用域 则注重的是 run-time ,即 运行时上下文 。词法作用域中我们需要知道一个函数 在什么地方被定义 ,而动态作用域中我们需要关心的是函数 在什么地方被调用
而 javascript 使用的则是词法作用域
1let value = 1
2
3function foo() {
4 console.log(value)
5}
6
7function bar() {
8 let value = 2
9 foo()
10}
11
12bar() // 1
在 javascript 解析模式中,当 foo 被调用的时候:
- 检查 foo 函数内是否存在 value
- 存在则使用这个 value
- 不存在则根据书写代码的位置查找上一层代码(这里的 window),找到 value 为 1
在动态作用域的解析模式中,当 foo 被调用的时候:
- 检查 foo 函数内是否存在 value
- 存在则使用这个 value
- 不存在则根据调用该函数的作用域中去寻找也就是这里的 bar 函数,找到 value 为 2
在从内层到外层的变量搜索过程中,当前作用域到外层作用域再到更外层作用域直到最外层的全局作用域,整个搜寻轨迹就是 作用域链
20190307092333.png
变量的两种查找类型
一种是 rhs 一种是 lhs
假设有这么一段代码:
1console.log(a) // 输出 undefined
2console.log(a2) // 报错 a2 is not defined
3var a = 1
上述代码实际上在变量提升的作用下应该是下面这个顺序:
1var a
2console.log(a) // 输出 undefined
3console.log(a2) // 报错 a2 is not defined
4a = 1
- 第一个 console 输出 undefined 因为还未执行赋值操作,查询过程是
rhs
也就是right-hand-side
- 第二个 console 报错,是因为 rhs 查询 a2 变量不存在因此报错
-
a = 1
则是赋值操作,也就是lhs
,英文left-hand-side
20190307093837.png
闭包
闭包是啥?闭包就是从函数外部访问函数内部的变量,函数内部的变量可以持续存在的一种实现。
在了解了词法作用域和变量的查询方式之后,我们看看一个简单的闭包的实现逻辑:
1function f() {
2 num = 1 // 里面的变量
3 function add() {
4 num += 1
5 }
6 function log() {
7 console.log(num)
8 }
9 return { add, log } // 我要到外面去了
10}
11
12add = f().add
13log = f().log
14
15log() // 1 我从里面来,我在外面被调用,还是可以获得里面的变量
16add()
17log() // 2
- 首先定义一个 f 函数,函数内部维护一个变量 num,然后定义两个函数 add 和 log
- add 函数每次调用会增加 num 的值
- log 函数每次调用会打印 num 的值
- 然后我们将两个函数通过 return 方法返回
- 紧接着先调用外部的 log 方法打印 f 方法维护的 num,此时为 1
- 然后调用外部的 add 方法增加 num 的值
- 最后再次调用 log 方法打印 num,此时则为 2
为什么外部定义的 add 函数可以访问 f 函数内部的变量呢。正常情况下外部作用域不可访问内部作用域的变量,但我们将内部访问其内部变量的方法“导出”出去,以至于可以从外部直接调用函数内部的方法,这样我们就可以从函数的外部访问函数内部的变量了。
经典的 for 循环问题
1arr = []
2for (var i = 0; i < 10; i ++) {
3 arr[i] = function() {
4 console.log(i)
5 }
6}
7arr[2]() // 10
首先我们知道 for 循环体内的 i 实际上会被定义在全局作用域中
每次循环我们都将 function 推送到一个 arr 中,for 循环执行完毕后,arr 中张这样:
20190307101411.png
随后我们执行代码 arr[2]()
此时 arr[2]
对应的函数 function(){ console.log(i) }
会被触发
函数尝试搜索函数局部作用域中的 i 变量,搜索不到则会继续向外层搜索,i 被定义到了外层,因此会直接采用外层的 i,就是这里的全局作用域中的 i,等到这个时候调用这个函数,i 早已变成 10 了
那么有什么方法能够避免出现这种情况吗?
ES6 之前的解决方案:
了解了闭包我们就知道了闭包内的变量可以持续存在,所以修改代码将 arr 中的每一项改为指向一个闭包:
1arr = []
2for (var i = 0; i < 10; i ++) {
3 arr[i] = (function() { // 这是一个闭包
4 var temp = i // 闭包内部维护一个变量,这个变量可以持续存在
5 return function() {
6 console.log(temp)
7 }
8 })()
9}
这样程序就能按照我们的想法运行了
20190307102109.png
ES6 之后的解决方案:
ES6 之后我们就有了块级作用域因此代码可以改为这样:
1arr = []
2for (let i = 0; i < 10; i ++) { // 使用 let
3 arr[i] = function() {
4 console.log(i)
5 }
6}
在使用 let 之后,我们每次定义 i 都是通过 let i
的方法定义的,这个时候 i 不再是被定义到全局作用域中了,而是被绑定在了 for 循环的块级作用域中
因为是块级作用域所以对应 i 的 arr 每一项都变成了一个闭包,arr 每一项都在不同的块级作用域中因此不会相互影响
20190307102815.png
参考:
- https://github.com/mqyqingfeng/blog/issues/3
- https://www.datchley.name/basic-scope/
- https://segmentfault.com/a/1190000006671020
- 谈谈WCF中的Data Contract(4):WCF Data Contract Versioning
- 如何在silverlihgt中使用右键
- WCF技术剖析之十二:数据契约(Data Contract)和数据契约序列化器(DataContractSerializer)
- silverlight向服务器post数据类
- WCF技术剖析之十三:序列化过程中的已知类型(Known Type)
- 44 Amazing Silverlight 2.0 Screencasts
- CaseStudy(showcase)类库篇-用agTweener来实现动画效果
- CaseStudy(showcase)数据篇-Loading的制作
- CaseStudy(showcase)数据篇-加载图片
- CaseStudy(showcase)数据篇-从XML中获取数据
- CaseStudy(showcase)布局篇-全屏效果
- 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(12)-系统日志和异常的处理②
- 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(14)-EasyUI缺陷修复与扩展
- 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(10)-系统菜单栏[附源码]
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 斗图狂魔必备沙雕表情包,python多线程爬取斗图啦表情图片
- 5个基本Linux命令行工具的现代化替代品
- Chrome 84 正式发布,支持私有方法、用户空闲检测!
- 类及数据库的应用,G-MARK网站数据Python爬虫系统的构建
- 获取素材图无忧,Pixabay图库网Python多线程采集下载
- Python关键词数据采集案例,5118查询网站关键词数据采集
- Python结巴分词,字符串余弦相似度算法实现关键词筛选及整理
- git的分支远程连接和远程分支的拉取推送及冲突处理
- requests session的应用,python金点设计奖数据爬虫
- 站长工具关键词挖掘采集,Python关键词批量挖掘采集工具
- python百度关键词相关搜索词采集,链轮查询采集exe工具
- Python最火爬虫框架Scrapy入门与实践,豆瓣电影 Top 250 数据采集
- Python爬虫三种解析方式,Pyhton360搜索排名查询
- Python关键词百度指数采集,抓包Cookie及json数据处理
- 常用的package.json,还有这么多你不知道的骚技巧