热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

原标题:从零开始讲解JavaScript中作用域链的概念及用途从零开始讲解JavaScript中作用域链的概念及用途

原标题:从零开始讲解Javascript中作用域链的概念及用途


从零开始讲解Javascript中作用域链的概念及用途



  • 引言

  • 正文


    • 一、执行环境

    • 二、作用域链

    • 三、块级作用域

    • 四、其他情况

    • 五、总结


  • 结束语




引言


  • 公众号:前端印象

  • 不定时有送书活动,记得关注~

  • 关注后回复对应文字领取:【面试题】、【前端必看电子书】、【数据结构与算法完整代码】、【前端技术交流群】


之前我写过一篇关于Javascript中的对象的一篇文章,里面也提到了作用域链的概念,相信大家对这个概念还是没有很深的理解,并且这个概念也是面试中经常问到的,因为这个概念实在太重要了,在我们平时写代码时,也可能会因为作用域链的问题,而出现莫名其妙的bug,导致我们花费大量的时间都查找不出原因。所以我就准备单独写一篇关于作用域链的文章,来帮大家更好地理解这个概念。


正文


一、执行环境

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

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

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
let fruit = 'banana'
文章来源站点https://www.yii666.com/ 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 + ',我最喜欢的颜色是' +www.yii666.com color)
}
fn()
//报错, color is undefined
console.log('我最喜欢的水果是' + fruit + ',我最喜欢的颜色是' + color)
</script>

首先执行了函数 fn ,此时函数内的作用域链就是这样的

在这里插入图片描述
我们看到,在函数 fn 中,我们使用了变量 fruitcolor,所以此时会从作用域链的头部开始,从第一个活动变量(本例中第一个变量对象就是函数fn的活动变量)中,寻找变量 fruitcolor,发现该变量对象中存在变量color,于是就成功引用了变量color,但是因为没有找到变量 fruit,于是再沿着作用域链往下找到下一个变量对象(本例中第二个活动变量就是全局window的变量对象),发现该变量对象中有我们想要的变量 fru文章来源地址31230.htmlit,则引用该变量 fruit ,同时,因为找到了需要引用的变量,就不会继续沿着作用域链继续向下寻找了。

