声明提前,函数声明提前,好吧,老生常谈的问题了。正好,前些天在掘金看到一道关于声明提前的笔试题,那么这里就以这道题来作为本文的引子吧,代码如下:

  1. console.log(a)//?
  2. a();//?
  3. var a =;
  4. function a(){
  5. console.log();
  6. }
  7. console.log(a);//?
  8. a = ;
  9. a();//?

四处分别输出什么?为什么?读完本文,最少也能在你心中激起一丝波澜了。

 壹 ❀ 什么是声明提前

先来了解一个函数作用域的概念:变量在声明它们的函数体以及这个函数体嵌套的任意函数体内始终可见。说直白点,在声明一个变量的前后,你都可以直接使用它,并不会报错。举个例子:

  1. (function(){
  2. console.log(a);//undefined
  3. var a ="小钻风";
  4. console.log(a);//小钻风
  5. }())

前面已经说了,变量在声明它们的函数体内始终可见,尽管第一个console输出在声明a之前,但它依旧能输出,并不会报错,那是因为声明统一提前,赋值原地不变。上面代码等同于:

  1. (function(){
  2. var a;
  3. console.log(a);//声明了但未赋值,所以输出undefined;
  4. a ="小钻风";
  5. console.log(a);//上一步赋值了,所以输出小钻风
  6. }())

声明提前了,只是没有赋值,赋值仍保留远处不变,所以说变量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));

直接上个例子:

  1. num()//
  2. console.log(num)//函数本身
  3. function num(){
  4. console.log();
  5. }
  6. num();//
  7. console.log(num)//函数本身

有疑问的应该就是函数之前的函数调用与console了,前面说过了,函数声明的情况与变量声明类似,你可以理解为,在同一作用域内函数声明后,此函数会跑到本作用域的最前面。上面的代码等同于:

  1. function num(){
  2. console.log();
  3. }
  4. num()//
  5. console.log(num)//函数本身
  6.  
  7. num();//
  8. console.log(num)//函数本身

那么再来看看函数表达式是否会函数体提前:

  1. num()//报错
  2. console.log(num)//undefined
  3. var num = function (){
  4. console.log();
  5. }
  6. num();//
  7. console.log(num)//函数本身

第一个num()就会报错,后面三个是看不到输出的,这里是假设不受num()报错影响本应输出的情况。为什么会这样呢,还记得前面变量声明提前的原理吗,这里只是将后面的普通赋值换成了函数,所以以上代码等同于:

  1. var num;
  2. num()//报错,这时候都没有函数声明
  3. console.log(num)//undefined,因为已经声明了num
  4. num = function (){
  5. console.log();
  6. }
  7. num();//1,有函数了啊,可以调用了
  8. console.log(num)//函数本身,有函数了。

声明提前,赋值不变,前面只声明了num,并不存在函数,又怎么能调用num函数呢,所以第一个就报错了,这里总该明白了吧。

 叁 ❀ 变量声明提前,函数声明提前顺序

这里就有个问题了,函数声明提前,变量声明也提前,到底谁会更提的更前?假设两者都用的同一命名声明,到底最后会输出啥,我们来看个例子:

  1. console.log(a);
  1. var a = "孙悟空";
  1. function a(){ console.log("小钻风"); }

照理说,函数先提前,然后变量a在提前,a未赋值,覆盖了上面声明的函数a,应该输出undefined,但为什么输出的还是函数本体?

引入一个概念,你不知道的JavaScript(上卷)一书的第40页中写到:函数会首先被提升,然后才是变量。也就是说,同一作用域下提前,函数会在更前面。以上代码等同于:

  1. function a(){
  2. console.log("小钻风");
  3. }
  4. var a;//由于上面函数已声明a,相同的变量名声明会被直接忽略
  1. console.log(a);//输出函数本体
  1. a = "孙悟空";

为啥函数提前之后又var a;了怎么不输出undefined,因为这里只是再次声明a,并未修改现有a的值,做个简单测试就可以了:

  1. var a=;
  2. var a;
  3. console.log(a);//

变量a已经声明过了,而且也赋值了,后面再次声明只是声明并未修改值,这种声明方式会被直接忽略,所以还是输出1.

这里讨论了变量声明提前,函数声明提前以及提前先后顺序,那么我们再回头,改写文章开头的笔试题,那么它等同于:

正确的修改:

  1. function a(){
  2. console.log();
  3. }
  4. var a;//再次声明a,并未修改a的值,忽略此处声明
  5. console.log(a)//输出函数本体
  6. a();//函数声明提前,可调用,输出10
  7. a =;//这里修改值了,a=3,函数已不存在
  8. console.log(a);//输出3
  9. a = ;//再次修改为6,函数已不存在
  10. a();//a已经为6,没有函数所以没法调用,直接报错

错误的修改:

  1. var a;//再次声明a,并未修改a的值
  2. function a(){
  3. console.log();
  4. }
  5. console.log(a)//输出函数本体
  6. a();//函数声明提前,调用输出10
  7. a =;//这里修改值了,a=3,不在是函数了
  8. console.log(a);//输出3
  9. a = ;//再次修改为6
  10. a();//a已经为6,不存在函数了,所以没法调用,报错

可能很多人的思路是,var a=3在前,函数声明在后,var a先提前,然后函数再次提前覆盖了var a;你们也能发现上面两种改写结果都是一样的,因为我在上面的橙色解释中说了,但其实它们的提前是有固定的先后顺序的,这里希望大家能清楚。

那么本文的介绍就这里了,作为自己的笔记,也希望能对大家有所帮助。文章思路借鉴了一下博文以及问题,挺厉害的文章,大家也可以阅读看看。

