JS引擎 — 一个读取代码并运行的引擎,没有单一的“JS引擎”;,每个浏览器都有自己的引擎,如谷歌有V。
作用域 — 可以从中访问变量的“区域”。
词法作用域— 在词法阶段的作用域,换句话说,词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的,因此当词法分析器处理代码时会保持作用域不变。
块作用域 — 由花括号{}创建的范围
作用域链 — 函数可以上升到它的外部环境(词法上)来搜索一个变量,它可以一直向上查找,直到它到达全局作用域。
同步 — 一次执行一件事, “同步”引擎一次只执行一行,JavaScript是同步的。
异步 — 同时做多个事,JS通过浏览器API模拟异步行为
事件循环(Event Loop) - 浏览器API完成函数调用的过程,将回调函数推送到回调队列(callback queue),然后当堆栈为空时,它将回调函数推送到调用堆栈。
堆栈 —一种数据结构,只能将元素推入并弹出顶部元素。 想想堆叠一个字形的塔楼; 你不能删除中间块,后进先出。
堆 — 变量存储在内存中。
调用堆栈 — 函数调用的队列,它实现了堆栈数据类型,这意味着一次可以运行一个函数。 调用函数将其推入堆栈并从函数返回将其弹出堆栈。
执行上下文 — 当函数放入到调用堆栈时由JS创建的环境。
闭包 — 当在另一个函数内创建一个函数时,它“记住”它在以后调用时创建的环境。
垃圾收集 — 当内存中的变量被自动删除时,因为它不再使用,引擎要处理掉它。
变量的提升— 当变量内存没有赋值时会被提升到全局的顶部并设置为undefined。
this —由JavaScript为每个新的执行上下文自动创建的变量/关键字。
调用堆栈(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
有几个点需要注意:
变量声明的位置(一个在上,一个在下)
函数a调用下面定义的函数b, 函数b调用函数c
当它被执行时你期望发生什么? 是否发生错误,因为b在a之后声明或者一切正常? console.log 打印的变量又是怎么样?
以下是打印结果:
"myVar" undefined "myOtherVar" 10 "Hello world!"
来分解一下上述的执行步骤。
1. 变量和函数声明(创建阶段)
第一步是在内存中为所有变量和函数分配空间。 但请注意,除了undefined之外,尚未为变量分配值。 因此,myVar在被打印时的值是undefined,因为JS引擎从顶部开始逐行执行代码。
函数与变量不一样,函数可以一次声明和初始化,这意味着它们可以在任何地方被调用。
所以以上代码看起来像这样子:
var myOtherVar = undefined var myVar = undefined function a() {...} function b() {...} function c() {...}
这些都存在于JS创建的全局上下文中,因为它位于全局空间中。
在全局上下文中,JS还添加了:
全局对象(浏览器中是 window 对象,NodeJs 中是 global 对象)
this 指向全局对象
2. 执行
接下来,JS 引擎会逐行执行代码。
myOtherVar = 10在全局上下文中,myOtherVar被赋值为10
已经创建了所有函数,下一步是执行函数 a()
每次调用函数时,都会为该函数创建一个新的上下文(重复步骤1),并将其放入调用堆栈。
function a() { console.log('myVar', myVar) b() }
如下步骤:
创建新的函数上下文
a 函数里面没有声明变量和函数
函数内部创建了 this 并指向全局对象(window)
接着引用了外部变量 myVar,myVar 属于全局作用域的。
接着调用函数 b ,函数b的过程跟 a一样,这里不做分析。
下面调用堆栈的执行示意图:
创建全局上下文,全局变量和函数。
每个函数的调用,会创建一个上下文,外部环境的引用及 this。
函数执行结束后会从堆栈中弹出,并且它的执行上下文被垃圾收集回收(闭包除外)。
当调用堆栈为空时,它将从事件队列中获取事件。
作用域及作用域链