搞不懂作用域链?这篇文章让你一眼秒懂!

前言

新手哈基米搞不懂什么是作用域链?深入了解JS代码的执行过程让你秒懂作用域链!

经典案例

js 复制代码
function fn() {
    console.log(myName);
}
function fn2() {
    var myName = '南北绿豆'
    fn()
}
var myName = '哈基米'
fn2()
  • 究竟是会出现"哈基米",还是"南北绿豆"呢?诶,先不要着急哈气。想要真正弄清输出结果是"哈基米"还是"南北绿豆"。我们先要深入理解一下这段js代码究竟是怎样的一个执行过程。

作用域与它的动态载体

作用域

  • 在程序中定义变量的区域,该位置决定了变量的生命周期。通俗来讲,作用域就是变量和函数的可访问范围

函数执行上下文 : 函数作用域的动态载体

  1. 什么是函数执行上下文?
    • 当一个函数被调用时,js引擎会先创建一个对应的"函数执行上下文"对象,并将函数中声明的变量与函数都存在其中。供函数执行时使用,由此才实现了函数作用域的概念。
  2. 其主要组成部分:
    • 变量环境:保存使用var关键字声明的对象和函数内部声明的子函数,由此实现常见的"函数作用域"。
    • 词法环境:保存使用let关键字声明的对象,主要用于实现"块级作用域"的功能,非本文重点,不做详细描述。
  3. 例子:
js 复制代码
function fn2() {
    var myName = '南北绿豆'
    fn()
}

图片的抽象表达:js引擎会为本函数创建一个函数上下文

  1. 新手可能都会有个疑问?
    • 为什么fn函数没有存在这个函数执行上下文当中呢?
      • 欸,这就告诉你fn函数的声明被存在哪里。

全局执行上下文:全局作用域的动态载体

js 复制代码
function fn() {
    console.log(myName);
}
function fn2() {
    var myName = '南北绿豆'
    fn()
}
var myName = '哈基米'
fn2()
  1. 全局执行上下文与函数执行上下文类似
  • 由这份代码可知,在该js文件的全局下,声明了两个函数:fn与fn2,声明了一个变量: myName。

  • 所以js引擎同样会为其创建一个"全局执行上下文供全局参考,由此形成全局作用域。

待解决的疑问

  • 光靠这些我们还无法得知fn2是如何调用到fn函数的,所以我们还得了解这两个执行上下文的出现顺序,以及js引擎是如何对他们进行处理,才能够正常的执行代码。所以我们的新朋友"调用栈"便可以登场了。

js引擎的好兄弟:调用栈

  • 帮助js引擎控制代码执行顺序的"无形态大手"

什么是调用栈?

  • 后进先出:它与我们所知的"栈"这一数据结构一样,有着"后进先出"(LIFO)的特点。
  • "函数执行上下文生命周期"的管理者:js引擎通过调用栈间接控制"函数执行上下文的生命周期"来管理"函数的执行顺序",由此直接决定代码的执行流程。
    • 具体过程是: 每当一个函数被调用时,js引擎会为其创建一个"函数执行上下文",然后将该"函数执行上下文"压入栈顶,js引擎就会开始执行处于栈顶的函数,当函数执行完毕,便会让栈顶元素出栈。

函数执行上下文被压入栈的过程

  • 全局被编译时,js引擎创建全局执行上下文并将其压入调用栈,然后开始执行全局沿着全局一行一行往下执行。
  • 当准备执行fn2()时,js引擎会在全局执行上下文中寻找fn2()的定义,发现其存在。js引擎对其进行编译并创建对应函数执行上下文并将其压入调用栈,然后开始执行函数fn2,一行一行往下执行。
  • 当准备执行fn()时,js引擎会在fn2函数执行上下文中寻找fn()的定义,未发现定义,于是继续向全局执行上下文中寻找fn()的定义。发现fn()定义,js引擎对其进行编译并创建对应函数执行上下文压入调用栈,然后开始执行fn,一行一行往下执行。

消失的"南北绿豆":交流的失败?

js引擎对变量与函数的寻找过程难道真的只是"由内向外寻找"?如果是这样输出的结果应该是"南北绿豆"才对。

怎么是哈基米?

作用域链:作用域之间以outer指针为链接,形成的单向链表

outer指针:函数执行上文间的链接者,向外寻找的罪魁祸首

  • outer指针:当一个函数执行上文被创建时,其内部存在一个指针outer指向该函数被定义时所在的词法作用域
  • 词法作用域:一个函数被编译时一定会用一个outer指针记录该执行上下文(作用域)的外层(作用域)是谁。
  • 例子:
js 复制代码
var num=0;
function f1()
{
    console.log(num);
}
fn1()

由此形成了一个最简单的作用域链

  • 当执行函数内部时,在函数执行上文内部无法找到num的声明,js引擎便会沿着outer指针指向的全局执行上下文,寻找num的声明
  • 全局执行上文中存在num的声明,且num为1
  • 于是程序执行输出为1

作用域链:作用域之间以outer指针为链接,形成的单向链表

  • 由上述逻辑不断推导,最开始的"南北绿豆"消失之迷便迎刃而解了。
js 复制代码
function fn() {
    console.log(myName);
}
function fn2() {
    var myName = '南北绿豆'
    fn()
}
var myName = '哈基米'
fn2()

执行fn()函数时,寻找myName的定义的过程:

  • 第一步:在fn函数执行上文中寻找,未查找到。
  • 第二部:沿着outer指针指向查找上一级,在全局执行上下文中寻找到myName="哈基米"
  • 输出哈基米
  • 由此我们发现:作用域链的形成与函数调用的过程无关。
  • 作用域链的形成:依赖outer指针的指向。

总结

  1. 作用域: 在程序中定义变量的区域,该位置决定了变量的生命周期。通俗来讲,作用域就是变量和函数的可访问范围
  2. 调用栈:LIFO(后进先出)的执行上下文栈,用于管理函数调用顺序
  3. 执行上下文:作用域的动态载体,存储变量的具体环境
  4. outer指针:每一个函数内部都会存在一个指针outer,指向该函数的外层作用域(其所在的词法作用域)
  5. 词法作用域:函数或者变量被定义时的代码嵌套位置
  6. 作用域链:作用域之间以outer指针为链接,形成的单向链表
相关推荐
沐怡旸2 小时前
【底层机制】Ashmem匿名共享内存:原理与应用深度解析
android·面试
apollo_qwe2 小时前
Set 和 Map常用场景代码片段
javascript
Hilaku3 小时前
我为什么说全栈正在杀死前端?
前端·javascript·后端
程序猿_极客3 小时前
【期末网页设计作业】HTML+CSS+JS 旅行社网站、旅游主题设计与实现(附源码)
javascript·css·html·课程设计·期末网页设计
用户283209679374 小时前
为什么我的页面布局总是乱糟糟?可能是浮动和BFC在作怪!
javascript
会篮球的程序猿4 小时前
原生表格文本过长展示问题,参考layui长文本,点击出现文本域
前端·javascript·layui
哆啦A梦15884 小时前
48 我的地址页面布局
javascript·vue.js·node.js
bug爱好者5 小时前
vue3.x 使用vue3-tree-org实现组织架构图 + 自定义模版内容 - 附完整示例
前端·javascript·vue.js
flashlight_hi5 小时前
LeetCode 分类刷题:1669. 合并两个链表
javascript·leetcode·链表