这样理解闭包是正确的吗?

闭包相对来说一直是一个比较难理解的点, 为什么说比较难理解, 我这边直接引入MDN对他的说明: 闭包是一个函数及捆绑周边状态环境的引用组合, 闭包可以让开发者从内部函数访问外部函数的作用域.

这一段话其实也就说明了闭包是个什么东西, 包括他有什么作用. 但如果只是光看这个概念的文字解释, 其实很难理解这到底是个什么东西

那么我们针对于这段话拆解出几个关键词: 函数, 周边状态环境(词法环境), 内部函数, 外部函数, 作用域

然后我们针对于每个词做一些理解,

  • 函数: 这个就不用多说了
  • 词法环境: 是js在代码编译阶段记录变量声明, 函数声明, 函数声明的形参合计(将简单一些就是用来存放变量函数声明, 代码执行的时候会从词法环境里面去拿变量值和函数)
  • 内部函数, 外部函数: 就是字面的意思

我们主要需要了解一下最后一个关键词: 作用域

作用域

当前执行上下文, 值和表达式在其中可见或可被访问, 子作用域可以访问父作用域, 反之则不行(也就是作用域链)

JS常见的作用域分为以下三种

  • 全局作用域: 在浏览器环境下就是window, node环境下就是global, 也就是最外层作用域, 一直存在
  • 函数作用域: 函数被定义时创建
  • 块级作用域(ES6新增): 包含在{}内的作用域

那么说了这么多的概念, 作用域是用来做什么的 -> 最主要的场景与功能: 模块化防止全局变量污染

假设我们只有一个全局作用域, 那么所有的变量都要放在全局作用域下, 包括一些函数内部的变量, 比如说一些公共的命名currentTime, 如果说没有作用域这个概念, 那么随着我们的带没带量越来越大, 我们的命名也会越来越复杂

解释完作用域, 我们再看作用域链: 也就是说, 当我们在一个作用域内使用变量时, 由于他可以访问副作用域的变量, 所以他会一直向上查找, 但比如说我们想在全局范围内console一个函数内申明的变量, 就会查找不到该变量抛出错误

闭包

看完作用域后, 我们再看一下闭包, 至此我们对于闭包的几个关键词都已经有了了解, 把MDN的解释简化一下, 其实就是说闭包可以访问函数作用域内的变量, 然后这个访问是通过函数的内部函数实现的, 这边可能会有些绕, 那么我们来看一段代码

function out(){
    let outFunctionVal = 0
    function inner(){
        console.log(outFunctionVal)
    }
}
console.log(outFunctionVal)

那么我们其实可以看到, 当我们想拿到outFunctionVal这个值的时候, 我们在全局作用域下是拿不到的, 我们在里面定于了另一个函数, 通过作用域链向上查找, 取到了变量. 那么这边我为什么不直接在out这个方法里面进行输出, 还要多定义一个内部函数呢

闭包的功能: 私有变量

这里我们再看另一段代码

function MyName() {
    let name = 'lili';
    return {
        getName() {
            return name;
        }
    }
}

const myName = MyName();
// 只能通过getName访问对应的名字,别的方式访问不到
console.log(myName.getName()); // lili

这里其实就是闭包的主要功能之一, 维持私有变量. 可能看起来多此一举, 但是我们不妨设想一下, 你需要开发一个模块, 需要提供一个金额, 并提供该金额的set, get方法, 并且你不希望这个金额被通过其他方式访问或者修改的时候, 就可以使用闭包去实现

闭包的其他功能

那么除了私有变量之外, 这边参考了许多文章, 对于闭包的其他功能其实并没有一个很好的总结, 更多的其实是我们可以用闭包来实现的一些功能, 例如使用闭包绑定事件处理程序, 使用闭包来执行异步编程, 使用闭包来实现函数的柯里化, 使用闭包来实现防抖节流, 诸如此类, 其实都是对应的功能中含有闭包的写法, 关于这一块闭包其他的作用如果有一个比较好的总结可以评论一下

闭包的缺点

性能的消耗

闭包在处理速度和内存消耗方面有负面影响

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
  this.getName = function () {
    return this.name;
  };

  this.getMessage = function () {
    return this.message;
  };
}

例如这个方法, 每次新建该对象时, 方法都会被重新赋值, 这就是闭包对于性能消耗的一个例子

下面是更改后的写法


function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype.getName = function () {
  return this.name;
};
MyObject.prototype.getMessage = function () {
  return this.message;
};

闭包的内存泄漏

闭包的另一个缺点是有可能会造成内存泄漏, 也就是分配的内存空间在使用完毕后并没有被释放, 我们直接举个简单的例子

function foo () {
    var name = 'foo'
    var age = 20

    function bar () {
        console.log(name)
        console.log(age)
    }
    return bar
}

var fn = foo()
fn()

bar函数中存在name, age的变量引用, 根据引用计数法, foo函数已经被调用, 那么他里面的值已经不再需要了, 但是name和age的引用数却不为0, 所以垃圾回收机制是没有办法释放这块内存的, 从而就会导致内存泄漏

综上, 如果有什么不对的内容可以帮忙评论改正一下, 万分感谢

原文链接:https://juejin.cn/post/7344274333890723867 作者:资深摸鱼师

(0)
上一篇 2024年3月11日 上午10:05
下一篇 2024年3月11日 上午10:15

相关推荐

发表回复

登录后才能评论