목차
소개
간략한 질문
몇 가지 구체적인 예
jQuery의 클로저
深入理解闭包
模块与闭包
性能考量
闭包的使用场景:
클로저의 장점과 단점
결론
jQuery中的闭包
闭包的优缺点
结语
웹 프론트엔드 JS 튜토리얼 클로저에는 할 말이 있습니다 - 큰 프런트 엔드

클로저에는 할 말이 있습니다 - 큰 프런트 엔드

Feb 08, 2017 pm 05:57 PM
javascript 폐쇄

소개

처음 프론트엔드를 접했을 때 클로저(Closure)라는 단어를 보고 항상 헷갈렸는데, 면접 때 이런 질문을 받았을 때 대답이 막연하고 늘 느꼈어요. 혼란의 층이 있었다는 것입니다. 다이어프램, 이 개념을 마스터할 수 있다면 실력이 크게 향상될 것이라고 생각합니다. 사실 폐쇄는 그다지 신비한 것이 아니라 어디에나 존재합니다.

간략한 질문

먼저 질문을 살펴보겠습니다.

클로저가 무엇인지 한 문장으로 설명하고 설명할 수 있는 코드를 작성해 주세요.

거침없이 말하고 설명할 수 있다면 더 이상 읽을 필요가 없습니다.
이 문제에 대해 제가 검토한 정보와 경험을 종합하여 여기서 잘못된 점이 있으면 바로잡아 주시기 바랍니다.

먼저 위의 질문에 답해 보세요. 클로저란 무엇인가요?

클로저(Closure)는 함수가 실행된 후에도 여전히 메모리에 남아 있는 현상을 설명하는 개념입니다.

코드 설명:

function foo() {

    var a = 2;

    function bar(){
        console.log(a);
    }

    return bar;
}

var test = foo();
test(); //2
로그인 후 복사
로그인 후 복사

위 코드는 클로저를 명확하게 보여줍니다.

bar() 함수의 어휘 범위는 foo()의 내부 범위에 접근할 수 있습니다. 그런 다음 bar() 함수 자체를 값 유형으로 전달합니다. 위의 예에서는 bar()가 참조하는 함수 객체 자체를 반환 값으로 사용합니다.

foo()가 실행된 후에도 bar()는 여전히 내부 범위에 대한 참조를 유지하므로 내부 범위가 파괴되지 않습니다. bar()의 위치 덕분에 foo( ) 클로저를 적용할 수 있습니다. 내부 범위의 범위를 유지하여 bar()가 언제든지 참조할 수 있도록 합니다. 이 참조는 실제로 클로저입니다.
이 때문에 테스트가 실제로 호출될 때 정의된 어휘 범위에 액세스할 수 있으므로 a에 액세스할 수 있습니다.

함수 전달은 간접적일 수도 있습니다:

    var fn;
    function foo(){

        var a = 2;

        function baz() {
            console.log( a );
        }
        fn = baz; //将baz 分配给全局变量
    }

    function bar(){
        fn();
    }
    foo();
    bar(); //2
로그인 후 복사
로그인 후 복사

따라서 내부 함수가 어휘 범위 외부로 어떻게 전달되더라도 여전히 원래 정의 범위에 대한 참조를 유지합니다. 즉, 이 함수가 실행될 때마다 클로저가 사용됩니다. 콜백 함수를 구체적인 세부 사항에 신경쓰지 않고도 매우 편리하게 사용할 수 있는 것도 바로 이런 이유 때문입니다.

사실 타이머, 이벤트 리스너, Ajax 요청, 창 간 통신, 웹 작업자 또는 기타 동기식 또는 비동기식 작업에서 콜백 함수를 사용하는 한 실제로 클로저를 사용하고 있는 것입니다. 가방.

이 시점에서 여러분은 이미 클로저에 대한 일반적인 이해를 갖고 있을 것입니다. 클로저에 대한 이해를 심화하는 데 도움이 되는 몇 가지 예를 더 들어보겠습니다.

몇 가지 구체적인 예

먼저 소위 즉시 실행 기능을 살펴보겠습니다.

var a = 2;