我们再来看在函数外,也就是全局window中,也执行了console.log('我最喜欢的水果是' + fruit + ',我最喜欢的颜色是' + color),此时在全局环境中的作用域链是这样的
在这里插入图片描述
此时也使用了变量 fruitcolor,所以这时会从作用域链的头部开始,找到第一个变量对象(本例中第一个活动变量就是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中,出现了使用 letconst声明变量的方式,来解决了Javascript中没有块级作用域的问题。
你们可以看我之前写的一篇关于letconst 声明变量的文章——还没有理解let 和 const的用法和区别吗,几百字让你立马搞懂


四、其他情况

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

function fn() {
sum = 1 + 2
}
fn()
console.log(sum) //返回 3

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

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


五、总结



  1. 作用域链可以看成是将变量对象按顺序连接起来的一根链子

  2. 每个执行环境中的作用域链都是不同的

  3. 当我们引用变量时,会顺着当前执行环境的作用域链,从作用域链的开头开始依次往下寻找对应的变量,直到找到作用域链的尾部,报错undefined

  4. 作用域链保证了变量的有序访问



结束语

www.yii666.com了,对于作用域链的讲解就到这里了,相信这下大家对Javascript中的作用域链有了很深的理解了吧,我相信,理解了这个概念,可以消除我们代码中大部分没必要的BUG。

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

来源于:从零开始讲解Javascript中作用域链的概念及用途


推荐阅读
  • 浅析python实现布隆过滤器及Redis中的缓存穿透原理_python
    本文带你了解了位图的实现,布隆过滤器的原理及Python中的使用,以及布隆过滤器如何应对Redis中的缓存穿透,相信你对布隆过滤 ... [详细]
  • 深入解析HTML5字符集属性:charset与defaultCharset
    本文将详细介绍HTML5中新增的字符集属性charset和defaultCharset,帮助开发者更好地理解和应用这些属性,以确保网页在不同环境下的正确显示。 ... [详细]
  • MySQL 5.7 学习指南:SQLyog 中的主键、列属性和数据类型
    本文介绍了 MySQL 5.7 中主键(Primary Key)和自增(Auto-Increment)的概念,以及如何在 SQLyog 中设置这些属性。同时,还探讨了数据类型的分类和选择,以及列属性的设置方法。 ... [详细]
  • javascript分页类支持页码格式
    前端时间因为项目需要,要对一个产品下所有的附属图片进行分页显示,没考虑ajax一张张请求,所以干脆一次性全部把图片out,然 ... [详细]
  • 本文总结了Java初学者需要掌握的六大核心知识点,帮助你更好地理解和应用Java编程。无论你是刚刚入门还是希望巩固基础,这些知识点都是必不可少的。 ... [详细]
  • JUC(三):深入解析AQS
    本文详细介绍了Java并发工具包中的核心类AQS(AbstractQueuedSynchronizer),包括其基本概念、数据结构、源码分析及核心方法的实现。 ... [详细]
  • 结城浩(1963年7月出生),日本资深程序员和技术作家,居住在东京武藏野市。他开发了著名的YukiWiki软件,并在杂志上发表了大量程序入门文章和技术翻译作品。结城浩著有30多本关于编程和数学的书籍,其中许多被翻译成英文和韩文。 ... [详细]
  • 网站访问全流程解析
    本文详细介绍了从用户在浏览器中输入一个域名(如www.yy.com)到页面完全展示的整个过程,包括DNS解析、TCP连接、请求响应等多个步骤。 ... [详细]
  • 解决Bootstrap DataTable Ajax请求重复问题
    在最近的一个项目中,我们使用了JQuery DataTable进行数据展示,虽然使用起来非常方便,但在测试过程中发现了一个问题:当查询条件改变时,有时查询结果的数据不正确。通过FireBug调试发现,点击搜索按钮时,会发送两次Ajax请求,一次是原条件的请求,一次是新条件的请求。 ... [详细]
  • 本文详细介绍了 PHP 中对象的生命周期、内存管理和魔术方法的使用,包括对象的自动销毁、析构函数的作用以及各种魔术方法的具体应用场景。 ... [详细]
  • 本文介绍了如何利用HTTP隧道技术在受限网络环境中绕过IDS和防火墙等安全设备,实现RDP端口的暴力破解攻击。文章详细描述了部署过程、攻击实施及流量分析,旨在提升网络安全意识。 ... [详细]
  • 利用REM实现移动端布局的高效适配技巧
    在移动设备上实现高效布局适配时,使用rem单位已成为一种流行且有效的技术。本文将分享过去一年中使用rem进行布局适配的经验和心得。rem作为一种相对单位,能够根据根元素的字体大小动态调整,从而确保不同屏幕尺寸下的布局一致性。通过合理设置根元素的字体大小,开发者可以轻松实现响应式设计,提高用户体验。此外,文章还将探讨一些常见的问题和解决方案,帮助开发者更好地掌握这一技术。 ... [详细]
  • 在PHP中如何正确调用JavaScript变量及定义PHP变量的方法详解 ... [详细]
  • 解决Only fullscreen opaque activities can request orientation错误的方法
    本文介绍了在使用PictureSelectorLight第三方框架时遇到的Only fullscreen opaque activities can request orientation错误,并提供了一种有效的解决方案。 ... [详细]
  • 深入解析 Lifecycle 的实现原理
    本文将详细介绍 Android Jetpack 中 Lifecycle 组件的实现原理,帮助开发者更好地理解和使用 Lifecycle,避免常见的内存泄漏问题。 ... [详细]
author-avatar
Ag冫g彡ie琪琪
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有