1. 编译阶段对变量与函数声明的的处理
JavaScript引擎会在编译阶段进行数项优化,有些优化通过对代码静态分析,并预先确定所有变量和函数的定义位置,并用合适的作用域将它们关联起来,才能在执行过程中快速找到标识符。
因此,正确的思路是:包含变量和函数在内的所有声明都会在任何代码执行前首先被处理。
2. 变量提升的各种情况
2.1 正常变量使用,先声明再使用
var a = 'test';
console.log(a); // test
复制代码
实际执行过程中会做如下处理:
var a; // 变量定义会提升到作用域顶部
a = 'test' // 赋值操作会保留在原位置
console.log(a); //test
复制代码
2.2 在变量声明之前使用变量
console.log(a); // undefined
var a = "test";
复制代码
如果去除变量声明
console.log(a); // throw Error: Uncaught ReferenceError: a is not defined
复制代码
变量使用在声明前实际执行过程中会做如下处理:
var a;
console.log(a); // undefined
a = 'test';
复制代码
2.3 同名变量多次声明
// example 1
var a=9;
console.log(a);//9
var a;
console.log(a);//9
// example 2
var a=9;
console.log(a);// 9
var a=3;
console.log(a); // 3
复制代码
重复的
var
声明会被忽略,但是会保留赋值或者其他操作。
2.4 函数声明提升
func(); //test
function func(){
console.log('test');
}
复制代码
实际执行过程会做如下处理:
function func(){
console.log('test');
}
test(); // test
复制代码
2.5 重复的函数声明
func(); // 2
function func (){
console.log(1);
}
function func(){
console.log(2);
}
复制代码
与变量重复声明不同,出现在后面的函数声明会覆盖前面的,实际执行过程会做如下处理:
function func (){
console.log(1);
}
function func(){
console.log(2);
} // 覆盖之前的函数声明
func(); //2
复制代码
2.6 函数声明会被提升,函数表达式不会被提升
func(); // throw Error: Uncaught TypeError: func is not a function
var func = function(){
console.log(1)
}
复制代码
实际执行过程中处理如下:
var func; // undefined
func(); // 由于undefined不是function,函数调用操作报错
func = function(){
console.log(1)
}
复制代码
2.7 变量声明与函数声明重复-函数优先
/* example 1 */
func(); // function
var func= function(){
console.log("variable")
};
function func(){
console.log('function');
}
func();//variable
/* example 2 */
func(); //function
function func(){
console.log('function');
}
func(); // function
var func= function(){
console.log("variable")
};
func(); // variable
复制代码
在实际执行过程中,变量声明被忽略,但是赋值操作保留处理如下:
function func(){
console.log('function');
} // 函数声明被提升
func(); // function
func(); // function
/* var func; */ //变量声明被忽略
func= function(){
console.log("variable")
};
func(); // variable
复制代码
2.8 普通块内部的变量与函数声明
/*
* 测试环境
* 1. Microsoft Edge 96.0.1054.41
* 2. Firefox 95.0.2
*/
// example 1
console.log(typeof func); // undefined
func(); // throw Error: Uncaught TypeError: func is not a function
{
function func() {
console.log('a');
}
}
// example 2
console.log(typeof func); // undefined
func(); // throw Error: Uncaught TypeError: func is not a function
var a =true;
if(a){
function func() {
console.log('a');
}
}else{
function func() {
console.log('b');
}
}
复制代码
在最新的浏览器中普通块内部的函数声明提升规则发生了改变,实际执行过程类似于:
var func;
console.log(func); // undefined
func(); // throw Error: Uncaught TypeError: func is not a function
{
func = function() {
console.log('a');
}
}
复制代码
2.9 内部变量、函数声明与形式参数标识符相同
function func(inner, val) {
console.log(typeof inner, typeof val);
function inner() {
console.log(111);
}
var val=100;
console.log(typeof inner, typeof val);
inner();
}
func('test', 'test variable');
func(function () {
console.log(222);
}, function () {
console.log(333);
})
/* output
* function string
* function number
* 111
* function function
* function number
* 111
*/
复制代码
在内部作用域声明的函数会覆盖外部函数的参数;如果内部声明的变量名称与某个参数相同,声明会被忽略掉。
3 let
与 const
对声明提升的影响
3.1 块作用域
{
let a='test';
}
console.log(a); // throw Error: Uncaught ReferenceError: a is not defined
复制代码
使用let
和const
关键字声明的变量只能在块内部使用,不能在块外部使用。
3.2 必须先声明,再使用
let a = 1;
function test() {
console.log(a);
const a = 2;
}
test(); // throw Error: Uncaught ReferenceError: can't access lexical declaration 'a' before initialization
复制代码
3.3 同一作用域不能存在相同标识符的变量或者函数
// example 1
function test(params) {
let params = 1;
}
test(0);// throw Error: Uncaught SyntaxError: redeclaration of formal parameter params
// example 2
function func() {
let test = 1;
let test = 3;
}
func();// throw Error: Uncaught SyntaxError: redeclaration of let test
// example 3
function func() {
let inner=2;
function inner (){
console.log(111);
}
}
func();// throw Error: Uncaught SyntaxError: redeclaration of let inner
复制代码
3.4 class
关键字的在声明方面类似let
和const
// example 1
{
class Test {
}
}
var t1 = new Test();
// throw Error: Uncaught ReferenceError: can't access lexical declaration 'Test' before initialization
// example 2
var t1=new Test();
class Test{}
// throw Error: Uncaught SyntaxError: redeclaration of let Test
// example 3
class Test{}
var Test=1;
// throw Error: Uncaught SyntaxError: redeclaration of class Test
复制代码
需要注意的点:
- 只有声明本身会被提升,赋值或者其他运算逻辑会留在原地,不会改变代码执行的顺序。
- 每个作用域都会进行提升操作。
- 在实际使用中要注意避免重复声明,特别是普通变量与函数声明混合在一起的时候,否则会引起很多危险的问题,推荐使用
let
和const
声明变量。