(function IIFE() { 
   console.log(a); 
 })();

//2
로그인 후 복사
로그인 후 복사

이 즉시 실행 기능은 일반적으로 고전적인 것으로 간주됩니다. 클로저 예제는 잘 작동하지만 엄밀히 말하면 클로저가 아닙니다.
왜요?

이 IIFE 함수는 자체 어휘 범위 밖에서 실행되지 않기 때문입니다. 정의된 범위 내에서 실행됩니다. 또한 변수 a는 클로저가 아닌 일반 어휘 범위를 통해 조회됩니다.

클로저를 설명하는 데 사용되는 또 다른 예는 루프입니다.

    <p class="tabs">
        <li class="tab">some text1</li>
        <li class="tab">some text2</li>
        <li class="tab">some text3</li>
    </p>
로그인 후 복사
로그인 후 복사
rrree

예상 결과는 로그 0, 1, 2입니다.

실행 후 결과는 3입니다.

왜일까요?

먼저 이 3이 어떻게 나오는지 설명하세요.

루프 본문을 살펴보세요. 루프의 종료 조건은 i < 처음으로.
따라서 출력에는 루프 끝에서 i의 최종 값이 표시됩니다. 범위의 작동 원리에 따르면 루프의 함수는 각 반복에서 별도로 정의되지만 모두 공유 전역 범위에 포함되므로 실제로는 i가 하나만 있습니다.

handler의 원래 의도는 다음과 같습니다. 함수는 고유한 i를 이벤트 핸들러에 전달하는 것이었지만 실패했습니다.
이벤트 핸들러 함수는 함수가 생성될 때 i 값이 아니라 i 자체를 바인딩하기 때문에

이 사실을 알고 나면 그에 맞게 조정할 수 있습니다.

var handler = function(nodes) {

    for(var i = 0, l = nodes.length; i < l ; i++) {
        
        nodes[i].onclick = function(){

            console.log(i);

        }
    }
}

var tabs = document.querySelectorAll('.tabs .tab');
    handler(tabs);
로그인 후 복사

도우미 함수를 만듭니다. 루프 외부에서 이 도우미 함수가 현재 i 값에 바인딩된 함수를 반환하도록 하여 혼동이 없도록 하세요.

이것을 이해하고 나면 위의 처리는 새로운 범위를 생성하는 것임을 알 수 있습니다. 즉, 각 반복마다 블록 범위가 필요합니다.

블록 범위에 대해서는 한 단어만 언급하자면, 그것은 let입니다.

따라서 클로저를 너무 많이 사용하고 싶지 않다면 let을 사용할 수 있습니다:

var handler = function(nodes) {

    var helper = function(i){
        return function(e){
            console.log(i); // 0 1 2
        }
    }

    for(var i = 0, l = nodes.length; i < l ; i++) {
        
        nodes[i].onclick = helper(i);
    }
}
로그인 후 복사
로그인 후 복사

jQuery의 클로저

먼저 예제를 살펴보겠습니다

var handler = function(nodes) {

    for(let i = 0, l = nodes.length; i < l ; i++) {
        
        //nodes[i].index = i;

        nodes[i].onclick = function(){

            console.log(i); // 0 1 2


        }
    }
}
로그인 후 복사
로그인 후 복사

위 코드는 jQuery의 선택기를 사용하여 id가 con인 요소를 찾아 타이머를 등록하고 2초 후에 배경색을 회색으로 설정하는 코드입니다.

이 코드 조각의 마술은 setTimeout 함수를 호출한 후에도 con이 2초 후에도 여전히 함수 내에 유지된다는 것입니다. ID가 con인 p 요소의 배경색이 실제로 변경됩니다. 호출 후에 setTimeout이 반환되었지만 con은 해제되지 않았는데 이는 con이 전역 범위에서 con 변수를 참조하기 때문입니다.

위의 예는 클로저에 대해 더 자세히 이해하는 데 도움이 됩니다.

深入理解闭包

首先看一个概念-执行上下文(Execution Context)。

执行上下文是一个抽象的概念,ECMAScript 规范使用它来追踪代码的执行。它可能是你的代码第一次执行或执行的流程进入函数主体时所在的全局上下文。

