JavaScript代码是如何执行的

一些概念

栈:先进后出的存储结构。

堆:完全二叉树,这里不讨论。

调用堆栈:暂存函数的栈,可进可出。

执行上下文:当函数放入到调用堆栈时由 JS 创建的环境。

闭包:当在另一个函数内创建一个函数时,它“记住”它在以后调用时创建的环境。

垃圾收集:当内存中的变量被自动删除时,因为它不再使用,引擎要处理掉它。

Call Stack

var myOtherVar = 10;
function a() {
    console.log("myVar", myVar);
    b();
}
function b() {
    console.log("myOtherVar", myOtherVar);
    c();
}
function c() {
    console.log("Hello world!");
}
a();
var myVar = 5;
/*
myVar undefined
myOtherVar 10
Hello world!
*/
复制代码

函数只是声明了但没有执行,等到调用函数时候把函数放入到堆栈区,如果有函数的嵌套调用,就不断的入栈

v2-0915a6ed453d68285de320893c6b2744_b.gif

词法与作用域

function a() {
    var myOtherVar = "inside A";
    b();
}
function b() {
    var myVar = "inside B";
    console.log("myOtherVar:", myOtherVar);
    function c() {
        console.log("myVar:", myVar);
    }
    c();
}
var myOtherVar = "global otherVar";
var myVar = "global myVar";
a();
复制代码

v2-4584d82a841fba037eff2fd6124e1e8f_1440w.jpg

这张图显示的很明显了,对上述代码,放入函数和函数内的局部变量,形成了两条运行链:

  • a -> global

  • c -> b -> global

setTimeout

执行条件

function logMessage2() {
    console.log("Message 2");
}
console.log("Message 1");
setTimeout(logMessage2, 1000);
console.log("Message 3");
复制代码

setTimeout包裹的函数会被放入CallBack Queue(回调队列)中,满足两个条件才能移入到Call Stack(调用堆栈)里面:

  • 到达设定时间

  • Call Stack为空

值得一提的是,即使设置时间为0,即setTimeOut(logMessage2, 0);,也会移入回调队列中,只有调用堆栈为空才会加入到里面

v2-9201342bbb80f0007689300a3ab9463d_b.gif

嵌套的setTimeout

function a() {
    let x = 2;
    let y = 3;
    function b() {
        setTimeout(() => {
            console.log(x + y);
        }, 5000);
        return 'b函数';
    }
    setTimeout(() => {
        console.log(x ** y);
    }, 1000);
    let res = b();
    console.log(res);
    return 'a函数';
}
console.log(a());


/*
b函数
a函数
8
5
*/
复制代码

从上述代码可以看出,setTimeout的执行是晚于函数的返回值的,这更验证了“Call Stack为空”这个条件

第二个问题:既然函数执行结束返回,变量为啥不清除,还能被调用?答:变量存储在堆中,函数执行过程是在栈中,互相不干预。

image.png

值得一提的是,装入到Call Queue的函数出队顺序是时间越短的先出队,上面两处setTimeout的时间设定交换下,会打印出:

/*
a函数
b函数
8
5
*/
复制代码

案例分析

见如下代码,分析词法环境和执行过程:

let x = 1;
let y = 'Y';
function a(name) {
    function b() {
        console.log(x);
        return function(){
            console.log(y);
        }
    }
    let x = 3;
    y = 'YES';
    let f = b();
    setTimeout(f,1000);
    console.log('a函数里面:'+name);
    return;
}
a('Hellen');
复制代码

1.Global

声明变量xyfunction a(),但不赋初值

执行x = 1;,入栈,紧接着出栈实现赋值

执行y = 'Y';,入栈,紧接着出栈实现赋值

执行a();,入栈

现在栈中只有a()函数了,在Global环境下

  • x = 1

  • y = 'Y'

2.function a()

声明变量namefunction b()xf,但不赋初值

执行name = Hellen;,入栈,紧接着出栈实现赋值,这一步是函数参数的赋值

执行x = 3;,入栈,紧接着出栈实现赋值

执行y = 'YES';,入栈,紧接着出栈实现赋值

执行b(),入栈

现在栈中有a()b(),在function a()环境下:

  • x = 3

  • f = 'undefined'

另外修改了Global环境下y = 'YES'

3.function b()

执行console.log(x);,由于此时使用最近的外层function a()环境下的x = 3,打印输出

返回匿名函数function()

4.function a()

执行let f = b();,给变量f赋值返回的匿名函数function()

执行setTimeout(f,1000);,将函数f()放入到回调队列中等待

执行console.log('a函数里面:'+name);,此时name = 'Hellen',打印输出

紧接着b()a()会依次出栈,此时栈为空

5.function()

满足setTimeout执行条件(Call Stack为空、到达设定时间),调用f(),将f()放入堆栈中

f()出栈执行,打印y

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享