前言
我们知道JavaScript有预编译环节,这个环节发生在引擎编译代码前,用于做一些代码的“拼凑”工作,方便之后执行代码。预编译往往导致两个奇怪的现象:变量声明提升和函数整体提升。当然这两句话只是简单的总结,不能解决所有问题。
今天看了《你不知道的JS》上卷第四章,让我对“提升”有了更加深刻的理解。所以我将这次理解的加上以前对JS提升的认知写成此文
首先,来看两个简单的例子
首先,来看个简单的例子,函数fn会被执行吗?
fn();
function fn(){
console.log("fn");
}
再看一个复杂一点点的例子,下面代码打印的结果是啥?
function fn(){
return f;
f = 10;
function f(){
var f = 11;
}
}
console.log(typeof fn());
第一个答案是:会执行,打印出 fn ;
第二个答案是:function
如果答对了,那恭喜你,说明你对预编译有了较深的理解!如果答错了也别灰心,带着你的疑问接着往下看,这篇文章会给你解答
理解什么是提升
关于什么是提升,我还是喜欢先从字面意思理解,“提”是提起、拎起的意思,“升”是升起、向上移动的意思,所以提升大概就是向上移动的意思。 在JS中,要理解提升,我觉得先要理解一下几点:
- 提升的东西是什么,把要提升的东西提升到哪里去?
- 为什么要有提升,有什么好处?
- 提升的原理及具体过程是怎样的?
下面分别来讲:
提升的东西是什么,提升到哪
之前我写过一文关于引擎、编译器和作用域,里面提到:
引擎会在解释JavaScript代码之前首先对其进行编译,编译阶段中的一部分工作就是找到所有的声明,并用合适的作用域将它们关联起来。
这个机制也正是词法作用域的核心内容,用一句话来概括这两个问题,就是编译器会将所有的函数和变量声明提升到各自作用域的最顶端。 因此,我们回到第一个题目
fn();
function fn(){
console.log("fn");
}
由于函数会在代码执行前首先被处理,即将函数提升到所在作用域的最顶端;因此fn函数执行时,是可以访问到的,因此打印fn
提升的好处
提升的好处简单来说就是方便引擎执行JavaScript代码时,可以更方便的查找变量
JS提升的详细过程
参考了之前的学习笔记并从新整理,我将JS提升的详细过程总结为一下四步:
- 创建
AO对象;(Active Object,执行期上下文) - 找形参和变量声明,将变量名和形参名作为
AO对象的属性名,值为undefined; - 将实参和形参相统一;
- 在函数体里面找函数声明(不包括函数表达式),将函数声明的名作为
AO对象的属性名挂起,值为该函数。
这个过程需要注意一下几点
- 函数和变量都会提升,但是变量是声明提升,而函数是整体提升,并且函数优先
- 函数表达式不会被提升
- 每个作用域都会进行提升,包括全局作用域
有了以上知识的铺垫,我们来看第二道题
function fn(){
return f;
f = 10;
function f(){
var f = 11;
}
}
console.log(typeof fn());
在fn函数执行时,里面fn的作用域大致是:
(记住,为什么我说是大致?)
//fn执行前的作用域的伪代码
AO : {
f : function f(){ var f = 11}
}
在fn执行时,里面第一条就是return f,所以fn的执行结果是就f函数,因此typeof fn()的结果就是"function"
解决了两个问题,我们再来闲聊我刚刚为什么说是大致?换句话说我们来聊聊作用域的本质,作用域里头还有什么?
作用域本质是执行期上下文,一个执行期上下文定义了一个环境执行时的环境,函数每次执行时对应的执行期上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行期上下文,当函数执行完毕,它所产生的执行期上下文会被销毁。
通常执行上下文都会呈链式链接,我们叫做作用域链。作用域链在JavaScript中的具体表示形式是[[scope]],[[scope]]指的就是我们所说的作用域链,该属性仅供JavaScript引擎存取调用。在函数作用域中,不仅有上下文AO,还有this、arguments等。全局作用域会更多,但为了方便,一般在分析作用域时,那些我们不关系的就不会考虑写出来。
以上就是关于JS提升个人总结
最后在出一道题来检测你的学习成果
var a = 1;
function a(a){
console.log(a)
var a = 2
}
console.log(a)
a(3)
先给出你的答案,答案之后会在评论区出现