闭包有话说 - 大前端

在任意一个时间点,只能有唯一一个执行上下文在运行之中。

这就是为什么 JavaScript 是“单线程”的原因,意思就是一次只能处理一个请求。

一般来说,浏览器会用栈来保存这个执行上下文。

栈是一种“后进先出” (Last In First Out) 的数据结构,即最后插入该栈的元素会最先从栈中被弹出(这是因为我们只能从栈的顶部插入或删除元素)。

当前的执行上下文,或者说正在运行中的执行上下文永远在栈顶。

当运行中的上下文被完全执行以后,它会由栈顶弹出,使得下一个栈顶的项接替它成为正在运行的执行上下文。

除此之外,一个执行上下文正在运行并不代表另一个执行上下文需要等待它完成运行之后才可以开始运行。

有时会出现这样的情况,一个正在运行中的上下文暂停或中止,另外一个上下文开始执行。暂停的上下文可能在稍后某一时间点从它中止的位置继续执行。

一个新的执行上下文被创建并推入栈顶,成为当前的执行上下文,这就是执行上下文替代的机制。

闭包有话说 - 大前端

当我们有很多执行上下文一个接一个地运行时——通常情况下会在中间暂停然后再恢复运行——为了能很好地管理这些上下文的顺序和执行情况,我们需要用一些方法来对其状态进行追踪。而实际上也是如此,根据ECMAScript的规范,每个执行上下文都有用于跟踪代码执行进程的各种状态的组件。包括:

  • 代码执行状态:任何需要开始运行,暂停和恢复执行上下文相关代码执行的状态
     函数:上下文中正在执行的函数对象(正在执行的上下文是脚本或模块的情况下可能是null)

  • Realm:一系列内部对象,一个ECMAScript全局环境,所有在全局环境的作用域内加载的ECMAScript代码,和其他相关的状态及资源。

  • 词法环境:用于解决此执行上下文内代码所做的标识符引用。

  • 变量环境:一种词法环境,该词法环境的环境记录保留了变量声明时在执行上下文中创建的绑定关系。

模块与闭包

现在的开发都离不开模块化,下面说说模块是如何利用闭包的。

先看一个实际中的例子。
这是一个统计模块,看一下代码:

    define("components/webTrends", ["webTrendCore"], function(require,exports, module) {
    
    
        var webTrendCore = require("webTrendCore");  
        var webTrends = {
             init:function (obj) {
                 var self = this;
                self.dcsGetId();
                self.dcsCollect();
            },
    
             dcsGetId:function(){
                if (typeof(_tag) != "undefined") {
                 _tag.dcsid="dcs5w0txb10000wocrvqy1nqm_6n1p";
                 _tag.dcsGetId();
                }
            },
    
            dcsCollect:function(){
                 if (typeof(_tag) != "undefined") {
                    _tag.DCSext.platform="weimendian";
                    if(document.readyState!="complete"){
                    document.onreadystatechange = function(){
                        if(document.readyState=="complete") _tag.dcsCollect()
                        }
                    }
                    else _tag.dcsCollect()
                }
            }
    
        };
    
      module.exports = webTrends;
    
    })
로그인 후 복사
로그인 후 복사

在主页面使用的时候,调用一下就可以了:

var webTrends = require("webTrends");
webTrends.init();
로그인 후 복사
로그인 후 복사

在定义的模块中,我们暴露了webTrends对象,在外面调用返回对象中的方法就形成了闭包。

模块的两个必要条件:

  • 必须有外部的封闭函数,该函数必须至少被调用一次

  • 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态。

性能考量

如果一个任务不需要使用闭包,那最好不要在函数内创建函数。
原因很明显,这会 拖慢脚本的处理速度,加大内存消耗 。

举个例子,当需要创建一个对象时,方法通常应该和对象的原型关联,而不是定义到对象的构造函数中。 原因是 每次构造函数被调用, 方法都会被重新赋值 (即 对于每个对象创建),这显然是一种不好的做法。

