js作用域(scope)的学习

128 阅读3分钟

js作用域(scope)的学习

v8引擎的原理

js源代码->parse(解析)->ast->lgnition(v8的库(模块)) -> 字节码(bytecode) -> cpu可以运行的代码
bytecoed的优势,就是跨平台

全局代码执行过程

console.log(num); // undefined vo找GO的num值,执行第一行num值为undefined -> 作用域提升
var name = 'Fhup'
var num = 3 // 解析时加入GO 默认值为undefined

console.log(window);

/**
 * 1. js源代码被解析,v8引擎内部帮助我们在堆内存创建一个对象 (globalObject => GO)
 * 2. 运行代码
 *        2.1 v8为了执行代码,内部有一个 执行上下文栈(ECStack) (函数调用栈)
 *        2.2 全局执行的代码,会创建 全局执行上下文(Global Execution Content GEC) (全局代码执行时才会创建)
 *                2.2.1 全局执行上下文里面有VO (VO指向GO)
 *        2.3 开始执行代码(在VO中按顺序执行),同时确定其父类作用域 vo指向go,改变默认的undefined值
 * 
 * 
 *   解析时发现是函数(数组也是一样):
 *        1.1 开始解析时发现是函数,堆内存中创建该函数对象,确定父类作用域和函数体
 *        1.2 执行函数时,创建一个 函数执行上下文(FEC) 内部维持VO:->AO, 在堆内存中创建AO对象.对函数体
 *            内容进行解析执行
 */
var globalObject = {
  String:'类',
  Data:'类',
  setTimeoutL:'函数',
  window:globalObject,
  name: '' | '执行时赋值',
  num: undefined
}

// 全局对象里面还有全局对象 GO里面的window又指向GO
console.log(window.window.window.window);

image.png

函数执行过程

var name = 'Fhup'


foo(9)
// 函数的执行在 函数执行上下文(FEC) 里面,里面也有VO,而VO对应AO(AO称为函数的活跃对象但会被销毁)
function foo(num) {
  console.log(h); // undefined
  var f = 100
  var h = 300 // 将f,h提升到AO中且值为undefined,而AO与GO相似 
  
  console.log(name); /// name=Fhup 查找变量时,沿着作用域链来查找.作用域链为AO(当前)+GO(父类作用域)
}
// 执行完,VO弹栈,AO销毁


var globalObject = {
  String:'类',
  window:globalObject,
  name: '' | '执行时赋值',

  // 发现是函数,开辟新的内存空间进行存储(称为:存储函数空间).
  foo: `0xa00` // 内存地址
}

image.png

函数嵌套(作用域链)

var name = 'Fhup'
// 注意: name变量在window对象上存在,一般不使用

foo(9)
function foo(num) {
  console.log(h);
  var h = 300
  
  function bar() {
    console.log(name) // 沿着作用域链找:AO(当前) + parent scope(上层作用域:foo的AO) + GO ,如果还没有,就会报错
  }
  bar()
  // 1.bar执行完后,bar的VO弹栈,bar的AO销毁.
}
// 2.bar的VO弹栈之后,foo执行完,foo的VO弹栈,foo的AO销毁


var globalObject = {
  String:'类',
  window:globalObject,
  name: '' | '执行时赋值',

  foo: `0xa00`,
  bar: `0xb00` // bar指向oxb00空间
}

image.png

函数互调(作用域链)

var mes = 'hello global'

/**
 * 父级作用域: 在编译时确定,从上往下(从第一行到最后),与调用无关     foo作用域: AO(当前)+GO
 */

function foo() {
  // 在编译时确定foo的父级作用域,为GO
  // 不是看代码的父级作用域(跟调用位置无关,跟定义位置有关) 所以mes去GO找mes的值
  // 编译时父级作用域就会确定好,执行时不会改变
  console.log(mes); // hello global
}

function bar() {
  var mes = 'Fhup'
  foo()
}

bar()

image.png

ECMA规范

/**
 * 早期的ECMA版本规范:
 * 每一个执行上下文会被关联到一个变量对象(variable object,VO),在源代码中的变量和函数声明会被作为属性添加到VO
 * 中。对于函数来说,参数也会被添加到VO中。
 */

/**
 * 最新的ECMA版本规范:
 * 每一个执行上下文会关联到一个变量环境(VariableEnvironment)中,在执行代码中变量和函数的声明会作为环境记录(E
 * Environment Record)环境中。对于函数来说,参数也会被作为环境记录添加到变量环境中。
 * 
 * 注意: 变量环境(VariableEnvironment)和环境记录(Environment Record)存在于词法环境中
 */