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

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

引言

先点赞,再看博客,顺手能够点个关注。
微信公众号搜索【Lpyexplore的编程小屋】,关注我,带你在python爬虫的过程当中学习前端javascript

以前我写过一篇关于JavaScript中的对象的一篇文章,里面也提到了做用域链的概念,相信你们对这个概念仍是没有很深的理解,而且这个概念也是面试中常常问到的,由于这个概念实在过重要了,在咱们平时写代码时,也可能会由于做用域链的问题,而出现莫名其妙的bug,致使咱们花费大量的时间都查找不出缘由。因此我就准备单独写一篇关于做用域链的文章,来帮你们更好地理解这个概念。html

正文

1、执行环境

首先,咱们要引入一个概念,叫作执行环境(下面简称环境)。在一个执行环境中,有一个与之关联的变量对象(下面简称对象),在该对象中,储存着这个执行环境中定义的变量和函数。但这个对象只是个形式上的对象,并不能被外界所访问到。前端

例如,在浏览器中,咱们在全局运行下列代码,那么当前的执行环境就是window,也就是全局,而且与该全局环境关联的对象中存储着定义的变量fruitjava

<!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 和函数 fn1python

<!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>

2、做用域链

看了上面两个例子,咱们对执行环境应该有了必定的了解,那么这里就将引入做用域链的概念了,当代码执行在一个环境中时,会针对环境中储存变量和函数的对象建立一个做用域链,做用域链的最前端就是当前环境的对象,若是当前环境是个函数,则做用域链的下一部分就是全局的window环境的变量对象。web

先来看一下代码部分面试

<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 ,此时函数内的做用域链就是这样的编程

在这里插入图片描述
咱们看到,在函数 fn 中,咱们使用了变量 fruitcolor,因此此时会从做用域链的头部开始,从第一个活动变量(本例中第一个变量对象就是函数fn的活动变量)中,寻找变量 fruitcolor,发现该变量对象中存在变量color,因而就成功引用了变量color,可是由于没有找到变量 fruit,因而再沿着做用域链往下找到下一个变量对象(本例中第二个活动变量就是全局window的变量对象),发现该变量对象中有咱们想要的变量 fruit,则引用该变量 fruit ,同时,由于找到了须要引用的变量,就不会继续沿着做用域链继续向下寻找了。浏览器

咱们再来看在函数外,也就是全局window中,也执行了console.log('我最喜欢的水果是' + fruit + ',我最喜欢的颜色是' + color),此时在全局环境中的做用域链是这样的
在这里插入图片描述
此时也使用了变量 fruitcolor,因此这时会从做用域链的头部开始,找到第一个变量对象(本例中第一个活动变量就是window全局变量对象),发现该变量对象中有变量 fruit,因此成功引用该变量对象中的 fruit,但由于没找到变量 color,因此继续沿着做用域链向下寻找下一个活动变量,但此时已经找到了做用域链的尾部,并无别的变量对象了,因此也就没法找到变量 color了,因此最后返回的就是 undefined。在本例中咱们能够看到,当代码处于全局环境中时,是没有访问函数fn执行环境中的变量color的权力的,这里咱们能够这种现象当作是变量color做用域只是在函数fn的执行环境内。微信

这就是代码执行时,做用域链起到的做用,因此做用域链就保证了执行环境中代码对变量的有序访问。

3、块级做用域

在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中,出现了使用 letconst声明变量的方式,来解决了JavaScript中没有块级做用域的问题。
大家能够看我以前写的一篇关于letconst 声明变量的文章——尚未理解let 和 const的用法和区别吗,几百字让你立马搞懂

4、其余状况

其实,还有一种状况,会影响变量的访问顺序,那就是在声明变量时,直接给一个未声明的变量赋值,例如这样

function fn() {
	sum = 1 + 2
}
fn()

console.log(sum)      //返回 3

按照咱们本文前面讲解的做用域链的知识,当执行到最后一局代码时,此时处于全局执行环境中,查询不到变量 sum,因此应当会报错 undefined,但这里却返回了 3。

这是由于,在咱们使用var声明变量时,会自动将该变量放到离该代码最近的活动变量中去,也就是函数fn的活动变量中,因此在全局执行环境中的代码就没法访问到该变量。可是若是不使用var,而是像这个例子中同样,直接给一个未定义的变量赋值,这时会自动地将该变量放到全局的活动变量中去,这就是致使本例中在全局环境中还能访问到变量sum的缘由。

5、总结

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

结束语

好了,对于做用域链的讲解就到这里了,相信这下你们对JavaScript中的做用域链有了很深的理解了吧,我相信,理解了这个概念,能够消除咱们代码中大部分不必的BUG。

我是前端Lpyexplore,原创不易,喜欢个人文章的点个关注,甩个赞,不嫌麻烦的评论支持一下,谢谢你们啦~

相关文章
相关标签/搜索