Heim  >  Artikel  >  Web-Frontend  >  Diskussion über den Kontext der Javascript-Ausführungsanweisung

Diskussion über den Kontext der Javascript-Ausführungsanweisung

一个新手
一个新手Original
2017-09-06 14:51:041682Durchsuche

In diesem Artikel erklären wir ausführlich den Ausführungskontext – das grundlegendste und wichtigste Konzept in JavaScript. Ich glaube, dass Sie nach dem Lesen dieses Artikels verstehen werden, was in der JavaScript-Engine vor der Ausführung des Codes geschieht, warum bestimmte Funktionen und Variablen verwendet werden können, bevor sie deklariert werden, und wie ihre endgültigen Werte definiert werden.

Was ist Ausführungskontext?

Die Ausführungsumgebung von Code in Javascript ist in die folgenden drei Typen unterteilt:

  • Code auf globaler Ebene – das ist Die Standardcode-Laufumgebung. Sobald der Code geladen ist, ist dies die Umgebung, in die die Engine zuerst eintritt.

  • Code auf Funktionsebene – Wenn eine Funktion ausgeführt wird, wird der Code im Funktionskörper ausgeführt.

  • Evals Code – der Code, der innerhalb der Eval-Funktion ausgeführt wird.

Im Internet finden Sie viele Ressourcen, die den Umfang erläutern. Um diesen Artikel für alle verständlicher zu machen, können wir uns „Ausführungskontext“ als die laufende Umgebung oder den Umfang vorstellen der aktuelle Code. Schauen wir uns unten ein Beispiel an, das globale Ausführungskontexte und Ausführungskontexte auf Funktionsebene umfasst:

In der obigen Abbildung werden insgesamt 4 Ausführungskontexte verwendet. Lila repräsentiert den globalen Kontext; Grün repräsentiert den Kontext innerhalb der Personenfunktion; Blau und Orange repräsentieren den Kontext der anderen beiden Funktionen innerhalb der Personenfunktion. Beachten Sie, dass es unabhängig von der Situation nur einen globalen Kontext gibt, auf den jeder andere Kontext zugreifen kann. Mit anderen Worten: Wir können im globalen Kontext im Personenkontext auf die Variable sayHello zugreifen. Natürlich können wir auch in der Funktion firstName oder lastName auf die Variable zugreifen.

Die Anzahl der Funktionskontexte ist unbegrenzt. Jedes Mal, wenn eine Funktion aufgerufen und ausgeführt wird, erstellt die Engine automatisch einen neuen Funktionskontext Wenn private Variablen in einem lokalen Bereich deklariert werden, kann im externen Kontext nicht direkt auf die Elemente im lokalen Bereich zugegriffen werden. Im obigen Beispiel kann die innere Funktion auf die im äußeren Kontext deklarierten Variablen zugreifen, jedoch nicht umgekehrt. Was ist also der Grund dafür? Wie wird es im Motor gehandhabt?

Ausführungskontextstapel

Im Browser arbeitet die JavaScript-Engine als einzelner Thread. Das heißt, zu einem bestimmten Zeitpunkt wird nur ein Ereignis zur Verarbeitung aktiviert und andere Ereignisse werden in die Warteschlange gestellt und warten auf die Verarbeitung. Das folgende Beispieldiagramm beschreibt einen solchen Stapel:

Wir wissen bereits, dass beim Laden einer Javascript-Codedatei durch den Browser standardmäßig zuerst ein globaler Ausführungskontext eingegeben wird . Wenn eine Funktion im globalen Kontext aufgerufen und ausgeführt wird, tritt der Programmfluss in die aufgerufene Funktion ein. Zu diesem Zeitpunkt erstellt die Engine einen neuen Ausführungskontext für die Funktion und verschiebt ihn an die Spitze des Ausführungskontextstapels. Der Browser führt immer den Kontext aus, der sich aktuell oben im Stapel befindet. Sobald die Ausführung abgeschlossen ist, wird der Kontext oben im Stapel abgelegt und führt dann den Code im darunter liegenden Kontext aus. Auf diese Weise werden die Kontexte im Stapel nacheinander ausgeführt und vom Stapel entfernt, bis sie zum globalen Kontext zurückkehren. Schauen Sie sich bitte das folgende Beispiel an:

(function foo(i) {            
        if (i === 3) {                
               return;
            }            
      else {                
          foo(++i);
            }
 }(0));

Nachdem das obige foo deklariert wurde, wird es gezwungen, direkt über den ()-Operator ausgeführt zu werden. Der Funktionscode ruft sich dreimal auf und jedes Mal wird die lokale Variable i um 1 erhöht. Jedes Mal, wenn die foo-Funktion für sich selbst aufgerufen wird, wird ein neuer Ausführungskontext erstellt. Immer wenn die Ausführung eines Kontexts abgeschlossen ist, wird der vorherige Kontext vom Stapel entfernt und zum vorherigen Kontext zurückgeführt, bis er wieder zum globalen Kontext zurückkehrt. Der gesamte Prozess wird wie folgt abstrahiert:

Es ist ersichtlich, dass das abstrakte Konzept des Ausführungskontexts in den folgenden Punkten zusammengefasst werden kann:

  • Single-Threaded

  • Synchronisierte Ausführung

  • Der einzige globale Kontext

  • Funktion Es gibt keine Begrenzung für die Anzahl der Ausführungskontexte

  • Jedes Mal, wenn eine Funktion aufgerufen wird, wird ein neuer Ausführungskontext dafür erstellt, auch wenn es sich um die aufgerufene Funktion selbst handelt.

