首页 > 其他分享 >JS 的 9 种作用域,你能说出几种?

JS 的 9 种作用域,你能说出几种?

时间:2022-12-10 11:33:42浏览次数:42  
标签:Closure 闭包 const 函数 作用域 几种 JS 变量

作用域想必大家都知道,就是变量生效的范围,比如函数就会生成一个作用域,声明的变量只在函数内生效。

而这样的作用域一共有 9 种,其中几种绝大多数前端都说不出来。

下面我们就一起过一遍这 9 种作用域吧,看看你知道几种:

(为了保证准确性,所有的作用域类型都是通过调试所得)

Global 作用域

通过 var 声明一个变量,打个断点,可以看到 Scope 里有 Global 类型的作用域,也就是全局作用域,里面保存了变量 a:

JS 的 9 种作用域,你能说出几种?_作用域

在浏览器环境下,可以通过 a 访问全局变量,也可以通过 window.a 访问。

JS 的 9 种作用域,你能说出几种?_Node.js_02

Local 作用域

声明个函数,在函数内声明一个变量,调用这个函数的时候,可以看到 Scope 里有 Local 类型的作用域,也就是本地作用域,里面保存了变量 b:

JS 的 9 种作用域,你能说出几种?_全局变量_03

这两种作用域都很常见,没啥好说的。

Block 作用域

es6 加入了块语句,它也同样会生成作用域:

JS 的 9 种作用域,你能说出几种?_前端_04

如图,会把里面声明的变量 a 放到 Block 作用域内,也就是块级作用域。

if、while、for 等语句都会生成 Block 作用域:

JS 的 9 种作用域,你能说出几种?_作用域_05

JS 的 9 种作用域,你能说出几种?_Node.js_06

前几种作用域很常规,但下面这种作用域绝大部分前端就不知道了:

Script 作用域

这段代码大家觉得会生成什么作用域:

JS 的 9 种作用域,你能说出几种?_Node.js_07

很多同学都会说,不是全局作用域么?

那这个现象你能解释么:

JS 的 9 种作用域,你能说出几种?_Node.js_08

a、b、c 如果都是全局变量,那在浏览器里就可以通过 window.xx 来访问,但结果 window.a 和 window.b 都是 undefined,而直接访问 a、b 能拿到值。

看下现在的作用域就知道了:

JS 的 9 种作用域,你能说出几种?_JavaScript_09

你会发现 let、const 声明的全局变量被放到了 script 作用域,而 var 声明的变量被放到了 global 作用域。

这就是浏览器环境下用 let const 声明全局变量时的特殊作用域,script 作用域。可以直接访问这个全局变量,但是却不能通过 window.xx 访问。

所以你再看到这样的代码,就不奇怪了:

window.xxx = xxx;

这个 xxx 肯定是通过 let、const 声明的全局变量,需要手动挂到 window 上。

那上面这个 script 作用域在 node 环境里有么?

我们用 node 调试下:

模块作用域

同样的代码,在 node 环境下就没有了 Script 作用域,但是多了一个 Local 作用域:

JS 的 9 种作用域,你能说出几种?_前端_10

这个 Local 作用域还有 module、exports、require 等变量,这个叫做模块作用域。

这个作用域有些特殊,其实它也是函数作用域。为什么呢?后面会有解释。

说到特殊的作用域,其实还有一些:

Catch Block 作用域

Catch 语句也会生成一个特殊的作用域,Catch Block 作用域,特点是能访问错误对象:

JS 的 9 种作用域,你能说出几种?_JavaScript_11

在 node 里也是一样,只不过还有一层模块作用域:

JS 的 9 种作用域,你能说出几种?_Node.js_12

有同学会问,那 finally 语句呢?

这个就没啥特殊的了,就是 Block 作用域:

JS 的 9 种作用域,你能说出几种?_Node.js_13

JS 的 9 种作用域,你能说出几种?_JavaScript_14

类似的还有 With Block:

With Block 作用域

大家猜下这个 with 语句里的作用域是是啥:

JS 的 9 种作用域,你能说出几种?_全局变量_15

想必你猜到了,with 语句里的作用域就是这个对象:

JS 的 9 种作用域,你能说出几种?_作用域_16

换成普通的对象更明显一些:

JS 的 9 种作用域,你能说出几种?_全局变量_17

Closure 作用域

闭包是 JS 的常见概念,它是一个函数返回另一个函数的形式,返回的函数引用了外层函数的变量,就会以闭包的形式保存下来。

比如这样:

function fun() {
const a = 1;
const b = 2;
return function () {
const c = 2;

console.log(a, c);
debugger;
};
}

const f = fun();
f();

那闭包的变量怎么保存的呢?

通过 node 可以看到:

JS 的 9 种作用域,你能说出几种?_全局变量_18

通过 Closure 作用域保存了变量 a 的值,这个 Closure 作用域就是闭包的核心。

那为啥只保存了 a 没保存 b、c 呢?

c 是返回的函数的作用域里的,不是外部作用域,而 b 则是没用到,所以 Closure 作用域里只保存了 a。

然后执行的时候就会恢复这个 Closure 作用域:

JS 的 9 种作用域,你能说出几种?_JavaScript_19

这样函数需要的外部变量都在 Closure 作用域里,啥也没丢,可以正常执行。

是不是很巧妙!

这就是闭包的核心。

当然,Closure 作用域也可以多层,比如这样:

function fun() {
const a = 1;
const b = 2;
return function () {
const c = 2;
const d = 4;

return function () {
const e = 5;

console.log(a, c, e);
};
};
}

const f = fun()();
f();

用到的外部变量分别在两个作用域里,那就会生成两个 Closure 作用域:

JS 的 9 种作用域,你能说出几种?_前端_20

只留下用到的作用域的变量 a、c。

执行的时候就会恢复这两层闭包作用域:

JS 的 9 种作用域,你能说出几种?_作用域_21

这样函数需要的外部环境一点都不少。

理解了 Closure 作用域,就真正理解了闭包。

闭包里还有一种特殊情况,就是 eval:

上面的代码如果我改动一下,把打印语句变成 eval,会发生什么呢?

function fun() {
const a = 1;
const b = 2;
return function () {
const c = 2;
const d = 4;

return function () {
const e = 5;

eval("console.log(a, c, e);");
};
};
}

const f = fun()();
f();

有的同学会说,这不是一样么,都会形成闭包。

没错,都会形成闭包,但是保存的变量不一样了:

JS 的 9 种作用域,你能说出几种?_JavaScript_22

你会发现它把所有外部的作用域的变量都保存到了 Closure 作用域,包括模块作用域的变量。

为什么呢?

因为它根本不会去分析字符串呀,也没法分析,万一你这段 JS 是动态从服务端获取再 eval 的呢?

没法分析!

没法分析怎么保证代码执行不出错呢?

全部保存不就行了?

所以当返回的函数有 eval 的时候,JS 引擎就会形成特别大的 Closure,会把所有的变量都放到里面。

这样再执行 eval 的时候就不会出错了:

JS 的 9 种作用域,你能说出几种?_前端_23

所有的变量都给你了,怎么可能出错呢?

但是这样明显性能不好,会占用更多的内存,所以闭包里尽量不要用 eval。

前面说模块作用域是特殊的函数作用域,为什么这么说呢?

这就与 node 模块的执行机制有关系了。

比如这样一段代码:

function func() {
require;
debugger;
}
func();

执行后发现形成了闭包:

JS 的 9 种作用域,你能说出几种?_全局变量_24

而如果不访问模块作用域的变量,就没有这一层了:

JS 的 9 种作用域,你能说出几种?_JavaScript_25

我这明明没有闭包的代码呀!

这就与 node 模块的执行机制有关系了:

node 会把模块变为一个函数,它有 exports、require、module、__dirname、__filename 这五个参数,然后传入这五个参数来执行:

JS 的 9 种作用域,你能说出几种?_作用域_26

所以模块作用域就是个函数作用域而已!

模块里的函数引用模块作用域的变量,再执行,自然就形成了闭包。

Eval 作用域

最后一种特殊的作用域就是 eval 作用域了。

