Inhaltsverzeichnis
Einführung
Eine kurze Frage
Ein paar konkretere Beispiele
深入理解闭包
模块与闭包
性能考量
闭包的使用场景:
Vor- und Nachteile von Abschlüssen
Fazit
jQuery中的闭包
闭包的优缺点
结语
Heim Web-Frontend js-Tutorial Verschlüsse haben etwas zu sagen – großes Frontend

Verschlüsse haben etwas zu sagen – großes Frontend

Feb 08, 2017 pm 05:57 PM
javascript 闭包

Einführung

Als ich zum ersten Mal etwas über Front-End hörte, war ich immer verwirrt, wenn mir diese Frage während des Interviews gestellt wurde, und das hatte ich immer gespürt Es gab eine Ebene der Verwirrung. Ich denke, dieses Konzept ist sehr magisch. Wenn Sie es beherrschen, werden Ihre Fähigkeiten erheblich verbessert. Tatsächlich ist die Schließung nicht so mysteriös, sie ist überall.

Eine kurze Frage

Schauen wir uns zunächst eine Frage an.

Bitte beschreiben Sie in einem Satz, was ein Abschluss ist, und schreiben Sie Code zur Veranschaulichung.

Wenn Sie es ohne zu zögern sagen und eine Erklärung abgeben können, brauchen Sie nicht weiterzulesen.
Zu diesem Problem werde ich in Kombination mit den Informationen und Erfahrungen, die ich überprüft habe, hier kurz darauf eingehen. Wenn etwas nicht stimmt, korrigieren Sie mich bitte.

Beantworten Sie zunächst die obige Frage, was ein Verschluss ist.

Abschluss ist ein Konzept, das das Phänomen beschreibt, dass eine Funktion nach ihrer Ausführung noch im Speicher verbleibt.

Codebeschreibung:

function foo() {

    var a = 2;

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

    return bar;
}

var test = foo();
test(); //2
Nach dem Login kopieren
Nach dem Login kopieren

Der obige Code zeigt deutlich den Abschluss.

Der lexikalische Bereich der Funktion bar() hat Zugriff auf den internen Bereich von foo(). Dann übergeben wir die Funktion bar() selbst als Werttyp. Im obigen Beispiel verwenden wir das Funktionsobjekt selbst, auf das bar() verweist, als Rückgabewert.

Nachdem foo() ausgeführt wurde, wurde sein interner Bereich nicht zerstört, da bar() immer noch einen Verweis auf den internen Bereich beibehält. Dank der Position von bar() deckt es den Abschluss von foo() ab des internen Bereichs, sodass der Bereich jederzeit für bar() zum Verweisen bestehen bleibt. Dieser Verweis ist eigentlich ein Abschluss.
Aus diesem Grund kann test beim tatsächlichen Aufruf auf den lexikalischen Bereich zugreifen, wenn er definiert ist, also auf a zugreifen.

Funktionsübertragung kann auch indirekt erfolgen:

    var fn;
    function foo(){

        var a = 2;

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

    function bar(){
        fn();
    }
    foo();
    bar(); //2
Nach dem Login kopieren
Nach dem Login kopieren

Unabhängig davon, wie die innere Funktion außerhalb des lexikalischen Bereichs, in dem sie sich befindet, übergeben wird, enthält sie einen Verweis auf den ursprünglichen Definitionsbereich. Das heißt, überall dort, wo diese Funktion ausgeführt wird, wird der Abschluss verwendet. Aus diesem Grund können wir die Callback-Funktion auch sehr bequem nutzen, ohne uns um ihre spezifischen Details kümmern zu müssen.

Tatsächlich verwenden Sie bei Timern, Ereignis-Listenern, Ajax-Anfragen, fensterübergreifender Kommunikation, Web Workern oder anderen synchronen oder asynchronen Aufgaben tatsächlich Abschlüsse, solange Sie Rückruffunktionen verwenden. Tasche.

Vielleicht haben Sie zu diesem Zeitpunkt bereits ein allgemeines Verständnis für Schließungen. Lassen Sie mich Ihnen noch ein paar Beispiele geben, die Ihnen helfen sollen, Ihr Verständnis für Schließungen zu vertiefen.

Ein paar konkretere Beispiele

Werfen wir zunächst einen Blick auf die sogenannte Sofortausführungsfunktion.

var a = 2;

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

//2
Nach dem Login kopieren
Nach dem Login kopieren

Diese Sofortausführungsfunktion wird üblicherweise in Betracht gezogen Ein Beispiel für ein klassisches Verschlusspaket, es funktioniert gut, ist aber nicht unbedingt ein Verschluss.
Warum?

Weil diese IIFE-Funktion nicht außerhalb ihres eigenen lexikalischen Bereichs ausgeführt wird. Es wird in dem Umfang ausgeführt, in dem es definiert wurde. Darüber hinaus wird die Variable a im normalen lexikalischen Bereich und nicht über Abschlüsse nachgeschlagen.

Ein weiteres Beispiel zur Veranschaulichung von Abschlüssen ist eine Schleife.

    <p class="tabs">
        <li class="tab">some text1</li>
        <li class="tab">some text2</li>
        <li class="tab">some text3</li>
    </p>
Nach dem Login kopieren
Nach dem Login kopieren
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);
Nach dem Login kopieren
Nach dem Login kopieren

