深入探讨JavaScript的执行机制

news2024/11/26 14:37:35

预编译

  • 首先下面这段代码的执行是一个怎样的结果呢?
showName();
console.log(MyName);

var MyName = '小陈同学'

function showName() {
    console.log('函数showName被执行');
}

在这段代码中我们声明了一个变量MyName和一个函数showName,调用函数,打印MyName

因为在函数编译的过程中会存在变量的声明提示,默认赋值为undefined,方法的整体提升

所以这段代码又可以看做成以下代码

var MyName = undefined

function showName() {
    console.log('函数showName被执行');
    
showName();
console.log(MyName);

MyName = '小陈同学'
}

所以执行结果显而易见

image.png

接下来我们再细致的分析一下

在编译过程中,首先会创建一个上下文对象

image.png

当执行showName时,又会创建一个showName的执行上下文,这里就形成了一个调用栈

调用栈

调用栈是js引擎用来维护函数调用关系的一个数据结构

这段代码会出现什么结果?

function fn(){
    fn()
}
fn()

答案是

image.png

超出了最大的栈内存调用大小,发生了栈溢出,这里我们可以知道,每当一个函数调用的时候就会发生栈帧的创建,以及压栈操作

这里我们明白了这个道理后,我们继续分析一下代码

var a = 2

function add(b, c) {
    return b + c
}

function addAll(b, c) {
    var d = 10
    var result = add(b, c)
    return a + result + d
}

addAll(3, 6)

首先会创建全局上下文

image.png

第二步会创建addAll的执行上下文

image.png

第三步会创建add的执行上下文

image.png

这便是这段代码在引擎里面维护的一个执行上下文调用栈

前面一直没有用到词法变量,现在我们要引入词法变量了,来分析一下下面的代码

function foo() {
    var a = 1
    let b = 2
    {
        let b = 3
        var c = 4
        let d = 5
        console.log(a);
        console.log(b);
    }
    console.log(b);
    console.log(c);
    console.log(d);
}
foo()

首先会创建一个全局的上下文,这里包括了fanction foo,比较简单

接下会创建foo的执行全局上下文,这里我们重点分析一下

首先会进行一个预编译

image.png

预编译执行后开始执行代码

  • 在函数内,定义了变量 a 为 1(使用 var ),b 为 2(使用 let )。
  • 然后在一个代码块中,又重新定义了 let b = 3 ,此时在这个代码块内的 b 是 3 ,而不是外面的 2 ;同时定义了 var c = 4 和 let d = 5 。
  • 在代码块内打印 a 为 1 ,打印 b 为 3 。
  • 之后在函数内再次打印 b 为 2 (因为外面的 b 没有被修改)。
  • 由于 c 是在代码块内用 var 定义的,所以在函数内可以访问到,打印 c 为 4 。
  • 但是 d 是在代码块内用 let 定义的,在代码块外无法访问,会报错说 d 未定义。

那么这里我提一个问题

代码块中的console.log(b);为什么打印的是3而不是2?

你肯定会说,因为你使用了{}啊,这只是表层

其实是因为,在查找变量的时候,首先会去词法环境查找数据,然后再去变量环境查找,并且在词法环境里面维护了一个栈的结构,从上往下找

image.png

这里就出来了另一个问题,既然是首先会去词法环境查找数据,然后再去变量环境查找,那为什么

image.png

这个打印的b不是3呢?

这里面就是作用域链在起作用了

  • 作用域链

这里举个代码例子,分析一下这段代码执行的结果

function bar(params) {
    console.log(myName);
}

function foo(params) {
    var myName = 'Tom'
    bar();
}

var myName = 'Jerry';

foo();

结果为

image.png

为什么结果是Jerry呢?

不是在foo里面var myName = 'Tom’吗?

首先我们分析一下这段代码的调用栈

image.png

  • 首先,程序开始执行,遇到 foo 函数的调用,进入 foo 函数,此时调用栈中添加了 foo

  • 在 foo 函数内部,定义了变量 myName 为 'Tom' ,然后调用 bar 函数,此时调用栈中添加了 bar 在 foo 之上

  • 进入 bar 函数后,它尝试打印 myName ,由于 bar 函数内部没有定义 myName ,它会沿着作用域链向上查找。首先在 bar 自身的作用域内未找到,然后到 foo 函数的作用域内也未找到(尽管 foo 内部有定义,但不是同一个作用域),最后到全局作用域中找到了定义的 myName 为 'Jerry' ,所以打印出 'Jerry'

  • 当 bar 函数执行完毕后,从调用栈中弹出 bar ,回到 foo 函数继续执行,直到 foo 函数执行完毕,再从调用栈中弹出 foo

