浅谈JS变量声明和函数声明提升
先来两个问题
很多时候,在直觉上,我们都会认为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变量声明和函数声明提升的更多相关文章
- 浅谈js变量作用域
变量的作用域也是前端面试题常考的一个问题,掌握下面几个规律可以帮你更好的理解js的作用域. 1.作用域优先级遵循就近原则,函数内部的作用域优先级大于外部 var a=456; var b=111; f ...
- 浅谈JS中的高级函数
在JavaScript中,函数的功能十分强大.它们是第一类对象,也可以作为另一个对象的方法,还可以作为参数传入另一个函数,不仅如此,还能被一个函数返回!可以说,在JS中,函数无处不在,无所不能,堪比孙 ...
- 浅谈JS中 var let const 变量声明
浅谈JS中 var let const 变量声明 用var来声明变量会出现的问题: 1. 允许重复的变量声明:导致数据被覆盖 2. 变量提升:怪异的数据访问.闭包问题 3. 全局变量挂载到全局对象:全 ...
- 浅谈JavaScript变量声明提升
前段时间阿里实习生内推,一面就被刷了,也是郁闷.今天系统给发通知,大致意思就是内推环节不足以了解彼此,还可以参加笔试,于是赶紧再投一次.官网流程显示笔试时间3月31日,时间快到了,开始刷题.网上搜了一 ...
- [js]变量声明、函数声明、函数定义式、形参之间的执行顺序
一.当函数声明和函数定义式(变量赋值)同名时 function ledi(){ alert('ledi1'); }; ledi(); var ledi = function (){ alert('le ...
- 浅谈JS中的闭包
浅谈JS中的闭包 在介绍闭包之前,我先介绍点JS的基础知识,下面的基础知识会充分的帮助你理解闭包.那么接下来先看下变量的作用域. 变量的作用域 变量共有两种,一种为全局变量,一种为局部变量.那么全局变 ...
- 浅谈JS严格模式
浅谈JS严格模式 简介 何为严格模式?严格模式(strict mode)即在严格的条件下运行,在严格模式下,很多正常情况下不会报错的问题语句,将会报错并阻止运行. 但是,严格模式可以显著提高代码的健壮 ...
- 浅谈JS中的!=、== 、!==、===的用法和区别 JS中Null与Undefined的区别 读取XML文件 获取路径的方式 C#中Cookie,Session,Application的用法与区别? c#反射 抽象工厂
浅谈JS中的!=.== .!==.===的用法和区别 var num = 1; var str = '1'; var test = 1; test == num //tr ...
- 浅谈 js 语句块与标签
原文:浅谈 js 语句块与标签 语句块是什么?其实就是用 {} 包裹的一些js代码而已,当然语句块不能独立作用域.可以详细参见这里<MDN block> 也许很多人第一印象 {} 不是对象 ...
随机推荐
- 对于错误“Refused to execute script from '...' because its MIME type ('') is not executable, and strict MIME type checking is enabled.”的处理。
今天在是用公司的报表插件Stimulsoft时发现的问题.之前可以正常使用,突然不能加载了.查看发现得到这个错误. 查看请求头 可以看到,请求正常响应,但是发现 Content-Type是空的,但是引 ...
- Java面向对象之封装 入门实例(一)
一.基础概念 (一)面向对象的三大特征: 1.封装 2.继承 3.多态 (二)封装:隐藏实现细节,对外提供公共的访问方式(接口). 封装的体现之一:将属性都 ...
- Tumblr:我们是如何从 PHP 5 升级到 PHP 7 的
Tumblr 团队经常在寻找新的方式来提升网站的性能.这意味着要给访问量大的代码增加缓存,找到更快的 CDN 配置,或者升级基础软件. 最近,通过一次跨团队的努力,我们将全部 web 服务器战舰从 P ...
- 愿奴胁下生双翼——— 详解cookie和session
cookie和session都是基于web服务器的,不同的是cookie存储在客户端而session存储在服务器. 当用户浏览网站时,web服务器会在浏览器上存储一些当前用户的相关信息,在本地Web客 ...
- 给工作添点乐趣--- 为Idea编译时打印图案
控制台输出图案--banner 之前用vs开发时,也可以自己设置编译时控制台打印出的图案,我们管它叫banner. 给idea设置banner 先见一下效果图 还有重磅的 好了.其实设置这个很简单.分 ...
- Linux下文件权限(一)用户ID和用户组ID
最近在读<unix环境高级编程>,看到文件权限这里比较糊涂,主要设计多个用户ID和用户组ID,包括下面两个: (1)实际用户ID和实际用户组ID:这一部分表示我们究竟是谁.这两个字段在登录 ...
- drupal8之分类
示例: 我的相册 一.创建分类 1.创建一个相册 点击[结构]>[Taxonomy]>[+add vocabulary] 点击[保存] 2.创建相册的分类 点击[+add term] 点击 ...
- Canvas - 时钟绘制
导语:距离上一次写canvas,已经过去两年半,如今业务需要,再次拾起,随手记录. [思考] 时钟的绘制主要在于圆的绘制:1. 使用context.arc()方法直接绘制圆或圆弧: 2. 使用圆的方程 ...
- linux必知必会命令
- Docker(六):Docker 三剑客之 Docker Swarm
实践中会发现,生产环境中使用单个 Docker 节点是远远不够的,搭建 Docker 集群势在必行.然而,面对 Kubernetes, Mesos 以及 Swarm 等众多容器集群系统,我们该如何选择 ...