Unser erwartetes Ergebnis ist log 0, 1, 2;

Das Ergebnis nach der Ausführung ist drei 3

Warum ist dieses Wolltuch?

Erklären Sie zunächst, wie diese 3 zustande kommt.

Sehen Sie sich den Schleifenkörper an. Der Wert von i ist 3, wenn die Bedingung festgelegt ist das erste Mal.
Daher zeigt die Ausgabe den Endwert von i am Ende der Schleife. Gemäß dem Funktionsprinzip des Bereichs werden die Funktionen in der Schleife zwar in jeder Iteration separat definiert, sind jedoch alle in einem gemeinsamen globalen Bereich eingeschlossen, sodass es tatsächlich nur einen i gibt.

Handler Die ursprüngliche Absicht von Die Funktion bestand darin, das eindeutige i an den Ereignishandler zu übergeben, was jedoch fehlschlug.
Da die Event-Handler-Funktion i selbst bindet, nicht den Wert von i, wenn die Funktion erstellt wird

Nachdem wir dies wissen, können wir entsprechende Anpassungen vornehmen:

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);
    }
}
Nach dem Login kopieren
Nach dem Login kopieren

Erstellen Sie eine Hilfsfunktion außerhalb der Schleife und lassen Sie diese Hilfsfunktion eine Funktion zurückgeben, die an den aktuellen Wert von i gebunden ist, damit keine Verwirrung entsteht.

Nachdem Sie dies verstanden haben, werden Sie feststellen, dass die obige Verarbeitung dazu dient, einen neuen Bereich zu erstellen. Mit anderen Worten, wir benötigen einen Blockbereich für jede Iteration um ein Wort zu nennen, das heißt let.

Wenn Sie also nicht zu oft Verschlüsse verwenden möchten, können Sie let:

in jQuery Closure
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


        }
    }
}
Nach dem Login kopieren
Nach dem Login kopieren
Schauen wir uns zunächst ein Beispiel an

Der obige Code verwendet den Selektor von jQuery, um das Element mit der ID con zu finden, einen Timer zu registrieren und nach zwei Sekunden die Hintergrundfarbe auf Grau zu setzen.

     var sel = $("#con"); 
     setTimeout( function (){ 
         sel.css({background:"gray"}); 
     }, 2000);
Nach dem Login kopieren
Nach dem Login kopieren
Der Zauber dieses Codeausschnitts besteht darin, dass con nach dem Aufruf der setTimeout-Funktion immer noch in der Funktion verbleibt. Nach zwei Sekunden wird die Hintergrundfarbe des p-Elements mit der ID con tatsächlich geändert. Es ist zu beachten, dass setTimeout nach dem Aufruf zurückgegeben wurde, con jedoch nicht freigegeben wurde. Dies liegt daran, dass con auf die Variable con im globalen Bereich verweist.

Die obigen Beispiele helfen uns, mehr Details über Verschlüsse zu verstehen. Lassen Sie uns tiefer in die Welt der Verschlüsse eintauchen.

深入理解闭包

首先看一个概念-执行上下文(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;
    
    })
Nach dem Login kopieren
Nach dem Login kopieren

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

var webTrends = require("webTrends");
webTrends.init();
Nach dem Login kopieren
Nach dem Login kopieren

