变量提升和函数声明提升
本文最后更新于:1 年前
变量提升和函数声明提升
关于 Javascript 中的变量提升(variable hoisting)可能在前端界已经耳熟能详了,可能你会觉得设计初衷是什么呢,或者这里有出于什么考虑以及解决了什么问题呢?因为这块“特性”成为了前端人必须了解并绕过的坑,也可能成为了不少前端人的困扰。
其实根据 Brendan Eich 的说法:
大概意思就是,变量提升是函数提升的意外结果。(手动狗头)
题目
下面开始先给几道有趣的题:
1、
if (!(Ben in window)) {
console.log(1);
var Ben = 'handsome';
}
console.log(Ben in window);
console.log(Ben);
2、
var a;
(function () {
a = 1;
console.log(a);
if (false) {
var a = 2;
console.log(a);
}
console.log(a);
})();
console.log(a);
3、
var a = 1;
{
console.log(a);
var a = 2;
console.log(a);
}
4、
var b = 1;
(function () {
console.log(b);
var b = 2;
console.log(b);
})();
5、
{
a = 123;
function a() {}
}
console.log(a);
6、
{
function a() {}
a = 123;
}
console.log(a);
7、
{
a = 123;
function a() {}
a = 456;
function a() {}
}
console.log(a);
变量提升
JavaScript 代码从开始经过分词(tokenize)、预解析(preparse)、解析(parse),而变量声明在 prepare 时期就完成了。
可以这么理解,一个变量有着声明
、初始化
、赋值
的过程,而声明
会被预解析时就完成并且进入到各自的执行环境中,而 var 在此时将初始化
也完成了(undefined),函数声明则是连赋值
也完成了(函数内存地址指向)。
- let、const 提升了
声明
,并将变量放入词法环境中,这时候值为未初始化(uninitialized),所以不允许使用(暂时性死区) - var 提升了
声明
和初始化
,并将变量放入变量环境中,这时候值为 undefined - 函数定义提升了
声明
、初始化
和赋值
,将函数创建进入堆,并将值改为函数内存地址。
(可参考let、const 是否有变量提升?))
if (false) {
var a;
}
console.log('a' in window); // true
这块代码内,window 对象内并没有 a 变量,而且条件为 false 也并不会执行内部代码,但是这段打印确实 true,这里就说明了在代码未解析执行时就已经完成了声明。
题目解析
题目 1 解析
因为 var 声明会在 preparse 时期执行,先于执行语句解析声明变量,且 var 声明作用于函数执行的作用域,所以会穿透 for 和 if 语句。题目 2 解析
因为 var 声明会在 preparse 时期执行,先于执行语句解析声明变量,且 var 声明作用于函数执行的作用域,所以会穿透 for 和 if 语句。而 function 是有函数作用域的,所以 iife 内的 a 变量为私有,所以内部都会打印 1,函数外的 a 因未被赋值所以 undefined。题目 3 解析
因为 var 不进入块语句中的词法环境,并且会提升,而且对于 var 来说没块级作用域。题目 4 解析
这里就是因为函数作用域的问题,但是为什么第一个打印还是 undefined,因为 javascript 寻找变量顺序是当前作用域一直向上寻找,而这里因为变量提升所以在 iife 函数中声明了私有变量,但是未赋值,所以为 undefined。
函数提升
函数定义说的是 function 定义的函数(function func(){}
),而不是匿名函数(var func = function(){}
)赋值。而 function 定义不在块语句中({}
)的话,它会在预解析时声明、初始化、赋值。
而 function 定义如果在块语句中的话,包括 if/for/try/with 等,在 ES3 标准中,预解析,都会将 function 的声明和初始化和赋值都会提升到块语句外层;而 ES6 标准后,块语句中 function 的提升只是声明和初始化,并不赋值。而为了兼容旧标准,块语句中的 function 会将执行时,将当前块语句中的变量环境
映射一份到外层上下文中,使得外层拥有了 function 语句这一行以上的所有变量的赋值,而后面的所有变量都不会被提升,成为了当前块语句的私有变量。
而上面的题目 5、6、7 就是典型的例子,其实如果只要劫持一下 window 的属性,就会知道是否赋值了(因为 var 提升会将变量定义到 window 内,而 let、const 并不会,即使是全局定义的 let、const 也不进入到 window 对象内的属性)
Object.defineProperty(window, 'a', {
set(e) {
console.log('setA', e);
},
});
{
a = 123;
function a() {}
a = 456;
}
// setA 123
{
a = 123;
function a() {}
a = 456;
function a() {}
}
// setA 123
// setA 456
{
function a() {}
a = 123;
function a() {}
a = 456;
function a() {}
a = 789;
}
// setA f a(){}
// setA 123
// setA 456
大概解析如下:
总结
这里能看出 var 和 function 因为历史原因或者新旧标准的原因导致了多个奇怪的问题,所以在平时开发工作中,也应该尽量使用 let 和 const。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!