专栏首页京程一灯一网打尽 JavaScript 的作用域[每日前端夜话0x49]

一网打尽 JavaScript 的作用域[每日前端夜话0x49]

翻译:疯狂的技术宅 https://medium.freecodecamp.org/an-introduction-to-scope-in-javascript-cbd957022652

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

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

全局作用域

在任何函数、块或模块范围之外定义的变量具有全局作用域。可以在程序的任意位置访问全局变量。

当启用模块系统时,创建全局变量会变得困难,但仍然可以做到这一点。可以在 HTML 中定义一个变量,这个变量需要在函数之外声明,这样就可以创建一个全局变量:

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

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

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

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

1window.GLOBAL_DATA = { value: 1 };

这样 GLOBAL_DATA 变量会随处可见。

1console.log(GLOBAL_DATA)

不过你也知道这种做法是不好的。

模块作用域

如果不启用模块,在所有函数之外声明的变量是全局变量。在模块中,在函数外部声明的变量都是隐藏的,除非显式导出,否则不可用于其他模块。

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

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

当前模块可以通过导入来使用其他模块的函数或对象成。

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

在某种程度上,我们可以认为模块是一个自动执行的函数,它将 import 的数据作为输入,然后返回 export 的数据。

函数作用域

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

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

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

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

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

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

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

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

var声明的变量可以在同一作用域下多次重新声明:

1function doSomething(){
2  var x = 1
3  var x = 2;
4  console.log(x);
5}
6doSomething();

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

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

也许我们可以不必关心这一点,因为 var 已经开始变得过时了。

块作用域

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

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

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

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

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

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

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

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

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

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

词法作用域

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

看一下这段代码:

 1(function autorun(){
 2    let x = 1;
 3    function log(){
 4      console.log(x);
 5    };
 6
 7    function run(fn){
 8      let x = 100;
 9      fn();
10    }
11
12    run(log);//1
13})();

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

闭包函数可以访问创建它的作用域,而不是它自己的作用域。

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

作用域链

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

 1let x0 = 0;
 2(function autorun1(){
 3 let x1 = 1;
 4
 5 (function autorun2(){
 6   let x2 = 2;
 7
 8   (function autorun3(){
 9     let x3 = 3;
10
11     console.log(x0 + " " + x1 + " " + x2 + " " + x3);//0 1 2 3
12    })();
13  })();
14})();

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

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

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

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

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

总结

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

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

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

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

本文分享自微信公众号 - 前端先锋(jingchengyideng),作者:京程一灯

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-04-02

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 理解 JavaScript 中的作用域

    作用域是 JavaScript 中的一个重要而又模糊的概念。只有正确使用 JavaScript 作用域,才能使用优秀的设计模式,帮助你规避副作用。本文中,我们将...

    疯狂的技术宅
  • 面试中关于 JavaScript 作用域的 5 个陷阱

    在 JavaScript 中,代码块、函数或模块为变量创建作用域。例如 if 代码块为变量 message 创建作用域:

    疯狂的技术宅
  • 推翻JavaScript中的三座大山:作用域篇

    javascript是一门很有意思的编程语言,融合了很多编程语言的特点。上手特别容易,使用javascript开发项目也是很轻松的一件事。但是当你深入其中,你会...

    疯狂的技术宅
  • 作用域及作用域链的解释说明

    javascript中作用域是指变量与函数可访问的范围。作用域分为两类,一种是全局作用域,一种是局部作用域。全局变量拥有全局作用域,在JavaScript代码中...

    无邪Z
  • JS入门难点解析3-作用域

    (注1:如果有问题欢迎留言探讨,一起学习!转载请注明出处,喜欢可以点个赞哦!) (注2:更多内容请查看我的目录。)

    love丁酥酥
  • JavaScript 作用域不完全指北

    对于几乎所有编程语言,最基本的功能之一就是能够存储变量的值,并且能在之后对这个值进行访问和修改。这样就会带来几个问题,这些变量存储在哪里?程序在需要的时候又是如...

    撸码那些事
  • 稳扎稳打JavaScript(一)——作用域链内存模型

    几个概念 在开始之前,先了解几个概念。 1.1. 作用域 作用域是指当前正在执行的代码能够访问到变量的范围; 每个函数都有各自的作用域,存储函数所有的局部变量...

    大闲人柴毛毛
  • Python变量作用域

      Python 中,程序的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的。

    tonglei0429
  • 深入理解JavaScript作用域

    在上一篇文章 深入理解JavaScript 执行上下文 中提到 只有理解了执行上下文,才能更好地理解 JavaScript 语言本身,比如变量提升,作用域,闭包...

    木子星兮
  • 彻底理解JavaScript作用域

      几乎所有编程语言就是在变量中存储值,并且能读取和修改此值。事实上,在变量中存储值和取出值的能力,给程序赋予了状态。 如果没有这样的概念,一个程序虽然可以执行...

    小周sri的码农

扫码关注云+社区

领取腾讯云代金券