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

作用域和const/let——《你不知道的JavaScript》读书笔记 #377

Open
HuaYJ1996 opened this issue Dec 30, 2019 · 0 comments
Open

Comments

@HuaYJ1996
Copy link

前言

在大学时编译原理的学习中认识到,编译过程大致分为这几个步骤:词法分析-语法分析-语义分析-中间代码生成-代码优化-代码生成。JavaScript虽然是一门解释型语言,但是也避免不了编译过程,只不过不同于C/Java的构建时编译,它是在执行过程中编译。
提到编译,一个很重要的概念就是作用域。作用域的存在限制了变量的使用范围,可以这么举例:在一个部门里找到张三很简单,而在一个公司内可能找到好几个叫张三的同学,在全国范围内就更多了。作用域在很大程度上保证了代码中变量不发生冲突。

函数作用域

同其他大部分语言类似,js中的函数作用域应该是一个最明显的作用域体现了。在定义一个函数时,函数中的定义的变量只允许在这个函数中使用,在函数外部访问这个变量就会出现ReferenceError错误:xxx is not defined

    var a = 'a';
    function foo(){
        var a = 'aa';
        var b = 'b';
        console.log(a); // 'aa'
    }
    console.log(a); // 'a'
    console.log(b); // Uncaught ReferenceError: b is not defined

在编译器寻找变量的过程中,会先从本级作用域开始,逐步往上一层作用域寻找,直到找到这个变量为止。

块级作用域

块级作用域是大部分语言都支持的优秀特性,然而在ES5及之前的JS规范中都不支持块级作用域。

    for(var i=0; i < 10; i++) {
        // todo something
    }

    console.log(i); // 10

在上面这个简单都循环结构中,设想中变量i只是作为一个临时变量存在,在for循环结束之后就没有意义了。但是实际上,i会被绑定在for循环所处的那个作用域中,在之后都代码中,直到离开这个作用域前,仍会存在一个变量i=10。
不支持块级作用域往往会造成很多困扰,如多次对变量的定义(尽管这在js中并不报错...诡异)、变量在使用结束后未能及时销毁带来的逻辑问题或性能问题等。在传统方法中,为了创造出块级作用域,开发者们往往使用with或者try/catch,而在ES6中,letconst的出现很好地解决来这一困恼。

let关键字

let关键字于ES6中引入,它提供了除了var以为的另外一种变量声明方式。相比于var关键字声明无法创建块级作用域的问题,它的另一个问题似乎更加严重:变量提升。

    console.log(a);
    var a = 1;

对于以上的代码,在正常的理解中,应该会出现ReferenceError: a is not defined的错误,然而实际上,程序会正常打印出undefiend。在代码的执行过程中,以上代码会由于变量提升的原因,等同于以下形式:

    var a;
    console.log(a);
    a = 1;

变量提升的存在更容易引发各种问题,幸好,let关键字能够很好地解决这一问题。let关键字不存在变量提升的问题,即只能在同一个作用域内先定义后使用,这更加符合大多数人的思维逻辑。

    console.log(a); // ReferenceError: Cannot access 'aa' before initialization
    let a = 1;

另外一点,let能够利用花括号显式地创建一个块级作用域。如下:

    {
        let a = 1;
        console.log(a); // 1
    }
    console.log(a); // ReferenceError: a is not defined

const关键字

const关键字和let关键字一样,能够创建块级作用域,也能够防止变量提升。相比于let,它的区别在于,它定义的是一个常量。

    const a = 1; 
    a = 2; // TypeError: Assignment to constant variable.

但是对于引用类型的数据而言,“不可以修改”指的是这个所引用地址不可改变,这个引用对象的值是可以改变的。

    const person = { name: "Tom"};
    person.name = "Jerry";
    console.log(person); // { name: 'Jerry' }
    person = { name: "ToT"} // TypeError: Assignment to constant variable.

用C语言的方式解释就是,指针是不可变的,但是指针所指向的地址的值是可变的。也因此,修改person.name时不会报错,执行person={ name: "ToT"}时,会先创建一个对象{name: "ToT"},然后将person指向这个对象,这会改变引用的地址,因此会报错。

总结

相比于使用var定义变量,使用let和const能够创建块级作用域,能够避免变量提升带来的潜在问题,能更明确地表达量的类型(常量或变量),能够让执行引擎恰时执行垃圾回收,强烈建议使用let和const代替var关键字。

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

No branches or pull requests

1 participant