JS 的 9 种作用域,你能说出几种? | 您所在的位置:网站首页 › io流分为几种 › JS 的 9 种作用域,你能说出几种? |
作用域想必大家都知道,就是变量生效的范围,比如函数就会生成一个作用域,声明的变量只在函数内生效。 而这样的作用域一共有 9 种,其中几种绝大多数前端都说不出来。 下面我们就一起过一遍这 9 种作用域吧,看看你知道几种: (为了保证准确性,所有的作用域类型都是通过调试所得) Global 作用域通过 var 声明一个变量,打个断点,可以看到 Scope 里有 Global 类型的作用域,也就是全局作用域,里面保存了变量 a: 在浏览器环境下,可以通过 a 访问全局变量,也可以通过 window.a 访问。 声明个函数,在函数内声明一个变量,调用这个函数的时候,可以看到 Scope 里有 Local 类型的作用域,也就是本地作用域,里面保存了变量 b: 这两种作用域都很常见,没啥好说的。 Block 作用域es6 加入了块语句,它也同样会生成作用域: 如图,会把里面声明的变量 a 放到 Block 作用域内,也就是块级作用域。 if、while、for 等语句都会生成 Block 作用域: 前几种作用域很常规,但下面这种作用域绝大部分前端就不知道了: Script 作用域这段代码大家觉得会生成什么作用域: 很多同学都会说,不是全局作用域么? 那这个现象你能解释么: a、b、c 如果都是全局变量,那在浏览器里就可以通过 window.xx 来访问,但结果 window.a 和 window.b 都是 undefined,而直接访问 a、b 能拿到值。 看下现在的作用域就知道了: 你会发现 let、const 声明的全局变量被放到了 script 作用域,而 var 声明的变量被放到了 global 作用域。 这就是浏览器环境下用 let const 声明全局变量时的特殊作用域,script 作用域。可以直接访问这个全局变量,但是却不能通过 window.xx 访问。 所以你再看到这样的代码,就不奇怪了: window.xxx = xxx;这个 xxx 肯定是通过 let、const 声明的全局变量,需要手动挂到 window 上。 那上面这个 script 作用域在 node 环境里有么? 我们用 node 调试下: 模块作用域同样的代码,在 node 环境下就没有了 Script 作用域,但是多了一个 Local 作用域: 这个 Local 作用域还有 module、exports、require 等变量,这个叫做模块作用域。 这个作用域有些特殊,其实它也是函数作用域。为什么呢?后面会有解释。 说到特殊的作用域,其实还有一些: Catch Block 作用域Catch 语句也会生成一个特殊的作用域,Catch Block 作用域,特点是能访问错误对象: 在 node 里也是一样,只不过还有一层模块作用域: 有同学会问,那 finally 语句呢? 这个就没啥特殊的了,就是 Block 作用域: 类似的还有 With Block: With Block 作用域大家猜下这个 with 语句里的作用域是是啥: 想必你猜到了,with 语句里的作用域就是这个对象: 换成普通的对象更明显一些: 闭包是 JS 的常见概念,它是一个函数返回另一个函数的形式,返回的函数引用了外层函数的变量,就会以闭包的形式保存下来。 比如这样: function fun() { const a = 1; const b = 2; return function () { const c = 2;console.log(a, c); debugger; };} const f = fun();f(); 那闭包的变量怎么保存的呢? 通过 node 可以看到: 通过 Closure 作用域保存了变量 a 的值,这个 Closure 作用域就是闭包的核心。 那为啥只保存了 a 没保存 b、c 呢? c 是返回的函数的作用域里的,不是外部作用域,而 b 则是没用到,所以 Closure 作用域里只保存了 a。 然后执行的时候就会恢复这个 Closure 作用域: 这样函数需要的外部变量都在 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 作用域: 只留下用到的作用域的变量 a、c。 执行的时候就会恢复这两层闭包作用域: 这样函数需要的外部环境一点都不少。 理解了 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(); 有的同学会说,这不是一样么,都会形成闭包。 没错,都会形成闭包,但是保存的变量不一样了: 你会发现它把所有外部的作用域的变量都保存到了 Closure 作用域,包括模块作用域的变量。 为什么呢? 因为它根本不会去分析字符串呀,也没法分析,万一你这段 JS 是动态从服务端获取再 eval 的呢? 没法分析! 没法分析怎么保证代码执行不出错呢? 全部保存不就行了? 所以当返回的函数有 eval 的时候,JS 引擎就会形成特别大的 Closure,会把所有的变量都放到里面。 这样再执行 eval 的时候就不会出错了: 所有的变量都给你了,怎么可能出错呢? 但是这样明显性能不好,会占用更多的内存,所以闭包里尽量不要用 eval。 前面说模块作用域是特殊的函数作用域,为什么这么说呢? 这就与 node 模块的执行机制有关系了。 比如这样一段代码: function func() { require; debugger;}func();执行后发现形成了闭包: 而如果不访问模块作用域的变量,就没有这一层了: 我这明明没有闭包的代码呀! 这就与 node 模块的执行机制有关系了: node 会把模块变为一个函数,它有 exports、require、module、__dirname、__filename 这五个参数,然后传入这五个参数来执行: 所以模块作用域就是个函数作用域而已! 模块里的函数引用模块作用域的变量,再执行,自然就形成了闭包。 Eval 作用域最后一种特殊的作用域就是 eval 作用域了。 比如这样一段代码: eval(` const a = 1; const b = 2; const c = 3;console.log(a,b,c); debugger;`); 执行之后是这样的: 可以看到有单独的 Eval 作用域,eval 的代码里声明的变量都在这个作用域里: JS 总共有 9 种作用域,我们通过调试的方式来分析了下: Global 作用域: 全局作用域,在浏览器环境下就是 window,在 node 环境下是 globalLocal 作用域:本地作用域,或者叫函数作用域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 种作用域,你能说出几种? |
CopyRight 2018-2019 实验室设备网 版权所有 |