• 如何在 JavaScript 中使用闭包——初学者指南


    闭包是一个难以理解的 JavaScript 概念,因为很难看出它们是如何实际使用的。

    与函数、变量和对象等其他概念不同,您并不总是认真而直接地使用闭包。你不会说:哦!在这里,我将使用闭包作为解决方案。

    但与此同时,你可能已经使用过这个概念一百次了。了解闭包更多的是确定何时使用闭包,而不是学习一个新概念。

    JavaScript 中的闭包是什么?

    当函数读取或修改在其上下文之外定义的变量的值时,您有一个闭包。

    1. const value = 1
    2. function doSomething() {
    3. let data = [1,2,3,4,5,6,7,8,9,10,11]
    4. return data.filter(item => item % value === 0)
    5. }

    这里函数doSomething使用变量value。但是函数item => item % value === 0也可以这样写:

    1. function(item){
    2. return item % value === 0
    3. }

    value您使用在函数本身之外定义的变量的值。

     

    函数可以脱离上下文访问值

    与前面的示例一样,函数可以访问和使用在其“主体”或上下文之外定义的值,例如:

    1. let count = 1
    2. function counter() {
    3. console.log(count)
    4. }
    5. counter() // print 1
    6. count = 2
    7. counter() // print 2

    这允许我们count从模块中的任何位置修改变量的值。然后当调用计数器函数时,它就会知道如何使用当前值。

    我们为什么要使用函数?

    但是为什么我们在程序中使用函数呢?当然,在不使用我们定义的函数的情况下编写程序是可能的——虽然很困难,但也是可能的。那么我们为什么要创建合适的函数呢?

    想象一段代码做了一些很棒的事情,无论如何,它由 X 行组成。

    /* My wonderful piece of code */
    

    现在假设你必须在程序的各个部分使用这段美妙的代码,你会怎么做?

    “自然”的选择是将这段代码放在一起,形成一个可复用的集合,而这个可复用的集合就是我们所说的函数。函数是在程序中重用和共享代码的最佳方式。

    现在,您可以尽可能多次地使用您的函数。而且,忽略一些特殊情况,调用你的函数 N 次与编写那段精彩的代码N 次是一样的。这是一个简单的替换。

     

    但是闭包在哪里?

    使用反例,让我们将其视为一段精彩的代码。

    1. let count = 1
    2. function counter() {
    3. console.log(count)
    4. }
    5. counter() // print 1

    现在,我们想在很多部分重用它,所以我们将它“包装”在一个函数中。

    1. function wonderfulFunction() {
    2. let count = 1
    3. function counter() {
    4. console.log(count)
    5. }
    6. counter() // print 1
    7. }

    现在我们有什么?一个函数:counter它使用在它之外声明的值count。还有一个值:countwonderfulFunction函数范围内声明但在函数内部使用counter

    也就是说,我们有一个函数使用在其上下文之外声明的值:闭包

    很简单,不是吗?现在,当函数wonderfulFunction执行时会发生什么?执行父函数后,变量count和函数会发生什么?counter

    在其主体中声明的变量和函数“消失”(垃圾收集器)。

    现在,让我们稍微修改一下示例:

    1. function wonderfulFunction() {
    2. let count = 1
    3. function counter() {
    4. count++
    5. console.log(count)
    6. }
    7. setInterval(counter, 2000)
    8. }
    9. wonderfulFunction()

    现在里面声明的变量和函数会发生什么wonderfulFunction

    在这个例子中,我们告诉浏览器counter每 2 秒运行一次。因此,JavaScript 引擎必须保留对函数的引用以及对它使用的变量的引用。即使在父函数wonderfulFunction完成其执行周期后,该函数counter和值计数仍将“存活”

    由于 JavaScript 支持函数的嵌套,因此出现这种具有闭包的“效果”。或者换句话说,函数是语言中的first class citizens,您可以像使用任何其他对象一样使用它们:嵌套、作为参数传递、作为返回值等等。

    我可以用 JavaScript 中的闭包做什么?

    立即调用函数表达式 (IIFE)

    这是一种在 ES5 时代被大量用于实现“模块”设计模式的技术(在本机支持之前)。这个想法是将您的模块“包装”在一个立即执行的函数中。

    1. (function(arg1, arg2){
    2. ...
    3. ...
    4. })(arg1, arg2)

    这使您可以在函数中使用只能由模块本身使用的私有变量——也就是说,它可以模拟访问修饰符。

    1. const module = (function(){
    2. function privateMethod () {
    3. }
    4. const privateValue = "something"
    5. return {
    6. get: privateValue,
    7. set: function(v) { privateValue = v }
    8. }
    9. })()
    10. var x = module()
    11. x.get() // "something"
    12. x.set("Another value")
    13. x.get() // "Another Value"
    14. x.privateValue //Error
     

    函数工厂

    由于闭包而实现的另一种设计模式是“函数工厂”。这是函数创建函数或对象的时候,例如,允许您创建用户对象的函数。

    1. const createUser = ({ userName, avatar }) => ({
    2. id: createID(),
    3. userName,
    4. avatar,
    5. changeUserName (userName) {
    6. this.userName = userName;
    7. return this;
    8. },
    9. changeAvatar (url) {
    10. // execute some logic to retrieve avatar image
    11. const newAvatar = fetchAvatarFromUrl(url)
    12. this.avatar = newAvatar
    13. return this
    14. }
    15. });
    16. console.log(createUser({ userName: 'Bender', avatar: 'bender.png' }));
    17. {
    18. "id":"17hakg9a7jas",
    19. "avatar": "bender.png",
    20. "userName": "Bender",
    21. "changeUsername": [Function changeUsername]
    22. "changeAvatar": [Function changeAvatar]
    23. }
    24. */c

    使用这种模式,您可以从函数式编程中实现一个称为currying的想法。

    Currying

    Currying是一种设计模式(也是某些语言的特征),其中一个函数会立即被求值并返回第二个函数。此模式允许您执行专业化和组合。

    您使用闭包创建这些“curried”函数,定义并返回闭包的内部函数。

    1. function multiply(a) {
    2. return function (b) {
    3. return function (c) {
    4. return a * b * c
    5. }
    6. }
    7. }
    8. let mc1 = multiply(1);
    9. let mc2 = mc1(2);
    10. let res = mc2(3);
    11. console.log(res);
    12. let res2 = multiply(1)(2)(3);
    13. console.log(res2);

    这些类型的函数采用单个值或参数,并返回另一个也接收参数的函数。这是论点的部分应用。也可以使用 ES6 重写此示例。

    1. let multiply = (a) => (b) => (c) => {
    2. return a * b * c;
    3. }
    4. let mc1 = multiply(1);
    5. let mc2 = mc1(2);
    6. let res = mc2(3);
    7. console.log(res);
    8. let res2 = multiply(1)(2)(3);
    9. console.log(res2);

    我们可以在哪里应用柯里化?在组合中,假设您有一个创建 HTML 元素的函数。

    1. unction createElement(element){
    2. const el = document.createElement(element)
    3. return function(content) {
    4. return el.textNode = content
    5. }
    6. }
    7. const bold = crearElement('b')
    8. const italic = createElement('i')
    9. const content = 'My content'
    10. const myElement = bold(italic(content)) // My content
     

    事件监听器

    另一个可以使用和应用闭包的地方是使用 React 的事件处理程序。

    假设您正在使用第三方库来呈现数据集合中的项目。该库公开了一个名为的组件,该组件RenderItem只有一个可用的 prop onClick。该道具不接收任何参数,也不返回值。

    现在,在您的特定应用程序中,您要求当用户单击项目时,应用程序会显示带有项目标题的警报。但是onClick你有空的事件不接受参数——那你能做什么?关闭救援

    1. // Closure
    2. // with es5
    3. function onItemClick(title) {
    4. return function() {
    5. alert("Clicked " + title)
    6. }
    7. }
    8. // with es6
    9. const onItemClick = title => () => alert(`Clcked ${title}`)
    10. return (
    11. <Container>
    12. {items.map(item => {
    13. return (
    14. <RenderItem onClick={onItemClick(item.title)}>
    15. <Title>{item.title}Title>
    16. RenderItem>
    17. )
    18. })}
    19. Container>
    20. )

    在这个简化的示例中,我们创建了一个函数,该函数接收您要显示的标题并返回另一个满足 RenderItem 作为道具接收的函数定义的函数。

    结论

    您甚至可以在不知道您正在使用闭包的情况下开发应用程序。但是,当您创建解决方案时,了解它们的存在以及它们的实际工作方式会开启新的可能性。

    闭包是刚开始时很难理解的概念之一。但是一旦你知道你正在使用它们并理解它们,它就可以让你增加你的工具并推进你的职业生涯。

  • 相关阅读:
    面试了1个月连续失败4次,自动化测试真没想象的那么简单
    【TensorRT】PyTorch模型转换为ONNX及TensorRT模型
    基于Android的旅游管理系统 微信小程序
    关于排序算法
    C++ -- 学习系列 std::deque 的原理与使用
    网络安全工具Docker容器渗透测试工具CDK使用
    vue.js路由如何配置,及全局前置路由守卫(做所有请求登录验证)、路由独享守卫(访问路由前身份验证)
    82-Java异常:概述、分类、认识、异常的默认处理流程
    java计算机毕业设计高校实习实训管理系统源码+mysql数据库+系统+lw文档+部署
    uniapp:启动图 .9png 制作教程
  • 原文地址:https://blog.csdn.net/qq_20173195/article/details/126783667