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

console.log(a)//?
a();//?
var a =;
function a(){
console.log();
}
console.log(a);//?
a = ;
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()//
console.log(num)//函数本身
function num(){
console.log();
}
num();//
console.log(num)//函数本身

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

function num(){
console.log();
}
num()//
console.log(num)//函数本身 num();//
console.log(num)//函数本身

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

num()//报错
console.log(num)//undefined
var num = function (){
console.log();
}
num();//
console.log(num)//函数本身

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

var num;
num()//报错,这时候都没有函数声明
console.log(num)//undefined,因为已经声明了num
num = function (){
console.log();
}
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=;
var a;
console.log(a);//

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

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

正确的修改:

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

错误的修改:

var a;//再次声明a,并未修改a的值
function a(){
console.log();
}
console.log(a)//输出函数本体
a();//函数声明提前,调用输出10
a =;//这里修改值了,a=3,不在是函数了
console.log(a);//输出3
a = ;//再次修改为6
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. 无法链接glew的解决办法-编译开源库出现: error LNK2001: 无法解析的外部符号

    无法链接glew的解决办法-编译开源库出现: error LNK2001: 无法解析的外部符号 参考官方配置指南:http://glew.sourceforge.net/install.html 1. ...

  2. poj 2488 A Knight's Journey

    题目 题意:给出一个国际棋盘的大小 p*q,判断马能否不重复的走过所有格,并记录下其中按字典序排列的第一种路径. 因为要求字典序输出最小,所以按下图是搜索的次序搜素出来的就是最小的. 初始方向数组:i ...

  3. 2.Java面向对象编程三大特性之继承

    在<Think in Java>中有这样一句话:复用代码是java众多引人注目的功能之一.但要想成为极具革命性的语言,仅仅能够复用代码并对其加以改变是不够的,他还必须能够做更多的事情.复用 ...

  4. kafka_shell操作

    单机版 开启进程: ./bin/kafka-server-start.sh config/server.properties 查看topic列表: ./bin/kafka-topics.sh --li ...

  5. 防Xss注入

    转自博客:https://blog.csdn.net/qq_21956483/article/details/54377947 1.什么是XSS攻击 XSS又称为CSS(Cross SiteScrip ...

  6. Concurrency Programming Guide 并发设计指引(二)

    以下翻译是本人通过谷歌工具进行翻译,并进行修正后的结果,希望能对大家有所帮助.如果您发现翻译的不正确不合适的地方,希望您能够发表评论指正,谢谢.转载请注明出处. Concurrency and App ...

  7. TryEnterCriticalSection___Delphi

    VOID EnterCriticalSection:非阻塞函数.将当前线程对指定临界区的引用计数减1:在使用计数变为零时,另一等待此临界区的一个线程将被唤醒. BOOL TryEnterCritica ...

  8. Android-Kotlin-Activity直接的跳转

    1.选中应用包名packageName,右键: 2.选中Kotlin: 3.创建Kotlin的Activity完成: 第一个Activity,MainActivity package cn.kotli ...

  9. Elasticsearch 系列2 --- 安装elasticsearch-head管理工具

    elasticsearch-head是elasticsearch的一个管理页面,它的官网是https://github.com/mobz/elasticsearch-head 通过官网我们得知,ES5 ...

  10. spark-mllib 密集向量和稀疏向量

    spark-mllib 密集向量和稀疏向量 MLlib支持局部向量和矩阵存储在单台服务器,也支持存储于一个或者多个rdd的分布式矩阵 . 局部向量和局部矩阵是用作公共接口的最简单的数据模型. 基本的线 ...