从零开始讲解JavaScript中作用域链的概念及用途
引言
先点赞,再看博客,顺手可以点个关注。
微信公众号搜索【Lpyexplore的编程小屋】,关注我,带你在python爬虫的过程中学习前端
之前我写过一篇关于JavaScript中的对象的一篇文章,里面也提到了作用域链的概念,相信大家对这个概念还是没有很深的理解,并且这个概念也是面试中经常问到的,因为这个概念实在太重要了,在我们平时写代码时,也可能会因为作用域链的问题,而出现莫名其妙的bug,导致我们花费大量的时间都查找不出原因。所以我就准备单独写一篇关于作用域链的文章,来帮大家更好地理解这个概念。
正文
一、执行环境
首先,我们要引入一个概念,叫做执行环境(下面简称环境)。在一个执行环境中,有一个与之关联的变量对象(下面简称对象),在该对象中,储存着这个执行环境中定义的变量和函数。但这个对象只是个形式上的对象,并不能被外界所访问到。
例如,在浏览器中,我们在全局运行下列代码,那么当前的执行环境就是window
,也就是全局,并且与该全局环境关联的对象中存储着定义的变量fruit
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
let fruit = 'banana'
alert(fruit)
</script>
</body>
</html>
那么,在javascript中,函数也会形成一个环境,例如下列的代码中,函数的内部就是一个局部的环境,与该环境关联的对象中存储着变量my_favorite
;而此时全局环境window
中,也有一个与之关联的对象,该对象中存储着变量fruit
和函数 fn1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
let fruit = 'banana'
alert(fruit)
function fn1() {
let my_favorite = 'apple'
return my_favorite
}
fn1()
</script>
</body>
</html>
二、作用域链
看了上面两个例子,我们对执行环境应该有了一定的了解,那么这里就将引入作用域链的概念了,当代码执行在一个环境中时,会针对环境中储存变量和函数的对象创建一个作用域链,作用域链的最前端就是当前环境的对象,如果当前环境是个函数,则作用域链的下一部分就是全局的window
环境的变量对象。
先来看一下代码部分
<script>
let fruit = 'banana'
function fn() {
let color = 'red'
//返回 '我最喜欢的水果是banana,我最喜欢的颜色是red'
console.log('我最喜欢的水果是' + fruit + ',我最喜欢的颜色是' + color)
}
fn()
//报错, color is undefined
console.log('我最喜欢的水果是' + fruit + ',我最喜欢的颜色是' + color)
</script>
首先执行了函数 fn
,此时函数内的作用域链就是这样的
我们看到,在函数 fn
中,我们使用了变量 fruit
和 color
,所以此时会从作用域链的头部开始,从第一个活动变量(本例中第一个变量对象就是函数fn的活动变量)中,寻找变量 fruit
和 color
,发现该变量对象中存在变量color
,于是就成功引用了变量color
,但是因为没有找到变量 fruit
,于是再沿着作用域链往下找到下一个变量对象(本例中第二个活动变量就是全局window的变量对象),发现该变量对象中有我们想要的变量 fruit
,则引用该变量 fruit
,同时,因为找到了需要引用的变量,就不会继续沿着作用域链继续向下寻找了。
我们再来看在函数外,也就是全局window
中,也执行了console.log('我最喜欢的水果是' + fruit + ',我最喜欢的颜色是' + color)
,此时在全局环境中的作用域链是这样的
此时也使用了变量 fruit
和 color
,所以这时会从作用域链的头部开始,找到第一个变量对象(本例中第一个活动变量就是window全局变量对象),发现该变量对象中有变量 fruit
,所以成功引用该变量对象中的 fruit
,但因为没找到变量 color
,所以继续沿着作用域链向下寻找下一个活动变量,但此时已经找到了作用域链的尾部,并没有别的变量对象了,所以也就无法找到变量 color
了,所以最后返回的就是 undefined
。在本例中我们可以看到,当代码处于全局环境中时,是没有访问函数fn
执行环境中的变量color
的权力的,这里我们可以这种现象看成是变量color
的作用域只是在函数fn
的执行环境内。
这就是代码执行时,作用域链起到的作用,所以作用域链就保证了执行环境中代码对变量的有序访问。
三、块级作用域
在JavaScript中是没有块级作用域的,也就是说,由花括号或小括号封闭起来的区域内没有自己的作用域,例如这两个例子
if(true) {
var fruit = 'banana'
}
console.log(fruit) //返回 banana
我们可以看到,if
语句中的花括号内,使用 var
定义了一个变量 fruit
,按照作用域链的规则来说,外部是无法访问到该变量的,但是我们可以看到,确实返回了这个变量的值 banana
。
再来看下一个例子
for(var i=0; i<4; i++) {
alert(i)
}
console.log(i) //返回4
在使用 for
语句时,我们在小括号里使用var
定义了一个临时变量i
,同样的的,在 for
循环结束以后,在外部访问该变量,也成功返回了相应的值。
以上两个例子,都是因为JavaScript没有块级作用域引起的,所以有时会因为这种情况,导致一些不必要的麻烦。在ES6中,出现了使用 let
和 const
声明变量的方式,来解决了JavaScript中没有块级作用域的问题。
你们可以看我之前写的一篇关于let
和 const
声明变量的文章——还没有理解let 和 const的用法和区别吗,几百字让你立马搞懂
四、其他情况
其实,还有一种情况,会影响变量的访问顺序,那就是在声明变量时,直接给一个未声明的变量赋值,例如这样
function fn() {
sum = 1 + 2
}
fn()
console.log(sum) //返回 3
按照我们本文前面讲解的作用域链的知识,当执行到最后一局代码时,此时处于全局执行环境中,查询不到变量 sum
,所以应当会报错 undefined
,但这里却返回了 3。
这是因为,在我们使用var
声明变量时,会自动将该变量放到离该代码最近的活动变量中去,也就是函数fn
的活动变量中,所以在全局执行环境中的代码就无法访问到该变量。但是如果不使用var
,而是像这个例子中一样,直接给一个未定义的变量赋值,这时会自动地将该变量放到全局的活动变量中去,这就是导致本例中在全局环境中还能访问到变量sum
的原因。
五、总结
- 作用域链可以看成是将变量对象按顺序连接起来的一根链子
- 每个执行环境中的作用域链都是不同的
- 当我们引用变量时,会顺着当前执行环境的作用域链,从作用域链的开头开始依次往下寻找对应的变量,直到找到作用域链的尾部,报错undefined
- 作用域链保证了变量的有序访问
结束语
好了,对于作用域链的讲解就到这里了,相信这下大家对JavaScript中的作用域链有了很深的理解了吧,我相信,理解了这个概念,可以消除我们代码中大部分没必要的BUG。
我是前端Lpyexplore,原创不易,喜欢我的文章的点个关注,甩个赞,不嫌麻烦的评论支持一下,谢谢大家啦~
从零开始讲解JavaScript中作用域链的概念及用途的更多相关文章
- 理解JavaScript中作用域链的关系
javascript里的关系又多又乱.作用域链是一种单向的链式关系,还算简单清晰:this机制的调用关系,稍微有些复杂:而关于原型,则是prototype.proto和constructor的三角关系 ...
- 初探JavaScript(四)——作用域链和声明提前
前言:最近恰逢毕业季,千千万万的学生党开始步入社会,告别象牙塔似的学校生活.往往在人生的各个拐点的时候,情感丰富,感触颇深,各种对过去的美好的总结,对未来的展望.与此同时,也让诸多的老“园”工看完这些 ...
- JavaScript中作用域和作用域链的简单理解(变量提升)
通过阅读<JS高级程序设计>这本书,对js中的作用域和作用域链知识有了初步的了解和认识,准备成笔记供大家参考,笔记中字数比较多,但个人认为叙述的挺详细的,所以希望读者耐心看.再者,本人了解 ...
- JavaScript中作用域和作用域链解析
学习js,肯定要学习作用域,js作用域和其他的主流语言的作用域还存在很大的区别. 一.js没有块级作用域. js没有块级作用域,就像这样: if(){ : console.log(a) //输出100 ...
- 理解JavaScript的作用域链
上一篇文章中介绍了Execution Context中的三个重要部分:VO/AO,scope chain和this,并详细的介绍了VO/AO在JavaScript代码执行中的表现. 本文就看看Exec ...
- JavaScript系列----作用域链和闭包
1.作用域链 1.1.什么是作用域 谈起作用域链,我们就不得不从作用域开始谈起.因为所谓的作用域链就是由多个作用域组成的.那么, 什么是作用域呢? 1.1.1作用域是一个函数在执行时期的执行环境. 每 ...
- 从函数作用域和块级作用域看javascript的作用域链
在ES6之前,javascript只有全局作用域和函数作用域.所谓作用域就是一个变量定义并能够被访问到的范围.也就是说如果一个变量定义在全局(window)上,那么在任何地方都能访问到这个变量,如果这 ...
- javascript 之作用域链-07
复习作用域 上一节我们说到作用域:是指变量可以访问的范围,他规定了如何查找变量,以及确定当前执行代码对变量的访问权限:也说到静态作用域即词法作用域,是在编译阶段决定变量的引用(由程序定义的位置决定,和 ...
- javascript 之作用域链-10
前言 在<执行环境>文中说到,当JavaScript代码执行一段可执行代码时,会创建对应的执行上下文(execution context). 变量对象(Variable object,VO ...
随机推荐
- DJANGO-天天生鲜项目从0到1-010-购物车-购物车操作页面(勾选+删改)
本项目基于B站UP主‘神奇的老黄’的教学视频‘天天生鲜Django项目’,视频讲的非常好,推荐新手观看学习 https://www.bilibili.com/video/BV1vt41147K8?p= ...
- vue学习(二) 三个指令v-cloak v-text v-html
//style <style> [v-cloak]{ display:none } </style> //html <div id="app"> ...
- Java基础加强笔记——测试、反射、注解
目录 1. Junit单元测试 2. 反射 3. 注解 Junit单元测试: 测试分类: 1. 黑盒测试:不需要写代码,给输入值,看程序是否能够输出期望的值. 2. 白盒测试:需要写代码的.关注程序具 ...
- Java MD5获取
MD5算法具有以下特点: 1.压缩性:任意长度的数据,算出的MD5值长度都是固定的. 2.容易计算:从原数据计算出MD5值很容易. 3.抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的MD ...
- 萌新学渗透系列之Hack The Box_Lame
我将我的walkthrough过程用视频解说的形式记载 视频地址https://www.bilibili.com/video/BV1Mv411z75c 一是因为看我视频的后来者应该都是刚入门的新手,视 ...
- 动态规划入门(dp)
dp的基本思想,是把大问题转化成一个个小问题,然后递归解决. 所以本质思想的话还是递归. dp最重要的是要找到状态转移方程,也就是把大问题化解的过程. 举个例子 一个数字金字塔 在上面的数字三角形中寻 ...
- Fortify Audit Workbench 笔记 Race Condition: Singleton Member Field 竞争条件:单例的成员字段
Race Condition: Singleton Member Field 竞争条件:单例的成员字段 Abstract Servlet 成员字段可能允许一个用户查看其他用户的数据. Explanat ...
- [草稿]基于 Virtuoso 环境比较便捷的项目文件及权限管理方案
https://www.cnblogs.com/yeungchie/ 假设如下情况: 1 项目名称 Project_01 2 包含 4 名研发用户,user01 和 user02 为前端工程师,use ...
- luogu P3645 [APIO2015]雅加达的摩天楼 分块 根号分治
LINK:雅加达的摩天楼 容易想到设\(f_{i,j}\)表示第i个\(doge\)在第j层楼的最小步数. 转移显然是bfs.值得一提的是把初始某层的\(doge\)加入队列 然后转移边权全为1不需要 ...
- PAM学习笔记
想了想 还是要先把字符串的东西先都学完告一段落了再说 时间不多了 加油~. PAM 回文自动机 比SAM简单到不知道哪里去了. 回文自动机和其他自动机一样有字符集 有状态 有转移. 一个字符串的回文自 ...