其实在每个上下文中都有一个outer属性,他通过去关联上一个作用域,来形成作用域链

image.png

那么它是一局什么规则呢?

词法作用域在哪里,outer就指向哪里,词法作用域的意思是在函数定义时所在的作用域

这也就是为什么打印的是Jerry,因为bar定义在全局,所以会去全局找myName

那么接下来这段代码呢?

function foo(params) {
    var myName = 'Tom'
    
    function bar(params) {
    console.log(myName);
}
    bar();
}

var myName = 'Jerry';

foo();
  • 首先,执行 foo 函数。在 foo 函数内部定义了变量 myName 为 'Tom' ,然后定义了内部函数 bar 。
  • 当调用 bar 函数时,它要打印 myName 。由于 bar 函数内部没有定义 myName ,它会沿着作用域链查找。
  • 首先在 bar 自身的局部作用域中找不到,然后会在其直接外层,也就是 foo 函数的作用域中找到 myName 为 'Tom' ,所以最终会打印出 'Tom' 。而全局定义的 myName 为 'Jerry' ,在这里并不会被 bar 函数访问到

总结

本文深入探讨JavaScript的执行机制,从预编译,引擎的调用栈,作用域链等方面分析,相信看到这里你一定对JavaScript的执行机制有了更加深刻的理解

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1833641.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

TWM论文阅读笔记

