先来两个问题

很多时候,在直觉上,我们都会认为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. ORA-01578和ORA-26040--NOLOGGING操作引起的坏块-错误解释和解决方案(文档ID 1623284.1)

    ORA-01578和ORA-26040--NOLOGGING操作引起的坏块-错误解释和解决方案(文档ID 1623284.1) (一)NOLOGGING操作引起的坏块(ORA-01578和ORA-26 ...

  2. Django 相关

    Web框架本质 其实所有的Web应用本质就是一个socket服务端,而用户的浏览器就是一个socket客户端.简单的socket代码如下: import socket sk = socket.sock ...

  3. 04、NetCore2.0下Web应用之Startup源码解析

    04.NetCore2.0Web应用之Startup源码解析   通过分析Asp.Net Core 2.0的Startup部分源码,来理解插件框架的运行机制,以及掌握Startup注册的最优姿势. - ...

  4. transform做2d和3d变形(css动画一)

    前言:这是笔者学习之后自己的理解与整理.如果有错误或者疑问的地方,请大家指正,我会持续更新! 有段时间我是没理清transform.translate.transition和animation之间的关 ...

  5. CF 472 div1 D. Contact ATC

    #include <algorithm> #include <cmath> #include <cstdio> #include <cstring> # ...

  6. iframe 里的高度适应的问题

    iframe 这个东西功能是很强大,但是有一个巨大的问题就是高度自适应的问题: 不过这个问题,百度或者谷歌上有很多解决办法,但是,很多时候都有兼容性问题: 所有我就每个方法都试了一遍,终于找到了一个 ...

  7. BZOJ 4260 Codechef REBXOR

    Description Input 输入数据的第一行包含一个整数N,表示数组中的元素个数. 第二行包含N个整数A1,A2,…,AN. Output 输出一行包含给定表达式可能的最大值. Sample ...

  8. poj 2960 S-Nim

    S-Nim Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 4113   Accepted: 2158 Description ...

  9. hdu 3247 AC自动+状压dp+bfs处理

    Resource Archiver Time Limit: 20000/10000 MS (Java/Others)    Memory Limit: 100000/100000 K (Java/Ot ...

  10. POJ 3415 不小于k的公共子串的个数

    Common Substrings Time Limit: 5000MS   Memory Limit: 65536K Total Submissions: 9248   Accepted: 3071 ...