提升

先有蛋还是先有鸡

到现在,我们已经明白作用域的概念了,以及根据声明的位置和方式将变量分配给作用域的相关原理。函数作用域和块作用域的行为是一样的,可以总结为:任何声明在某个作用域内的变量,都将附属于这个作用域。

但是,作用域同其中的变量声明出现的位置有某种微妙的联系,而这个细节正是我们将要讨论的内容。

直觉上会认为 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 声明和函数声明混合在一起的时候吗,否则会引起很多危险的问题!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 你不知道的this(2)

    在上一张,我们排除了一些对this的误解,并且明白了每个函数的this是在调用时被绑定的,完全取决于函数的调用位置(也就是函数的调用方法)

    Karl Du
  • JavaScript 数据类型

    除了object其他都是基本类型,是的,null也是基本类型,但是有很多人把它当作对象类型,这其实是语言本身的一个bug。对null执行typeof null返...

    Karl Du
  • 你不知道的this(3)

    我们可以看到,我们预期这里会输出awesome,结果打印结果显示我们丢失了this的绑定,解决这个问题的办法有很多种,最常见的就是 var self = thi...

    Karl Du
  • Js中的提升

         Js的引擎机制是先编译,再执 ,先从编译器说起,编译过程中,我们知道编译会先根据声明为其确定作用域。上面的例子中实际上编译器会将其看成两个声明,分别为...

    菜的黑人牙膏
  • 前端-part4-JavaScript字符串+数组+循环

    少年包青菜
  • 文言文也能编程?此诚年度最骚语言也

    近日,GitHub 上一个叫做文言文(wenyan)的编程语言项目火了,该项目迅速引发了猿们的关注热议,其 Star数一路涨到过万,热度还在持续上涨。只需要在在...

    养码场
  • Go之基础类型

    院长技术
  • JavaScript函数与对象

    函数 函数的定义 JavaScript中的函数和Python中的非常类似,只是定义方式有点区别。 // 普通函数定义 function f1() { ...

    人生不如戏
  • Phantomjs+Nodejs+Mysql数据抓取(2.抓取图片)

    概要 这篇博客是在上一篇博客Phantomjs+Nodejs+Mysql数据抓取(1.抓取数据) http://blog.csdn.net/jokerko...

    九灵
  • 第14天:逻辑运算符、if、for语句

    一假即假,同真为真 2、||(或) 一真即真,同假为假 3、!(非) 切记:参与逻辑运算的,都是布尔值。也就是说,只有true、false才能参与 逻辑运算,...

    半指温柔乐

扫码关注云+社区

领取腾讯云代金券