先来两个问题

很多时候,在直觉上,我们都会认为JS代码在执行时都是自上而下一行一行执行的,但是实际上,有一种情况会导致这个假设是错误的。

a = 2;
var a;
console.log(a);

按照传统眼光,console.log(a)输出的应该是undefined,因为var a在a = 2之后。但是,输出的是2。

再看第二段代码:

console.log(a);
var a = 2;

有人会想到第一段代码,然后回答undefined。还有人会认为a在使用前未被声明,因此抛出ReferenceError异常。遗憾的是,结果是undefined。

为什么呢?

从编译器的角度看问题

JS在编译阶段,编译器的一部分工作就是找到所有声明,并用合适的作用域将他们关联起来。对于一般人来说var a = 2仅仅是一个声明,但是,JS编译器会将该段代码拆为两段,即:var a和a = 2。var a这个定义声明会在编译阶段执行,而a = 2这个赋值声明会在原地等待传统意义上的从上到下的执行。

所以,在编译器的角度来看,第一段代码实际上是这样的:

var a;	// 编译阶段执行
a = 2;
console.log(a);

所以,输出的是2。

类似的,第二个代码片段实际上是这样执行的:

var a;
console.log(a);
a = 2;

这样的话,很明显,输出的应该是undefined,因为只对a进行了定义声明,没有对a进行赋值声明。

从上面这两个例子可以看出,变量声明会从它们在代码中出现的位置被移动到当前作用域的最上方进行执行,这个过程叫做提升

函数提升

下面,再来看一段代码

foo();

function foo () {
console.log(a);
var a = 2;
}

在这个例子中,输出undefined而不会报错,因为,函数变量也能提升。即,实际上像如下的情况运行。

function foo () {
var a;
console.log(a);
a = 2;
} foo();

说到这里,你是不是认为提升很简单,只要把变量都放到当前作用域最上方执行就好了?

下面,我来说一种意外情况:函数表达式的提升情况。

函数表达式的提升情况

foo();

var foo = function bar () {
console.log(a);
var a = 2;
}

你是不是想说,这个例子不是和之前的那个差不多吗?输出的当然是undefined呀。但是,结果是,不输出,因为JS报了TypeError错误!

因为,函数表达式不会进行提升!

该例子的实际运行情况是这样的:

var foo;
foo();
foo = function bar () {
var a;
console.log(a);
a = 2;
}

由于执行时,在作用域中找得到foo(该作用域最上方声明了foo),所以不会报ReferenceError错误,但是,foo此时没有进行赋值(如果foo是一个函数声明而不是函数表达式,那么就会赋值),也就是说实际上foo()是对一个值为undefined的变量进行函数调用,所以,理所应当抛出TypeError异常。

值得一提的是,即使是具名的函数表达式,名称标识符在赋值之前也无法在所在作用域中使用,即:

foo();	// TypeError
bar(); // ReferenceError var foo = function bar () {}

函数优先

函数声明和变量声明都会被提升,但是有一个值得注意的细节,那就是,函数会首先提升,然后才是变量!

看下面这一段代码:

foo();
var foo;
function foo () {
console.log(1);
}
foo = function () {
console.log(2);
}

这一段代码会输出1,原因就在于,函数优先。

这一段代码可以转换为以下形式:

function foo () {
console.log(1);
}
var foo; // 重复声明,被忽略
foo(); // 输出1
foo = function () {
console.log(2);
}

如果,在代码的结尾再执行一次foo函数,此时,输出的是1。

function foo () {
console.log(1);
}
var foo; // 重复声明,被忽略
foo(); // 输出1
foo = function () {
console.log(2);
}
foo(); // 输出2

因为,尽管重复的声明会被忽略了,但是后面的函数还是可以覆盖前面的函数。

明白了这个道理,你就可以理解下面这个问题了:

foo();
var a = true;
if (a) {
function foo () {
console.log("a");
}
} else {
function foo () {
console.log("b");
}
}

你猜这道题输出的结果是什么?是b!为什么?因为foo进行了两次的声明,但是,后一次函数覆盖了前一次的函数。所以调用foo时,永远调用的都是console.log("b")。

总结

1.所有声明(变量和函数)都会被移动到各自作用域的最顶端,这个过程被称为提升

2.函数表达式等各种赋值操作并不会被提升

3.函数优先原则

4.尽量避免产生提升问题

参考资料:You Dont't Know JS: SCope & Closures

