JavaScript作用域与闭包

JavaScript作用域与闭包

JavaScript作用域问题

​ JavaScript 有个特性称为作用域。尽管对于很多开发者来说,作用域的概念不容易理解,但它确实是JavaScript这门语言中非常重要的一个概念。理解作用域能让你编写更优雅、错误更少的代码,并能帮助你获得更好的开发体验。

​ 作用域(scope),程序设计概念,通常来说,一段程序代码中所用到的名字并不总是有效/可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。

  • 作用域的使用提高了程序逻辑的局部性,增强程序的可靠性,减少名称冲突。
  • 作用域为你的代码提供了一个安全层级 ,可以提升性能,跟踪 bug 并减少 bug。

JavaScript中的作用域

  • 全局作用域
  • 局部作用域(函数作用域)
  • 块级作用域(try catch, 或者ES6的let、const关键字所创建)

var、let、const 关键字

  • var定义的变量,没有块的概念,可以跨块访问, 不能跨函数访问,存在变量提升。

  • let定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问。

  • const用来定义常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,而且不能修改(如果const定义的常量被赋值为一个引用数据类型,这个常量的引用不能修改,但是通过这个常量可以修改这个引用数据类型本身)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 关于变量提升的说明
    // 在js中函数声明、var定义的变量声明都会被提升

    console.log(a); // undefined
    var a = 10;

    // 上面这段代码在js编译器看来等价于
    var a
    console.log(a); // undefined
    a = 10;

    // 但是对于let 和 const定义的关键字来说, 编译器会直接报错!
    console.log(a); // ReferenceError: a is not defined
    let a = 10;

JavaScript闭包

​ 简单的来说:当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。

​ 下面我们来看一段代码,清晰地展示了闭包:

1
2
3
4
5
6
7
8
9
function foo() {
var a = 2;
function bar() {
console.log( a );
}
return bar;
}
var baz = foo();
baz(); // 2 —— 这就是闭包的效果!

而,一般来说我们使用闭包主要来实现两种效果

  • 第一、使用闭包可以在JavaScript中模拟块级作用域(在ES6 规范之前,js除了try catch之后全局作用域和函数作用域,使用闭包的形式用函数作用域来模拟块级作用域)
  • 第二、闭包可以用于在对象中创建私有变量

对于目前大量使用ES6规范的情况下,使用闭包来模拟块级作用域的时候确实少了。但是在创建私有变量的情况中,暂时没有更完美的方法了。即使在ES6的情况下,私有变量也只是一个提案(在变量前加 #号)。

或者使用symbol类型的值来作为属性名,防止外部调用时一般的遍历方式访问,但是这种形式也可以使用 Object.getOwnPropertySymbols() 返回自身的Symol属性,或者Symbol.for(),获取具体的symbol类型的值来访问。

《你不知道的JavaScript (上卷)》

​ 这本书中详细说明了作用域与闭包的问题,想要深入了解的话,可以去阅读。以下是第一部分的目录:

  • 第1章 作用域是什么
  • 第2章 词法作用域
  • 第3章 函数作用域和块作用域
  • 第4章 提升
  • 第5章 作用域闭包