资讯专栏INFORMATION COLUMN

Javascript作用域和变量提升

whlong / 3373人阅读

摘要:有何区别在中,存在关键字,它声明的变量同样存在块级作用域。而且函数本身的作用域,只存在其所在的块级作用域之内,例如重复声明一次函数上面这段代码在中的输出结果为因为被条件语句中的上升覆盖了。如果对的使用,或的其他新特性感兴趣,请自行阅读文档。

引子

首先大家看一下下面的代码,猜猜会输出什么结果?

var foo = 1;
function bar() {
    if (!foo) {
        var foo = 10;
    }
    alert(foo);
}
bar();

答案是10!
你是否会疑惑条件语句if(!foo)并不会执行,为什么foo会被赋值为10

再来看第二个例子

var a = 1;
function b() {
    a = 10;
    return;
    function a() {}
}
b();
alert(a);

答案还是10吗?显然不是,alert输出了1

如果你仍然对上面两个输出结果摸不着头脑,那么请认真阅读这篇文章

Scoping in Javascript

Javascript的作用域已经是老生常谈的问题了,但是不一定每个人都能准确理解。
我们先来看一下C语言的一个例子:

#include 
int main() {
    int x = 1;
    printf("%d, ", x); // 1
    if (1) {
        int x = 2;
        printf("%d, ", x); // 2
    }
    printf("%d
", x); // 1
}

程序依次输出了1,2,1
为什么第三个输出了1而不是2呢?因为在C语言中,我们有块级作用域(block-level scope)。在一个代码块的中变量并不会覆盖掉代码块外面的变量。我们不妨试一下Javascript中的表现

var x = 1;
console.log(x); // 1
if (true) {
    var x = 2;
    console.log(x); // 2
}
console.log(x); // 2

输出的结果为1,2,2 if代码块中的变量覆盖了全局变量。那是因为JavaScript是一种函数级作用域(function-level scope)所以if中并没有独立维护一个scope,变量x影响到了全局变量x

C,C++,C#和Java都是块级作用域语言,那么在Javascript中,我们怎么实现一种类似块级作用域的效果呢?答案是闭包

function foo() {
    var x = 1;
    if (x) {
        (function () {
            var x = 2;
            // some other code
        }());
    }
    // x is still 1.
}

上面代码在if条件块中创建了一个闭包,它是一个立即执行函数,所以相当于我们又创建了一个函数作用域,所以内部的x并不会对外部产生影响。

Hoisting in Javascript

在Javascript中,变量进入一个作用域可以通过下面四种方式:

语言自定义变量:所有的作用域中都存在this和arguments这两个默认变量

函数形参:函数的形参存在函数作用域中

函数声明:function foo() {}

变量定义:var foo

其中,___在代码运行前,函数声明和变量定义通常会被解释器移动到其所在作用域的最顶部___,如何理解这句话呢?

function foo() {
    bar();
    var x = 1;
}

上面这段在吗,被代码解释器编译完后,将变成下面的形式:

function foo() {
    var x;
    bar();
    x = 1;
}

我们注意到,x变量的定义被移动到函数的最顶部。然后在bar()后,再对其进行赋值。
再来看一个例子,下面两段代码其实是等价的:

function foo() {
    if (false) {
        var x = 1;
    }
    return;
    var y = 1;
}
function foo() {
    var x, y;
    if (false) {
        x = 1;
    }
    return;
    y = 1;
}

所以变量的上升(Hoisting)只是其定义上升,而变量的赋值并不会上升。

我们都知道,创建一个函数的方法有两种,一种是通过函数声明function foo(){}
另一种是通过定义一个变量var foo = function(){} 那这两种在代码执行上有什么区别呢?

来看下面的例子:

function test() {
    foo(); // TypeError "foo is not a function"
    bar(); // "this will run!"
    var foo = function () { // function expression assigned to local variable "foo"
        alert("this won"t run!");
    }
    function bar() { // function declaration, given the name "bar"
        alert("this will run!");
    }
}
test();

在这个例子中,foo()调用的时候报错了,而bar能够正常调用
我们前面说过变量会上升,所以var foo首先会上升到函数体顶部,然而此时的fooundefined,所以执行报错。而对于函数bar, 函数本身也是一种变量,所以也存在变量上升的现象,但是它是上升了整个函数,所以bar()才能够顺利执行。

再回到一开始我们提出的两个例子,能理解其输出原理了吗?

var foo = 1;
function bar() {
    if (!foo) {
        var foo = 10;
    }
    alert(foo);
}
bar();

其实就是:

var foo = 1;
function bar() {
    var foo;
    if (!foo) {
        foo = 10;
    }
    alert(foo);
}
bar();
var a = 1;
function b() {
    a = 10;
    return;
    function a() {}
}
b();
alert(a);

其实就是:

var a = 1;
function b() {
    function a() {}
    a = 10;
    return;
}
b();
alert(a);

这就是为什么,我们写代码的时候,变量定义总要写在最前面。

ES6有何区别

在ES6中,存在let关键字,它声明的变量同样存在块级作用域。
而且函数本身的作用域,只存在其所在的块级作用域之内,例如:

function f() { console.log("I am outside!"); }
if(true) {
   // 重复声明一次函数f
   function f() { console.log("I am inside!"); }
}
f();

上面这段代码在ES5中的输出结果为I am inside!因为f被条件语句中的f上升覆盖了。
在ES6中的输出是I am outside!块级中定义的函数不会影响外部。

如果对let的使用,或ES6的其他新特性感兴趣,请自行阅读ES6文档。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/85885.html

相关文章

  • JavaScript作用域和闭包

    摘要:依然持有对该作用域的引用,而这个引用就叫作闭包。循环和闭包正常情况下,我们对这段代码行为的预期是分别输出数字,每秒一次,每次一个。 一、作用域 作用域共有两种主要的工作模型:第一种是最为普遍的,被大多数编程语言所采用的词法作用域,另外一种叫作动态作用域; JavaScript所采用的作用域模式是词法作用域。 1.词法作用域 词法作用域意味着作用域是由书写代码时函数声明的位置来决定...

    animabear 评论0 收藏0
  • JavaScript作用域和闭包

    摘要:依然持有对该作用域的引用,而这个引用就叫作闭包。循环和闭包正常情况下,我们对这段代码行为的预期是分别输出数字,每秒一次,每次一个。 一、作用域 作用域共有两种主要的工作模型:第一种是最为普遍的,被大多数编程语言所采用的词法作用域,另外一种叫作动态作用域; JavaScript所采用的作用域模式是词法作用域。 1.词法作用域 词法作用域意味着作用域是由书写代码时函数声明的位置来决定...

    CNZPH 评论0 收藏0
  • 关于javascript中的作用域和作用域链

    摘要:作用域的类别可以影响到变量的取值,分为词法作用域静态作用域和动态作用域。而,采用的就是词法作用域,或者叫静态作用域。 关于javascript中的作用域和作用域链 我GitHub上的菜鸟仓库地址: 点击跳转查看其他相关文章 文章在我的博客上的地址: 点击跳转         前面的文章说到, 执行上下文的创建阶段,主要有三个内容:         1、创建变量对象;2、初始化作用域...

    lcodecorex 评论0 收藏0
  • 你应该要知道的作用域和闭包

    摘要:写在前面对于一个前端开发者,应该没有不知道作用域的。欺骗词法作用域有两个机制可以欺骗词法作用域和。关于你不知道的的第一部分作用域和闭包已经结束了,但是,更新不会就此止住未完待续 这是《你不知道的JavaScript》的第一部分。 本系列持续更新中,Github 地址请查阅这里。 写在前面 对于一个前端开发者,应该没有不知道作用域的。它是一个既简单有复杂的概念,简单到每行代码都有它的影子...

    JouyPub 评论0 收藏0
  • JavaScript面向对象~ 作用域和闭包

    摘要:大名鼎鼎的作用域和闭包,面试经常会问到。声明理解闭包,先理解函数的执行过程。闭包的基本结构因为闭包不允许外界直接访问,所以只能间接访问函数内部的数据,获得函数内部数据的使用权。 大名鼎鼎的作用域和闭包,面试经常会问到。闭包(closure)是Javascript语言的一个难点,也是它的特色。 声明 理解闭包,先理解函数的执行过程。 代码在执行的过程中会有一个预解析的过程,也就是在代码的...

    WilsonLiu95 评论0 收藏0

发表评论

0条评论

whlong

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<