JavaScript使用var关键字声明变量为什么会存在变量提升?
JavaScript函数为什么可以提前调用?
JavaScript代码,在浏览器中又是如何被执行的?
我们可以通过了解V8引擎的运行机制了解JS代码在浏览器中的编译过程,来解释为什么存在声明提升的情况
Blink将源码交给V8引擎,Stream获取到源码并且进行编码转换;
Scanner会进行词法分析(lexical analysis),词法分析会将代码转换成tokens;
接下来tokens会被转换成AST树,经过Parser和PreParser:
Parser就是直接将tokens转成AST树架构;
重点:预解析是导致JavaScript代码变量提升的关键
这是因为并不是所有的JavaScript代码,在一开始时就会被执行。那么对所有的JavaScript代码进行解析,必然会影响网页的运行效率;
所以V8引擎就实现了Lazy Parsing
(延迟解析)的方案,它的作用是将不必要的函数进行预解析,也就是只解析暂
时需要的内容,而对函数的全量解析是在函数被调用时才会进行;
比如我们在一个函数outer内部定义了另外一个函数inner,那么inner函数就会进行预解析;
它不会解析inner中的代码;只有在调用inner后才会解析。以此提高浏览器性能;
生成AST树后,会被Ignition转成字节码(bytecode),之后的过程就是代码的执行过程(后续会详细分析)
js引擎会在执行代码之前,会在堆内存中创建一个全局对象:Global Object(GO)
该对象 所有的作用域(scope)都可以访问;
里面会包含Date
、Array
、String
、Number
、setTimeout
、setInterval
等等;
其中还有一个window属性指向自己;
node环境: 是global而不是window
js引擎内部有一个执行上下文栈(Execution Context Stack,简称ECS),它是用于执行代码的调用栈。
那么现在它要执行谁呢?执行的是全局的代码块:
Global Execution Context(GEC)
;GEC
会 被放入到ECS
中 执行;hoisting
)假如我们有下面一段代码,它在JavaScript中是如何被执行的呢?
执行过程1:预解析
执行过程2: 开始执行依次往下执行代码
第一行的name变成 => ‘八嘎’
第三行的foo函数内的name因为没调用 name =‘undefined’
第八行打印GO预解析中的num1 :undefined
第十四行调用foo函数 往下执行name从undefined 被覆盖为‘压路’
第五行打印‘压路
第十五行打印1346
打印num
在执行的过程中执行到一个函数时,就会根据函数体创建一个函数执行上下文(Functional Execution Context
,
简称FEC
),并且压入到EC Stack
中。
FEC中包含三部分内容:
function foo() {
console.log(n)
var n = 200
console.log(n)
}
var n = 100
foo()
执行过程:预解析
直到foo()调用函数
根据函数体创建一个函数执行上下文,压入ECS
继续从上往下执行代码
我们打印第一个console.log(n)时会发现是n 是 undefined
因为在解析函数成为AST树结构时,会创建一个Activation Object(AO),而AO中不会管代码怎么上下执行,foo函数内var定义的a直接被定义为了undefined,根据作用域链的规则,我们会首先打印内部的n,所以打印才是undefined,而不是100
继续执行下一句代码 var n=200 AO中的 n:undefined 被覆盖为200
打印 n 既得 200