提升
先有蛋还是先有鸡
到现在,我们已经明白作用域的概念了,以及根据声明的位置和方式将变量分配给作用域的相关原理。函数作用域和块作用域的行为是一样的,可以总结为:任何声明在某个作用域内的变量,都将附属于这个作用域。
但是,作用域同其中的变量声明出现的位置有某种微妙的联系,而这个细节正是我们将要讨论的内容。
直觉上会认为 JavaScript 代码在执行时是由上到下一行一行执行的。但是实际上这并不是完全正确,有一种特殊情况会导致这个假设错误。
a = 2;
var a;
console.log(a);
很多人认为这里会是 undefined,因为 var a; 在 a = 2 之后,我们就自然而然地认为,变量被重新赋值了,因此会被赋值为 undefined,但是,这里真正地输出结果是 2
再看这段代码,鉴于上一段代码片段表现出来的某种非自上而下的特点,你很可能认为这里应该输出 2,还有人认为由于变量 a 在使用前没有声明,因此会抛出 ReferenceError 异常。
console.log(a);= 2;
var a = 2;
这两种猜测都不对,输出会是 undefined
那么到底发生了什么,看起来我们面对的是一个先有鸡还是先有蛋的问题,是声明在前,还是赋值在前?
变量提升
第一章介绍了编译器,我们知道引擎在解释代码前会进行编译
编译阶段一部分工作就是找到所有声明然后用作用域和他们关联起来
第二章展示了这种机制,这也正是词法作用域的核心内容
这里正确的思路是:包括变量和函数在内的所有声明都会在任何代码被执行之前首先被处理
当我们看到 var a = 2 时,可能会认为这是一个声明,但实际上,JavaScript 会认为这是两个声明,var a 和 a = 2,第一个定义声明在编译阶段进行,第二个赋值声明在原地等待执行阶段。
// 编译前
a = 2;
var a;
console.log(a);
// 编译后
var a;
a = 2;
console.log(a);
// 编译前
console.log(a);= 2;
var a = 2;
// 编译后
var a;
console.log(a);
a = 2;
这个过程就好像是变量和函数声明从它们地代码出现的位置被劫持到了最上面,这个过程就叫作提升。
换句话说,先有蛋(声明)后又鸡(赋值)
foo(); // 这里不会报错,因为 foo 函数声明提升了
function foo() {
console.log(a); // undefined
var a = 2;
}
foo(); // TypeError
var foo = function() {
console.log('123');
}
第二段代码可以看到,函数声明会被提升,但是函数表达式不会被提升
为什么是 TypeError 而不是 ReferenceError 呢?因为 var foo 会提升,但是类型是不确定的
函数优先
函数声明和变量声明都会被提升,但是函数会首先提升,然后才是变量
foo(); // 1
var foo;
function foo() {
console.log(1);
}
foo = function() {
console.log(2);
}
这里尽管 var foo 出现在 function foo() 之前,但是它是重复声明,这里会被忽略掉,因为函数声明会被提升到普通变量之前。
foo(); // 3
var foo;
function foo() {
console.log(1);
}
foo = function() {
console.log(2);
}
function foo() {
console.log(3);
}
我们习惯将 var a = 2; 看作是一个声明,而实际上 JavaScript 引擎并不是这么认为,它将 var a 和 a = 2 当作两个单独的声明,第一个是编译阶段的任务,而第二个是执行阶段的任务。
这意味着无论作用域的声明出现在什么位置,都将在代码本身被执行前被首先执行,可以将这个过程形象的想象成所有的声明都会被移动到各自作用域的最顶端,这个过程被称为提升。
声明本身会被提升,而包括函数表达式的赋值在内的赋值操作并不会提升。
要注意避免重复声明,特别是当普通的 var 声明和函数声明混合在一起的时候吗,否则会引起很多危险的问题!
- CSS Auto Reload:解放F5 键,自动刷新浏览器的前端利器(Chrome 扩展)
- zepto 基础知识(3)
- 1.[Andriod]之Andriod布局 VS WinPhone布局
- WordPress自定义url 中的“author” 别名
- ASP.NET 4 AppFabric 输出缓存提供程序
- WordPress 中禁止编辑“已发布”的文章
- Windows Server 2008 R2 网络负载平衡 (NLB)资料汇总
- 0.[Andriod]之从零安装配置Android Studio并编写第一个Android App
- WordPress 后台编辑文章页面添加自定义提示文字
- 使用json 和jQuery制作级联dropdownlist
- 在64位Windows 7/2008操作系统上部署32位的Web应用程序错误
- 云计算浪潮
- 2.[Andriod]Andriod Studio结合Visual Studio Emulator for Android调试Android App
- Windows Server AppFabric Caching
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Spring源码学习笔记(12)——IoC容器启动流程
- HashMap详解
- ElementUI使用autocomplete组件输入搜索
- Hacking with iOS: SwiftUI Edition - 愿望清单项目(三)
- 浏览器在线预览offce,vue-pdf预览pdf文件
- vivo商城前端架构升级-总览篇
- 《JSON笔记之三》---postman中传入json串
- Spring源码学习笔记(13)——JDBC
- java系列之注解
- NodeJs初入Http模块和Url模块
- Spring系列之实例化、初始化处理器
- NodeJs安装supervisor实现重载
- MyBatis系列之浅谈SQL执行流程分析
- NodeJs引入自定义模块
- NodeJs引入操作第三方插件