JavaScript 之 作用域&变量提升&闭包

一、JavaScript 代码的执行

  • 浏览器内核是由两部分组成的,以 webkit 为例
    • WebCore:负责HTML解析、布局、渲染等等相关的工作
    • JavaScriptCore:解析、执行 JavaScript 代码
  • 另外一个强大的 JavaScript 引擎就是 V8 引擎

二、深入 V8 引擎原理

  • V8 是用 C ++ 编写的 Google 开源高性能 JavaScript 和 WebAssembly (未来 js 替代)引擎,它用于 Chrome 和 Node.js
    • 跨平台
  • 它实现 ECMAScriptWebAssembly ,并在 Windows 7 或更高版本,macOS 10.12+ 和使用 x64,IA-32,ARM 或 MIPS 处理器的 Linux 系统上运行
  • V8 可以独立运行,也可以嵌入到任何 C ++ 应用程序
  • 步骤
    • 高级语言转化成二进制代码
      • 抽象语法树
        • 知道整个代码的结构
      • 字节码
        • 类似于汇编代码
        • 跨平台
          • mac os
          • java:一次编写,到处运行
            • 需要安装并依赖于 java 虚拟机
      • 优化的机器码
        • 尽量保证传入的参数的类型一致
        • ts 能够提高代码性能
    • lgnition:AST 转化为字节码
    • TurboFan

