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 相关文章推荐
兼容IE/Firefox/Opera/Safari的检测页面装载完毕的脚本Ext.onReady的实现
Jul 14 Javascript
Javascript 汉字字节判断
Aug 01 Javascript
JQuery插件Style定制化方法的分析与比较
May 03 Javascript
浅谈JavaScript中的string拥有方法的原因
Aug 28 Javascript
jquery判断对象是否为空并遍历对象的简单实例
Jul 26 Javascript
JavaScript模仿Pinterest实现图片预加载功能
Oct 25 Javascript
实例解析jQuery中如何取消后续执行内容
Dec 01 Javascript
JavaScript 计算笛卡尔积实例详解
Dec 02 Javascript
JS对象创建的几种方式整理
Feb 28 Javascript
jQuery完成表单验证的实例代码(纯代码)
Sep 30 jQuery
swiper 解决动态加载数据滑动失效的问题
Feb 26 Javascript
vue-cli中使用高德地图的方法示例
Mar 28 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根据树的前序遍历和中序遍历构造树并输出后序遍历的方法
2017/11/10 PHP
Laravel 加载第三方类库的方法
2018/04/20 PHP
JS创建优美的页面滑动块效果 - Glider.js
2007/09/27 Javascript
很多人都是用下面的js刷新站IP和PV
2008/09/05 Javascript
JS打开图片另存为对话框实现代码
2012/12/26 Javascript
JS操作HTML自定义属性的方法
2015/02/10 Javascript
jQuery+jRange实现滑动选取数值范围特效
2015/03/14 Javascript
基于js实现微信发送好友如何分享到朋友圈、微博
2015/11/30 Javascript
jQuery实现批量判断表单中文本框非空的方法(2种方法)
2015/12/09 Javascript
BootStrap和jQuery相结合实现可编辑表格
2016/04/21 Javascript
jQuery stop()用法实例详解
2016/07/28 Javascript
Nodejs中解决cluster模块的多进程如何共享数据问题
2016/11/10 NodeJs
bootstrap datetimepicker日期插件超详细使用方法介绍
2017/02/23 Javascript
解决JS外部文件中文注释出现乱码问题
2017/07/09 Javascript
微信小程序使用slider设置数据值及switch开关组件功能【附源码下载】
2017/12/09 Javascript
Angular.JS读取数据库数据调用完整实例
2019/07/02 Javascript
vue使用swiper实现中间大两边小的轮播图效果
2019/11/24 Javascript
微信小程序激励式视频广告组件使用详解
2019/12/06 Javascript
jQuery实现轮播图效果demo
2020/01/11 jQuery
解决vue单页面应用进入页面加载所有 js 的问题
2020/08/12 Javascript
openlayers实现地图弹窗
2020/09/25 Javascript
深入了解Vue3模板编译原理
2020/11/19 Vue.js
如何在vue中使用HTML 5 拖放API
2021/01/14 Vue.js
[01:03:27]NAVI vs EG 2019国际邀请赛小组赛 BO2 第一场 8.15
2019/08/17 DOTA
详细解读Python中的__init__()方法
2015/05/02 Python
python 实现查询Neo4j多节点的多层关系
2019/12/23 Python
python获取linux系统信息的三种方法
2020/10/14 Python
Django URL参数Template反向解析
2020/11/24 Python
CSS3实现伪类hover离开时平滑过渡效果示例
2017/08/10 HTML / CSS
应届生污水处理求职信
2013/11/06 职场文书
人力资源管理专业求职信
2014/07/23 职场文书
2014审计局领导班子民主生活会对照检查材料思想汇报
2014/09/20 职场文书
语文教师求职信范文
2015/03/20 职场文书
2015年党建工作总结
2015/03/30 职场文书
团支部书记竞选稿
2015/11/21 职场文书
python爬虫之利用selenium模块自动登录CSDN
2021/04/22 Python