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 相关文章推荐
Javascript面象对象成员、共享成员变量实验
Nov 19 Javascript
JS实现文字掉落效果的方法
May 06 Javascript
7个去伪存真的JavaScript面试题
Jan 07 Javascript
鼠标悬停小图标显示大图标
Jan 22 Javascript
分享一道关于闭包、bind和this的面试题
Feb 20 Javascript
jQuery实现滚动到底部时自动加载更多的方法示例
Feb 18 jQuery
Node.js搭建WEB服务器的示例代码
Aug 15 Javascript
对vue中methods互相调用的方法详解
Aug 30 Javascript
vue form 表单提交后刷新页面的方法
Sep 04 Javascript
Bootstrap4 gulp 配置详解
Jan 06 Javascript
浅谈Vue的响应式原理
May 30 Javascript
element-ui树形控件后台返回的数据+生成组织树的工具类
Mar 05 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
重置版战役片段
2020/04/09 魔兽争霸
用php实现像JSP,ASP里Application那样的全局变量
2007/01/12 PHP
深入PHP5中的魔术方法详解
2013/06/17 PHP
解析link_mysql的php版
2013/06/30 PHP
mac下安装nginx和php
2013/11/04 PHP
php异步多线程swoole用法实例
2014/11/14 PHP
php命令行(cli)下执行PHP脚本文件的相对路径的问题解决方法
2015/05/25 PHP
详解PHP的Yii框架中扩展的安装与使用
2016/04/01 PHP
修改yii2.0用户登录使用的user表为其它的表实现方法(推荐)
2017/08/01 PHP
php实现单笔转账到支付宝功能
2018/10/09 PHP
巧妙破除网页右键禁用的十大绝招
2006/08/12 Javascript
javascript应用:Iframe自适应其加载的内容高度
2007/04/10 Javascript
jquery学习笔记 用jquery实现无刷新登录
2011/08/08 Javascript
DOM基础教程之使用DOM设置文本框
2015/01/20 Javascript
jQuery拖动元素并对元素进行重新排序
2015/12/30 Javascript
JavaScript的设计模式经典之建造者模式
2016/02/24 Javascript
AngularJS指令用法详解
2016/11/02 Javascript
js通过classname来获取元素的方法
2016/11/24 Javascript
angularJS利用ng-repeat遍历二维数组的实例代码
2017/06/03 Javascript
bootstrap table实现双击可编辑、添加、删除行功能
2017/09/27 Javascript
微信小程序tabBar模板用法实例分析【附demo源码下载】
2017/11/28 Javascript
es6新特性之 class 基本用法解析
2018/05/05 Javascript
python re正则表达式模块(Regular Expression)
2014/07/16 Python
在Python中操作字典之fromkeys()方法的使用
2015/05/21 Python
Python数据结构与算法之链表定义与用法实例详解【单链表、循环链表】
2017/09/28 Python
python中多个装饰器的执行顺序详解
2018/10/08 Python
详解利用django中间件django.middleware.csrf.CsrfViewMiddleware防止csrf攻击
2018/10/09 Python
Python程序包的构建和发布过程示例详解
2019/06/09 Python
CSS3绘制超炫的上下起伏波动进度加载动画
2016/04/21 HTML / CSS
香港钟表珠宝首饰商城:OneMallTime网摩间
2016/10/14 全球购物
玉兰油美国官网:OLAY美国
2018/10/25 全球购物
路由表示做什么用的?在linux环境中怎么来配置一条默认路由?
2013/06/07 面试题
安全生产感想
2015/08/07 职场文书
Django项目配置Memcached和Redis, 缓存选择哪个更有优势
2021/04/06 Python
我家女友可不止可爱呢 公开OP主题曲无字幕动画MV
2022/04/11 日漫
MySQL sql模式设置引起的问题
2022/05/15 MySQL