Maison  >  Article  >  interface Web  >  Discussion sur le contexte de l'instruction d'exécution Javascript

Discussion sur le contexte de l'instruction d'exécution Javascript

一个新手
一个新手original
2017-09-06 14:51:041731parcourir

Dans cet article, nous expliquerons en profondeur le contexte d'exécution - le concept le plus basique et le plus important en JavaScript. Je pense qu'après avoir lu cet article, vous comprendrez ce qui se fait dans le moteur JavaScript avant d'exécuter le code, pourquoi certaines fonctions et variables peuvent être utilisées avant d'être déclarées et comment leurs valeurs finales sont déterminées.

Qu'est-ce que le contexte d'exécution ?

L'environnement d'exécution du code en Javascript est divisé en trois types suivants :

  • Code de niveau global – c'est l'environnement d'exécution du code par défaut, une fois le code chargé, c'est l'environnement dans lequel le moteur entre en premier.

  • Code de niveau fonction – Lorsqu'une fonction est exécutée, le code dans le corps de la fonction est exécuté.

  • Code d'Eval – le code qui s'exécute à l'intérieur de la fonction Eval.

Vous pouvez trouver de nombreuses ressources expliquant la portée sur Internet. Afin de rendre cet article plus facile à comprendre pour tout le monde, nous pouvons considérer le « contexte d'exécution » comme l'environnement d'exécution ou la portée de. le code actuel. Regardons un exemple ci-dessous, qui inclut des contextes d'exécution globaux et au niveau des fonctions :

Dans la figure ci-dessus, un total de 4 contextes d'exécution sont utilisés. Le violet représente le contexte global ; le vert représente le contexte au sein de la fonction personne ; le bleu et l'orange représentent le contexte des deux autres fonctions au sein de la fonction personne. Notez que quelle que soit la situation, il n’existe qu’un seul contexte global, accessible par n’importe quel autre contexte. En d’autres termes, nous pouvons accéder à la variable sayHello dans le contexte global dans le contexte de personne. Bien entendu, nous pouvons également accéder à la variable dans la fonction firstName ou lastName.

Il n'y a pas de limite au nombre de contextes de fonction. Chaque fois qu'une fonction est appelée et exécutée, le moteur créera automatiquement un nouveau contexte de fonction, en d'autres termes, il créera une nouvelle portée locale, qui peut. be Si des variables privées sont déclarées dans une portée locale, les éléments de la portée locale ne sont pas directement accessibles dans le contexte externe. Dans l'exemple ci-dessus, la fonction interne peut accéder aux variables déclarées dans le contexte externe, mais pas l'inverse. Alors, quelle en est la raison ? Comment est-il géré à l’intérieur du moteur ?

Pile de contexte d'exécution

Dans le navigateur, le moteur JavaScript fonctionne comme un seul thread. C'est-à-dire qu'un seul événement est activé pour le traitement à un moment donné, et les autres événements sont placés dans la file d'attente, en attente d'être traités. L'exemple de diagramme suivant décrit une telle pile :

Nous savons déjà que lorsqu'un fichier de code javascript est chargé par le navigateur, un contexte d'exécution global est saisi en premier par défaut. . Lorsqu'une fonction est appelée et exécutée dans le contexte global, le flux du programme entre dans la fonction appelée. À ce moment, le moteur crée un nouveau contexte d'exécution pour la fonction et le place en haut de la pile de contextes d'exécution. Le navigateur exécute toujours le contexte actuellement en haut de la pile, une fois l'exécution terminée, le contexte est extrait du haut de la pile, puis exécute le code dans le contexte situé en dessous. De cette façon, les contextes de la pile seront exécutés séquentiellement et retirés de la pile jusqu'à revenir au contexte global. Veuillez regarder l'exemple suivant :

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

Une fois que foo ci-dessus est déclaré, il est forcé de s'exécuter directement via l'opérateur (). Le code de fonction s'appelle trois fois, et à chaque fois la variable locale i est augmentée de 1. Chaque fois que la fonction foo est appelée sur elle-même, un nouveau contexte d'exécution est créé. Chaque fois qu'un contexte termine son exécution, le contexte précédent est retiré de la pile et renvoyé au contexte précédent jusqu'à ce qu'il revienne à nouveau au contexte global. L'ensemble du processus est résumé comme suit :

On peut voir que le concept abstrait de contexte d'exécution peut être résumé dans les points suivants :

  • Mono-thread

  • Exécution synchrone

  • La seule fonction de contexte global

  • Il n'y a pas de limite au nombre de contextes d'exécution

  • Chaque fois qu'une fonction est appelée, un nouveau contexte d'exécution sera créé pour elle, même s'il s'agit de la fonction appelée elle-même.

Processus d'établissement du contexte d'exécution

Nous savons désormais qu'à chaque fois qu'une fonction est appelée, un nouveau contexte d'exécution sera créé. Cependant, au sein du moteur JavaScript, le processus de création de contexte est divisé en deux étapes :

  1. phase d'établissement (se produit lorsqu'une fonction est appelée, mais lorsque le code spécifique dans le corps de la fonction est exécuté Précédemment)

  • Créer des variables, des fonctions, des arguments, des objets, des paramètres

  • Créer une chaîne de portée

  • Déterminer la valeur de ceci

  • Phase d'exécution du code :

    • Affectation de variable, référence de fonction, exécution de autres 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。

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

    Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

    Déclaration:
    Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn