深入探讨JavaScript的执行机制

预编译

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

  • 13
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值