在定义的模块中,我们暴露了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;
      };
    }
Nach dem Login kopieren
Nach dem Login kopieren

上面的代码并没有很好的利用闭包,我们来改进一下:

    function MyObject(name, message) {
      this.name = name.toString();
      this.message = message.toString();
    }
    
    MyObject.prototype = {
      getName: function() {
        return this.name;
      },
      getMessage: function() {
        return this.message;
      }
    };
Nach dem Login kopieren
Nach dem Login kopieren

好一些了,但是不推荐重新定义原型,再来改进下:

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;
};
Nach dem Login kopieren
Nach dem Login kopieren

很显然,在现有的原型上添加方法是一种更好的做法。

上面的代码还可以写的更简练:

    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);
Nach dem Login kopieren
Nach dem Login kopieren

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

闭包的使用场景:

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

  • Abschlüsse können verwendet werden, um private Variablen in einem Objekt zu erstellen.

Vor- und Nachteile von Abschlüssen

Vorteile:

  • Logische Kontinuität, wenn der Abschluss als Parameter einer anderen Funktion verwendet wird call Dies verhindert, dass Sie von der aktuellen Logik abweichen und zusätzliche Logik separat schreiben.

  • Ruft bequem lokale Variablen des Kontexts auf.

  • Durch die Verbesserung der Kapselung kann durch die Erweiterung von Punkt 2 der Schutz von Variablen erreicht werden.

Nachteile:

  • Speicherverschwendung. Diese Speicherverschwendung liegt nicht nur daran, dass sie sich im Speicher befindet, sondern auch durch die unsachgemäße Verwendung von Schließungen wird ungültiger Speicher generiert.

Fazit

Ich habe einige einfache Erklärungen zu Verschlüssen gegeben. Tatsächlich sind die Verschlüsse nichts Besonderes 🎜>

  • Funktionsverschachtelte Funktion

  • Sie können auf externe Variablen oder Objekte innerhalb der Funktion zugreifen

  • Vermeiden Sie Müll Recycling

Willkommen bei der Kommunikation oben;-)

Referenzmaterialien

Lassen Sie uns gemeinsam JavaScript-Verschlüsse lernen

Verstehen Sie den JavaScript-Bereich und Abschlüsse

Abschlüsse

Einführung

Als ich das Frontend zum ersten Mal lernte, sah ich diesen Abschluss, den ich immer spüre Ich war verwirrt über Ci. Als mir diese Frage während des Interviews gestellt wurde, hatte ich immer das Gefühl, dass es ein Hindernis gab, wenn ich es beherrschen würde. Tatsächlich ist die Schließung nicht so mysteriös, sie ist überall.

Eine kurze Frage

Schauen wir uns zunächst eine Frage an.

Bitte beschreiben Sie in einem Satz, was ein Abschluss ist, und schreiben Sie Code zur Veranschaulichung.

Wenn Sie es ohne zu zögern sagen und eine Erklärung abgeben können, brauchen Sie nicht weiterzulesen.

Zu diesem Problem werde ich in Kombination mit den Informationen und Erfahrungen, die ich überprüft habe, hier kurz darauf eingehen. Wenn etwas nicht stimmt, korrigieren Sie mich bitte.

Beantworten Sie zunächst die obige Frage, was ein Verschluss ist.

Abschluss ist ein Konzept, das das Phänomen beschreibt, dass eine Funktion nach ihrer Ausführung noch im Speicher verbleibt.

Codebeschreibung:

function foo() {

    var a = 2;

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

    return bar;
}

var test = foo();
test(); //2
Nach dem Login kopieren
Nach dem Login kopieren
Der obige Code zeigt deutlich den Abschluss.

Der lexikalische Bereich der Funktion bar() hat Zugriff auf den internen Bereich von foo(). Dann übergeben wir die Funktion bar() selbst als Werttyp. Im obigen Beispiel verwenden wir das Funktionsobjekt selbst, auf das bar() verweist, als Rückgabewert.

Nachdem foo() ausgeführt wurde, wurde sein interner Bereich nicht zerstört, da bar() immer noch einen Verweis auf den internen Bereich beibehält. Dank der Position von bar() deckt es den Abschluss von foo() ab des internen Bereichs, sodass der Bereich jederzeit für bar() zum Verweisen bestehen bleibt. Dieser Verweis ist eigentlich ein Abschluss.