比如这样一段代码:

eval(`
const a = 1;
const b = 2;
const c = 3;

console.log(a,b,c);
debugger;
`);

执行之后是这样的:

JS 的 9 种作用域,你能说出几种?_全局变量_27

可以看到有单独的 Eval 作用域,eval 的代码里声明的变量都在这个作用域里:

JS 的 9 种作用域,你能说出几种?_前端_28

总结

JS 总共有 9 种作用域,我们通过调试的方式来分析了下:

  • Global 作用域: 全局作用域,在浏览器环境下就是 window,在 node 环境下是 global
  • Local 作用域:本地作用域,或者叫函数作用域
  • Block 作用域:块级作用域
  • Script 作用域:let、const 声明的全局变量会保存在 Script 作用域,这些变量可以直接访问,但却不能通过 window.xx 访问
  • 模块作用域:其实严格来说这也是函数作用域,因为 node 执行它的时候会包一层函数,算是比较特殊的函数作用域,有 module、exports、require 等变量
  • Catch Block:catch 语句的作用域可以访问错误对象
  • With Block 作用域:with 语句的作用域就是传入的对象的值
  • Closure 作用域:函数返回函数的时候,会把用到的外部变量保存在 Closure 作用域里,这样再执行的时候该有的变量都有,这就是闭包。eval 的闭包比较特殊,会把所有变量都保存到 Closure 作用域
  • Eval 作用域:eval 代码声明的变量会保存在 Eval 作用域

上面这些都是调试得出的,是 JS 引擎执行代码时的真实作用域。

JavaScript 的 9 种作用域,你能说出几种?

标签:Closure,闭包,const,函数,作用域,几种,JS,变量
From: https://blog.51cto.com/u_15506823/5927381

相关文章

  • 软件测试缺陷管理BUG的几种状态
    软件的缺陷是软件开发过程中的重要属性,它提供了许多信息。不同成熟度的软件组织采用不同的方式管理缺陷。低成熟度的软件组织会记录缺陷,并跟踪缺陷纠正过程。高成熟度......
  • Java 变量作用域、构造方法官方教程
    一、变量作用域Java中的变量有3种:......
  • js模块化
    OverridetheentrypointofanimageIntroducedinGitLabandGitLabRunner9.4.Readmoreaboutthe extendedconfigurationoptions.Beforeexplainingtheav......
  • JavaScript:变量的作用域,window对象,关键字var/let与function
    为什么要将这些内容放在一起,因为他们都跟初始化有关系,我们慢慢说吧。我们在代码中,都会声明变量、函数和对象,然后由浏览器解释器(下面简称浏览器)执行;我们还说过,变量和对象......
  • JS 数组方法 every 和 some 的区别
    OverridetheentrypointofanimageIntroducedinGitLabandGitLabRunner9.4.Readmoreaboutthe extendedconfigurationoptions.Beforeexplainingtheav......
  • fastadmin中js是如何调用的
    fastadmin中js是如何调用的时间  2019-12-10标签 fastadmin 如何 调用 栏目 JavaScript 繁體版原文   https://www.cnblogs.com/zmdComeOn/p/10861135.html......
  • 前端开发系列019-基础篇之JavaScript和JSON
    title:'前端开发系列019-基础篇之JavaScript和JSON'tags:-javaScript系列categories:[]date:2017-06-1620:20:13本文输出和JSON有关的以下内容>❐JSON......
  • 移动端js下载 图片
    https://juejin.cn/post/6844903763312902151/***base64转blob*@param{String}codebase64个数数据*@return{undefined}*@authorxxx*/base64ToBlob(c......
  • JVM 命令 jps jstat jstack
    jps显示出所有的JAVA进程以及PIDjstat查看堆内存各部分的使用量,以及加载类的数量jstack–用来查看堆栈信息jps-lvmVtop-Hppid将线程转换为16进制,因为堆......
  • Spring注入Bean的几种方式
    属性注入属性注入即通过setXXX()方法注入bean的属性值或依赖对象。由于属性注入方式具有可选择性和灵活性高的特点,因此它也是实际开发中最常用的注入方式。Spring首先......