看一个能说明问题,但是不推荐的做法:

    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;
      },
      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 MyObject(name, message) {
        this.name = name.toString();
        this.message = message.toString();
    }
    
    (function() {
        this.getName = function() {
            return this.name;
        };
        this.getMessage = function() {
            return this.message;
        };
    }).call(MyObject.prototype);
로그인 후 복사
로그인 후 복사

在前面的三个示例中,继承的原型可以由所有对象共享,并且在每个对象创建时不需要定义方法定义。如果想看更多细节,可以参考对象模型。

闭包的使用场景:

  • 使用闭包可以在JavaScript中模拟块级作用域;

  • 클로저를 사용하여 객체에 개인 변수를 생성할 수 있습니다.

클로저의 장점과 단점

장점:

  • 클로저가 다른 함수의 매개변수로 사용될 때 논리적 연속성 call 이는 현재 로직에서 벗어나 추가 로직을 별도로 작성하는 것을 방지합니다.

  • 컨텍스트의 지역변수를 편리하게 호출합니다.

  • 캡슐화를 강화하여 2번 항목을 확장하면 변수 보호를 달성할 수 있습니다.

단점:

  • 메모리 낭비. 이러한 메모리 낭비는 메모리에 상주하기 때문일 뿐만 아니라 클로저를 부적절하게 사용하면 유효하지 않은 메모리가 생성됩니다.

결론

마지막으로 클로저에 대한 간단한 설명을 드렸습니다. 사실 클로저의 특징은 다음과 같습니다. 🎜>

  • 함수 중첩 함수

  • 함수 내부의 외부 변수나 객체에 접근할 수 있습니다

  • 쓰레기 방지 재활용

의사소통에 오신 것을 환영합니다, 위;-)

참고 자료

JavaScript 클로저를 함께 배우자

JavaScript 범위 이해 and Closures

Closures

Introduction

프론트엔드를 처음 배웠을 때 이 클로저를 보고 늘 느끼는 거지만 면접 때 이 질문을 받았을 때 막연한 대답을 했고, 항상 장벽이 있다고 느꼈어요. 이 개념을 익히면 실력이 많이 향상될 거라 생각했어요. 사실 폐쇄는 그다지 신비한 것이 아니라 어디에나 존재합니다.

간략한 질문

먼저 질문을 살펴보겠습니다.

클로저가 무엇인지 한 문장으로 설명하고 설명할 수 있는 코드를 작성해 주세요.

거침없이 말하고 설명할 수 있다면 더 이상 읽을 필요가 없습니다.

이 문제에 대해 제가 검토한 정보와 경험을 종합하여 여기서 잘못된 점이 있으면 바로잡아 주시기 바랍니다.

먼저 위의 질문에 답해 보세요. 클로저란 무엇인가요?

클로저(Closure)는 함수가 실행된 후에도 여전히 메모리에 남아 있는 현상을 설명하는 개념입니다.

코드 설명:

function foo() {

    var a = 2;

    function bar(){
        console.log(a);
    }

    return bar;
}

var test = foo();
test(); //2
로그인 후 복사
로그인 후 복사
위 코드는 클로저를 명확하게 보여줍니다.

bar() 함수의 어휘 범위는 foo()의 내부 범위에 접근할 수 있습니다. 그런 다음 bar() 함수 자체를 값 유형으로 전달합니다. 위의 예에서는 bar()가 참조하는 함수 객체 자체를 반환 값으로 사용합니다.

foo()가 실행된 후에도 bar()는 여전히 내부 범위에 대한 참조를 유지하므로 내부 범위가 파괴되지 않습니다. bar()의 위치 덕분에 foo( ) 클로저를 적용할 수 있습니다. 내부 범위의 범위를 유지하여 bar()가 언제든지 참조할 수 있도록 합니다. 이 참조는 실제로 클로저입니다.

이 때문에 테스트가 실제로 호출될 때 정의된 어휘 범위에 액세스할 수 있으므로 a에 액세스할 수 있습니다.

함수 전달은 간접적일 수도 있습니다:

    var fn;
    function foo(){

        var a = 2;

        function baz() {
            console.log( a );
        }
        fn = baz; //将baz 分配给全局变量
    }

    function bar(){
        fn();
    }
    foo();
    bar(); //2
