likes
comments
collection
share

作用域和闭包,两大抽象概念如何简单理解!

作者站长头像
站长
· 阅读数 60

作用域及作用域链

作用域分为全局作用域和函数作用域,es6 后又引入了块级作用域的概念。 作用域在代码定义的时候就产生了且不会改变,全局变量作用在全局作用域中,而函数内声明的局部变量是在函数作用域中,外部作用域无法访问内部的函数作用域中的变量。

作用域: 变量的可访问范围,控制变量的可访问性与生命周期。

作用域的作用: 限制变量的访问权限和访问顺序,隔离变量使不同作用域下的同名变量不会产生冲突。

JS的生命周期

1、JS的生命周期在声明时初始化 2、局部变量在函数执行结束后销毁 3、全局变量在html页面关闭时销毁

作用域链: # 作用域链 -->一种查找的关系链(也就是查找变量的顺序)

  1. 函数在执行前预编译,会创建执行上下文对象
  2. 变量环境中有一个内定的outer指针属性,用于指向该函数的外层作用域
  3. outer的指向是根据词法作用域来定的

词法作用域:一个域所处的环境,并不是自己的作用域。是由函数声明的位置来决定的

例子1:

 var x = 10
 function foo() {
   console.log(x)
 }
 function bar(f) {
   var x = 20
   foo()//即调用函数 foo(),访问变量 x,其自身函数作用域没有向外访问全局作用域,x 值为10
 }
 bar(foo)//10

调用 bar(foo),执行 bar 函数,在其内部调用了 foo 函数,但是 foo 函数是在bar 函数外部定义的,所以 foo 函数作用域与 bar 函数作用域不嵌套,是同级的,访问 x 变量,foo 函数作用域中没有 x 变量,因为向外查找,外部全部作用域中有,因此 打印 x 值为10。

function bar(){
    console.log(a)
}
function foo(){
    var a = 100
    bar()
}
var a = 200
foo()

// 为什么会输出200?? // bar()现在函数中查找但是bar()中没有声明a, // 所以会通过函数上下文对象中的outer指针指向查找a的值, // 然而outer的指向是通过词法作用域来定的 // 所以会指向全局,而不是foo()中的a

由此可见,外部作用域是无法访问函数内部作用域的,当访问的变量在作用域链中没有找到时会报错,定义的全局变量 a 作用在全局作用域中,其值是引用对象类型的数据.

闭包

什么是闭包? 红宝书上解释:

闭包 是指有权访问另外一个函数作用域中的变量的函数.闭包就是能够读取其他函数内部变量的函数

MDN 上解释:

闭包是由函数以及创建该函数的词法环境组合而成。这个环境包含了这个闭包创建时所能访问的所有局部变量。

闭包有什么作用?

1、 使函数内部的变量在函数执行完毕后仍然存活在内存中(延长了局部变量的生命周期) 2、 让函数外部可以操作(读/写)到函数内部的数据(变量/函数)

何时会出现闭包

根据js词法作用域的规则,内部函数总是能访问外部函数中的变量,当调用一个外部函数返回的一个内部函数后,即使外部函数执行已经结束了,但是内部函数引用了外部函数中的变量也依旧需要被保存在内存中,我们把这些变量的集合叫做闭包。

在函数体外部通过闭包可以访问到函数体内部的变量,可以通过下面的例子感受: 例子1

var a=100
function fo1() {
    var a = 2
    function fo2() {
        a++
        console.log(a)
    }
    return fo2
}
var f=fo1()
f()//3
f()//4
var f2=fo1()
f2()// 3

例子 1 中 fo2 作为 fo1 函数的返回值,在全局作用域中可以被调用,且函数体中使用了外部函数的局部变量 a 值为 2,因此本应该执行完 fo1就立马销毁的局部变量 a 被保存了下来,且通过外部也可以改变其值。

Attention: f 和 f2 是两个闭包,彼此不会互相影响。闭包的数量与外部函数被调用的次数有关。

闭包不会造成内存泄漏

先了解两个概念

1、 内存泄漏:不再用到的内存,没有及时释放,就叫做内存泄漏(memory leak)。对于持续运行的服务进程(daemon),必须及时释放不再用到的内存。否则,内存占用越来越高,轻则影响系统性能,重则导致进程崩溃。 2、 内存溢出:一种程序运行出现的错误,当程序运行需要的内存超过了剩余的内存时就会抛出内存溢出的错误。

闭包并不会引起内存泄漏,只是由于IE9之前的版本对JScript对象和COM对象使用不同的垃圾收集,从而导致内存无法进行回收,这是IE的问题,与闭包并没关系。

闭包应用场景

  1. 实现共有变量(企业模块开发)在企业级的模块开发中,闭包可以用来创建模块间的共有变量,而又不让这些变量暴露给全局作用域,从而减少命名冲突和潜在的错误。这样做的好处是能够保持代码的整洁性和模块间的独立性。
  2. 做缓存(闭包不会被销毁) 闭包可以用来实现函数结果的缓存,这对于那些计算量大或耗时的操作特别有用,可以避免重复计算,提高程序性能。
  3. 封装模块,防止全局变量污染 闭包可以用来创建独立的模块,确保模块内部的变量和函数不会影响到全局作用域,从而减少全局变量污染的问题。

小结

虽然闭包在现实的应用场景中会看起来很复杂,但是在了解完闭包的底层原理后,我们就能够更加清晰地理解闭包在何时能够派上用场,该使用何种结构构造使用闭包的条件

转载自:https://juejin.cn/post/7372813290650599439
评论
请登录