当我们编写JavaScript代码时,有一些概念是至关重要的,比如"声明提升"和"预编译"。这些概念对于理解代码执行过程和作用域内变量和函数的行为至关重要。
1. 声明提升
1.1 var 声明的变量会存在声明提升
首先,让我们来了解一下声明提升。在JavaScript中,变量和函数声明会发生提升。这意味着在代码执行之前,JavaScript引擎会将它们移动到当前作用域的顶部,但并不是实际的赋值,而是赋予了一个默认值undefined
。这样的特性使得我们可以在声明前使用变量或者调用函数,因为在预编译阶段,它们已经被提升到了作用域的顶部。
ini
console.log(a); // 输出:undefined
var a = 5;
上面的代码中,尽管a
在console.log
语句前面被声明,但由于声明提升的存在,这段代码在预处理阶段会被处理成如下形式:
ini
var a;
console.log(a); // 输出:undefined
a = 5;
1.2 函数声明整体提升
在 JavaScript 中,函数声明整体提升是指在预编译阶段,函数声明会被提升到当前作用域的顶部,这意味着可以在函数声明之前调用该函数。这种行为被称为"函数声明提升"或者"整体提升"。
让我们通过代码示例来理解函数声明整体提升:
scss
myfunction(); // 输出 "Hello"
function myfunction() {
console.log("Hello");
}
在上面的代码中,我们在函数声明之前就调用了 myFunction
,并且代码能够正常执行并输出 "Hello, World"。这是因为在预编译阶段,函数声明会被提升到当前作用域的顶部,所以在调用函数之前,函数已经被存储在内存中。所以上面代码就等同于下面这段代码:
scss
function myfunction() {
console.log("Hello");
}
myfunction(); // 输出 "Hello"
注意: 在 JavaScript 中,函数声明和使用 var 关键字进行的变量声明都会发生提升(hoisting),但它们之间存在优先级的差异。
函数声明的提升优先级高于变量声明。这意味着在同一个作用域内,函数声明会首先被提升到当前作用域的顶部,然后才是变量声明。让我们通过代码示例来说明这一点:
css
var a = 1
function foo(a) {
var a = 2
function a() {}
var b = a
console.log(a);//输出结果为"2"
}
foo()
这是因为在 JavaScript 中,函数声明会被提升到当前作用域的顶部,而变量声明也会被提升,但是它们的赋值不会被提升,变量声明会被提升,但是它们的赋值不会被提升。这就是为什么函数声明的提升优先级高于变量声明的原因,等同于下面的代码:
css
var = 1
function foo(a) {
funcation a() {}
var a
a = 2
var b = a
console.log(a);//输出结果为'2'
}
foo()
2. 预编译
2.1 预编译发生在函数体内时
- 创建函数的AO对象 (Action Object)
- 找形参和变量声明,将形参和变量声明作为AO的属性名,值赋予underfined
- 将形参和实参统一
- 在函数体内找函数声明,将函数名作为AO对象的属性名,值赋予函数体
如何了解上面的话呢,看下面代码:
function
console.log(a);//
var a = 123
console.log(a); //
function a() {}
console.log(a); //
var b = function() {}
console.log(b); //
function d() {}
var d = a
console.log(d); //
}
fu(1)
相信有同学看到这里已经被绕迷糊了,不过没关系,这时候就可以结合前面那四句话来帮助你理解了。
1、首先我们得创建一个AO对象:
AO:{
}
2、找形参和变量声明,将形参和变量声明作为AO的属性名,值赋予underfined
javascript
AO:{
a:undefined
b:undefined
c:undefined
}
3、将实参与形参统一
这里的实参是 fu(1)中的 1,所以我们可以得到:
javascript
AO:{
a:undefined , 1
b:undefined
c:undefined
}
4、在函数体内找函数声明,将函数名作为AO对象的属性名,值赋予函数体
AO:{
a:undefined , 1, function a() {}
b:undefined ,
c:undefined , function d() {}
}
到这一步我们已经找出全部的函数声明了,要注意的是:var b = function() {}
并不是函数声明,而是函数表达式!!!
5、以上四步就是编译结束,现在我们来看执行 执行还是从上自下开始:
1、当执行到第一行 console.log(a)
时,我们在刚刚创建的AO对象里面找值,所以输出的是 function a( ) {}
。
2、执行到第二行var a = 123
,发现后半句是一个赋值语句,所以找到AO对象里面的a修改为:
AO:{
a:undefined , 1, function a() {} , 123
b:undefined ,
c:undefined , function d() {}
}
3、 执行到第四行时,编译器发现这是一个函数声明,只有函数被调用时才会执行,往下执行到第五行时,在AO对象找值,所以输出的还是123
。
4、 执行到第六行时,这是一个对b
进行赋值的语句,所以在AO对象进行修改为function() {}
:
AO:{
a:undefined , 1, function a() {} , 123
b:undefined , function() {}
c:undefined , function d() {}
}
5、 执行到第七行时,需要输出b
的值,所以在AO对象找到当前b
的值,输出function() {}
。
6、 第八行与第四行同理。
7、 第九行var d = a
, 后面也是一个赋值语句,所以找到AO对象中d
的值修改为123
:
AO:{
a:undefined , 1, function a() {} , 123
b:undefined , function() {}
c:undefined , function d() {} ,123
}
自此执行完成!最后我们总体来看看代码:
function
console.log(a);//输出'function a() {}'
var a = 123
console.log(a); //输出'123'
function a() {}
console.log(a); //输出'123'
var b = function() {}
console.log(b); //输出'function() {}'
function d() {}
var d = a
console.log(d); //输出'123'
}
fu(1)
2.2 预编译发生在全局
- 创建GO对象 (Global Object)
- 找形参和变量声明,将变量声明作为GO的属性名,值赋予underfined
- 在全局找函数声明,将函数名作为GO对象的属性名,值赋予函数体
和预编译发生在函数体不同的是执行步骤少了 形参与实参的统一 这一步骤,下面我们进入实战:
javascript
global = 100;
function fn() {
console.log(global); // 输出 undefined
global = 200;
console.log(global); // 输出 200
var global = 300;
}
fn();
var global;
首先,在预编译阶段,会创建全局对象(GO)。然后会寻找形参和变量声明,在这段代码中,发现了全局变量 global
的声明,因此将其作为全局对象的属性名,并赋予初始值 undefined。
接着,在全局范围内找到了函数声明 fn(),所以将函数名 fn 作为全局对象的属性名,并将函数体赋值给这个属性。
csharp
GO:{
global:undefined,100
fu:fuction fn() {},
}
在函数 fn() 被调用时,会创建一个新的执行上下文,也就是活动对象(AO)。在该活动对象中,函数参数和变量的声明同样会被提升。
在函数 fn() 的内部,首先输出了变量 global 的值,由于预编译阶段已经将其声明并初始化为 undefined,所以输出结果为 undefined。接着将全局变量 global 的值修改为 200,并输出该值,结果为 200。
css
AO:{
global:undefined,200
}
在函数体中,出现了一个新的变量声明 var global = 300
,这会在当前作用域内声明一个新的局部变量 global
,并将其值设置为 300。这个声明会遮蔽全局作用域中的同名变量。
最后,全局范围内的最后一行代码是对全局变量 global
的声明,但由于之前已经存在了全局变量globa
,因此这条语句不会改变全局变量的值。
总的来说,预编译阶段主要是对变量和函数声明进行提升,使得我们能够更好地理解 JavaScript 代码的执行顺序和作用域。
以上就是今天的全部内容了,希望对看到这里的小伙伴有所帮助~