声明提前,函数声明提前,好吧,老生常谈的问题了。正好,前些天在掘金看到一道关于声明提前的笔试题,那么这里就以这道题来作为本文的引子吧,代码如下:
console.log(a)//? a();//? var a =3; function a(){ console.log(10); } console.log(a);//? a = 6; a();//?
四处分别输出什么?为什么?读完本文,最少也能在你心中激起一丝波澜了。
壹 ❀ 什么是声明提前
先来了解一个函数作用域的概念:变量在声明它们的函数体以及这个函数体嵌套的任意函数体内始终可见。说直白点,在声明一个变量的前后,你都可以直接使用它,并不会报错。举个例子:
(function(){ console.log(a);//undefined var a ="小钻风"; console.log(a);//小钻风 }())
前面已经说了,变量在声明它们的函数体内始终可见,尽管第一个console输出在声明a之前,但它依旧能输出,并不会报错,那是因为声明统一提前,赋值原地不变。上面代码等同于:
(function(){ var a; console.log(a);//声明了但未赋值,所以输出undefined; a ="小钻风"; console.log(a);//上一步赋值了,所以输出小钻风 }())
声明提前了,只是没有赋值,赋值仍保留远处不变,所以说变量a在function每一处都是可用的,就是这么个怪逻辑。
贰 ❀ 什么是函数声明提前(函数体提前)
函数声明提前的原理与变量声明提前情况类似,需要提醒的是,只有函数声明格式的函数才会存在函数声明提前,比如函数表达式,构造函数,都不存在函数声明提前。
函数创建的三种写法:
a.函数声明:function fun(a){console.log(a)};(只有这个家伙存在函数声明提前)
b.函数表达式:var fun = function(a){console.log(a)};
c.构造函数:var fun = new Function("a",console.log(a));
直接上个例子:
num()//1 console.log(num)//函数本身 function num(){ console.log(1); } num();//1 console.log(num)//函数本身
有疑问的应该就是函数之前的函数调用与console了,前面说过了,函数声明的情况与变量声明类似,你可以理解为,在同一作用域内函数声明后,此函数会跑到本作用域的最前面。上面的代码等同于:
function num(){ console.log(1); } num()//1 console.log(num)//函数本身 num();//1 console.log(num)//函数本身
那么再来看看函数表达式是否会函数体提前:
num()//报错 console.log(num)//undefined var num = function (){ console.log(1); } num();//1 console.log(num)//函数本身
第一个num()就会报错,后面三个是看不到输出的,这里是假设不受num()报错影响本应输出的情况。为什么会这样呢,还记得前面变量声明提前的原理吗,这里只是将后面的普通赋值换成了函数,所以以上代码等同于:
var num; num()//报错,这时候都没有函数声明 console.log(num)//undefined,因为已经声明了num num = function (){ console.log(1); } num();//1,有函数了啊,可以调用了 console.log(num)//函数本身,有函数了。
声明提前,赋值不变,前面只声明了num,并不存在函数,又怎么能调用num函数呢,所以第一个就报错了,这里总该明白了吧。
叁 ❀ 变量声明提前,函数声明提前顺序
这里就有个问题了,函数声明提前,变量声明也提前,到底谁会更提的更前?假设两者都用的同一命名声明,到底最后会输出啥,我们来看个例子:
console.log(a);
var a = "孙悟空";
function a(){ console.log("小钻风"); }
照理说,函数先提前,然后变量a在提前,a未赋值,覆盖了上面声明的函数a,应该输出undefined,但为什么输出的还是函数本体?
引入一个概念,你不知道的JavaScript(上卷)一书的第40页中写到:函数会首先被提升,然后才是变量。也就是说,同一作用域下提前,函数会在更前面。以上代码等同于:
function a(){ console.log("小钻风"); } var a;//由于上面函数已声明a,相同的变量名声明会被直接忽略
console.log(a);//输出函数本体
a = "孙悟空";
为啥函数提前之后又var a;了怎么不输出undefined,因为这里只是再次声明a,并未修改现有a的值,做个简单测试就可以了:
var a=1; var a; console.log(a);//1
变量a已经声明过了,而且也赋值了,后面再次声明只是声明并未修改值,这种声明方式会被直接忽略,所以还是输出1.
这里讨论了变量声明提前,函数声明提前以及提前先后顺序,那么我们再回头,改写文章开头的笔试题,那么它等同于:
正确的修改:
function a(){ console.log(10); } var a;//再次声明a,并未修改a的值,忽略此处声明 console.log(a)//输出函数本体 a();//函数声明提前,可调用,输出10 a =3;//这里修改值了,a=3,函数已不存在 console.log(a);//输出3 a = 6;//再次修改为6,函数已不存在 a();//a已经为6,没有函数所以没法调用,直接报错
错误的修改:
var a;//再次声明a,并未修改a的值 function a(){ console.log(10); } console.log(a)//输出函数本体 a();//函数声明提前,调用输出10 a =3;//这里修改值了,a=3,不在是函数了 console.log(a);//输出3 a = 6;//再次修改为6 a();//a已经为6,不存在函数了,所以没法调用,报错
可能很多人的思路是,var a=3在前,函数声明在后,var a先提前,然后函数再次提前覆盖了var a;你们也能发现上面两种改写结果都是一样的,因为我在上面的橙色解释中说了,但其实它们的提前是有固定的先后顺序的,这里希望大家能清楚。
那么本文的介绍就这里了,作为自己的笔记,也希望能对大家有所帮助。文章思路借鉴了一下博文以及问题,挺厉害的文章,大家也可以阅读看看。
本文只是解释了什么是变量提升,准确来说变量提升是执行上下文搞得鬼,代码在执行前都会做一番准备工作,也就是创建执行上下文,如果大家对于变量提升是何原理有兴趣,可以读读博主 一篇文章看懂JS执行上下文 这篇文章。对于你加深理解一定有所帮助。
肆 ❀ 参考
csdn中js中是函数声明先提升还是变量先提升
那么就写到这里了。