Prozess zur Einrichtung des Ausführungskontexts

Wir wissen jetzt, dass bei jedem Aufruf einer Funktion ein neuer Ausführungskontext erstellt wird. Innerhalb der JavaScript-Engine ist der Kontexterstellungsprozess jedoch in zwei Phasen unterteilt:

  1. Einrichtungsphase (tritt auf, wenn eine Funktion aufgerufen wird, aber wenn der spezifische Code im Funktionskörper ausgeführt wird Zuvor)

  • Variablen, Funktionen, Argumente, Objekte, Parameter erstellen

  • Bereichskette erstellen

  • Bestimmen Sie den Wert davon

  • Codeausführungsphase:

    • Variablenzuweisung, Funktionsreferenz, Ausführung von andere Code

    实际上,可以把执行上下文看做一个对象,其下包含了以上3个属性:

        
              (executionContextObj = {
                variableObject: { /* 函数中的arguments对象, 参数, 内部的变量以及函数声明 */ },
                scopeChain: { /* variableObject 以及所有父执行上下文中的variableObject */ },            
                this: {}
              }

    建立阶段以及代码执行阶段的详细分析

    确切地说,执行上下文对象(上述的executionContextObj)是在函数被调用时,但是在函数体被真正执行以前所创建的。函数被调用时,就是我上述所描述的两个阶段中的第一个阶段 – 建立阶段。这个时刻,引擎会检查函数中的参数,声明的变量以及内部函数,然后基于这些信息建立执行上下文对象(executionContextObj)。在这个阶段,variableObject对象,作用域链,以及this所指向的对象都会被确定。

    上述第一个阶段的具体过程如下:

    1. 找到当前上下文中的调用函数的代码

    2. 在执行被调用的函数体中的代码以前,开始创建执行上下文

    3. 进入第一个阶段-建立阶段:

    • 建立variableObject对象:

    • 初始化作用域链

    • 确定上下文中this的指向对象

    1. 建立arguments对象,检查当前上下文中的参数,建立该对象下的属性以及属性值

    2. 检查当前上下文中的函数声明:

      每找到一个函数声明,就在variableObject下面用函数名建立一个属性,属性值就是指向该函数在内存中的地址的一个引用

      如果上述函数名已经存在于variableObject下,那么对应的属性值会被新的引用所覆盖。

  • 代码执行阶段:

    执行函数体中的代码,一行一行地运行代码,给variableObject中的变量属性赋值。

  • 下面来看个具体的代码示例:

    function foo(i) {            
        var a = 'hello';            
        var b = function privateB() {
                        };            
       function c() {
                            }
                 }        
        foo(22);

    在调用foo(22)的时候,建立阶段如下:

        
            fooExecutionContext = {
                variableObject: {
                    arguments: {                    0: 22,
                        length: 1
                    },
                    i: 22,
                    c: pointer to function c()
                    a: undefined,
                    b: undefined
                },
                scopeChain: { ... },            
                this: { ... }
            }

    由此可见,在建立阶段,除了arguments,函数的声明,以及参数被赋予了具体的属性值,其它的变量属性默认的都是undefined。一旦上述建立阶段结束,引擎就会进入代码执行阶段,这个阶段完成后,上述执行上下文对象如下:

                fooExecutionContext = {
                variableObject: {
                    arguments: {                    0: 22,
                        length: 1
                    },
                    i: 22,
                    c: pointer to function c()
                    a: 'hello',
                    b: pointer to function privateB()
                },
                scopeChain: { ... },            
                this: { ... }
            }

    我们看到,只有在代码执行阶段,变量属性才会被赋予具体的值。

    局部变量作用域提升的缘由

    在网上一直看到这样的总结: 在函数中声明的变量以及函数,其作用域提升到函数顶部,换句话说,就是一进入函数体,就可以访问到其中声明的变量以及函数。这是对的,但是知道其中的缘由吗?相信你通过上述的解释应该也有所明白了。不过在这边再分析一下。看下面一段代码:

        
            (function() {
                console.log(typeof foo); // function pointer
                console.log(typeof bar); // undefined        
                var foo = 'hello',                
                bar = function() {                    
                return 'world';
                    };        
                function foo() {                
                return 'hello';
                }
            
            }());

    上述代码定义了一个匿名函数,并且通过()运算符强制理解执行。那么我们知道这个时候就会有个执行上下文被创建,我们看到例子中马上可以访问foo以及bar变量,并且通过typeof输出foo为一个函数引用,bar为undefined。

    为什么我们可以在声明foo变量以前就可以访问到foo呢?

    因为在上下文的建立阶段,先是处理arguments, 参数,接着是函数的声明,最后是变量的声明。那么,发现foo函数的声明后,就会在variableObject下面建立一个foo属性,其值是一个指向函数的引用。当处理变量声明的时候,发现有var foo的声明,但是variableObject已经具有了foo属性,所以直接跳过。当进入代码执行阶段的时候,就可以通过访问到foo属性了,因为它已经就存在,并且是一个函数引用。

    为什么bar是undefined呢?

    因为bar是变量的声明,在建立阶段的时候,被赋予的默认的值为undefined。由于它只要在代码执行阶段才会被赋予具体的值,所以,当调用typeof(bar)的时候输出的值为undefined。

    好了,到此为止,相信你应该对执行上下文有所理解了,这个执行上下文的概念非常重要,务必好好搞懂之!

    Das obige ist der detaillierte Inhalt vonDiskussion über den Kontext der Javascript-Ausführungsanweisung. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

    Stellungnahme:
    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