JavaScript中作用域链的概念及用途讲解


Posted in Javascript onAugust 06, 2020

从零开始讲解JavaScript中作用域链的概念及用途

引言

之前我写过一篇关于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 ,此时函数内的作用域链就是这样的

JavaScript中作用域链的概念及用途讲解

我们看到,在函数 fn 中,我们使用了变量 fruit 和 color,所以此时会从作用域链的头部开始,从第一个活动变量(本例中第一个变量对象就是函数fn的活动变量)中,寻找变量 fruit和 color,发现该变量对象中存在变量color,于是就成功引用了变量color,但是因为没有找到变量 fruit,于是再沿着作用域链往下找到下一个变量对象(本例中第二个活动变量就是全局window的变量对象),发现该变量对象中有我们想要的变量 fruit,则引用该变量 fruit ,同时,因为找到了需要引用的变量,就不会继续沿着作用域链继续向下寻找了。

我们再来看在函数外,也就是全局window中,也执行了console.log('我最喜欢的水果是' + fruit + ',我最喜欢的颜色是' + color),此时在全局环境中的作用域链是这样的

JavaScript中作用域链的概念及用途讲解

此时也使用了变量 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的原因。

五、总结

  1. 作用域链可以看成是将变量对象按顺序连接起来的一根链子
  2. 每个执行环境中的作用域链都是不同的
  3. 当我们引用变量时,会顺着当前执行环境的作用域链,从作用域链的开头开始依次往下寻找对应的变量,直到找到作用域链的尾部,报错undefined
  4. 作用域链保证了变量的有序访问

结束语

好了,对于作用域链的讲解就到这里了,相信这下大家对JavaScript中的作用域链有了很深的理解了吧,我相信,理解了这个概念,可以消除我们代码中大部分没必要的BUG。

到此这篇关于JavaScript中作用域链的概念及用途讲解的文章就介绍到这了,更多相关JavaScript中作用域链内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
jQuery 页面 Mask实现代码
Jan 09 Javascript
JS可以控制样式的名称写法一览
Jan 16 Javascript
js使用eval解析json实例与注意事项分享
Jan 18 Javascript
javaScript中slice函数用法实例分析
Jun 08 Javascript
Javascript实现的Map集合工具类完整实例
Jul 31 Javascript
jQuery实现手机自定义弹出输入框
Jun 13 Javascript
浅谈js使用in和hasOwnProperty获取对象属性的区别
Apr 27 Javascript
JavaScript 隐性类型转换步骤浅析
Mar 15 Javascript
Vue中保存数据到磁盘文件的方法
Sep 06 Javascript
es6 filter() 数组过滤方法总结
Apr 03 Javascript
用js限制网页只在微信浏览器中打开(或者只能手机端访问)
Dec 24 Javascript
js实现简单商品筛选功能
Feb 02 Javascript
解决vue字符串换行问题(绝对管用)
Aug 06 #Javascript
vue 路由缓存 路由嵌套 路由守卫 监听物理返回操作
Aug 06 #Javascript
Vue Router中应用中间件的方法
Aug 06 #Javascript
JavaScript的垃圾回收机制与内存管理
Aug 06 #Javascript
vue-路由精讲 二级路由和三级路由的作用
Aug 06 #Javascript
如何利用javascript接收json信息并进行处理
Aug 06 #Javascript
浅谈vue 二级路由嵌套和二级路由高亮问题
Aug 06 #Javascript
You might like
php格式化日期和时间格式化示例分享
2014/02/24 PHP
PHP扩展Memcache分布式部署方案
2015/12/06 PHP
php封装的表单验证类完整实例
2016/10/19 PHP
js之WEB开发调试利器:Firebug 下载
2007/01/13 Javascript
用jQuery打造TabPanel效果代码
2010/05/22 Javascript
动态改变div的z-index属性的简单实例
2013/08/08 Javascript
javascript+html5实现仿flash滚动播放图片的方法
2015/04/27 Javascript
Node.js中使用socket创建私聊和公聊聊天室
2015/11/19 Javascript
【经典源码收藏】基于jQuery的项目常见函数封装集合
2016/06/07 Javascript
AngularJS表达式讲解及示例代码
2016/08/16 Javascript
jQuery插件HighCharts实现气泡图效果示例【附demo源码】
2017/03/13 Javascript
微信小程序实现tab和swiper切换结合效果
2020/07/17 Javascript
微信小程序实现人脸识别
2018/05/25 Javascript
Bootstrap的aria-label和aria-labelledby属性实例详解
2018/11/02 Javascript
用Cordova打包Vue项目的方法步骤
2019/02/02 Javascript
微信小程序实现星级评价
2019/11/20 Javascript
JS精确判断数据类型代码实例
2019/12/18 Javascript
jquery绑定事件 bind和on的用法与区别分析
2020/05/22 jQuery
[02:33]2014DOTA2 TI每日综述 LGD涉险晋级DK闯入胜者组
2014/07/14 DOTA
详解使用 pyenv 管理多个版本 python 环境
2017/10/19 Python
详解Python发送email的三种方式
2018/10/18 Python
python切片的步进、添加、连接简单操作示例
2019/07/11 Python
PyInstaller将Python文件打包为exe后如何反编译(破解源码)以及防止反编译
2020/04/15 Python
keras 简单 lstm实例(基于one-hot编码)
2020/07/02 Python
python os.listdir()乱码解决方案
2021/01/31 Python
HTML+CSS3 模仿Windows7 桌面效果
2010/06/17 HTML / CSS
金智子午JAVA面试题
2015/09/04 面试题
课改先进个人汇报材料
2014/01/26 职场文书
酒店保安领班职务说明书
2014/03/04 职场文书
校庆接待方案
2014/03/18 职场文书
企业安全生产责任书
2014/04/14 职场文书
上课说话检讨书
2015/01/27 职场文书
整改通知书
2015/04/20 职场文书
大学生干部培训心得体会
2016/01/06 职场文书
Java面试题冲刺第十七天--基础篇3
2021/08/07 面试题
javascript实现计算器功能详解流程
2021/11/01 Javascript