로그인 후 복사
로그인 후 복사
따라서 내부 함수가 어휘 범위 외부로 어떻게 전달되더라도 여전히 원래 정의 범위에 대한 참조를 유지합니다. 즉, 이 함수가 실행될 때마다 클로저가 사용됩니다. 콜백 함수를 구체적인 세부 사항에 신경쓰지 않고도 매우 편리하게 사용할 수 있는 것도 바로 이런 이유 때문입니다.

사실 타이머, 이벤트 리스너, Ajax 요청, 창 간 통신, 웹 작업자 또는 기타 동기식 또는 비동기식 작업에서 콜백 함수를 사용하는 한 실제로 클로저를 사용하고 있는 것입니다. 가방.

이 시점에서 여러분은 이미 클로저에 대한 일반적인 이해를 갖고 있을 것입니다. 클로저에 대한 이해를 심화하는 데 도움이 되는 몇 가지 예를 더 들어보겠습니다.

몇 가지 구체적인 예

먼저 소위 즉시 실행 기능을 살펴보겠습니다.

var a = 2;

(function IIFE() { 
   console.log(a); 
 })();

//2
로그인 후 복사
로그인 후 복사
이 즉시 실행 기능은 일반적으로 고전적인 것으로 간주됩니다. 클로저 예제는 잘 작동하지만 엄밀히 말하면 클로저가 아닙니다.

왜요?

이 IIFE 함수는 자체 어휘 범위 밖에서 실행되지 않기 때문입니다. 정의된 범위 내에서 실행됩니다. 또한 변수 a는 클로저가 아닌 일반 어휘 범위를 통해 조회됩니다.

클로저를 설명하는 데 사용되는 또 다른 예는 루프입니다.

    <p class="tabs">
        <li class="tab">some text1</li>
        <li class="tab">some text2</li>
        <li class="tab">some text3</li>
    </p>
로그인 후 복사
로그인 후 복사
rrree예상 결과는 로그 0, 1, 2입니다.

실행 후 결과는 3입니다.

왜일까요?

먼저 이 3이 어떻게 나오는지 설명하세요.

루프 본문을 살펴보세요. 루프의 종료 조건은 i < 처음으로.

따라서 출력에는 루프 끝에서 i의 최종 값이 표시됩니다. 범위의 작동 원리에 따르면 루프의 함수는 각 반복에서 별도로 정의되지만 모두 공유 전역 범위에 포함되므로 실제로는 i가 하나만 있습니다.

handler의 원래 의도는 다음과 같습니다. 함수는 고유한 i를 이벤트 핸들러에 전달하는 것이었지만 실패했습니다.

이벤트 핸들러 함수는 함수가 생성될 때 i의 값이 아니라 i 자체를 바인딩하기 때문에

이 사실을 알고 나면 그에 맞게 조정할 수 있습니다.

var handler = function(nodes) {

    var helper = function(i){
        return function(e){
            console.log(i); // 0 1 2
        }
    }

    for(var i = 0, l = nodes.length; i < l ; i++) {
        
        nodes[i].onclick = helper(i);
    }
}
로그인 후 복사
로그인 후 복사

在循环外创建一个辅助函数,让这个辅助函数在返回一个绑定了当前i的值的函数,这样就不会混淆了。

明白了这点,就会发现,上面的处理就是为了创建一个新的作用域,换句话说,每次迭代我们都需要一个块作用域.

说到块作用域,就不得不提一个词,那就是let.

所以,如果你不想过多的使用闭包,就可以使用let:

var handler = function(nodes) {

    for(let i = 0, l = nodes.length; i < l ; i++) {
        
        //nodes[i].index = i;

        nodes[i].onclick = function(){

            console.log(i); // 0 1 2


        }
    }
}
로그인 후 복사
로그인 후 복사

jQuery中的闭包

先来看个例子

     var sel = $("#con"); 
     setTimeout( function (){ 
         sel.css({background:"gray"}); 
     }, 2000);
로그인 후 복사

上边的代码使用了 jQuery 的选择器,找到 id 为 con 的元素,注册计时器,两秒之后,将背景色设置为灰色。