三、V8 引擎的架构

  • 浏览器中存在渲染引擎,v8 是专门用于处理 js 代码的一部分

  • Scanner 进行扫描,进行词法分析

  • V8 引擎本身的源码非常复杂,大概有超过100w行C++代码

  • Parser 模块将 JavaScript 代码转换成 AST(抽象语法树)

    • 进行语法分析

    • 预解析(声明时候没有进行调用,被认为是不被执行的代码,进行预解析,后面执行的话就会进行全量解析) =》 全量解析(函数立即执行,只进行一次全量解析)

      • 避免函数嵌套
    • 解释器并不直接认识 JavaScript 代码

    • 如果函数没有被调用,那么是不会被转换成 AST 的

  • Ignition 是一个解释器,会将 AST 转换成 ByteCode(字节码)

    • 会收集 TurboFan 优化所需要的信息(比如函数参数的类型信息,有了类型才能进行真实的运算)
    • 如果函数只调用一次,Ignition会解释执行ByteCode
  • TurboFan是一个编译器,可以将 字节码 编译为 CPU 可以 直接执行的机器码

    • 如果一个函数被多次调用,会被标记为热点函数,那么就会经过 TurboFan 转换成优化的机器码,提高代码的执行性能
    • 但是,机器码实际上也会被还原为 ByteCode
      • 如果后续执行函数的过程中,类型发生了变化(比如sum函数原来执行的是number类型,后来执行变成了string类型)
      • 之前优化的机器码并不能正确的处理运算,就会逆向的转换成字节码
  • 词法分析(对一条语句进行此法分析,提取出关键字)=》 语法分析(解析成 js 中对应的语法,生成对应的 AST 树

JavaScript 之 作用域&变量提升&闭包_第1张图片

四、初始化全局对象

  • js 引擎会在执行代码之前,会在堆内存中创建一个全局对象:Global Object(GO)
    • 该对象 所有的作用域(scope)都可以访问
    • 里面会包含 Date、Array、String、Number、setTimeout、setInterval 等等
    • 其中还有一个 window 属性指向自己

五、执行上下文

  • js 引擎内部有一个执行上下文栈(Execution Context Stack,简称 ECS),它是用于执行代码的调用栈
    • 执行的是全局的代码块
      • 全局的代码块为了执行会构建一个 全局执行上下文 Global Execution Context(GEC)
      • GEC 会被放入到 执行上下文栈 ECS 中执行
  • GEC 被放入到 ECS 中里面包含两部分内容
    • 第一部分:变量的作用域提升:在代码执行前,在 parser 转成 AST 的过程中,会将全局定义的变量、函数等加入到 GlobalObject 中,但是并不会赋值
    • **第二部分:**在代码执行中,对变量赋值,或者执行其他的函数

六、VO 对象

  • 每一个执行上下文会关联一个 VO(Variable Object,变量对象),变量和函数声明会被添加到这个 VO 对象中
  • 全局代码被执行的时候,VO 就是 GO 对象
  • 全局对象就是 window 对象,this
  • 作用域链

七、函数执行

  • 在执行的过程中执行到一个函数时,就会根据函数体创建一个函数执行上下文(Functional Execution Context,简称 FEC),并且压入到 EC Stack
  • 每个执行上下文都会关联一个 VO,函数执行上下文关联的 VO 是什么
    • 当进入一个函数执行上下文时,会在堆内存中创建一个 AO 对象(Activation Object)
    • 这个 AO 对象会使用 arguments 作为初始化,并且初始值是传入的参数
    • 这个 AO 对象会作为执行上下文的 VO 来存放变量的初始化(都是 undefined)
  • 执行完后就销毁
  • 只有是函数的时候才会指向一个地址,其他都是 undefined
    • 创建 obj 对象(刚开始也是 undefined )
  • 函数多层嵌套,并不会把所有的对象都创建出来,只会创建第一个

八、JavaScript 的代码的执行流程

  • 首先在执行前会现在堆内存中开辟一块空间 (GO) 存放一些初始的值 如 Number String等等
  • 还有代码中定义的一些变量 函数(在 parser 转成 AST 树的过程中存放在 GO 中的 )并没有赋值
  • 同时在执行代码时在执行上下文栈(ECS)中存放一个全局执行上下文(GEC) 用于执行代码
    • GO中对应的函数 也会在堆内存中开辟出空间 为 Function Object 初始一些数据(name length scope chain等)
  • 开始执行代码
  • 每个EC中有着三个重要的内容(VO scope chain 以及this)
  • VO指向对应的作用域(全局作用域(GO) 函数作用域(AO))
1. 基本数据类型是按值进行操作
2. 基本数据类型是存放在栈区的
3. 无论当前看到的栈内存还是引用数据类型会使用的堆内存都属于计算机内存
4. GO 

九、作用域和作用域链

  • 当进入到一个执行上下文时,执行上下文也会关联一个作用域链Scope Chain
    • 作用域链是一个对象列表,用于变量标识符的求值
    • 当进入一个执行上下文时,这个作用域链被创建,并且根据代码类型,添加一系列的对象
  • 代码类型
    • 全局代码
    • 函数代码
  • 函数被创建的时候,作用域链已经确定了
    • 因此可以画出

十、GO/AO/VO以及作用域和作用域链

  • GO

    • Global Object JS代码在执行前会现在堆内存中创建一个全局对象(GO)
    • 有自己的地址,可以对其进行访问
    • 用于存放一些定义好的变量方法等
    • 包含 Date Array String Number setTimeout等
    • 同时有一个 window 属性指向自己
    • 同时在语法分析转成 AST 的过程中也会将一些变量 函数 存放在 GO 中 只是变量的初始值为 undefined
  • AO

    • 函数在执行前会先在堆内存中创建一个AO(Activation Object)对象 里面存放这arguments 对应函数的形参 以及在函数中定义的变量 初始值为 undefined
  • VO

    • Variable Object 在执行函数时 会在执行上下文栈(ECS)中进入一个函数执行上下文(FEC)其中有三个核心
    • 核心之一是VO 指向的是该函数在内存中解析时创建的AO 而在全局执行上下文中指向的是GO
  • 作用域,作用域链

    • 当进入到一个执行上下文时 执行上下文会关联一个作用域链
    • 通常作用域链在解析时就被确定,因此,作用域链域函数的定义位置有关,而与它的调用位置无关

十一、面试题

    // 1.面试题一:
    // var n = 100
    // function foo() {
    //   n = 200 //直接访问上一层的 n 
    // }
    // foo()

    // console.log(n)//200

    // 2.面试题二:
    // var n = 100
    // function foo() {
    //   console.log(n)
    //   var n = 200
    //   console.log(n)
    // }

    // foo()//undefined  200

    // 3.面试题三:
    // var n = 100

    // function foo1() {
    //   console.log(n)
    // }
    // function foo2() {
    //   var n = 200
    //   console.log(n)
    //   foo1()
    // }
    // foo2() //200 100

    // 4.面试题四:
    // var n = 100
    // function foo() {
    //   console.log(n)
    //   return//执行
    // 解析的时候
    //   var n = 200  //还是会在里面定义n:undefined
    // }
    // foo()

    // 5.在开发中可能会出现这样错误的写法
    // function foo() {
    //   message = "Hello World"//就是当成 全局变量 进行解析,没有 var
    // }
    // foo()
    // console.log(message)

    // 6.面试题五:
    function foo() {
      var a = b = 100
    }
    foo()
    console.log(a)//访问不到,报错
    console.log(b)//当成全局变量 100

你可能感兴趣的:(javascript,开发语言,ecmascript,前端,js)