本文只是解释了什么是变量提升,准确来说变量提升是执行上下文搞得鬼,代码在执行前都会做一番准备工作,也就是创建执行上下文,如果大家对于变量提升是何原理有兴趣,可以读读博主 一篇文章看懂JS执行上下文 这篇文章。对于你加深理解一定有所帮助。

 肆 ❀ 参考

myvin函数声明的声明提前;

csdn中js中是函数声明先提升还是变量先提升

欢喜颖宝宝JS的作用域和声明提前

那么就写到这里了。

【JS点滴】声明提前,变量声明提前,函数声明提前,声明提前的先后顺序的更多相关文章

  1. 原型模式故事链(4)--JS执行上下文、变量提升、函数声明

    上一章:JS的数据类型 传送门:https://segmentfault.com/a/11... 好!话不多少,我们就开始吧.对变量提升和函数声明的理解,能让你更清楚容易的理解,为什么你的程序报错了~ ...

  2. 变量声明置顶规则、函数声明及函数表达式和函数的arguments属性初始化

    一.变量声明和变量赋值: if (!("a" in window)) { ; } alert(a);//a为? 你可能认为alert出来的结果是1,然后实际结果是“undefine ...

  3. 使用var声明的变量 和 直接赋值并未声明的变量的区别

    在看JS高级程序设计时忽然想到这个问题,众所周知,直接赋值一个变量而为声明,会产生一个全局变量(或者说是全局对象的属性),但用var声明的变量 和 直接赋值而并未声明的变量 都有哪些区别呢,这是我在百 ...

  4. JS逻辑题 技术点: 1). 变量提升 2). 函数提升 3). 预处理 4). 调用顺序

    考查的技术点:  1). 变量提升 2). 函数提升  3). 预处理  4). 调用顺序 var c = 1; function c(c) { console.log(c); var c = 3; ...

  5. js中变量提升和函数提升

    变量提升和函数提升的总结 我们在学习JavaScript时,会遇到变量提升和函数提升的问题,为了理清这个问题,现做总结如下,希望对初学者能有所帮助 我们都知道 var 声明的变量有变量提升,而 let ...

  6. JS中的let变量

    介绍JS中的let变量: let允许你声明一个作用域被限制在块级中的变量.语句或者表达式.在Function中局部变量推荐使用let变量,避免变量名冲突. 作用域规则 let 声明的变量只在其声明的块 ...

  7. javascript中函数作用域和声明提前

    javascript不像java等其他强类型语句,没有块级作用域(括号内的代码都有自己的作用域,变量在声明它们的代码段之外不可见)一说,但有自己的独特地方,即函数作用域. 函数作用域:变量在声明它们的 ...

  8. JavaScript函数作用域和声明提前(3.10.1 page.57)

    <h4>3.函数作用域和声明提前</h4> <p> <!--<script type="text/javascript">-- ...

  9. JavaScript变量声明与变量声明提前

    JavaScript变量声明 JavaScript中存储数据的容器称为变量.用关键字和标识符创建新变量的语句,称为变量声明.可以通过关键字var进行变量声明,在ES6中增加了let.const关键字声 ...

随机推荐

  1. 关于CentOS下 yum包下载下的rpm包放置路径

    在CentOS下用yum安装,回发现在/var/cache/yum/下的base.extrs和updates下的packages下都没有发现下载的RPM 原来在/etc/yum.conf下没有设置下载 ...

  2. 转 iOS宏定义的使用与规范

    宏定义在很多方面都会使用,例如定义高度.判断iOS系统.工具类,还有诸如文件路径.服务端api接口文档.为了对宏能够快速定位和了解其功能,我们最好在定义的时候将其放入特定的头文件中,下面我抛砖引玉,对 ...

  3. 【VB.NET】通过 IPIP.NET 数据库来查询IP地址

    上一次介绍了利用纯真数据库查询IP地址详细信息的方法.然而纯真数据库是由网友反馈所提供的,很多数据描述并不准确,所以我上网找了一些其他的IP数据库,最后就找到了 ipip.net 这个网站所提供的IP ...

  4. WPF MeasureOverride和 ArrangeOverride做个 页面导航

    public class NavigationPanel:Panel { protected override Size MeasureOverride(Size availableSize) { S ...

  5. 浏览器环境下Javascript脚本加载与执行探析之DOMContentLoaded

    在”浏览器环境下Javascript脚本加载与执行探析“系列文章的前几篇,分别针对浏览器环境下JavaScript加载与执行相关的知识点或者属性进行了探究,感兴趣的同学可以先行阅读前几篇文章,了解相关 ...

  6. Django 实现第三方账号登录网站

    这里我们使用 django-allauth 模块来实现第三方账号验证登录,官方文档如下:https://django-allauth.readthedocs.io/en/latest/ . 安装 dj ...

  7. TmsTimeUtils 时间戳

    package com.sprucetec.tms.utils; import java.math.BigDecimal;import java.text.DateFormat;import java ...

  8. 冰与火之歌居然是在 DOS 系统上写出来的

    简评:<权力的游戏>第八季(最终季)终于开播了!这部美剧的原著小说有一个很有趣的冷知识 -- 它是在运行 DOS 系统的计算机上写出来的.其实不少老粉都已经知道这个典故,不过听到老爷子的亲 ...

  9. 《UNIX环境网络编程》第十四章第14.9小结(bug)

    1.源代码中的<sys/devpoll.h>头文件在我的CentOS7系统下的urs/include/sys/目录下没有找到. 而且我的CentOS7也不存在这个/dev/poll文件. ...

  10. 抓包和测试Api类工具

    1.PostMan  测试api 2.Fiddler4抓包工具使用教程一