Aus diesem Grund kann test beim tatsächlichen Aufruf auf den lexikalischen Bereich zugreifen, wenn er definiert ist, also auf a zugreifen.

Funktionsübertragung kann auch indirekt erfolgen:

    var fn;
    function foo(){

        var a = 2;

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

    function bar(){
        fn();
    }
    foo();
    bar(); //2
Nach dem Login kopieren
Nach dem Login kopieren
Unabhängig davon, wie die innere Funktion außerhalb des lexikalischen Bereichs, in dem sie sich befindet, übergeben wird, enthält sie einen Verweis auf den ursprünglichen Definitionsbereich. Das heißt, überall dort, wo diese Funktion ausgeführt wird, wird der Abschluss verwendet. Aus diesem Grund können wir die Callback-Funktion auch sehr bequem nutzen, ohne uns um ihre spezifischen Details kümmern zu müssen.

Tatsächlich verwenden Sie bei Timern, Ereignis-Listenern, Ajax-Anfragen, fensterübergreifender Kommunikation, Web Workern oder anderen synchronen oder asynchronen Aufgaben tatsächlich Abschlüsse, solange Sie Rückruffunktionen verwenden. Tasche.

Vielleicht haben Sie zu diesem Zeitpunkt bereits ein allgemeines Verständnis für Schließungen. Lassen Sie mich Ihnen noch ein paar Beispiele geben, die Ihnen helfen sollen, Ihr Verständnis für Schließungen zu vertiefen.

Ein paar konkretere Beispiele

Werfen wir zunächst einen Blick auf die sogenannte Sofortausführungsfunktion.

var a = 2;

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

//2
Nach dem Login kopieren
Nach dem Login kopieren
Diese Sofortausführungsfunktion wird üblicherweise in Betracht gezogen Ein Beispiel für ein klassisches Verschlusspaket, es funktioniert gut, ist aber nicht unbedingt ein Verschluss.

Warum?

Weil diese IIFE-Funktion nicht außerhalb ihres eigenen lexikalischen Bereichs ausgeführt wird. Es wird in dem Umfang ausgeführt, in dem es definiert wurde. Darüber hinaus wird die Variable a im normalen lexikalischen Bereich und nicht über Abschlüsse nachgeschlagen.

Ein weiteres Beispiel zur Veranschaulichung von Abschlüssen ist eine Schleife.

    <p class="tabs">
        <li class="tab">some text1</li>
        <li class="tab">some text2</li>
        <li class="tab">some text3</li>
    </p>
Nach dem Login kopieren
Nach dem Login kopieren
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);
Nach dem Login kopieren
Nach dem Login kopieren
Unser erwartetes Ergebnis ist log 0, 1, 2;

Das Ergebnis nach der Ausführung ist drei 3

Warum ist dieses Wolltuch?

Erklären Sie zunächst, wie diese 3 zustande kommt.

Sehen Sie sich den Schleifenkörper an. Der Wert von i ist 3, wenn die Bedingung festgelegt ist das erste Mal.

Daher zeigt die Ausgabe den Endwert von i am Ende der Schleife. Gemäß dem Funktionsprinzip des Bereichs werden die Funktionen in der Schleife zwar in jeder Iteration separat definiert, sind jedoch alle in einem gemeinsamen globalen Bereich eingeschlossen, sodass es tatsächlich nur einen i gibt.

Handler Die ursprüngliche Absicht von Die Funktion bestand darin, das eindeutige i an den Ereignishandler zu übergeben, was jedoch fehlschlug.

Da die Event-Handler-Funktion i selbst bindet, nicht den Wert von i, wenn die Funktion erstellt wird

Nachdem wir dies wissen, können wir entsprechende Anpassungen vornehmen:

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);
    }
}
Nach dem Login kopieren
Nach dem Login kopieren

在循环外创建一个辅助函数,让这个辅助函数在返回一个绑定了当前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


        }
    }
}
Nach dem Login kopieren
Nach dem Login kopieren

jQuery中的闭包

先来看个例子

     var sel = $("#con"); 
     setTimeout( function (){ 
         sel.css({background:"gray"}); 
     }, 2000);
Nach dem Login kopieren
Nach dem Login kopieren

上边的代码使用了 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;
    
    })
Nach dem Login kopieren
Nach dem Login kopieren

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

