变量提升和函数提升
JS中可以再变量和函数声明之前使用它们,这就称作变量提升/函数提升。
sayHi() // Hi there!
function sayHi() {
console.log("Hi there!")
}
name = 'John Doe'
console.log(name)
var name
但实际上js并不会移动代码,作为单线程语言js肯定是按顺序执行。
但这里按顺序并不是逐行分析执行,而是一段一段的进行,并且会先进行编译阶段再到执行阶段。
在编译阶段,代码真正执行前的几毫秒,会检测到所有的变量和函数声明,所有这些函数和变量声明都被添加到Lexial Environment(词法环境)的JS数据结构内存中。所以这些变量才能在被真正声明之前使用。
变量提升
var变量提升
console.log(name) // 'undefined'
var name = 'John Doe'
console.log(name) // John Doe
这段代码实际上分为两个部分:
- var name 表示声明变量 name
- = 'John Doe' 表示为变量 name赋值为'Jogn Doe'
只有声明操作var name会被提升,而赋值这个操作并不会被提升,但是为什么变量name的值会是undefined呢? 原因是当JavaScript在编译阶段会找到var关键字声明的变量会添加到词法环境中,并初始化一个值undefined,在之后执行代码到赋值语句时,会把值赋值到这个变量。 过程如下:
// 编译阶段
lexicalEnvironment = {
name: undefined
}
// 执行阶段
lexicalEnvironment = {
name: 'John Doe'
}
因此用var定义的函数表达式也不会被提升,如下helloworld是一个默认值为undefined的变量,而不是一个function对象:
helloWorld(); // TypeError: helloWorld is not a function
var helloWorld = function(){
console.log('Hello World!');
}
let const 变量提升和暂时性死区
那么我们来看一下使用let和const定义的变量是否会发生变量提升呢?
console.log(a) // ReferenceError: a is not defined
let a = 3
为什么会报一个ReferenceError错误,难道let和const声明的变量没有被提升吗?
实际上所有的声明(function, var, let, const, class)等都会被提升,但是只有用var关键字声明的变量才会被初始化undefined值,而let和const声明的变量则不会被初始化值。
只有在执行阶段js引擎遇到他们的赋值操作时,他们才会被初始化。也就是说使用let、const声明的变量在声明之前是无法访问的,这就是暂时性死区,即let和const声明的变量在声明之前无法访问。
如果JavaScript引擎在执行阶段let和const变量被声明的地方还找不到值的话,就会被赋值为undefined或者返回一个错误(const的情况下)。
let a
console.log(a) // undefined
a = 5
在编译阶段,JavaScript引擎遇到变量a并将它存到词法环境中,但因为使用let关键字声明的,JavaScript引擎并不会为它初始化值,所以在编译阶段,此刻的词法环境像这样:
lexicalEnvironment = {
a: <uninitialized>
}
如果我们要在变量声明之前使用变量,JavaScript引擎会从词法环境中获取变量的值,但是变量此时还是uninitialized状态,所以会返回一个错误ReferenceError。
在执行阶段,当JavaScript引擎执行到变量被声明的时候,如果声明了变量并赋值,会更新词法环境中的值,如果只是声明了变量没有被赋值,那么JavaScript引擎会给变量赋值为undefined。
tips: 我们可以在let和const声明之前使用他们,只要代码不是在变量声明之前执行:
function foo() {
console.log(name)
}
let name = 'John Doe'
foo() // 'John Doe'
函数提升
- 函数声明和初始化都会被提升
console.log(square(5)); // 25
function square(n) {
return n * n;
}
- 函数表达式不会被提升
console.log(square); // undefined
console.log(square(5)); // square is not a function
var square = function (n) {
return n * n;
}
function hoistFunction() {
foo(); // 2
var foo = function() {
console.log(1);
};
foo(); // 1
function foo() {
console.log(2);
}
foo(); // 1
}
hoistFunction();
优先级
函数提升在变量提升之前
- 变量的问题,莫过于声明和赋值两个步骤,而这两个步骤是分开的。
- 函数声明被提升时,声明和赋值两个步骤都会被提升,而普通变量却只能提升声明步骤,而不能提升赋值步骤。
- 变量被提升过后,先对提升上来的所有对象统一执行一遍声明步骤,然后再对变量执行一次赋值步骤。而执行赋值步骤时,会优先执行函数变量的赋值步骤,再执行普通变量的赋值步骤。
例子1
typeof a; // function
function a () {}
var a;
// typeof a; // function =》无论放在前面还是后面,解析后执行顺序都是一样
编译后
function a // => 声明一个function a
var a // =》 声明一个变量 a
a = () {} // => function a 初始化
typeof a; // function
例子2
function b(){};
var b = 11;
typeof b; // number
编译后
function b; // => 声明一个function b
var b; // =》 声明一个变量 b
b = (){}; // =》 function b 初始化
b = 11; // =》 变量 b 初始化 =》变量初始化没有被提升,还在原位
typeof b; // number
例子3
var foo = 'hello';
(function(foo){
console.log(foo);
var foo = foo || 'world';
console.log(foo);
})(foo);
console.log(foo);
// 依次输出 hello hello hello
编译后
var foo = 'hello';
(function (foo) {
var foo; // undefined;
foo= 'hello'; //传入的foo的值
console.log(foo); // hello
foo = foo || 'world';// 因为foo有值所以没有赋值world
console.log(foo); //hello
})(foo);
console.log(foo);// hello,打印的是var foo = 'hello' 的值(变量作用域)