目录
前言
如何定义闭包
闭包涉及哪些知识点
作用域与作用域链
词法作用域
题外话,如何欺骗词法作用域?
闭包能干啥?
如何区分闭包?
闭包真的会导致内存泄漏?
首页 web前端 js教程 一起认识闭包

一起认识闭包

Sep 08, 2020 pm 01:25 PM
闭包

一起认识闭包

相关学习推荐:javascript视频教程

前言

闭包 永远都是前端开发者绕不过去的一个坎,不管你喜欢与否,在工作和面试中,都会遇到。每个人对闭包的理解都不尽相同,这里笔者谈谈自身对闭包的理解。(如果与您的理解有出入,请以您自己为准  )

如何定义闭包

在给出定义之前,不妨看看别人是如何定义闭包的:

函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学文献中称为“闭包” -- JavaScript权威指南(第六版)

闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数。 -- JavaScript高级程序设计(第三版)

当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。 -- 你不知道的JavaScript(上卷)

虽然上面的几段话描述起来并不一样,但是您细细品味后还是能找出一些共同点。其中最重要的是不同作用域之间的联系。当然了,您可以直接引用上面的定义(毕竟上面几个定义还是比较权威的),这里笔者比较喜欢最后一段的定义,同时力推《你不知道的JavaScript(上卷)》这本书,值得反复细读。

闭包涉及哪些知识点

光给出定义是远远不够的,还必须探讨内部涉及了哪些知识点。下面是笔者认为有用到的知识点。

作用域与作用域链

嗯,其实笔者知道你们都想到了这点(不会吧,不会有人没想到这点吧)。既然大家都了解作用域。这里就简单描述一下,过一下场即可。

作用域:根据名称查找变量的一套规则。分为三种类型:全局作用域;函数作用域;块作用域。

需要注意的是块作用域,ES6新增的规范。 在花括号{}里面使用let,const定义的变量,都会绑定到该作用范围内,花括号以外的地方无法访问。注意:在花括号开始 到 let变量声明之前,存在暂时性死区(该点不在本文讨论范围)。

作用域链:当不同的作用域 (混~淆~在~一~起~ 呸,不小心出戏了) 圈套在一起时,就形成了作用域链。注意的是,查找方向是从内到外的。

为什么作用域的查找方向是从内到外的呢?这是个很有趣的问题。个人觉得是跟js执行函数的入栈方式决定的(感觉有点偏题了,有兴趣的小伙伴可以去查一下资料)。

词法作用域

函数之所以 可以访问另一个函数作用域的变量(或者说记住当前的作用域并在当前以外的地方访问)的关键点就是词法作用域在起作用。这一点很重要,但不是所有人都知道这个知识点,这里简单探讨一下。

在编程界中,存在两种作用域工作模式,一种是被大多数编程语言所采用的词法作用域;另一种就是与其相反的动态作用域(这个不在本文的讨论范围)。

词法作用域: 变量和块的作用域 在 您编写代码的阶段 就已经确定好了,不会随着调用的对象或者地方的不同而改变(感觉跟this相反)。

要不,举个栗子看看吧:

let a = 1;
function fn(){
    let a = 2;
    function fn2(){
        console.log(a);
    }
 return fn2;
}

let fn3 = fn();
fn3();
登录后复制

从上面的定义可以知道,fn是一个闭包函数,fn3拿到了fn2的指针地址,当fn3执行的时候,其实是执行fn2,而里面的a变量,根据作用域链的查找规则,找到的是fn作用域内的变量a,所以最终的输出是2,不是1。(可以看下图)

一起认识闭包

题外话,如何欺骗词法作用域?

虽然词法作用域是静态的,但依然有办法可以欺骗它,达到动态的效果。

第一种方法是使用eval. eval可以把字符串解析成一个脚本来运行,由于在词法分析阶段,无法预测eval运行的脚本,所以不会对其进行优化分析。

第二种方法是with. with通常被当作重复引用同一个对象中的多个属性的快捷方式,可以不需要重复引用对象本身。with本身比较难掌握,使用不当容易出现意外情况(如下例子),不推荐使用 -.-

function Fn(obj){
    with(obj){
        a = 2;
    }
}

var o1 = {
    a:1
}
var o2 = {
    b:1
}

Fn(o1);
console.log(o1.a); //2
Fn(o2);
console.log(o2.a); //undefined;
console.log(a); //2 a被泄漏到全局里面去了
// 这是with的一个副作用, 如果当前词法作用域没有该属性,会在全局创建一个
登录后复制

闭包能干啥?

闭包的使用场景可多了,平时使用的插件或者框架,基本上都有闭包的身影,可能您没留意过罢了。下面笔者列举一些比较常见的场景。

  1. 模拟私有变量和方法,进一步来说可以是模拟模块化;目前常用的AMD,CommonJS等模块规范,都是利用闭包的思想;

  2. 柯里化函数或者偏函数;利用闭包可以把参数分成多次传参。如下面代码:

// 柯里化函数
function currying(fn){
    var allArgs = [];

    function bindCurry(){
        var args = [].slice.call(arguments);
        allArgs = allArgs.concat(args);
        return bindCurry;
    }
    bindCurry.toString = function(){
        return fn.apply(null, allArgs);
    };

    return bindCurry;
}
登录后复制
  1. 实现防抖或者节流函数;

  2. 实现缓存结果(记忆化)的辅助函数:

// 该方法适合缓存结果不易改变的函数
const memorize = fn => {
    let memorized = false;
    let result = undefined;
    return (...args) => {
        if (memorized) {
            return result;
        } else {
            result = fn.apply(null,args); 
            memorized = true;
            fn = undefined;
            return result;
        }
    };
};
登录后复制