var webTrends = require("webTrends");
webTrends.init();
Nach dem Login kopieren
Nach dem Login kopieren

在定义的模块中,我们暴露了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;
      };
    }
Nach dem Login kopieren
Nach dem Login kopieren

上面的代码并没有很好的利用闭包,我们来改进一下:

    function MyObject(name, message) {
      this.name = name.toString();
      this.message = message.toString();
    }
    
    MyObject.prototype = {
      getName: function() {
        return this.name;
      },
      getMessage: function() {
        return this.message;
      }
    };
Nach dem Login kopieren
Nach dem Login kopieren

好一些了,但是不推荐重新定义原型,再来改进下:

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;
};
Nach dem Login kopieren
Nach dem Login kopieren

很显然,在现有的原型上添加方法是一种更好的做法。

上面的代码还可以写的更简练:

    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);
Nach dem Login kopieren
Nach dem Login kopieren

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

闭包的使用场景:

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

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

闭包的优缺点

优点:

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

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

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

缺点:

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

结语

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

  • 函数嵌套函数

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

  • 避免了垃圾回收

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




Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn

Heiße KI -Werkzeuge

Undresser.AI Undress

Undresser.AI Undress

KI-gestützte App zum Erstellen realistischer Aktfotos

AI Clothes Remover

AI Clothes Remover

Online-KI-Tool zum Entfernen von Kleidung aus Fotos.

Undress AI Tool

Undress AI Tool

Ausziehbilder kostenlos

Clothoff.io

Clothoff.io

KI-Kleiderentferner

Video Face Swap

Video Face Swap

Tauschen Sie Gesichter in jedem Video mühelos mit unserem völlig kostenlosen KI-Gesichtstausch-Tool aus!

Heiße Werkzeuge

Notepad++7.3.1

Notepad++7.3.1

Einfach zu bedienender und kostenloser Code-Editor

SublimeText3 chinesische Version

SublimeText3 chinesische Version

Chinesische Version, sehr einfach zu bedienen

Senden Sie Studio 13.0.1

Senden Sie Studio 13.0.1

Leistungsstarke integrierte PHP-Entwicklungsumgebung

Dreamweaver CS6

Dreamweaver CS6

Visuelle Webentwicklungstools

SublimeText3 Mac-Version

SublimeText3 Mac-Version

Codebearbeitungssoftware auf Gottesniveau (SublimeText3)

Was bedeutet der Abschluss im C++-Lambda-Ausdruck? Was bedeutet der Abschluss im C++-Lambda-Ausdruck? Apr 17, 2024 pm 06:15 PM

In C++ ist ein Abschluss ein Lambda-Ausdruck, der auf externe Variablen zugreifen kann. Um einen Abschluss zu erstellen, erfassen Sie die äußere Variable im Lambda-Ausdruck. Abschlüsse bieten Vorteile wie Wiederverwendbarkeit, Ausblenden von Informationen und verzögerte Auswertung. Sie sind in realen Situationen nützlich, beispielsweise bei Ereignishandlern, bei denen der Abschluss auch dann noch auf die äußeren Variablen zugreifen kann, wenn diese zerstört werden.

Wie implementiert man einen Abschluss im C++-Lambda-Ausdruck? Wie implementiert man einen Abschluss im C++-Lambda-Ausdruck? Jun 01, 2024 pm 05:50 PM

C++-Lambda-Ausdrücke unterstützen Abschlüsse, die Funktionsbereichsvariablen speichern und sie für Funktionen zugänglich machen. Die Syntax lautet [capture-list](parameters)->return-type{function-body}. Capture-Liste definiert die zu erfassenden Variablen. Sie können [=] verwenden, um alle lokalen Variablen nach Wert zu erfassen, [&], um alle lokalen Variablen nach Referenz zu erfassen, oder [Variable1, Variable2,...], um bestimmte Variablen zu erfassen. Lambda-Ausdrücke können nur auf erfasste Variablen zugreifen, den ursprünglichen Wert jedoch nicht ändern.

Was sind die Vor- und Nachteile von Abschlüssen in C++-Funktionen? Was sind die Vor- und Nachteile von Abschlüssen in C++-Funktionen? Apr 25, 2024 pm 01:33 PM

