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 相关文章推荐
用js得到网页中所有的div的id
Oct 19 Javascript
关于jquery性能最佳实践的讨论,与求教
Mar 30 Javascript
一个简单的jQuery插件ajaxfileupload.js实现ajax上传文件例子
Jun 26 Javascript
基于javascript实现单选及多选的向右和向左移动实例
Jul 25 Javascript
jquery中表单 多选框的一种巧妙写法
Sep 06 Javascript
window.setInterval()方法的定义和用法及offsetLeft与style.left的区别
Nov 11 Javascript
基于javascript实现精确到毫秒的倒计时限时抢购
Apr 17 Javascript
原生js获取iframe中dom元素--父子页面相互获取对方dom元素的方法
Aug 05 Javascript
Canvas 绘制粒子动画背景
Feb 15 Javascript
JS实现的二叉树算法完整实例
Apr 06 Javascript
微信小程序 动态绑定事件并实现事件修改样式
Apr 13 Javascript
Openlayers实现点闪烁扩散效果
Sep 24 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
PHP5.3与5.5废弃与过期函数整理汇总
2014/07/10 PHP
php实现监控varnish缓存服务器的状态
2014/12/30 PHP
php使用Header函数,PHP_AUTH_PW和PHP_AUTH_USER做用户验证
2016/05/04 PHP
postfixadmin忘记密码后的修改密码方法详解
2016/07/20 PHP
php 文件下载 出现下载文件内容乱码损坏的解决方法(推荐)
2016/11/16 PHP
laravel框架语言包拓展实现方法分析
2019/11/22 PHP
基于prototype的validation.js发布2.3.4新版本,让你彻底脱离表单验证的烦恼
2006/12/06 Javascript
FileUpload 控件 禁止手动输入或粘贴的实现代码
2010/04/07 Javascript
面向对象Javascript核心支持代码分享
2012/05/23 Javascript
jQuery Mobile页面跳转后未加载外部JS原因分析及解决
2013/03/18 Javascript
XMLHttpRequest处理xml格式的返回数据(示例代码)
2013/11/21 Javascript
Extjs表单常见验证小结
2014/03/07 Javascript
js统计页面的来访次数实现代码
2014/05/09 Javascript
jquery中$(#form :input)与$(#form input)的区别
2014/08/18 Javascript
JavaScript使用push方法添加一个元素到数组末尾用法实例
2015/04/06 Javascript
jquery分析文本里url或邮件地址为真实链接的方法
2015/06/20 Javascript
理解 JavaScript Scoping &amp; Hoisting(二)
2015/11/18 Javascript
JS跨域解决方案之使用CORS实现跨域
2016/04/14 Javascript
第一次接触神奇的Bootstrap
2016/10/14 Javascript
JS实现的数字格式化功能示例
2017/02/10 Javascript
babel的使用及安装配置教程
2018/02/22 Javascript
Vue 路由切换时页面内容没有重新加载的解决方法
2018/09/01 Javascript
8个有意思的JavaScript面试题
2019/07/30 Javascript
[52:52]DOTA2上海特级锦标赛C组资格赛#1 OG VS LGD第三局
2016/02/27 DOTA
[01:02:04]EG vs Liquid 2019国际邀请赛淘汰赛 败者组 BO3 第一场 8.23
2019/09/05 DOTA
记录Django开发心得
2014/07/16 Python
python中class的定义及使用教程
2019/09/18 Python
CSS3实现淘宝留白的方法
2020/06/05 HTML / CSS
The North Face北面德国官网:美国著名户外品牌
2018/12/12 全球购物
澳大利亚第一旅行车和房车配件店:Caravan RV Camping
2020/12/26 全球购物
银行毕业实习自我鉴定
2013/09/19 职场文书
北大青鸟学生求职信
2013/09/24 职场文书
Python机器学习之逻辑回归
2021/05/11 Python
Nginx如何配置Http、Https、WS、WSS的方法步骤
2021/05/11 Servers
Python人工智能之混合高斯模型运动目标检测详解分析
2021/11/07 Python
微信小程序实现轮播图指示器
2022/06/25 Javascript