JS学习系列 04 - 提高

2021年09月15日 阅读数:3
这篇文章主要向大家介绍JS学习系列 04 - 提高,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

到目前为止,你们应该很熟悉做用域的概念了,以及根据声明的位置和方式将变量分配给做用域的相关原理了。函数做用域和块做用域的行为是同样的,能够总结为:任何声明在某个做用域内的变量,都将属于这个做用域。微信

可是做用域同其中的变量声明出现的位置有某种微妙的关系,而这个细节就是咱们这节要探讨的内容。函数

1. 声明提高

先看代码:spa

a = 2;

var a;

console.log(a);

你们认为这里会输出什么?code

有一些人认为是 undefined ,由于 var a; 是在 a = 2; 以后,因此会以为 undefined 覆盖了 a 的值。可是,真正的结果是 2 。ip

再看一段代码:作用域

console.log(a);

var a = 2;

鉴于上一个例子,有些人会认为这里会输出 2 ,也有人认为因为 a 在使用前并无声明,因此这里会报错。可是,这里的结果是 undefined 。rem

以前讨论编译器的时候,咱们知道 JS 引擎会在解释代码以前首先对其进行编译。编译阶段的第一部分工做就是找到全部的声明,并用合适的做用域将它们关联起来。编译器

所以,正确的思路是,包括变量和函数在内的全部声明都会在任何代码执行前首先被处理。it

当你看到 var a = 2; 时,JavaScript 实际上会将其当作两个声明:var a;a = 2; 。第一个定义声明是在编译阶段进行的。第二个赋值声明会被留在原地等待执行阶段。io

因此,在第一个例子中,代码的等价形式是这样的:

var a;

a = 2;

console.log(a);

第二个例子中,代码的等价形式是这样的:

var a;

console.log(a);

a = 2;

这个过程就好像是变量和函数声明从它们的代码中出现的位置被“移动”到了最上面。这个过程就叫做“提高”。

注意,只有声明自己会被提高,而赋值操做和其余运行逻辑都会停留在原地,想象一下,若是提高会改变代码的执行顺序,那么会形成很是严重的破坏。

还有一点,函数声明会被提高,可是函数表达式不会被提高。

foo();      // 报错,TypeError: foo is not a function,由于这里 foo 是 undefined,并非一个函数

var foo = function foo() {
    // something else
}

这段程序中的变量标识符 foo 被提高并分配给所在的做用域(在这里是全局做用域),所以 foo() 不会致使 ReferenceError 。可是,foo 此时并无赋值(若是它是一个函数声明而不是函数表达式,那么就会被赋值)。foo() 因为对 undefined 值进行函数调用而致使非法操做,因此会抛出 TypeError 异常。

同时,即便是具名函数表达式,名称标识符在赋值以前也没法在所在做用域中使用:

foo();
bar();

var foo = function bar () {
    // something else
};

这段代码通过提高后,实际上等价于:

var foo;

foo();
bar();

foo = function () {
    var bar = ...self...
    // something else
};

2. 函数优先

函数声明和变量声明都会被提高。可是一个值得注意的细节是,函数声明会首先被提高,而后才是变量。

考虑以下代码:

foo();      // 1

var foo;

function foo () {
   console.log(1);
}

foo = function () {
   console.log(2);
};

这里会输出 1 而不是 2 。这段代码其实等价于:

function foo () {
   console.log(1);
}

foo();      // 1

foo = function () {
   console.log(2);
};

var foo; 尽管出如今 function foo() {...} 声明以前,可是它是重复声明,因此会被编译器忽略,由于函数声明会被提高到变量声明以前。

注意,尽管重复的 var 声明会被忽略,但重复的函数声明却会覆盖前一个同名函数。

foo();      // 3

function foo () {
   console.log(1);
}

var foo = function () {
   console.log(2);
};      

foo();        // 2

function foo () {
   cosole.log(3);
}

这个例子充分说明了在同一个做用域中进行重复定义是很是糟糕的,并且常常会致使各类奇怪的问题。上面那个例子,等价于:

function foo () {
   cosole.log(3);
}

foo();      // 3

foo = function () {
   console.log(2);
};      

foo();        // 2

还有一些人会犯以下错误:

foo();      // 2

var a = true;

if (a) {
   function foo () {
      console.log(1);
   }
} else {
   function foo () {
      console.log(2);
   }
}

由于 if 并无块做用域,因此这里的函数声明会提高到其做用域最前边,然后一个 function 声明会覆盖前一个,因此这里结果是 2 。这里代码等价以下:

function foo () {
   console.log(2);
}

var a;

foo();      // 2

a = true;

if (a) {

} else {

}

3. 总结

咱们习惯将 var a = 2; 看做一个声明,而实际上 JavaScript 引擎并不这么认为。它将 var a;a = 2; 看成两个单独的声明,第一个是编译阶段的任务,而第二个则是执行阶段的任务。

这意味着不管做用域中的声明出如今什么地方,都将在代码自己被执行前首先被处理(预编译)。能够将这个过程想象成全部的声明(变量和函数)都会被“移动”到各自的做用域的最顶端,这个过程叫做提高

欢迎关注个人公众号

微信公众号