由于本蒟蒻当初学习JS的时候只是了解了一下基础的,之后直接干Vue去了,对JS挺多知识点都不了解,趁着暑假有时间补补知识点(

概念

变量提升是当栈内存作用域形成时,JS代码执行前,浏览器会将带有var, function关键字的变量提前进行声明 (值默认就是 undefined),定义 (就是赋值操作),这种预先处理的机制就叫做变量提升机制也叫预定义。

在变量提升阶段:带 var 的只声明还没有被定义,带 function 的已经声明和定义。

示例1

1
2
3
4
5
6
7
console.log(name)			// 输出undefined
var name = '煎bingo子'

func() // 输出Function Scuccess!
function func(){
console.log('Function Scuccess!')
}

值得注意的是:变量提升只发生在当前作用域。比如:在页面开始加载时,只有window全局作用域发生变量提升,这时候的函数中存储的都是代码字符串,并不会把函数里面的局部变量提升到全局作用域。

示例2

1
2
3
4
out()
var out = function() {
console.log('out function')
}

对于上述代码浏览器会报错,因为out作为匿名函数,其变量提升后初值为undefined,因此会报错TypeError: out is not a function。

不同作用域下的var

  1. 对于window全局作用域下的var声明的变量,会将其作为属性添加到window对象中去(在全局作用域中若某一变量没有用var声明,和此种情况相同效果)
  2. 对于私有作用域(函数内部)中仅会对带var声明的变量进行变量提升,其变量声明会提升到其函数的头部。对于不带var的变量在使用时会向上级作用域进行查找,直到找到window对象为止,即JS的作用域链的执行机制

示例3

1
2
3
4
5
6
7
8
9
10
11
12
13
console.log(a, b)

var a = 0, b = 0

function func() {
console.log(a, b)

var a = b = 1
console.log(a, b)
}

func()
console.log(a, b)

对于上述代码在浏览器控制台输出如下图所示,同时为了验证全局作用域下的声明的变量是作为属性添加到window中还输出了window对象:

示例4

1
2
3
4
5
6
7
8
9
function func() {
console.log(a);

a = 1;
b = 2;
console.log(a, b);
}

func()

对于上述代码,浏览器会抛出ReferenceError: a is not defined的错误,因为func作用域中并没有var声明的变量,也就没有所谓的变量提升,在打印a的时候,会触发JS的作用域链的执行机制,但是一直查找到window对象都没有对应的a值,因此抛出错误。

条件判断中的变量提升

  1. 对于条件判断语句中的var声明,无论条件是否成立均会进行变量提升,但是赋值操作仍然是需要对应条件成立才可以进行。
1
2
3
4
5
6
console.log(b)  // undefinded

if (false) {
var b = 3
}

  1. 对于条件判断中判断表达式部分的内容不会进行变量提升,同时条件作用域内部如果有function声明,则变量提升时和作为匿名函数赋值给一个变量情况相同,只有在条件成立时才会进行函数定义(并且是先定义再执行内部的其他语句,可以参考示例6),不成立则为undefinded

示例5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var a = 'test-'
if (function f() { }) {
console.log(typeof f)

a = a + typeof f
}

console.log(a);


if(false){
function out() {
console.log('out function')
}
}
out()

上述代码的输出如下:

1
2
3
undefined
test-undefined
Uncaught TypeError: out is not a function

示例6

1
2
3
4
5
6
7
8
if(true){
out()
function out(){
console.log('Out Function!');
}
}

out()

上述代码输出如下:

1
2
Out Function!
Out Function!

变量提升的重名问题

  1. 如果对于某一个变量名既有var声明又由function声明,function优先,即该变量会作为function进行变量提升。
1
2
3
4
console.log(typeof a)   // function
var a = 10
function a() { }
console.log(typeof a) // number
  1. 变量重名在变量提升阶段如果有多个会按照顺序重复定义

示例7

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
console.log('1', fn())
function fn() {
console.log(1)
}

console.log('2', fn())
function fn() {
console.log(2)
}

console.log('3', fn())
var fn = 10

console.log('4', fn())
function fn() {
console.log(3)
}

上述代码的输出如下图所示:

函数形参的变量提升

  1. 函数的形参也会进行一次变量提升。

  2. 形参阶段声明的变量,即使函数内部还有同名变量也不会再次声明。

示例8

1
2
3
4
5
6
7
var a = 1
function func(a) {
console.log(a)
var a
console.log(a)
}
func(a) // 1 1

在函数的形参阶段,会先声明var a = undefined,a = 1,因此第一次打印1,后续出现同名变量a不会再次声明,因此打印还是1。

示例9

1
2
3
4
5
6
7
8
9
10
11
var a = 'test1';
(function(f){
console.log(a)
var a = f || 'test2'
console.log(a)
})(a)
console.log(a)

// undefined
// test1
// test1

在立即执行的函数表达式(IIFE)的形参阶段,先声明了var f = undefined,f = a(这里的a是全局的),并且变量提升会声明var a = undefined(这里的a是函数私有域的)。因此第一次打印为undefined,后面私有域的a被赋值为f即全局的a的值test1,因此第二次打印结果为test1,最后打印全局的a的值,还是test1。

IIFE

  1. IIFE匿名执行函数和非匿名自执行函数在全局环境下不具备变量提升的机制。
1
2
3
4
5
var a = 10;
console.log(c);
(function c(){
})()
// ReferenceError: c is not defined
  1. 匿名自执行函数在自己的作用域内存在正常的变量提升。(示例9)

  2. 非匿名自执行函数的函数名在自己的作用域内变量提升,且修改函数名的值无效。

1
2
3
4
5
6
7
8
9
10
var a = 10;
(function a(){
console.log(typeof a)
a = 20
console.log(typeof a)
})()
console.log(typeof a);
// function
// function
// number

参考链接:彻底解决 JS 变量提升| 一题一图,超详细包教包会😉 - 掘金 (juejin.cn)