这个代码片段的神奇之处在于,在调用了 setTimeout 函数之后,con 依旧被保持在函数内部,当两秒钟之后,id 为 con 的 p 元素的背景色确实得到了改变。应该注意的是,setTimeout 在调用之后已经返回了,但是 con 没有被释放,这是因为 con 引用了全局作用域里的变量 con。

以上的例子帮助我们了解了更多关于闭包的细节,下面我们就深入闭包世界探寻一番。

深入理解闭包

首先看一个概念-执行上下文(Execution Context)。

执行上下文是一个抽象的概念,ECMAScript 规范使用它来追踪代码的执行。它可能是你的代码第一次执行或执行的流程进入函数主体时所在的全局上下文。

闭包有话说 - 大前端

在任意一个时间点,只能有唯一一个执行上下文在运行之中。

这就是为什么 JavaScript 是“单线程”的原因,意思就是一次只能处理一个请求。

一般来说,浏览器会用栈来保存这个执行上下文。

栈是一种“后进先出” (Last In First Out) 的数据结构,即最后插入该栈的元素会最先从栈中被弹出(这是因为我们只能从栈的顶部插入或删除元素)。

当前的执行上下文,或者说正在运行中的执行上下文永远在栈顶。

当运行中的上下文被完全执行以后,它会由栈顶弹出,使得下一个栈顶的项接替它成为正在运行的执行上下文。

除此之外,一个执行上下文正在运行并不代表另一个执行上下文需要等待它完成运行之后才可以开始运行。

有时会出现这样的情况,一个正在运行中的上下文暂停或中止,另外一个上下文开始执行。暂停的上下文可能在稍后某一时间点从它中止的位置继续执行。

一个新的执行上下文被创建并推入栈顶,成为当前的执行上下文,这就是执行上下文替代的机制。

闭包有话说 - 大前端

当我们有很多执行上下文一个接一个地运行时——通常情况下会在中间暂停然后再恢复运行——为了能很好地管理这些上下文的顺序和执行情况,我们需要用一些方法来对其状态进行追踪。而实际上也是如此,根据ECMAScript的规范,每个执行上下文都有用于跟踪代码执行进程的各种状态的组件。包括:

  • 代码执行状态:任何需要开始运行,暂停和恢复执行上下文相关代码执行的状态
     函数:上下文中正在执行的函数对象(正在执行的上下文是脚本或模块的情况下可能是null)

  • Realm:一系列内部对象,一个ECMAScript全局环境,所有在全局环境的作用域内加载的ECMAScript代码,和其他相关的状态及资源。

  • 词法环境:用于解决此执行上下文内代码所做的标识符引用。

  • 变量环境:一种词法环境,该词法环境的环境记录保留了变量声明时在执行上下文中创建的绑定关系。

模块与闭包

现在的开发都离不开模块化,下面说说模块是如何利用闭包的。

先看一个实际中的例子。
这是一个统计模块,看一下代码:

    define("components/webTrends", ["webTrendCore"], function(require,exports, module) {
    
    
        var webTrendCore = require("webTrendCore");  
        var webTrends = {
             init:function (obj) {
                 var self = this;
                self.dcsGetId();
                self.dcsCollect();
            },
    
             dcsGetId:function(){
                if (typeof(_tag) != "undefined") {
                 _tag.dcsid="dcs5w0txb10000wocrvqy1nqm_6n1p";
                 _tag.dcsGetId();
                }
            },
    
            dcsCollect:function(){
                 if (typeof(_tag) != "undefined") {
                    _tag.DCSext.platform="weimendian";
                    if(document.readyState!="complete"){
                    document.onreadystatechange = function(){
                        if(document.readyState=="complete") _tag.dcsCollect()
                        }
                    }
                    else _tag.dcsCollect()
                }
            }
    
        };
    
      module.exports = webTrends;
    
    })
로그인 후 복사
로그인 후 복사

在主页面使用的时候,调用一下就可以了:

var webTrends = require("webTrends");
webTrends.init();
로그인 후 복사
로그인 후 복사

