JavaScript之变量及做用域

本文共 1700 字,读完只需 7 分钟javascript

概述

变量,编程语言中咱们用来模拟现实概念的工具,比方说,变量能够表示对象,数组,数字,字符。既然是工具,那么就用工具的适用范围,这个工具在这个适用范围中才有效,在编程语言中,咱们称这个适用范围叫做用域(scope)java

本文会总结 JS 中做用域的相关概念。编程

  1. 什么是做用域
  2. 全局做用域
  3. 函数做用域
  4. 块级做用域
  5. 词法做用域(静态做用域)
  6. 做用域链

1、什么是做用域?

做用域, 英文意思是 scope, 我本身的话来理解就是:windows

变量访问规则的有效范围数组

  1. 做用域外,没法引用做用域内的变量
  2. 离开做用域后,做用域的变量的内存空间会被清除,好比执行完函数或者关闭浏览器。

2、全局做用域

先看一段代码:浏览器

foo = "bar";
console.log(window.foo);  // bar
复制代码

在浏览器环境中声明变量,该变量会默认成为全局 windows 对象的属性。闭包

再看下面这段代码:app

function foo() {
    name = "bar"
}
foo();
console.log(window.name) // bar
复制代码

在函数中,若是不加 bar声明一个变量,那么这个变量会默认被声明为全局变量,若是是严格模式则会报错。编程语言

全局变量能够在任何地方访问到,可是有很大的问题存在。函数

全局变量会形成命名污染,若是在多处对同一个全局变量进行操做,那么就会覆盖全局变量的定义。同时全局变量数量过多,很是不方便管理。

这也是为何像jQuery 和 underscore 这样的类库,要在全局创建 $ 和 _ 变量,其他私有方法属性挂载到该全局变量下。

3、函数做用域

JS 是函数做用域,在函数中定义一个局部变量,那么该变量只能够在该函数做用域中被访问。

function doSomething() {
    var thing = "吃早餐";
}

console.log(thing);  // Uncaught ReferenceError: thing is not defined
复制代码

嵌套函数做用域:

function outter() {
    var thing = "吃早餐";
    function inner() {
        console.log(thing);
    }
    inner();
}

outter();  // 吃早餐
复制代码

在外层函数中,嵌套一个内层函数,那么这个内层函数能够向上访问到外部做用域的变量。

那么,既然内层函数能够访问到外层函数的变量,那么把内层函数返回后呢?

function outter() {
    var thing = "吃晚餐";
    
    function inner() {
        console.log(thing);
    }
    return inner;
}

var foo = outter();
foo();  // 吃晚餐

前面咱们提到了,函数执行完后,函数做用域的变量会被垃圾回收,以上代码能够看出当咱们返回了一个访问了外部函数变量的内部函数,最后外部函数的变量得以保存。

这种当变量存在的函数已经执行结束,但仍在能够访问的方式就是`闭包`。

闭包的具体实践,后续文章会详细说明。
复制代码

4、块级做用域

JS 在 ES6 以前只有函数做用域,没有块级做用域的概念。
看一下代码:

function doSomething() {
    for (var i = 0; i < 10; i++) {
        ...
    }
    console.log(i)
}
doSomething();  // 10
复制代码

因为 JS 没有块级做用域,变量 i 在函数做用域中只有一个,每次 for 循坏都在改变这一个变量。

再看阮一峰老师 ES6 教程里的一段代码:

var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6]();   // 10;
复制代码

以上代码中,因为没有块级做用域,i 变量全局只有一个,当 for 循坏结束,变量 i 的值等于 10, 因此 a[6]() 对应函数内的变量 i 的打印值就是 10。

ES 6 中经过 letconst关键字 引用了块级做用域的概念,所谓块级做用域,就是以 {}包裹的区域。

咱们将阮一峰老师 ES6 教程里的一段代码改为 let 的形式:

var a = [];
for (let i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6]();   // 6;
复制代码

这时,数组内的索引为6函数内的变量打印值为6,每次循环,会建立新的块级做用域,而后从新声明一个新的变量 i;JS 的解释引擎会记住上次循环的变量值,因此可以返回正确的结果。

letconst 会声明一个块级做用域的变量及常量,不易发生变量命名污染的问题,能规避冲突,帮助你写出简洁优雅的代码,建议一直使用。

5、词法做用域(静态做用域)

词法做用域,也能够叫作静态做用域,是什么意思呢?

不管函数在哪里调用,词法做用域都只由函数被声明时所处的位置决定。

既然有静态做用域,那么也有动态做用域。

而动态做用域的做用域则是由函数被调用执行的位置所决定。

var a = 123;

function func1() {
    console.log(a);
}

function func2() {
    var a = 456;
    func1();
}

func2(); // 123
复制代码

以上代码,最后输出结果 a 的值,来自于 func1 声明时所在位置访问到的 a 值 123。

因此 JS 的做用域是静态做用域,也叫词法做用域。

6、做用域链

在 JS 引擎中,经过标识符查找标识符的值,会从当前做用域向上寻找,直到做用域找到第一个匹配的标识符为止。就是 JS 的做用域链

若是嵌套做用域有多个相同标识符,那么,最内部的标识符会覆盖外层标识符,这叫作“遮蔽效应”

var a = 1;
function func1() {
    var a = 2;
    function func2() {
        var a = 3;
        console.log(a);  // 3
    }
    func2();
}

func1(); // 3
复制代码

func2 中变量 a,会从内部开始向外部上层寻找,找到最近的 a 标识符的声明为止。

总结

JS 是一门基于词法做用域(静态做用域)的语言,JS 会沿着做用域链像气泡同样向外部寻找变量声明。

JS 又是函数做用域的语言,在 ES6 中,使用 letconst 关键字后,能让变量处于块做用域中,并且不存在声明提高。

后面的文章会介绍 JS 中的声明提高和闭包,敬请期待。

欢迎关注个人我的公众号“谢南波”,专一分享原创文章。

掘金专栏 JavaScript 系列文章

  1. JavaScript之变量及做用域
  2. JavaScript之声明提高
  3. JavaScript之执行上下文
  4. JavaScript之变量对象
  5. JavaScript原型与原型链
  6. JavaScript之做用域链
  7. JavaScript之闭包
  8. JavaScript之this
  9. JavaScript之arguments
  10. JavaScript之按值传递
  11. JavaScript之例题中完全理解this
  12. JavaScript专题之模拟实现call和apply
相关文章
相关标签/搜索