Ein Abschluss ist eine verschachtelte Funktion, die auf Variablen im Bereich der äußeren Funktion zugreifen kann. Zu ihren Vorteilen gehören Datenkapselung, Zustandserhaltung und Flexibilität. Zu den Nachteilen gehören der Speicherverbrauch, die Auswirkungen auf die Leistung und die Komplexität des Debuggens. Darüber hinaus können Abschlüsse anonyme Funktionen erstellen und diese als Rückrufe oder Argumente an andere Funktionen übergeben.

Lösen Sie das durch Schließungen verursachte Speicherverlustproblem Lösen Sie das durch Schließungen verursachte Speicherverlustproblem Feb 18, 2024 pm 03:20 PM

Titel: Durch Abschlüsse und Lösungen verursachte Speicherlecks Einführung: Abschlüsse sind ein sehr verbreitetes Konzept in JavaScript, das internen Funktionen den Zugriff auf Variablen externer Funktionen ermöglicht. Allerdings können Schließungen bei falscher Verwendung zu Speicherverlusten führen. In diesem Artikel wird das durch Schließungen verursachte Speicherverlustproblem untersucht und Lösungen sowie spezifische Codebeispiele bereitgestellt. 1. Durch Schließungen verursachte Speicherlecks Das Merkmal von Schließungen besteht darin, dass interne Funktionen auf Variablen externer Funktionen zugreifen können, was bedeutet, dass in Schließungen referenzierte Variablen nicht durch Müll gesammelt werden. Bei unsachgemäßer Verwendung

Der Einfluss von Funktionszeigern und -abschlüssen auf die Golang-Leistung Der Einfluss von Funktionszeigern und -abschlüssen auf die Golang-Leistung Apr 15, 2024 am 10:36 AM

Die Auswirkungen von Funktionszeigern und -abschlüssen auf die Go-Leistung sind wie folgt: Funktionszeiger: Etwas langsamer als direkte Aufrufe, aber verbessert die Lesbarkeit und Wiederverwendbarkeit. Schließungen: Normalerweise langsamer, kapseln aber Daten und Verhalten. Praktischer Fall: Funktionszeiger können Sortieralgorithmen optimieren und Abschlüsse können Ereignishandler erstellen, aber sie bringen Leistungseinbußen mit sich.

Die Rolle des Golang-Funktionsschlusses beim Testen Die Rolle des Golang-Funktionsschlusses beim Testen Apr 24, 2024 am 08:54 AM

Funktionsabschlüsse der Go-Sprache spielen beim Unit-Testen eine wichtige Rolle: Werte erfassen: Abschlüsse können auf Variablen im äußeren Bereich zugreifen, sodass Testparameter erfasst und in verschachtelten Funktionen wiederverwendet werden können. Vereinfachen Sie den Testcode: Durch die Erfassung von Werten vereinfachen Abschlüsse den Testcode, indem sie die Notwendigkeit beseitigen, Parameter für jede Schleife wiederholt festzulegen. Verbessern Sie die Lesbarkeit: Verwenden Sie Abschlüsse, um die Testlogik zu organisieren und so den Testcode klarer und leichter lesbar zu machen.

Verkettete Aufrufe und Schließungen von PHP-Funktionen Verkettete Aufrufe und Schließungen von PHP-Funktionen Apr 13, 2024 am 11:18 AM

Ja, die Einfachheit und Lesbarkeit des Codes können durch verkettete Aufrufe und Abschlüsse optimiert werden: Verkettete Aufrufe verknüpfen Funktionsaufrufe in einer fließenden Schnittstelle. Abschlüsse erstellen wiederverwendbare Codeblöcke und greifen auf Variablen außerhalb von Funktionen zu.

Wie werden Schließungen in Java implementiert? Wie werden Schließungen in Java implementiert? May 03, 2024 pm 12:48 PM

Abschlüsse in Java ermöglichen es inneren Funktionen, auf äußere Bereichsvariablen zuzugreifen, selbst wenn die äußere Funktion beendet wurde. Durch anonyme innere Klassen implementiert, enthält die innere Klasse einen Verweis auf die äußere Klasse und hält die äußeren Variablen aktiv. Schließungen erhöhen die Codeflexibilität, Sie müssen sich jedoch des Risikos von Speicherverlusten bewusst sein, da Verweise auf externe Variablen durch anonyme innere Klassen diese Variablen am Leben halten.

See all articles