如何区分闭包?

说了那么多,我怎么知道自己写的代码是不是闭包呢?先不说新手,有些代码的确隐藏的深,老鸟不仔细看也可能发现不了。 那有没有方法可以帮助我们区分一个函数是不是闭包呢?答案是肯定的,要学会善于利用周边的工具资源,比如浏览器。

打开常用的浏览器(chrome或者其他),在要验证的代码中打上debugger断点,然后看控制台,在scope里面的Closure(闭包)里面是否有该函数(如下图)。

一起认识闭包

闭包真的会导致内存泄漏?

答案是有可能。内存泄漏的原因在于垃圾回收(GC)无法释放变量的内存,导致运行一段时候后,可用内存越来越少,最终出现内存泄漏的情况。常见的内存泄漏场景有4种:全局变量;闭包引用;DOM事件绑定;不合理使用缓存。其中,闭包导致内存泄漏都是比较隐蔽的,用肉眼查看代码判断是比较难,我们可用借助chrome浏览器的Memory标签栏工具来调试。由于篇幅问题,不展开说明了,有兴趣自己去了解一下如何使用。

想了解更多编程学习,敬请关注php培训栏目!

以上是一起认识闭包的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

<🎜>:泡泡胶模拟器无穷大 - 如何获取和使用皇家钥匙
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系统,解释
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆树的耳语 - 如何解锁抓钩
3 周前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

热门话题

Java教程
1670
14
CakePHP 教程
1428
52
Laravel 教程
1329
25
PHP教程
1276
29
C# 教程
1256
24
C++ lambda 表达式中闭包的含义是什么? C++ lambda 表达式中闭包的含义是什么? Apr 17, 2024 pm 06:15 PM

在C++中,闭包是能够访问外部变量的lambda表达式。要创建闭包,请捕获lambda表达式中的外部变量。闭包提供可复用性、信息隐藏和延迟求值等优势。它们在事件处理程序等实际情况中很有用,其中即使外部变量被销毁,闭包仍然可以访问它们。

C++ 函数中闭包的优点和缺点是什么? C++ 函数中闭包的优点和缺点是什么? Apr 25, 2024 pm 01:33 PM

闭包是一种嵌套函数,它能访问外层函数作用域的变量,优点包括数据封装、状态保持和灵活性。缺点包括内存消耗、性能影响和调试复杂性。此外,闭包还可以创建匿名函数,并将其作为回调或参数传递给其他函数。

解决闭包导致的内存泄漏问题 解决闭包导致的内存泄漏问题 Feb 18, 2024 pm 03:20 PM

标题:闭包引起的内存泄漏及解决方法引言:闭包是JavaScript中一个非常常见的概念,它可以让内部函数访问外部函数的变量。然而,闭包在使用不当的情况下可能导致内存泄漏。本文将探讨闭包引起的内存泄漏问题,并提供解决方法及具体代码示例。一、闭包引起的内存泄漏问题闭包的特性是内部函数可以访问外部函数的变量,这意味着在闭包中引用的变量不会被垃圾回收。如果使用不当,

C++ Lambda 表达式如何实现闭包? C++ Lambda 表达式如何实现闭包? Jun 01, 2024 pm 05:50 PM

C++Lambda表达式支持闭包,即保存函数作用域变量并供函数访问。语法为[capture-list](parameters)->return-type{function-body}。capture-list定义要捕获的变量,可以使用[=]按值捕获所有局部变量,[&]按引用捕获所有局部变量,或[variable1,variable2,...]捕获特定变量。Lambda表达式只能访问捕获的变量,但无法修改原始值。

PHP 函数的链式调用和闭包 PHP 函数的链式调用和闭包 Apr 13, 2024 am 11:18 AM

是的,可以通过链式调用和闭包优化代码简洁性和可读性:链式调用可将函数调用链接为一个流畅接口。闭包可创建可重用代码块,并在函数外部访问变量。

函数指针和闭包对Golang性能的影响 函数指针和闭包对Golang性能的影响 Apr 15, 2024 am 10:36 AM

函数指针和闭包对Go性能的影响如下:函数指针:稍慢于直接调用,但可提高可读性和可复用性。闭包:通常更慢,但可封装数据和行为。实战案例:函数指针可优化排序算法,闭包可创建事件处理程序,但会带来性能损失。

闭包中如何有效避免内存泄漏? 闭包中如何有效避免内存泄漏? Jan 13, 2024 pm 12:46 PM

如何在闭包中阻止内存泄漏的发生?闭包是JavaScript中非常强大的特性之一,它能够实现函数的嵌套和数据的封装。然而,闭包也容易导致内存泄漏的问题,特别是在处理异步和定时器的情况下。本文将介绍如何在闭包中阻止内存泄漏,并提供具体的代码示例。内存泄漏通常发生在不再需要某个对象时,却因为某些原因无法释放其所占用的内存。在闭包中,当函数引用外部的变量,而这些变量

golang函数闭包在测试中的作用 golang函数闭包在测试中的作用 Apr 24, 2024 am 08:54 AM

Go语言函数闭包在单元测试中发挥着至关重要的作用:捕获值:闭包可以访问外部作用域的变量,允许在嵌套函数中捕获和重用测试参数。简化测试代码:通过捕获值,闭包消除了对每个循环重复设置参数的需求,从而简化了测试代码。提高可读性:使用闭包可以组织测试逻辑,使测试代码更清晰、更易于阅读。

See all articles