这是ICLR2023的一篇world model论文,用transformer来做世界模型的sequence prediction。文章贡献是transformer-based world model(不同于以往的如transdreamer的world model,本文的transformer-based world model在inference 的时候可以丢掉…

手机usb共享网络电脑没反应的方法

适用于win10电脑,安卓手机上可以 开启usb网络共享选择,如果选择后一直跳,让重复选择usb选项的话,就开启 开发者模式,进到 开发者模式 里设置 默认usb 共享网络 选项 ,就不会一直跳让你选。 1.先用数据线 连…

一款.NET开源、功能强大、跨平台的绘图库 - OxyPlot

前言 今天大姚给大家分享一款.NET开源(MIT License)、免费、跨平台、功能强大的绘图库,支持多平台使用(包括:WPF、UWP、WinForm、Silverlight、Xamarin.iOS、Xamarin.Android、Xamarin.Forms 和 Xamarin.Mac等&#x…

Disk /dev/sda: 107.4 GB, 107374182400 bytes, 209715200 sectors

Disk /dev/sda: 107.4 GB, 107374182400 bytes, 209715200 sectors 块设备名称为: /dev/sda 设备的大小为:107.4 GB 107374182400 bytes : 107374182400/1024/1024/1024100G (1)块设备名称为:/dev/sd…

DS18B20温度传感器完整使用介绍(配合51单片机)

DS18B20是一款由Maxim Integrated(原Dallas Semiconductor)生产的数字温度传感器,以其高精度、低功耗、灵活的接口方式和易于使用的特性,在各种温度监测应用中被广泛采用。 以下是DS18B20的详细介绍: 基本特性 数字输…

【CSS in Depth2精译】1.1 层叠

CSS 本质上就是声明规则,并让这些特定的规则在各种情况下生效。一个类添加到某个元素上,则应用这个类包含的这一些样式;元素 X 是元素 Y 的一个子节点,则应用另一些样式。浏览器于是根据这些规则,判定所有样式生效的具…

数据结构-十大排序算法集合(四万字精讲集合)

前言 1,数据结构排序篇章是一个大的工程,这里是一个总结篇章,配备动图和过程详解,从难到易逐步解析。 2,这里我们详细分析几个具备教学意义和实际使用意义的排序: 冒泡排序,选择排序&#xff0c…

解决安全规模问题:MinIO 企业对象存储密钥管理服务器

在强大可靠的存储解决方案领域,MinIO 作为持久层脱颖而出,为组织提供安全、持久和可扩展的存储选项。MinIO 通常负责处理关键任务数据,在确保高可用性方面发挥着至关重要的作用,有时甚至在全球范围内。存储数据的性质,…

vue音乐播放条

先看效果 再看代码 <template><div class"footer-player z-30 flex items-center p-2"><div v-if"isShow" class"h-12 w-60 overflow-hidden"><div :style"activeStyle" class"open-detail-control-wrap&…

《现代通信原理与技术》数字调制与解调(MSK调制)实验报告

摘 要&#xff1a; 本实验旨在研究数字调制中的最小频移键控&#xff08;MSK&#xff09;调制技术&#xff0c;并使用MATLAB软件对其进行模拟和实现。首先&#xff0c;我们介绍了MSK调制的原理和特点&#xff0c;以及其在数字通信系统中的应用。然后&#xff0c;我们使用MATLAB…

分布式光纤测温DTS使用的单模光纤与多模光纤有何区别?

分布式光纤测温DTS中使用的单模光纤和多模光纤之间存在着本质区别。单模光纤是一种在光纤通信中应用广泛的光纤类型&#xff0c;几乎所有的光纤入户和主干线通信都采用单模光纤。从通信的角度来看&#xff0c;单模光纤就好比一条单行道的高速铁路&#xff0c;而多模光纤则类似于…

微型操作系统内核源码详解系列五(2):cm3下栈的初始化

系列一&#xff1a;微型操作系统内核源码详解系列一&#xff1a;rtos内核源码概论篇&#xff08;以freertos为例&#xff09;-CSDN博客 系列二&#xff1a;微型操作系统内核源码详解系列二&#xff1a;数据结构和对象篇&#xff08;以freertos为例&#xff09;-CSDN博客 系列…

Flutter 实现软鼠标

文章目录 前言一、如何实现&#xff1f;1、记录鼠标偏移2、MouseRegion获取偏移3、Transform移动图标 二、完整代码三、使用示例总结 前言 flutter在嵌入式系统中运行时&#xff0c;有可能遇到drm鼠标无法使用的情况&#xff0c;但鼠标事件却可以正常接收&#xff0c;此时如果…

一季度直播6000场,同比增长60%,遥望科技透露重要信息

6月17日&#xff0c;经由深圳证券交易所许可&#xff0c;遥望科技&#xff08;股票代码&#xff1a;002291&#xff09;正式对《年报问询函》进行公开回复&#xff0c;就经营的多个维度做出解释和回应。 在回复中&#xff0c;遥望科技预测2024年毛利率为14.4%&#xff0c;相比…

MybatisPlus:高效便捷的Java持久层框架

一、MybatisPlus简介 MybatisPlus&#xff08;简称MP&#xff09;是一个流行的Java持久层框架&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生&#xff0c;旨在简化数据库操作和提高开发效率。MybatisPlus为开发者提供了一套方便的API和…

【Java开发规范】IDEA 设置 text file encoding 为 UTF-8,且文件的换行符使用 Unix 格式

1. IDEA 设置 text file encoding 为 UTF-8 file -> settings -> editor -> code style -> file encoding Transparent-native-to-asci conversion 要不要勾选&#xff1f;> 不推荐勾选&#xff08;它的作用是用来自动转换ASCII编码&#xff0c;防止文件乱码&am…

vue3的基本使用方法

【 vue3实例 】 【 0 】对象、方法和属性 对象&#xff08;Object&#xff09;&#xff1a; 对象是编程中的一个数据结构&#xff0c;它可以包含多种数据类型&#xff0c;包括数字、字符串、布尔值、数组、其他对象等。对象通常由一系列属性和方法组成。在面向对象编程&…

如何在纯内网环境下,将EasyCVR视频汇聚网关通过4G与第三方公网云平台级联?

EasyCVR视频汇聚网关是TSINGSEE青犀软硬一体的一款产品&#xff0c;可提供多协议的接入、音视频采集、处理&#xff0c;能实现海量前端设备的轻量化接入/转码/分发、视频直播、云端录像、云存储、检索回看、智能告警、平台级联等&#xff0c;兼容多种操作系统&#xff0c;轻松扩…

基于CentOS的全新Linux机器安装Jenkins并生成Allure报告

目录 一、安装Docker 二、安装Docker Compose 三、准备测试用例 四、配置docker-compose.yml 五、启动Jenkins 六、配置Jenkins和Allure插件 七、创建含pytest的Jenkins任务 一、安装Docker 在CentOS上&#xff0c;首先更新包管理工具并安装所需的包。 sudo yum update…

Python将字符串用特定字符分割并前面加序号

Python将字符串用特定字符分割并前面加序号 Python将字符串用特定字符分割并前面加序号&#xff0c;今天项目中就遇到&#xff0c;看着不难&#xff0c;得花点时间搞出来急用啊&#xff0c;在网上找了一圈&#xff0c;没发现有完整流程的文章。所以就搞出来并写了这个文章。仅…