一网打尽 JavaScript 的做用域

一网打尽 JavaScript 的做用域

翻译:疯狂的技术宅
https://medium.freecodecamp.o...​​-javascript-cbd957022652

本文首发微信公众号:jingchengyideng
欢迎关注,天天都给你推送新鲜的前端技术文章javascript


做用域决定了变量的生命周期和可见性,变量在做用域范围以外是不可见的。前端

JavaScript 的做用域包括:模块做用域,函数做用域,块做用域,词法做用域和全局做用域。java

全局做用域

在任何函数、块或模块范围以外定义的变量具备全局做用域。能够在程序的任意位置访问全局变量。程序员

当启用模块系统时,建立全局变量会变得困难,但仍然能够作到这一点。能够在 HTML 中定义一个变量,这个变量须要在函数以外声明,这样就能够建立一个全局变量:面试

<script>
  let GLOBAL_DATA = { value : 1};
</script>
console.log(GLOBAL_DATA);

当没有模块系统时,建立全局变量会容易不少。在任何文件中的函数外声明的变量都是全局变量。segmentfault

全局变量贯穿于程序的整个生命周期。微信

另外一种建立全局变量的方法是在程序的任意位置使用 window 全局对象:闭包

window.GLOBAL_DATA = { value: 1 };

这样 GLOBAL_DATA 变量会随处可见。异步

console.log(GLOBAL_DATA)

不过你也知道这种作法是很差的。ide

模块做用域

若是不启用模块,在全部函数以外声明的变量是全局变量。在模块中,在函数外部声明的变量都是隐藏的,除非显式导出,不然不可用于其余模块。

导出使函数或对象可用于其余模块。在这个例子中,我从模块文件 sequence.js 中导出了一个函数:

// in sequence.js
export { sequence, toList, take };

当前模块能够经过导入来使用其余模块的函数或对象成。

import { sequence, toList, toList } from "./sequence";

在某种程度上,咱们能够认为模块是一个自动执行的函数,它将 import 的数据做为输入,而后返回 export 的数据。

函数做用域

函数做用域意味着在函数中定义的参数和变量在函数内的任何位置均可见,可是在函数外部不可见。

下面是一个自动执行的函数,被称为IIFE。

(function autoexecute() {
    let x = 1;
})();
console.log(x);
//Uncaught ReferenceError: x is not defined

IIFE 的意思是当即调用函数表达式,是一个在定义后当即运行的函数。

var 声明的变量只有函数做用域。更重要的是,用 var 声明的变量被提高到其做用域的顶部。经过这种方式,能够在声明以前访问它们。看看下面的代码:

function doSomething(){
  console.log(x);
  var x = 1;
}
doSomething(); //undefined

这种事不会发生在 let 中。用 let 声明的变量只能在定义后访问。

function doSomething(){
  console.log(x);
  let x = 1;
}
doSomething();
//Uncaught ReferenceError: x is not defined

var 声明的变量能够在同一做用域下屡次从新声明:

function doSomething(){
  var x = 1
  var x = 2;
  console.log(x);
}
doSomething();

letconst 声明的变量不能在同一做用域内从新声明:

function doSomething(){
  let x = 1
  let x = 2;
}
//Uncaught SyntaxError: Identifier 'x' has already been declared

也许咱们能够没必要关心这一点,由于 var 已经开始变得过期了。

块做用域

块做用域用花括号定义。它由 {} 分隔。

letconst 声明的变量能够受到块做用域的约束,只能在定义它们的块中访问。

思考下面这段关于 let 块范围的代码:

let x = 1;
{ 
  let x = 2;
}
console.log(x); //1

相反,var 声明不受块做用域的约束:

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

另外一个常见问题是在循环中使用相似 setTimeout() 的异步操做。下面的循环代码将显示五次数字 5。

(function run(){
    for(var i=0; i<5; i++){
        setTimeout(function logValue(){
            console.log(i);         //5
        }, 100);
    }
})();

带有 let 声明的 for 循环语句在每次循环都会建立一个新的变量并设置到块做用域。下一段循环代码将会显示 0 1 2 3 4 5

(function run(){
  for(let i=0; i<5; i++){
    setTimeout(function log(){
      console.log(i); //0 1 2 3 4
    }, 100);
  }
})();

词法做用域

词法做用域是内部函数访问定义它的外部做用域的能力。

看一下这段代码

(function autorun(){
    let x = 1;
    function log(){
      console.log(x);
    };
    
    function run(fn){
      let x = 100;
      fn();
    }
    
    run(log);//1
})();

log 函数是一个闭包。它从父函数 autorun() 引用 x 变量,而不是 run() 函数中的 x 变量。

闭包函数能够访问建立它的做用域,而不是它本身的做用域。

autorun() 的局部函数做用域是 log() 函数的词法做用域。

做用域链

每一个做用域都有一个指向父做用域的连接。当使用变量时,JavaScript 会向下查看做用域链,直到它找到所请求的变量或者到达全局做用域(即做用域链的末尾)。
看下面这个例子

let x0 = 0;
(function autorun1(){
 let x1 = 1;
  
 (function autorun2(){
   let x2 = 2;
  
   (function autorun3(){
     let x3 = 3;
      
     console.log(x0 + " " + x1 + " " + x2 + " " + x3);//0 1 2 3
    })();
  })();
})();

内部函数 autorun3() 能够访问本地 x3 变量。还能够从外部函数访问变量 x1x2 和全局变量 x0

若是找不到变量,它将在严格模式下返回错误。

"use strict";
x = 1;
console.log(x)
//Uncaught ReferenceError: x is not defined

非严格模式也被称为“草率模式”,它会草率的建立一个全局变量。

x = 1;
console.log(x); //1

总结

在全局做用域中定义的变量可在程序的任何位置使用。

在模块中,在函数外部声明的变量都是隐藏的,除非被显式导出,不然不可用于其余模块。

函数做用域意味着函数中定义的参数和变量在函数的任意位置均可见

letconst 声明的变量具备块做用域。 var 没有块做用域。


本文首发微信公众号:jingchengyideng

欢迎扫描二维码关注公众号,天天都给你推送新鲜的前端技术文章

欢迎扫描二维码关注公众号,天天都给你推送新鲜的前端技术文章


欢迎继续阅读本专栏其它高赞文章:

相关文章
相关标签/搜索