关于javascript的声明提升

113 阅读3分钟

来看几行代码:

console.log(x) //未定义变量直接打印,会报引用错误

-------------------------------------

console.log(x) //会打印出"undefined"
var x 

-------------------------------------

x=1
console.log(x) //会打印出1
var x

-------------------------------------

console.log(x) //会打印出"undefined"
var x=1

image.png

我们在实际编写javascript中会发现:var变量的赋值与使用如果早先于变量声明,代码执行并不会报错。

这是因为:JavaScript代码的执行首先要通过预编译,预编译中变量声明会被扫描并且添加到词法环境(Lexical Environment) 中,这样就能在(代码中)实际声明前使用它们。而所有声明语句像是被提升到代码的头部,此称之为“声明提升”(Hoisting)。


什么是词法环境?

词法环境是一种包含标识符变量映射的数据结构。(标识符是指变量/函数的名称,变量是对实际对象或原始值的引用)。 概念上,词法环境可以理解成这个样子:

LexicalEnvironment = {  
标识符:<值>,  
标识符:<函数对象>  
}

精简地讲,词法环境是程序执行期间存储变量和函数的地方。

var的声明提升

js引擎只能够提升变量的声明,无法提升实际赋值的初始化。但有趣的是,js添加var到词法环境时,会用undefined初始化var变量。也就是说,未实际执行var变量赋值的代码,var变量将属于undefined类型,值也就成了undefined。接着在执行期间,当运行代码到实际赋值时,js才会赋值到变量中。

image.png

在初始词法环境中,var声明提升看起来如同:

词法环境 = {  
x: undefined  
}

运行到实际赋值时,词法环境会更新:

词法环境 = {  
x: 'hello world!' 
}



let与const的声明提升

当用let声明变量时,声明提升会有些不同:

console.log(m)//报错,Uncaught ReferenceError: m is not defined
let m

console.log(x)//报错,Uncaught ReferenceError: x is not defined
const x=2

这里需要强调:声明提升适用所有声明(var、let、const、function等),js引擎在预编译会用undefined初始化var变量,但并不会用任何值去初始化let变量与const常量。因此访问未初始化的变量/常量会报错。此时两者的词法环境看起来像:

lexicalEnvironment = {  
x/m: <uninitialized>  
}

如果访问let变量在实际声明之后:

let x
console.log(x)//输出undefined

这里要跟上面做个区分,在编译阶段,被声明提升的let变量同样被添加到词法环境并且也没有被任何值初始化。但是在执行阶段,实际声明是在访问变量之前,实际声明时js发现没有任何赋值就会用undefined绑定x,因此此时不会报错并且输出undefined。


class的声明提升

类的声明提升与let/const的声明提升相似,类被添加到词法环境时也不会被任何值初始化,因此在实际声明类之前访问类会报错。


函数的声明提升

函数可以通过声明定义,也可以是一个表达式。对于不同方式定义的函数,它的声明提升会有些不同:

test1()//输出test!
function test1(){
    console.log('test!')
}

test2()//Uncaught TypeError: test2 is not a function
var test2=function(){
    console.log('test!')
}

声明定义的函数整个都会被提升(预处理的时候函数会被添加到内存中),而通过表达式定义的函数只有变量会被提升(var变量预编译会被undefined初始化),因此此时词法环境中的test2被视为变量而不是函数。

总结

1)声明提升适用所有变量、常量、类、函数。但声明提升只能提升声明定义,不能提升实际赋值。
2)var变量被添加到词法环境时会被undefined初始化,但let与const不会。
3)声明定义的函数能够被整体提升,表达式定义的函数只有外部变量被提升。