JS执行上下文

306 阅读3分钟

js执行机制

编译-->执行

编译过程

showName()
console.log(myName)
var myName = "xxx"
function showName(){
    console.log("hi")

}

image.png

  • 第 1 行和第 2 行,由于这两行代码不是声明操作,所以 JavaScript 引擎不会做任何处理;
  • 第 3 行,由于这行是经过 var 声明的,因此 JavaScript 引擎将在环境对象中创建一个名为 myname 的属性,并使用 undefined 对其初始化;
  • 第 4 行,JavaScript 引擎发现了一个通过 function 定义的函数,所以它将函数定义存储到堆 (HEAP)中,并在环境对象中创建一个 showName 的属性,然后将该属性值指向堆中函数的位置

编译过程生成执行上下文和可执行代码

执行上下文又包括

  1. 变量对象

    • 变量提升后的变量
    • 函数参数
  2. 词法环境

    let/const

  3. this

  4. 作用域

变量对象

  1. 函数形参
  2. 函数声明
  3. 变量声明
function foo(a) {
  var b = 2;
  function c() {}
  var d = function() {};

  b = 3;

}

foo(1);

变量对象如下:

AO = {
    arguments: {
        0: 1,
        length: 1
    },
    a: 1,
    b: undefined,
    c: reference to function c(){},
    d: undefined
}

变量提升(hositing)

扫一遍代码,提升函数声明和变量声明

两个原则:

  1. var 声明提到全局作用域,并赋值为undefined
  2. 函数声明就是引用 // 一段代码如果定义了两个相同名字的函数,那么最终生效的是最后一个函数

// ps let const 也会被提升,但是如果在未被赋值前引用,会报错,暂时性死区(TDZ)

词法环境

块级作用域

块级作用域,同一个执行上下文变量查找顺序:

function foo(){
var a = 1
let b = 2
{
let b = 3
var c = 4
let d = 5
console.log(a)
console.log(b)
}
console.log(b)
console.log(c)
console.log(d)
}
foo(

image.png

当作用域块执行结束之后,其内部定义的变量就会从词法环境的栈顶弹出

this

  1. new 指向实例
  2. call,apply指向传入的对象
  3. 对象的方法指向调用对象
  4. 全局指向window

作用域链

作用域链在函数定义时就确定,指向其定义时的环境

image.png

闭包

// 将外部函数变量的装入背包

在JS中,内部函数时可以访问外部函数的,当调用外部函数返回一个内部函数,外部函数已经执行结束,但是内部函数对外部函数的引用依然在内存中,我们将这些变量叫闭包

// 在 JavaScript 中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包。

// 闭包的定义很简单:函数 A 返回了一个函数 B,并且函数 B 中使用了函数 A 的变量,函数 B 就被称为闭包。

你是否会疑惑,为什么函数 A 已经弹出调用栈了,为什么函数 B 还能引用到函数 A 中的变量。因为函数 A 中的变量这时候是存储在堆上的。现在的 JS 引擎可以通过逃逸分析辨别出哪些变量需要存储在堆上,哪些需要存储在栈上。