一、js是单线程的,单线程存在着同步、异步的问题。JavaScript 执行主要包括同步任务和异步任务。
二、JavaScript 事件循环机制,它主要与任务队列有关。

浏览器

image.png

事件循环机制

一、相关知识点包括(线程、进程),(同步任务、异步任务),(任务队列:宏任务、微任务),事件循环机制

运行时的描述

一、相关知识点包括javascript核心:对象,原型链,构造函数,执行上下文堆栈,执行上下文,变量对象,活动对象,作用域链,闭包,This。

对于每个执行上下文,都有三个重要属性:

  • 变量对象(Variable object, VO)
  • 作用域链(Scope chain)
  • this(this是静态作用域)

处理上下文代码的2个阶段

两个阶段:

  1. 进入执行上下文
  2. 执行代码

    进入执行上下文

    一旦进入执行上下文(在执行代码之前),VO就会被一些属性填充:
  • 函数的形参(当进入函数执行上下文时)——变量对象的一个属性,其属性名就是形参的名字,其值就是实参的值,对于没有传递的参数,其值为undefined
  • 函数声明(FunctionDeclaration, FD)——变量对象的一个属性,其属性名和值都是函数对象创建出来的,如果变量对象已经包含了相同名字的属性,则替换它的值
  • 变量声明(var, VariableDeclaration)——变量对象的一个属性,其属性名即为变量名,其值为undefined;如果变量名和已经声明的函数名或者函数的参数名相同,则不会影响已经存在的属性
    1. function test(a, b) {
    2. var c = 10
    3. function d() {}
    4. var e = function _e() {}
    5. (function x() {})
    6. }
    7. test(10) // call
    当以10为参数进入“test”函数上下文的时候,对应的AO如下所示:
    1. AO(test) = {
    2. a: 10,
    3. b: undefined,
    4. c: undefined,
    5. d:
    6. e: undefined
    7. }
    注意了,上面的AO并不包含函数”x”,这是因为这里的“x”并不是函数声明而是函数表达式(FunctionExpression,简称FE),函数表达式不会对VO造成影响,尽管函数“_e”也是函数表达式,然而,正如我们所看到的,由于它被赋值给了变量“e”,因此它可以通过“e”来访问到。

    执行代码

    此时,AO/VO的属性已经填充好了。(尽管,大部分属性都还没有赋予真正的值,都只是初始化时候的undefined值)
    继续以上一例子为例,到了执行代码阶段,AO/VO就会修改成为如下形式:
    1. AO['c'] = 10
    2. AO['e'] = <指向函数表达式“_e”>
    再次注意到,这里函数表达式“_e”仍在内存中,这是因为它被保存在声明的变量“e”中,而同样是函数表达式的“x”却不在AO/VO中:如果尝试在定义前或者定义后调用“x”函数,这时会发生“x未定义”的错误。未保存的函数表达式只有在定义或者递归时才能调用。
    如下是更典型的例子: ```javascript alert(x) // function

var x = 10 alert(x) // => 10

x = 20

function x() {}

alert(x) // 20

上述例子中,为何“x”打印出来是函数呢?为何在声明前就可以访问到?又为何不是10或者20呢?<br />原因在于,根据规则——在进入上下文的时候,VO会被填充函数声明;同一阶段,还有变量声明“x”,但是,正如此前提到的,变量声明是在函数声明和函数形参之后,并且,变量声明不会对已经存在的同样名字的函数声明和函数形参发生冲突,因此,在进入上下文的阶段,VO填充为以下形式:
```javascript
VO = {}
VO['x'] = 
// 发现var x = 10
// 如果函数"x"还未定义
// 则"x"为undefined,但是,在我们的例子中
// 变量声明并不会影响同名的函数值
VO['x'] =

随后,在执行代码阶段,VO被修改为如下所示:

VO['x'] = 10
VO['x'] = 20

正如在第二个和第三个alert显示的那样,
如下例子再次看到在进入上下文阶段,变量存储在VO中(因此,尽管else的代码块永远都不会执行到,而”b”却仍然在VO中):

if (true) {
    var a = 1
} else {
    var b = 2
}

alert(a) // 1
alert(b) // undefined, but not "b is not defined"

运行时的可视化编译

浏览器控制台演绎

image.png

// 函数执行栈演绎 -> 函数调用过程

function fun3() {
    console.log('fun3')
}

function fun2() {
    fun3()
}

function fun1() {
    fun2()
}

fun1()

当执行一个函数的时候,就会创建一个执行上下文,并且压入执行上下文栈,当函数执行完毕的时候,就会将函数的执行上下文从栈从弹出。知道了这样的工作原理,让我们来看看如何处理上面这段代码:

// 伪代码

// fun1()
ECStack.push(<fun1> functionContext)

// fun1中竟然调用了fun2,还要创建fun2的执行上下文
ECStack.push(<fun2> functionContext)

// 摔,fun2还调用了fun3
ECStack.push(<fun3> functionContext)

// fun3执行完毕
ECStack.pop()

// fun2执行完毕
ECStack.pop()

// fun1执行完毕
ECStack.pop()

// javascript接着执行下面的代码,但是ECStack底层永远有个globalContext

Node运行环境断点编译(webstorm)

image.png

问题

1、如果我们在浏览器控制台中运行“foo”函数,是否会导致堆栈溢出错误?

function foo() {
    setTimeout(foo, 0); // 是否存在堆栈溢出错误
}
function foo() {
    foo() // 是否存在堆栈溢出错误
}
foo()

2、如果在控制台中运行以下函数,页面(选项卡)的UI是否仍然响应

function foo() {
    return Promise.resolve().then(foo)
}

练习

问题1

if (!('a' in window)) {
    var a = 1
}
alert(a)

问题2

var a = 1,
    b = function a(x) {
        x && a(--x)
    }
alert(a)

问题3

function a(x) {
    return x * 2
}
var a 
alert(a)

问题4

function b(x, y, a) {
    arguments[2] = 10
  alert(a)
}
b(1, 2, 3)

问题5

function a() {
    alert(this)
}
a.call(null)

总结

自由变量:是指在函数中使用,但既不是函数参数,也不是函数的局部变量的变量
填充VO的顺序是:函数的形参-> 函数声明-> 变量声明
当变量声明遇到VO中已经有同名的时候,不影响已存在的属性

参考文档
https://www.yuque.com/docs/share/7aacebef-9842-4284-b906-3661919b0b16 (密码:pos0)