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 相关文章推荐
小议Function.apply() 之一------(函数的劫持与对象的复制)
Nov 30 Javascript
WebGame《逆转裁判》完整版 代码下载(1月24日更新)
Jan 29 Javascript
几个比较经典常用的jQuery小技巧
Mar 01 Javascript
使用JavaScript构建JSON格式字符串实现步骤
Mar 22 Javascript
js函数返回多个返回值的示例代码
Nov 05 Javascript
jQuery 动态云标签插件
Nov 11 Javascript
ztree获取当前选中节点子节点id集合的方法
Feb 12 Javascript
JavaScript中window.open用法实例详解
Apr 15 Javascript
微信小程序  简单实例(阅读器)的实例开发
Sep 29 Javascript
基本DOM节点操作
Jan 17 Javascript
ES6中let 和 const 的新特性
Sep 03 Javascript
微信小程序基于movable-view实现滑动删除效果
Jan 08 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/04/21 PHP
PHP解决URL中文GBK乱码问题的两种方法
2014/06/03 PHP
PHP实现获取某个月份周次信息的方法
2015/08/11 PHP
用javascript实现计算两个日期的间隔天数
2007/08/14 Javascript
js函数使用技巧之 setTimeout(function(){},0)
2009/02/09 Javascript
JavaScript CSS修改学习第二章 样式
2010/02/19 Javascript
js计算字符串长度包含的中文是utf8格式
2013/10/15 Javascript
js获取元素相对窗口位置的实现代码
2014/09/28 Javascript
jQuery获得子元素个数的方法
2015/04/14 Javascript
如何利用JS通过身份证号获取当事人的生日、年龄、性别
2016/01/22 Javascript
深入理解jquery中的事件与动画
2016/05/24 Javascript
jQuery插件echarts实现的单折线图效果示例【附demo源码下载】
2017/03/04 Javascript
JavaScript 字符串数字左补位,右补位,取固定长度,截位扩展函数代码
2017/03/25 Javascript
js 监控iframe URL的变化实例代码
2017/07/12 Javascript
详解开源的JavaScript插件化框架MinimaJS
2017/10/26 Javascript
通过seajs实现JavaScript的模块开发及按模块加载
2019/06/06 Javascript
JavaScript实现图片放大镜效果
2019/06/27 Javascript
微信接入之获取用户头像的方法步骤
2019/09/23 Javascript
[01:04:31]DOTA2-DPC中国联赛定级赛 iG vs Magma BO3第二场 1月8日
2021/03/11 DOTA
Django中利用filter与simple_tag为前端自定义函数的实现方法
2017/06/15 Python
python3反转字符串的3种方法(小结)
2019/11/07 Python
Python如何爬取qq音乐歌词到本地
2020/06/01 Python
Python定时任务框架APScheduler原理及常用代码
2020/10/05 Python
matplotlib事件处理基础(事件绑定、事件属性)
2021/02/03 Python
美国派对用品及装饰品网上商店:Shindigz
2016/07/30 全球购物
美国马匹用品和马钉购物网站:State Line Tack
2018/08/05 全球购物
护理学中专毕业生求职信
2013/11/11 职场文书
大学生村官心得体会范文
2014/01/04 职场文书
《台湾的蝴蝶谷》教学反思
2014/02/20 职场文书
施工安全责任书
2014/04/14 职场文书
2015年财务部年度工作总结
2015/05/19 职场文书
家长对学校的意见和建议
2015/06/03 职场文书
2015年中学图书馆工作总结
2015/07/22 职场文书
2016春季运动会开幕词
2016/03/04 职场文书
启迪人心的励志语录:脾气永远不要大于本事
2020/01/02 职场文书
手写实现JS中的new
2021/11/07 Javascript