Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

javascript变量提升详解 #12

Open
chenjigeng opened this issue Feb 28, 2019 · 0 comments
Open

javascript变量提升详解 #12

chenjigeng opened this issue Feb 28, 2019 · 0 comments

Comments

@chenjigeng
Copy link
Owner

对于大多数js开发者来说,变量提升可以说是一个非常常见的问题,但是可能很多人对其不是特别的了解。所以在此,我想来讲一讲。

先从一个简单的例子来入门:

a = 2;
var a;

console.log(a);

你觉得以上的代码会输出什么?是输出undefined吗?如果是按照程序的自上而下执行的话,那么这一段代码确实是输出undefined,因为var a语句把a = 2的语句给覆盖了。然而,javascript并不是严格的自上而下执行的语言

这一段代码的输出结果是2,是不是感到很意外?为什么会这样呢?这个问题的关键就在于变量提升(hoisting)。它会将当前作用域的所有变量的声明提升到程序的顶部,因此上面的代码其实等价于以下代码。这样是不是就很简单明了了。

var a;
a = 2;

console.log(a);

那么接下来,我们再来看这个例子。

console.log(a);

var a = 2;

你觉得以上的代码会输出什么?是直接报ReferenceError吗?还是输出2呢?

其实以上代码会输出undefined。为什么呢?我们之前说过,js会将变量的声明提升到顶部,可是赋值语句并不会提升。所以对于js来说,其实var a = 2是分为两步的:

  1. var a;
  2. a = 2;

而js只会将第一步提升到顶部,所以上面的语句等价于:

var a;

console.log(a);

a = 2;

为什么有变量提升

那么为什么会出现变量提升这个现象呢?

js和其他语言一样,都要经历编译和执行阶段。而js在编译阶段的时候,会搜集所有的变量声明并且提前声明变量,而不会改变其他语句的顺序,因此,在编译阶段的时候,第一步就已经执行了,而第二步则是在执行阶段执行到该语句的时候才执行。

变量声明

js的变量声明其实大体上可以分为三种:var声明、let与const声明和函数声明。

函数声明与其他声明一起出现的时候,就可能会引起一些困扰。我们来看下面的例子。

foo();

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

你觉得上面会输出什么?TypeError吗?还是输出foo呢?想一想再接着往下看。

当当当当,其实最后输出的结果是foo。这就引出了我们的问题了,当函数声明与其他声明一起出现的时候,是以谁为准呢?答案就是,函数声明高于一切,毕竟函数是js的第一公民。

那么,下面的例子呢?

foo();

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

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

当出现多个函数声明,那怎么办呢?

上面这个代码输出结果为2。因为有多个函数声明的时候,是由最后面的函数声明来替代前面的。

想必经历了以上的例子,你应该已经对变量声明已经有一定的了解了。那么我再来出一道题目来测试下。

foo();

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

这道题目是不是非常简单啊?这道题和上面的第二道例子其实是一样的。var foo = function() {}这种格式我们叫做函数表达式。

它其实也是分为两部分,一部分是var foo,而一部分是foo = function() {},参照例2,我们可以知道,这道题的结果应该是报了TypeError(因为foo声明但未赋值,因此foo是undefined)。

上面我们提到了var声明,函数声明,那么接下来我们来讲讲let和const声明。

let和const其实也是有函数提升的概念,不过它们会比较特殊,与var不一样,它们存在一个临死性死区的概念。我们可以通过一个例子来体现这一点。

var a = 2;
function test() {
    console.log(a);
    let a = 5;
}
test();

你觉得上面的代码会输出什么呢?是输出2,还是undefined呢?

如果说,let没有变量提升的效果的话,那么应该是输出2。如果说let拥有和var一样的变量提升效果的话,那么应该是输出undefined。然而,其实上面的代码是会报错的。会报"ReferenceError: a is not defined"错误。那么为什么呢?

这其实就是我说的,let虽然具有变量提升的功能,但是它又与var不一样,它具有一个临死性死区的概念。

临死性死区其实就是说,a我已经声明了,可是在没有到它赋值的时候,你都不能使用这个变量,不然就会报错。所以该当前作用域开始,一直到let a = 5为止这整一块,都是a变量的临死性死区,你不能使用它。

而const和let它们的变量提升的效果是一样的,也都存在着临死性死区的概念。

总结

那么接下来我们来总结一下。

  1. js会将变量的声明提升到js顶部执行,因此对于这种语句:var a = 2;其实上js会将其分为var a;和a = 2;两部分,并且将var a这一步提升到顶部执行。
  2. 变量提升的本质其实是由于js引擎在编译的时候,就将所有的变量声明了,因此在执行的时候,所有的变量都已经完成声明。
  3. 当有多个同名变量声明的时候,函数声明会覆盖其他的声明。如果有多个函数声明,则是由最后的一个函数声明覆盖之前所有的声明。
  4. let和const都具有变量提升的效果,但是它们都具有临死性死区,从作用域开始部门,一直到变量的声明语句这整一块,你都不能使用该变量。

以上,就是本文的内容。觉得有用的话麻烦点个赞,嘻嘻。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant