1.JavaScript作用域(Lexical scope)

  1. JavaScript是基于词法作用域的,也叫静态作用域。关于词法作用域的解释:**被调用函数的作用域链在函数定义时创建的,与函数被调用环境无关**。而与之相对应的是动态作用域,作用域链是动态的,依赖于被调用函数的外部环境.实例代码如下:

Example1

  1. let scope = "global scope"
  2. function checkScope() {
  3. let scope = "local scope"
  4. function f() {
  5. return scope
  6. }
  7. return f()
  8. }
  9. chekScope() //output:local scope

Example2

  1. let scope = "global scope"
  2. function checkScope() {
  3. let scope = "local scope"
  4. function f() {
  5. return scope
  6. }
  7. return f
  8. }
  9. checkScope()() //output:local scope

Example3

  1. let scope = "global scope"
  2. function checkScope() {
  3. let scope = "local scope"
  4. function f() {
  5. return this.scope
  6. }
  7. return f
  8. }
  9. checkScope()() //output:global scope

2.变量提升和函数提升

JS引擎在正式执行代码前会进行一次预编译,在这个过程中首先将变量声明和函数声明提升至当前作用域的顶端,然后进行下轮的处理。有三个关键点需要注意:

  • 变量提升只会提升变量名的声明,不会提升变量的赋值初始化
  • 函数提升的优先级大于变量提升的优先级,即存在同名的变量声明和函数提升,执行预编译时,不管先后顺序,函数提升会覆盖变量提升
  • 1.if里的函数声明首先会声明一个全局同名变量,值为undefined

2.if里的函数赋值会提升到块作用域顶部
3.执行到函数声明语句时会把块作用域里的变量赋值到全局同名变量
实例代码如下:

  1. var foo = 3
  2. function hoistFuncton() {
  3. console.log(foo)
  4. foo = 5
  5. console.log(foo)
  6. function foo() {}
  7. }
  8. hoistFunction()
  9. console.log(foo)
  10. ////////output///////////////
  11. //function foo() {}
  12. //5
  13. //3

3.执行上下文栈

JavaScript引擎通过执行上下文栈来管理上下文,当执行一个函数的时候,就会创建一个执行上下文,并且压入执行上下文栈,当函数执行完毕的时候就会将函数从上下文栈中弹出。
执行上下文栈的底部永远有一个globalContext

4.变量对象

当JavaScript代码执行一段可执行代码时,会创建对应的执行上下文。对于每个执行上下文,包含三个重要属性:

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

变量对象是与执行上下文相关的数据作用域,存储了在上下文中定义的变量和函数声明

4.1全局上下文

全局上下文中的变量对象就是全局对象

4.2函数上下文

函数上下文中,用活动对象(Activation Object)表示变量对象。活动对象和变量对象本质是一回事,只是变量对象是规范上的或者说是引擎实现的,不可以在JavaScript环境中访问,只有到当进入一个执行上下文中,这个变量对象才被激活,所以才叫Activation Object。活动对象是在进入函数上下文时被创建的,通过arguments属性初始化。

4.3执行过程

执行上下文的代码会分成两个阶段进行处理:分析和执行,也可以叫做:

  1. 进入执行上下文(变量预解析,其实就是变量提升,函数声明提升)
  2. 代码执行

在进入执行上下文阶段(预解析),这时候还未执行代码,变量对象会包括: JavaScript代码运行机制 - 图1

5.作用域链(Scope Chain)

当查找变量的时候,会先从当前上下文的变量对象中查找,如果没找到,就会从父级(词法层面上的父级)执行上下文的对象变量中查找,一直找到全局上下文的变量对象,也就是全局对象,这样由多个执行上下文的变量对象构成的链表就叫做作用域链。作用域链的形成需要经历两个阶段,分别为函数创建阶段和激活阶段。

5.1函数创建

函数的作用域在函数定义时就决定了,因为函数有一个内部属性[[scope]],当函数创建时,就会保存所有父级变量对象到其中,可以理解[[scope]]就是所有父级变量对象的层级链条,但是[[scope]]并不代表完整的作用域链.

5.2函数激活

当函数激活时,进入函数上下文,创建AO/VO后,就会将活动对象添加到作用域链的前端,至此作用域链创建完毕.伪代码示例:

  1. var scope = "global scope"
  2. function checkscope() {
  3. var scope2 = "local scope"
  4. return scope2
  5. }
  6. checkscope()
  7. //执行过程如下:
  8. //1:checkscope函数被创建,保存作用域链到内部属性[[scope]]
  9. checkscope.[[scope]] = [
  10. globalContext.VO
  11. ]
  12. //2.执行checkscope函数,创建checkscope函数上下文,checkscope函数执行上下文被压入执行上下文栈
  13. ECStack = [
  14. checkscopeContext,
  15. globalContext
  16. ]
  17. //3.复制函数[[scope]]属性创建作用域链
  18. checkscopeContext = {
  19. Scope:checkscope.[[scope]]
  20. }
  21. //4.用arguments创建活动对象,初始化活动对象
  22. checkscopeContext = {
  23. AO: {
  24. arguments:{
  25. length:0
  26. },
  27. scope2:undefined
  28. }
  29. }
  30. //5.将活动对象压入checkscope作用域链顶端
  31. checkscopeContext = {
  32. AO: {
  33. arguments: {
  34. length: 0
  35. },
  36. scope2: undefined
  37. },
  38. scope:[AO,[[Scope]]]
  39. }
  40. //6.函数执行,随着函数的执行,修改AO的属性值