在定义的模块中,我们暴露了webTrends对象,在外面调用返回对象中的方法就形成了闭包。

模块的两个必要条件:

  • 必须有外部的封闭函数,该函数必须至少被调用一次

  • 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态。

性能考量

如果一个任务不需要使用闭包,那最好不要在函数内创建函数。
原因很明显,这会 拖慢脚本的处理速度,加大内存消耗 。

举个例子,当需要创建一个对象时,方法通常应该和对象的原型关联,而不是定义到对象的构造函数中。 原因是 每次构造函数被调用, 方法都会被重新赋值 (即 对于每个对象创建),这显然是一种不好的做法。

看一个能说明问题,但是不推荐的做法:

    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;
      },
      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 MyObject(name, message) {
        this.name = name.toString();
        this.message = message.toString();
    }
    
    (function() {
        this.getName = function() {
            return this.name;
        };
        this.getMessage = function() {
            return this.message;
        };
    }).call(MyObject.prototype);
로그인 후 복사
로그인 후 복사

在前面的三个示例中,继承的原型可以由所有对象共享,并且在每个对象创建时不需要定义方法定义。如果想看更多细节,可以参考对象模型。

闭包的使用场景:

  • 使用闭包可以在JavaScript中模拟块级作用域;

  • 闭包可以用于在对象中创建私有变量。

闭包的优缺点

优点:

  • 逻辑连续,当闭包作为另一个函数调用的参数时,避免你脱离当前逻辑而单独编写额外逻辑。

  • 方便调用上下文的局部变量。

  • 加强封装性,第2点的延伸,可以达到对变量的保护作用。

缺点:

  • 内存浪费。这个内存浪费不仅仅因为它常驻内存,对闭包的使用不当会造成无效内存的产生。

结语

前面对闭包做了一些简单的解释,最后再总结下,其实闭包没什么特别的,其特点是:

  • 函数嵌套函数

  • 函数内部可以访问到外部的变量或者对象

  • 避免了垃圾回收

更多闭包有话说 - 大前端相关文章请关注PHP中文网!




본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

Video Face Swap

Video Face Swap

완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

뜨거운 도구

메모장++7.3.1

메모장++7.3.1

사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전

SublimeText3 중국어 버전

중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기

스튜디오 13.0.1 보내기

강력한 PHP 통합 개발 환경

드림위버 CS6

드림위버 CS6

시각적 웹 개발 도구

SublimeText3 Mac 버전

SublimeText3 Mac 버전

신 수준의 코드 편집 소프트웨어(SublimeText3)

C++ 람다 표현식에서 클로저의 의미는 무엇입니까? C++ 람다 표현식에서 클로저의 의미는 무엇입니까? Apr 17, 2024 pm 06:15 PM

C++에서 클로저는 외부 변수에 액세스할 수 있는 람다 식입니다. 클로저를 생성하려면 람다 표현식에서 외부 변수를 캡처하세요. 클로저는 재사용성, 정보 숨기기, 지연 평가와 같은 이점을 제공합니다. 이는 클로저가 외부 변수가 파괴되더라도 여전히 접근할 수 있는 이벤트 핸들러와 같은 실제 상황에서 유용합니다.

C++ 람다 표현식에서 클로저를 구현하는 방법은 무엇입니까? C++ 람다 표현식에서 클로저를 구현하는 방법은 무엇입니까? Jun 01, 2024 pm 05:50 PM

C++ Lambda 표현식은 함수 범위 변수를 저장하고 함수에 액세스할 수 있도록 하는 클로저를 지원합니다. 구문은 [캡처 목록](매개변수)->return-type{function-body}입니다. 캡처 목록은 캡처할 변수를 정의합니다. [=]를 사용하여 모든 지역 변수를 값으로 캡처하고, [&]를 사용하여 모든 지역 변수를 참조로 캡처하거나, [변수1, 변수2,...]를 사용하여 특정 변수를 캡처할 수 있습니다. 람다 표현식은 캡처된 변수에만 액세스할 수 있지만 원래 값을 수정할 수는 없습니다.

