image
image

js中变量的声明与函数的声明提升

说到javascript变量的声明使用还真的和其他一下语言(java,c++)有一定的区别。主要因为js在执行过程中存在预编译过程,这个过程很独特,也很坑,接下来我们来看一下在js中变量的声明,赋值,函数的声明,赋值以及使用var,let,const,进行变量声明有什么差异。

变量的声明提升

1
2
3
console.log(a); // undefined

var a = 'hello world'

js引擎将这段代码预编译成:

1
2
3
4
5
var a;

console.log(a);

a = 'hello world'

我们在看一下这段代码

1
2
3
4
5
6
7
var a = 3;

(function(){
console.log(a); // undefined

var a = 5;
}())

js引擎将这段代码预编译成:

1
2
3
4
5
6
7
8
9
var a = 3;

(function(){
var a

console.log(a); // undefined

a = 5;
}())

通过这两段代码我们不难发现,js引擎在预编译js时候,将变量的(var)声明提到了该变量作用域的最顶端执行,注意:这里提到顶端仅仅是声明(var ‘变量名称’)这个过程,赋值的过程并没有被提升。让我们来看一下let:

1
2
3
console.log(a); // ReferenceError: a is not defined

let a = 'hello world'
1
2
3
4
5
6
7
8
let a = 3;

(function(){

console.log(a); // ReferenceError: a is not defined

let a = 5;
}())

可见let声明变量并不会得到提升。

1
2
3
4
5
var a = 3;

var a;

console.log(a) // 3

重复声明不会报错但没有用当一次声明处理。

1
2
3
4
5
let a = 3;

let a; // SyntaxError: Identifier 'a' has already been declared

console.log(a) // 3

使用let 重复声明会报错

1
2
3
4
5
6
7
8
9
10
11
12
13
var a = 3;

(function(a){

console.log(a); // 3

var a = 5

console.log(a);// 5

}(a))

console.log(a) // 3

控制台输出的是3,5,3。首先基本数据类型穿的参数遵循值的拷贝,所以外面的a值始终不变,函数形势参数相当于在函数内部声明一次,上面这段代码在预编译阶段被处理成这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var a = 3;

(function(a){
var a = a;

var a; // var重复声明无效

console.log(a);

a = 5

console.log(a);

}(a))

console.log(a)

小伙伴们一定想看以下用let会是什么样:

1
2
3
4
5
6
7
8
9
10
11
12
13
var a = 3;

(function(a){

console.log(a); // 3

let a = 5 // Identifier 'a' has already been declared

console.log(a);// 5

}(a))

console.log(a) // 3

不出我们所料,重复定义直接报错,这这更佳证实了函数参数咱函数内部重新定义的理论。const 和let一样不会提升变量的声明,只是const声明变量修改就报错。

函数的声明提升

定义函数有多种方式我们来分析最常用的两种:

  • 函数是声明(用function)

    1
    function a() {}
  • 字面量形势声明

    1
    var a = function(){}

我们来看一下这段代码:

1
2
3
4
5
6
7
8
9
a();// 1

function a(){console.log(1)}

a();// 1

var a = function (){console.log(2)}

a();// 2

这时候我们把函数形势声明注视掉:

1
2
3
4
5
console.log(a);// undefined

var a = function (){console.log(2)}

a();// 2

通过对比我们发现不注视之前我们在最上面调用a方法输出1,注视之后我们在最上面发现原本是a函数现在是undefined,我们来看一下第一段代码预编译做了些什么。

1
2
3
4
5
6
7
8
9
10
11
var a;

function a(){
console.log(1);
}

a();// 1

a = function (){console.log(2)}

a();// 2

其实预编译时候是这样的,首先是变量的声明提升,接下来是函数式声明与赋值,(函数式声明赋值是一块的),然后是变量的赋值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var a = 2;

;(function(){
console.log(a);// function a(){ … }

var a = 5;

function a(){}

console.log(a); // 5

var a = function(){}

console.log(a);// function(){}
}());
console.log(a);// 2

在上面这段代码中函数内部a的值是这样变化的:

1
2
3
4
5
6
7
8
9
10
11
var a ;

a === undefined

function a(){};

a === function a(){}

a === 5

a === function(){}

总结

在js引擎预编译过程中,变量和函数的声明提升过程是这样的,首先是变量声明(var = 变量名),然后是函数式声明赋值过程,函数内部接受参数相当于var这个变量=传入的参数,最后才是根据程序上下文该赋值的赋值该输出的输出。这个过程中只有(var,function)声明过程被提升且由function 声明的函数声明赋值一块进行,var声明过程在function之前。