在 JavaScript 中,作用域(Scope)指的是变量和函数在代码中可访问的范围。作用域规定了在代码中定义的变量在何处以及在何时可被访问。
JavaScript 采用的是词法作用域(Lexical Scope)模型,也称为静态作用域模型。这意味着作用域是在代码编写阶段确定的,而不是在运行时确定的。
JavaScript 中有以下几种类型的作用域:
其实这里还有其他作用域.
1 | function a(x = 1) { |
x = 1 这里也是一个单独的作用域。
作用域链
作用域链(Scope Chain)是由嵌套的作用域组成的,它决定了变量查找的顺序。当在一个作用域中访问一个变量时,JavaScript 引擎会先在当前作用域查找,如果找不到,则会向上一级作用域继续查找,直到找到该变量或达到全局作用域。如果变量在任何作用域中都找不到,则会引发一个错误。
好了,这里大概了解了作用域的流程。
词法(Lexical) 指的是定义某个事物。
任何创建文字、表达式或变量的声明都叫词法。
词法作用域(Lexical Scope) 是定义表达式并能被访问的区间。
一个声明(定义变量、函数等)的词法作用域就是它被定义时所在的作用域。
词法作用域的本质是什么?词法作用域(Lexical Scope) 是定义表达式并能被访问的区间。
就是定义了程序的访问范围。
1 | const g = 'g'; |
词法作用域决定了你定义的变量如何访问,比如说 function d 和 function e.
他们内部的变量定义的词法作用域就在内部,也就是 d1 只在 d 内部有效, e1 只在 e 内部有效。
所以 d 无法访问 e1, e 无法访问得 d1。
换言之,只有词法作用域内的代码才可以访问该作用域内部的代码。
比如 g 在所有方法中都能调用。
词法作用域是必包的前提,我们来看一个必包.
1 | function tommyWords() { |
name 是在 tommyWords 的词法作用域下,也就是说只有返回的匿名函数能访问,形成了一个私有变量。
这就是必包的一个特性,私有变量,原生js并没有这个能力,但是借助必包就有了。
第二个点就是,缓存数据,这一点就需要知道垃圾回收的机制.
JavaScript 中的垃圾回收(Garbage Collection)是一种自动管理内存的机制,它负责检测不再使用的对象,并自动释放它们所占用的内存空间。垃圾回收器会周期性地扫描内存中的对象,并标记哪些对象是可达的(仍然被引用),哪些对象是不可达的(没有引用指向它们)。然后,垃圾回收器会清除不可达对象所占用的内存,以便可以被再次使用。
引用计数(Reference Counting):这是一种最简单的垃圾回收算法。它通过在对象上维护一个引用计数器,每当有新的引用指向对象时,计数器加1,当引用失效时,计数器减1。当计数器为0时,表示该对象不再被引用,即为不可达对象,可以被回收。然而,引用计数算法无法解决循环引用的情况,即使对象之间互相引用,但它们与程序的其他部分没有联系,也会被认为是垃圾并被回收。
标记清除(Mark and Sweep):这是一种常用的垃圾回收算法,用于解决引用计数算法无法处理的循环引用问题。标记清除算法分为两个阶段:标记阶段和清除阶段。在标记阶段,垃圾回收器会从根对象(全局对象、活动函数的变量等)开始遍历,标记所有可达的对象。在清除阶段,垃圾回收器会扫描整个堆内存,清除未被标记的对象,即不可达对象。被清除的内存空间会被标记为可再分配的,以便将来可以被新的对象使用。
比如说
1 | function a() { |
那么当 a() 调用完,就没有地方可以引用a1了,也就是无法标记,所以会被清除了,再来看必包。
1 | function a() { |
这样只要o,还有人调用,那么 a1就不会清除,但是如果 o1 不再有人调用,a1还是会被清除。
如果 o 不是常量,继续赋值 o = a(),那么也会清理。
所以必包中的变量不是不会被清除,还是看匿名方法是否还有引用。
但是如果你一致有引用,比如说
1 | function a() { |
这也是一种缓存。
拆分变量
比如说我有一个 request 模块,需要传入 url, callback。如果我需要不停的调用这个方法,那么每次都需要传入这两个参数,使用必包呢?
1 | function getDate(url) { |
这还有其他使用方式,大概是类似的使用方式。
必包提供了私有变量,拆分变量,状态管控,模块化开发,事件回调等等功能。