JavaScript中作用域链的概念及用途

更新日期: 2020-03-26 阅读: 2.2k 标签: 作用域

引言

之前我写过一篇关于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 ,此时函数内的作用域链就是这样的

我们看到,在函数 fn 中,我们使用了变量 fruit 和 color,所以此时会从作用域链的头部开始,从第一个活动变量(本例中第一个变量对象就是函数fn的活动变量)中,寻找变量 fruit和 color,发现该变量对象中存在变量color,于是就成功引用了变量color,但是因为没有找到变量 fruit,于是再沿着作用域链往下找到下一个变量对象(本例中第二个活动变量就是全局window的变量对象),发现该变量对象中有我们想要的变量 fruit,则引用该变量 fruit ,同时,因为找到了需要引用的变量,就不会继续沿着作用域链继续向下寻找了。我们再来看在函数外,也就是全局window中,也执行了console.log('我最喜欢的水果是' + fruit + ',我最喜欢的颜色是' + color),此时在全局环境中的作用域链是这样的

此时也使用了变量 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中没有块级作用域的问题。


四、其他情况

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

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

console.log(sum)      //返回 3

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

这是因为,在我们使用var声明变量时,会自动将该变量放到离该代码最近的活动变量中去,也就是函数fn的活动变量中,所以在全局执行环境中的代码就无法访问到该变量。但是如果不使用var,而是像这个例子中一样,直接给一个未定义的变量赋值,这时会自动地将该变量放到全局的活动变量中去,这就是导致本例中在全局环境中还能访问到变量sum的原因。


五、总结

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

来自:https://segmentfault.com/a/1190000023001603


本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!

链接: https://fly63.com/article/detial/9340

相关推荐

javascript作用域有几种类型?

作用域是可访问的变量的集合;在JavaScript中,作用域为可访问变量,对象,函数的集合。那么在JavaScript中有几种作用域类型?下面本篇文章就来给大家介绍一下,希望对大家有所帮助。

ES6之块级作用域

在ES5中,只全局作用域和函数作用域。这会导致函数作用域覆盖了全局作用域;亦或者循环中的变量泄露为全局变量。用let命令新增了块级作用域,外层作用域无法获取到内层作用域,非常安全明了。

作用域 CSS 回来了

几年前,消失的作用域 CSS,如今它回来了,而且比以前的版本要好得多。更好的是,W3C规范基本稳定,现在Chrome中已经有一个工作原型。我们只需要社区稍微关注一下,引诱其他浏览器构建它们的实现

Js作用域和执行上下文

作用域是在函数声明的时候就确定的一套变量访问规则,而执行上下文是函数执行时才产生的一系列变量的集合体。也就是说作用域定义了执行上下文中的变量的访问规则,执行上下文是在这个作用域规则的前提下执行代码的。

ES5中模仿块级作用域

有一定JavaScript开发经验的人应该会熟悉下面这种立即执行函数的写法:不过即使不熟悉也没关系,这里我会讲解这种写法的含义。先来看下面这个更容易理解的示例:

typescript三种作用域

全局作用域 − 全局变量定义在程序结构的外部,它可以在你代码的任何位置使用。类作用域 − 这个变量也可以称为 字段。类变量声明在一个类里头

Js中的Function类型_设置函数的作用域

先声明一个name变量,然后声明一个person对象,person包含name和sayName属性。当直接在对象上进行方法的调用时:person.sayName(),函数的作用域遵循“谁调用就是谁”的原则,sayName的作用域(也就是this)指向的就是person。

CSS的私有作用域scoped和深度选择器/deep/和>>>

当给<style scoped> </style>添加 scoped 属性之后,它会给每一个class添加一个唯一的属性值,避免了出现类名重复,样式重叠被污染的问题,相当于限制了作用域,它的 CSS 只作用于当前组件

不只是块级作用域,你不知道的let和const

ES6新增了两个重要的关键字let和const,相信大家都不陌生,但是包括我在内,在系统学习ES6之前也只使用到了【不存在变量提升】这个特性。let声明一个块级作用域的本地变量

深入理解 JavaScript, 从作用域与作用域链开始

作用域是你的代码在运行时,某些特定部分中的变量,函数和对象的可访问性。换句话说,作用域决定了变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!