最先实行剧本时,实行剧本的第一步是编译代码,然后再最先实行代码,如图
别的,在编译优化方面来讲,最最先时也并非悉数编译好剧本,而是当函数实行时,才会先编译,再实行剧本,如图
- 编译阶段:阅历了词法剖析,语法剖析天生AST,以及代码天生。而且在此阶段,它只会扫描而且抽出环境中的声明变量,声明函数以便预备分派内存,一切的函数声明和变量声明都邑被添加到名为
Lexical Environment
的JavaScript内部数据结构内的内存中。因而,它们能够在源代码中现实声明之前运用。然则,Javascript只会存储函数声明和变量声明在内存,并不会存储他们的值 - 实行阶段:给变量x赋值,起首讯问内存你这有变量x吗,假如有,则给变量x赋值,假如没有则建立变量x而且给它赋值。
变量提拔
以下图,左侧灰色块地区,是演示函数实行前的编译阶段,先抽出一切声明变量和声明函数,并进行内存分派。然后再最先实行代码,在实行第一行代码的时刻,如果变量a存在于内存中,则直接给变量a赋值。而实行到第二行时,变量b并没有在内存中,则会建立变量b并给它赋值。
Lexical enviroment
是一种包括标识符变量映照的数据结构
LexicalEnviroment = {
Identifier: <value>,
Indentifier: <function object>
}
简而言之,Lexical enviroment
就是顺序实行过程当中变量和函数存在的处所。
let,const变量
console.log(a)
let a = 3;
输出
ReferenceError: a is not defined
所以let和const变量并不会被提拔吗?
这个答案会比较复杂。一切的声明(function, var, let, const and class)在JavaScript中都邑被提拔,但是var
声明被undefined
值初始化,然则let
和const
声明的值依然未被初始化。
它们仅仅只在Javascript引擎运转时期它们的词法绑定被实行在才会被初始化。这意味着引擎在源代码中声明它的位置盘算其值之前,你没法接见该变量。这就是我们所说的时候死区,即变量建立和初始化之间的时候,我们没法接见该变量。
假如JavaScript引擎依然没法在声明它们的行中找到let
或许const
的值,它将为它们分派undefined
值或返回毛病值(在const
的情况下会返回毛病值)。
6a9a50532bf60f5fac6b3c.png](evernotecid://F2BCA3B5-CC5A-4EB3-BD61-DD865800F342/appyinxiangcom/10369121/ENResource/p1163)
let a;
console.log(a); // outputs undefined
a = 5;
在编译阶段,JavaScript引擎碰到变量a并将它存储在lexical enviroment
,然则由于它是一个let
变量,所以引擎不会为它初始化任何值。所以,在编译阶段,lexical enviroment
看起来像下面如许。
// 编译阶段
lexicalEnvironment = {
a: <uninitialized>
}
如今假如我们尝试在声明它之前接见该变量,JavaScript引擎将会尝试从词法环境中拿到这个变量的值,由于这个变量未被初始化,它将抛出一个援用毛病。
在实行时期,当引擎抵达了变量声明的行,它将试图实行它的绑定,由于该变量没有与之关联的值,因而它将为其赋值为unedfined
// 实行阶段
lexicalEnviroment = {
a: undefined
}
以后,undefined
将会被打印到控制台,然后将值5赋值给变量a,lexical enviroment
中变量a
的值也会从undefined
更新为5
functionn foo() {
console.log(a)
}
let a = 20;
foo();
function foo() {
console.log(a): // ReferenceError: a is not defined
}
foo();
let a = 20;
Class Declaration
就像let
和const
声明一样,class
在JavaScript中也会被提拔,而且和let
,const
一样,晓得实行之前,它们都邑坚持uninitialized
。因而它们同样会遭到Temporal Deal Zone(时候死区)
的影响。比方
let peter = new Person('Peter', 25); // ReferenceError: Person is not defined
console.log(peter);
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
因而要接见class
,必须先声明它
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
let peter = new Person('Peter', 25);
console.log(peter);
// Person { name: 'Peter', age: 25 }
所以在编译阶段,上面代码的lexical environment(词法环境)
将以下所示:
lexicalEnvironment = {
Person: <uninitialized>
}
当引擎实行class
声明时,它将运用值初始化类。
lexicalEnvironment = {
Person: <Person object>
}
提拔Class Expressions
let peter = new Person('Peter', 25);
console.log(peter);
let Person = class {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
let peter = new Person('Peter', 25);
console.log(peter);
var Person = class {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
所以如今我们晓得在提拔过程当中我们的代码并没有被JavaScript引擎现实挪动。正确理解提拔机制将有助于防止因变量提拔而发生的任何将来毛病和杂沓。为了防止像未定义的变量或援用毛病一样能够发生的副作用,请一直尝试将变量声明在各自作用域的顶部,并一直尝试在声明变量时初始化变量。