浅谈JS变量声明和函数声明提升的更多相关文章

  1. 浅谈js变量作用域

    变量的作用域也是前端面试题常考的一个问题,掌握下面几个规律可以帮你更好的理解js的作用域. 1.作用域优先级遵循就近原则,函数内部的作用域优先级大于外部 var a=456; var b=111; f ...

  2. 浅谈JS中的高级函数

    在JavaScript中,函数的功能十分强大.它们是第一类对象,也可以作为另一个对象的方法,还可以作为参数传入另一个函数,不仅如此,还能被一个函数返回!可以说,在JS中,函数无处不在,无所不能,堪比孙 ...

  3. 浅谈JS中 var let const 变量声明

    浅谈JS中 var let const 变量声明 用var来声明变量会出现的问题: 1. 允许重复的变量声明:导致数据被覆盖 2. 变量提升:怪异的数据访问.闭包问题 3. 全局变量挂载到全局对象:全 ...

  4. 浅谈JavaScript变量声明提升

    前段时间阿里实习生内推,一面就被刷了,也是郁闷.今天系统给发通知,大致意思就是内推环节不足以了解彼此,还可以参加笔试,于是赶紧再投一次.官网流程显示笔试时间3月31日,时间快到了,开始刷题.网上搜了一 ...

  5. [js]变量声明、函数声明、函数定义式、形参之间的执行顺序

    一.当函数声明和函数定义式(变量赋值)同名时 function ledi(){ alert('ledi1'); }; ledi(); var ledi = function (){ alert('le ...

  6. 浅谈JS中的闭包

    浅谈JS中的闭包 在介绍闭包之前,我先介绍点JS的基础知识,下面的基础知识会充分的帮助你理解闭包.那么接下来先看下变量的作用域. 变量的作用域 变量共有两种,一种为全局变量,一种为局部变量.那么全局变 ...

  7. 浅谈JS严格模式

    浅谈JS严格模式 简介 何为严格模式?严格模式(strict mode)即在严格的条件下运行,在严格模式下,很多正常情况下不会报错的问题语句,将会报错并阻止运行. 但是,严格模式可以显著提高代码的健壮 ...

  8. 浅谈JS中的!=、== 、!==、===的用法和区别 JS中Null与Undefined的区别 读取XML文件 获取路径的方式 C#中Cookie,Session,Application的用法与区别? c#反射 抽象工厂

    浅谈JS中的!=.== .!==.===的用法和区别   var num = 1;     var str = '1';     var test = 1;     test == num  //tr ...

  9. 浅谈 js 语句块与标签

    原文:浅谈 js 语句块与标签 语句块是什么?其实就是用 {} 包裹的一些js代码而已,当然语句块不能独立作用域.可以详细参见这里<MDN block> 也许很多人第一印象 {} 不是对象 ...

随机推荐

  1. Python之协程

    前言 在操作系统中进程是资源分配的最小单位,线程是CPU调度的最小单位.按道理来说我们已经算是把cpu的利用率提高很多了.但是我们知道无论是创建多进程还是创建多线程来解决问题,都要消耗一定的时间来创建 ...

  2. Hibernate HQL中的子查询

    子查询是SQL语句中非常重要的功能特性,它可以在SQL语句中利用另外一条SQL语句的查询结果,在Hibernate中HQL查询同样对子查询功能提供了支持.   如下面代码所示: List list=s ...

  3. hdu-2602&&POJ-3624---01背包裸题

    题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=2602 https://vjudge.net/problem/POJ-3624 都是01背包的裸题 这 ...

  4. 单例模式详解及java常用类

    [单例模式]      确保某一个类,只能产生一个实例. 设计思路: ====将构造函数私有化,确保类外部,不能使用new关键字自行创建对象. ====在类内部实例化一个对象,并通过静态方法返回. ( ...

  5. 关于MySQL Online DDL

    1. Online DDL 在 MySQL 5.1 (带InnoDB Plugin)和5.5中,有个新特性叫 Fast Index Creation(下称 FIC),就是在添加或者删除二级索引的时候, ...

  6. win10被微软流氓更新后编译基于visual Studio的web项目报[ArgumentOutOfRangeException: 指定的参数已超出有效值的范围

    最近忙得算焦头烂额.就在这个时候.一个不留神.微软的自动更新打开了.这流氓就在我百忙之中强迫我休息了一个多小时. 焦急等待它更新完以后赶紧打开visual studio跑代码.运行好几次都报错.想想不 ...

  7. 重拾Python(3):Pandas之Series对象的使用

    Pandas是Python下最强大的数据分析和探索库,是基于Numpy库构建的,支持类似SQL的结构化数据的增.删.查.改,具有丰富的数据处理函数.Pandas有两大数据结构:Series和DataF ...

  8. oracle11.2中分区功能测试之add&amp;split partition对global&amp;local index的影响

    生产库中某些大表的分区异常,需要对现有表进行在线操作,以添加丢失分区,因为是生产库,还是谨慎点好,今天有空,针对add&split分区对global&local索引的影响进行了测试,测 ...

  9. MySQL慢日志功能分析及优化增强

    本文由  网易云发布. MySQL慢日志(slow log)是MySQL DBA及其他开发.运维人员需经常关注的一类信息.使用慢日志可找出执行时间较长或未走索引等SQL语句,为进行系统调优提供依据.本 ...

  10. testng中使用reportng报告

    1.pom.xml文件中添加依赖,重构一下项目(mvn compile) <dependency> <groupId>org.uncommons</groupId> ...