先来两个问题

很多时候,在直觉上,我们都会认为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. linux下Tab及shell 补全python

    Python自动补全 Python自动补全有vim编辑下和python交互模式下,下面分别介绍如何在这2种情况下实现Tab键自动补全. vim python自动补全插件:pydiction 可以实现下 ...

  2. 简单的Nginx自动化安装啊脚本

    #!/bin/bash #description:Nginx installation script automatically #user:baomanji #date:2017-03-25 #ve ...

  3. with工作原理

    进入时,调用对象的__enter__ 退出时,调用对象的__exit__

  4. Text-文本撤销

    #撤销操作 from tkinter import * master = Tk() #打开undo按钮 text=Text(master,width=30,height=5,undo=True) te ...

  5. Typescript学习

    一 什么是Typescript 简单的说,TypeScript 是 JavaScript 的一个超集,主要提供了类型系统和对 ES6 的支持,它由 Microsoft 开发,代码开源于 GitHub  ...

  6. hue集成hive访问报database is locked

    这个问题这应该是hue默认的SQLite数据库出现错误,你可以使用mysql postgresql等来替换 hue默认使用sqlite作为元数据库,不推荐在生产环境中使用.会经常出现database ...

  7. 使用supervisor管理进程

    Supervisor (http://supervisord.org) 是一个用 Python 写的进程管理工具,可以很方便的用来启动.重启.关闭进程(不仅仅是 Python 进程).除了对单个进程的 ...

  8. 字段从varchar2修改为number,字段中的内容做修改,替换

    #测试表的内容结构:如下所示: 其中DATEHH字段:代表时间,字段在表中是varchar2格式 现有如下需求:字段类型,从varchar2改变为number, 字段中 '.'去除, 2013103少 ...

  9. 学习React系列(九)——高阶函数

    定义:高阶组件就是一个函数,且该函数接收一个组件作为参数,并返回一个新的组件. (上一篇已经说过了高阶组件可以用来解决交叉问题) 一.不要改变原始组件,使用组合 class A extends Rea ...

  10. springboot启动报错

    新建springboot整合aop记录web日志的过程中启动失败 错误如下: ***************************APPLICATION FAILED TO START******* ...