本文的上篇:五分钟带你秒杀JavaScript作用域
搭配学习效果更佳,欢迎前来学习指正。
关于js声明提升的疑惑
当你学到js变量提升的时候讲到:
console.log(a)
var a = 1
//在编译时,会这样:
//var a
//console.log(a)
//a=1
//因此输出的不是报错,而是undefined
- 变量声明提升到当前作用域顶部,但是赋值操作不会提升
是的,轻描淡写的一句话,仿佛是吃饭喝水一般理所应当。可是笔者疑惑:变量声明提升具体是怎么进行的?还有没有其他的声明提升?为了解决这个问题,笔者将为大家介绍一下声明提升,并深入讲讲JavaScript预编译。
声明提升有两种类型:
-
变量声明提升,这点我们已经了解过
-
函数声明提升
请看这样一段代码:
var a = 1
function fn() {
console.log(a);
var a = 2
function a() {}
}
fn()
这段代码的输出结果是function a(){},怎么回事?别着急,这涉及到JavaScript的预编译的知识点,也就是今天我们的话题。
JavaScript预编译
众所周知,V8引擎工作要经历这样几个步骤:
- 分词
- 语法分析
- 代码生成
- 代码执行之前,V8引擎会先进行编译,也就是说,预编译发生在代码执行之前
预编译分为两种:函数体内的预编译、全局预编译
函数体内的预编译
- V8引擎遇到函数声明时,创建一个执行上下文的对象
AO:{}。 - 找形参和变量声明,将形参和变量名作为属性名,添加到
AO中,其值为undefined。 - 把形参与实参统一,让实参的值传上去。
- 在函数体内找函数声明,将函数名作为
AO的属性名,函数体作为属性值,添加到AO中。
AO对象(Activation Object):活动对象。如果你不知道对象是什么,请移步至文末。
V8引擎与其预编译就像“老板与秘书的关系”。
V8引擎是执行代码的“老板”、预编译则像提前准备的“秘书”。秘书要提前准备好老板执行业务所需要的各类物品文件,老板则查看准备好的文件夹,按顺序取用里面的文件。
请看下面一段代码
function fn(a) {
console.log(a);
var a = 123
console.log(a);
function a() {}
var b = function() {}
console.log(b);
function c() {}
var c = a
console.log(c);
}
fn(1)
一下懵了?别急,我们一步步来。
1. 看见函数声明,我们想到来创建一个AO对象。
AO{
}
2. 紧接着,我们找到形参与变量声明,记录下来值为undefined
AO = {
a: undefined,
b: undefined,
c: undefined
}
3. 别忘了将形参与实参统一,即 a=1
AO = {
a: undefined 1, //为方便理解这样写,实际应当是覆盖为1
b: undefined,
c: undefined
}
4. 找函数声明,函数体覆盖
AO = {
a: undifined 1 function a() {},
b: undefined,
c: undifined function c() {}
}
5. 最后开始执行代码
function fn(a) {
console.log(a); //输出function a() {}
var a = 123
console.log(a); //输出123
function a() {}
var b = function() {}
console.log(b); //输出functionb() {}
function c() {}
var c = a
console.log(c);//123
}
fn(1)
恭喜你,学会了函数体内的预编译。不仅函数体内有预编译,在全局下也会进行预编译,下面我们来聊聊全局预编译。
全局预编译
1. 创建一个全局的执行上下文(对象) GO:{}
2. 找全局变量声明,将全局变量名作为属性名,添加到 GO 中,值为 undefined
3. 找全局函数声明,将函数名作为GO的属性名,函数体作为属性值
纸上得来终觉浅,知道了还不来段代码?请看:
var a
var b = 2
function a () {
console.log(a);
var c = 3
var a = b
function c() {}
console.log(c);
}
a()
console.log(a);
1. 还是来创建一个对象,全局的执行对象GO:{}在页面加载时创建。
GO = {
};
2.然后找全局变量声明,值为undefined
GO = {
a: undefined,
b: undefined
};
3. 找全局函数声明
GO = {
a: undefined function a(){},
b: undefined
};
4. 执行,要执行a()了。函数调用a(),创建AO。AO创建过程还记得吗?一口气来一遍:
// 创建空 AO
AO = {};
// 找形参和变量声明
AO = {
c: undefined, // var c
a: undefined // var a
};
// 找函数声明
AO = {
c: undefined function c() {},
a: undefined
};
5. 执行函数内部代码,再返回全局,输出详情如下
undifined
3
[Function:a]
GO对象(Global Object):全局对象。
很好,你现在已经掌握了全局预编译的必要知识。不难发现:全局预编译发生在函数体预编译之前。
至此,JavaScript的预编译已经讲完了,再看开头那段代码,疑惑已经迎刃而解。
总结
- 预编译发生在执行之前,全局预编译发生在函数体预编译之前
- 变量提升只提升声明,不提升赋值
- 函数声明完整提升,优先级高于变量
- AO 管理函数内部,GO管理全局
补充:什么是对象?
在JavaScript中,对象是一个键值对的集合,可以用来描述一个事务的属性和行为。例如:
var cat = {
name: "乐乐猫",
age: 4,
isFat: true,
hobbies: ["乐乐", "吃猫粮"],
};
这里我创建了一个对象,cat。在里面我定义了许多属性,有name、age、isFat、hobbies,并为这些属性赋好了值。
对象的基本操作:增、删、改、查,下面我将为你逐个演示如何操作。
增加一个属性,这是一只公猫。
cat.gender = "公"
改变一个属性,它成长了一岁。
cat.age = 5;
删除一个属性,它减肥了。
delete cat.isFat;
现在输出这些属性
console.log(cat)
得到一只公猫,名为乐乐猫,5岁,喜欢乐乐、吃猫粮。
{ name: '乐乐猫',
age: 5,
hobbies: [ '乐乐', '吃猫粮' ],
gender: '公' }
附乐乐猫图,希望读者天天都乐乐。如果本文对你有帮助,欢迎点赞、收藏、关注。你的支持是我创作的动力!🌹🌹