C++ 함수에서 클로저의 장점과 단점은 무엇입니까? C++ 함수에서 클로저의 장점과 단점은 무엇입니까? Apr 25, 2024 pm 01:33 PM

클로저는 외부 함수의 범위에 있는 변수에 액세스할 수 있는 중첩 함수입니다. 클로저의 장점에는 데이터 캡슐화, 상태 보존 및 유연성이 포함됩니다. 단점으로는 메모리 소비, 성능 영향, 디버깅 복잡성 등이 있습니다. 또한 클로저는 익명 함수를 생성하고 이를 콜백이나 인수로 다른 함수에 전달할 수 있습니다.

클로저로 인한 메모리 누수 문제 해결 클로저로 인한 메모리 누수 문제 해결 Feb 18, 2024 pm 03:20 PM

제목: 클로저로 인한 메모리 누수 및 솔루션 소개: 클로저는 내부 함수가 외부 함수의 변수에 액세스할 수 있도록 하는 JavaScript에서 매우 일반적인 개념입니다. 그러나 클로저를 잘못 사용하면 메모리 누수가 발생할 수 있습니다. 이 문서에서는 클로저로 인해 발생하는 메모리 누수 문제를 살펴보고 솔루션과 구체적인 코드 예제를 제공합니다. 1. 클로저로 인한 메모리 누수 클로저의 특징은 내부 함수가 외부 함수의 변수에 접근할 수 있다는 것입니다. 즉, 클로저에서 참조되는 변수는 가비지 수집되지 않습니다. 부적절하게 사용하는 경우,

함수 포인터와 클로저가 Golang 성능에 미치는 영향 함수 포인터와 클로저가 Golang 성능에 미치는 영향 Apr 15, 2024 am 10:36 AM

함수 포인터와 클로저가 Go 성능에 미치는 영향은 다음과 같습니다. 함수 포인터: 직접 호출보다 약간 느리지만 가독성과 재사용성이 향상됩니다. 클로저: 일반적으로 느리지만 데이터와 동작을 캡슐화합니다. 실제 사례: 함수 포인터는 정렬 알고리즘을 최적화할 수 있고 클로저는 이벤트 핸들러를 생성할 수 있지만 성능 저하를 가져옵니다.

테스트에서 golang 함수 클로저의 역할 테스트에서 golang 함수 클로저의 역할 Apr 24, 2024 am 08:54 AM

Go 언어 함수 클로저는 단위 테스트에서 중요한 역할을 합니다. 값 캡처: 클로저는 외부 범위의 변수에 액세스할 수 있으므로 테스트 매개변수를 캡처하고 중첩된 함수에서 재사용할 수 있습니다. 테스트 코드 단순화: 클로저는 값을 캡처함으로써 각 루프에 대해 매개변수를 반복적으로 설정할 필요가 없으므로 테스트 코드를 단순화합니다. 가독성 향상: 클로저를 사용하여 테스트 로직을 구성하고 테스트 코드를 더 명확하고 읽기 쉽게 만듭니다.

PHP 함수의 연쇄 호출 및 폐쇄 PHP 함수의 연쇄 호출 및 폐쇄 Apr 13, 2024 am 11:18 AM

예, 체인 호출 및 클로저를 통해 코드 단순성과 가독성을 최적화할 수 있습니다. 체인 호출은 함수 호출을 유창한 인터페이스에 연결합니다. 클로저는 재사용 가능한 코드 블록을 생성하고 함수 외부의 변수에 액세스합니다.

Java에서는 클로저가 어떻게 구현됩니까? Java에서는 클로저가 어떻게 구현됩니까? May 03, 2024 pm 12:48 PM

Java의 클로저를 사용하면 외부 함수가 종료된 경우에도 내부 함수가 외부 범위 변수에 액세스할 수 있습니다. 익명의 내부 클래스를 통해 구현된 내부 클래스는 외부 클래스에 대한 참조를 보유하고 외부 변수를 활성 상태로 유지합니다. 클로저는 코드 유연성을 높이지만 익명 내부 클래스에 의한 외부 변수 참조는 해당 변수를 활성 상태로 유지하므로 메모리 누수의 위험을 인지해야 합니다.

See all articles