首页 > 编程语言 >深入探讨JavaScript的执行机制

深入探讨JavaScript的执行机制

时间:2024-06-17 22:01:21浏览次数:20  
标签:bar 函数 作用域 JavaScript 深入探讨 myName var 机制 foo

预编译

  • 首先下面这段代码的执行是一个怎样的结果呢?
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的执行机制有了更加深刻的理解

标签:bar,函数,作用域,JavaScript,深入探讨,myName,var,机制,foo
From: https://blog.csdn.net/weixin_56440777/article/details/139752322

相关文章

  • 45.JavaScript基础【三】
    【一】JavaScript之函数1)函数声明函数须先声明,才能使用函数声明时不会立即执行,只有调用时才会执行function函数名(参数1,参数2){ 代码体}2)函数调用函数名()函数的执行与定位位置无关,只与调用位置有关可多次调用,每次调用都是独立不相关的3)函数分类1.无......
  • 44.JavaScript基础【二】
    【一】JavaScript之运算符1)算术运算符加法:+减法:-乘法:*除法:/优先运算:()取余:%特殊NaNNaN参与的运算结果全是NaN隐式转化null转成0undefined转成NaN2)比较运算符>大于<小于<=小于等于>=大于等于==相等!=不相等===全等与相等区别在于会判断数据......
  • 43.JavaScript基础【一】
    【一】JavaScript1)介绍也是一门编程语言,他可以写后端代码JS是由ECMAScript、DOM、BOM组成JS是运行在浏览器脚本的语言2)注释语法//单行注释/*多行注释多行注释*/3)js代码的书写位置head头里面的script标签中写在body体最下面直接常见一个js脚本文件,......
  • 47.JavaScript基础【五】
    【一】什么是jQuery1)概述是一个轻量的、兼容多浏览器的JavaScript的第三方库其内部封装了JS代码、能通过更少的代码操作DOM对象提高了代码效率、简化了代码量2)优势轻量级的JS框架丰富的DOM选择器链式表达式事件、样式、动画支持Ajax操作支持跨浏览器兼容插件扩展开......
  • 46.JavaScript基础【四】
    【一】弹出框是网页开发中常用的交互组件,用于显示警告、确认或提示,并于用户进行交互1)警告框用于向用户显示一条重要的警告信息通常包含一个确认按钮用于关闭alert("警告提示框")2)确认框用于向用户询问一个问题或确认某个操作通常包含一个确认按钮和一个取消......
  • Flink - [07] 容错机制
    题记部分 一、一致性检查点  Flink故障恢复机制的核心,就是应用状态的一致性检查点。有状态流应用的一致性检查点,其实就是所有任务的状态,在某个时间点的一份拷贝(一份快照);这个时间点,应该是所有任务都恰好处理完一个相同的输入数据的时候。 二、从检查点恢复状态  在......
  • [javascript]何为变量提升?
    【版权声明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权)https://www.cnblogs.com/cnb-yuchen/p/18252500出自【进步*于辰的博客】关于编译与解释,详述可查阅博文《[Java]知识点》中的【编译与解释】一栏。参考笔记二,P43.3、P46.1、P9.3。目录1、什么是“变量提升?2、va......
  • JavaScript妙笔生花:打造沉浸式中国象棋游戏体验
    前言随着信息技术的飞速发展,Web开发领域也出现了翻天覆地的变化。JavaScript作为前端开发中不可或缺的编程语言,其重要性不言而喻。而当我们谈论到利用JavaScript打造一款沉浸式的中国象棋游戏体验时,我们不仅仅是在开发一个游戏,更是在进行一种文化的传承和创新。以下将探讨......
  • MySQL中的锁机制及其应用
    MySQL中的锁是用于确保数据完整性和一致性的重要机制。当多个事务尝试同时访问或修改同一数据时,锁可以防止并发问题,如脏读、不可重复读和幻读。MySQL提供了多种类型的锁,以满足不同的应用场景和性能需求。以下是MySQL中常见的锁类型:共享锁(SharedLocks,S锁)和排他锁(Exclusi......
  • JavaScript 面试问题及答案
    什么是JavaScript模块?答: JavaScript模块是可重复使用的代码片段,可以在文件之间导入和导出,从而提高模块化和可维护性。解释原型链的概念。答:原型链是JavaScript中的一项功能,它允许对象通过原型链从其他对象继承属性和方法。什么是高阶函数?答:高阶函数是可以将其他函数作......