> 웹 프론트엔드 > JS 튜토리얼 > 클로저에 대해 함께 알아볼까요?

클로저에 대해 함께 알아볼까요?

coldplay.xixi
풀어 주다: 2020-09-08 14:00:27
앞으로
2619명이 탐색했습니다.

ㅋㅋㅋ ,을 만나게 됩니다. 클로저에 대한 이해는 사람마다 다릅니다. 여기서는 클로저에 대한 내 자신의 이해에 대해 이야기하겠습니다. (이해와 불일치가 있는 경우 직접 참조하세요.)

클로저에 대해 함께 알아볼까요?클로저 정의 방법

정의를 제공하기 전에 다른 사람들이 클로저를 어떻게 정의하는지 살펴보는 것이 좋습니다.

함수 객체는 범위를 통과할 수 있습니다. 체인은 서로 관련되어 있으며 함수 본문 내부의 변수는 함수 범위에 저장될 수 있습니다. 이 기능을 컴퓨터 과학 문헌에서는 "클로저"라고 합니다. - The Definitive Guide to JavaScript (6th Edition)

Closure는 A를 의미합니다. 다른 함수의 범위에 있는 변수에 액세스할 수 있는 함수입니다. 클로저를 생성하는 일반적인 방법은 다른 함수 내에 함수를 생성하는 것입니다. --JavaScript를 사용한 고급 프로그래밍(제3판)

함수가 현재 어휘 범위 밖에서 실행되더라도 함수가 자신이 속한 어휘 범위를 기억하고 액세스할 수 있을 때 클로저가 발생합니다. -- 당신이 모르는 JavaScript (1권)

위 단락의 설명은 다르지만, 주의 깊게 맛보면 몇 가지 공통점을 찾을 수 있습니다. 가장 중요한 것은

다른 범위 간의 연결

입니다. 물론 위의 정의를 직접 인용할 수도 있다(결국 위의 정의는 상대적으로 권위가 있다). 여기서 저자는 마지막 문단의 정의를 선호하며, 책 『당신이 모르는 자바스크립트(1권)』도 강력 추천한다. )'을 주의 깊게 반복해서 읽어 볼 가치가 있습니다.

클로저에 어떤 지식 포인트가 포함되어 있는지
단순히 정의하는 것만으로는 충분하지 않으며 내부적으로 어떤 지식 포인트가 포함되어 있는지 탐색해야 합니다. 저자가 유용하다고 생각하는 지식 포인트는 다음과 같습니다.

스코프 및 스코프 체인
글쎄, 사실 저자는 여러분 모두가 이에 대해 생각했다는 것을 알고 있습니다(아니요, 아무도 이것을 생각하지 않았습니다). 이제 모두가

scope

을 이해했습니다. 여기서는 이에 대해 간략하게 설명하고 그 과정을 살펴보겠습니다. 범위: 이름으로 변수를 찾는 규칙 집합입니다. 전역 범위, 블록 범위, 세 가지 유형으로 나뉩니다.

주의해야 할 것은 ES6의 새로운 사양인 블록 스코프입니다. 중괄호 {} 내부에서 let, const를 사용하여 정의된 변수는 범위에 바인딩되며 중괄호 외부에서는 액세스할 수 없습니다.

참고: 중괄호 시작 부분과 let 변수 선언

사이에 임시 데드존이 있습니다(이 지점은 이 기사의 범위를 벗어납니다).

스코프 체인: 서로 다른 스코프가 함께 갇히면 스코프 체인이 형성됩니다. 검색 방향은 내부에서 외부입니다. 스코프 검색 방향은 왜 내부에서 외부인가요? 이것은 흥미로운 질문입니다. 개인적으로 js 실행 함수가 스택에 푸시되는 방식에 따라 결정된다고 생각합니다(주제에서 조금 벗어난 느낌이 들므로 관심 있는 친구는 정보를 확인할 수 있습니다).

어휘 범위

함수가 다른 함수 범위의 변수에 접근할 수 있는 이유(또는 현재 범위를 기억하고 현재 범위 밖에서 접근할 수 있는 이유)핵심는 <code>어휘 범위가 작동 중임을 나타냅니다. 이것은 매우 중요하지만 모든 사람이 이 지식 포인트를 아는 것은 아닙니다. 여기서 간단히 논의해 보겠습니다.

프로그래밍 세계에는 두 가지 작업 모드가 있습니다. 하나는 대부분의 프로그래밍 언어에서 사용되는 어휘 범위이고, 다른 하나는 반대되는 동적 범위입니다. (이것은 이 기사의 범위를 벗어납니다.) {}里面使用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

클로저에 대해 함께 알아볼까요? 어휘 범위: 변수와 ​​블록의 범위는 코드를 작성할 때 이미 결정되어 있으며 호출하는 객체나 위치에 따라 변경되지 않습니다(이와 반대되는 느낌입니다).
예를 들어보면

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的一个副作用, 如果当前词法作用域没有该属性,会在全局创建一个
로그인 후 복사
로그인 후 복사

위의 정의에서 fn이 클로저 함수이고 fn3 포인터 주소를 얻는다는 것을 알 수 있습니다. fn2fn3가 실행되면 실제로 fn2가 실행되고 범위에 따라 내부에 a 변수가 검색됩니다. 체인의 규칙은 fn 범위에서 변수 a를 찾으므로 최종 출력은 1이 아닌 2입니다. (아래 그림을 볼 수 있습니다)

🎜🎜🎜🎜🎜주제에서 벗어나, 어휘 범위를 속이는 방법은 무엇입니까? 🎜🎜어휘 범위는 정적이지만 동적 효과를 얻기 위해 속이는 방법이 여전히 있습니다. 🎜

第一种方法是使用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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:juejin.im
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
최신 이슈
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