Maison >interface Web >js tutoriel >Une analyse approfondie de la portée en JavaScript

Une analyse approfondie de la portée en JavaScript

青灯夜游
青灯夜游avant
2021-05-31 17:28:372126parcourir

Cet article vous donnera une compréhension approfondie de la portée de JavaScript. Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer. J'espère qu'il sera utile à tout le monde.

Une analyse approfondie de la portée en JavaScript

Cet article est appelé de manière plus appropriée notes. Le contenu provient de la première partie de "JavaScript que vous ne connaissez pas (Volume 1)" Portée et fermeture . Très bien raconté et vaut le détour.

Qu'est-ce que Scope

Scope est un ensemble de règles permettant de rechercher des variables en fonction de leur nom.

Comprendre la portée

Comprendre d'abord quelques concepts de base :

  • Moteur : responsable de la compilation et de l'exécution de l'intégralité du programme JavaScript depuis le début pour terminer le processus.
  • Compilateur : Responsable de l'analyse syntaxique et de la génération de code. Cette partie peut également être vue dans Comment le code JavaScript est exécuté
  • Portée : Responsable de la collecte et de la maintenance d'une série de requêtes composées de tous les identifiants (variables) déclarés, et de la mise en œuvre d'un ensemble de très des règles strictes déterminent les droits d'accès du code en cours d'exécution à ces identifiants.

Jetons ensuite un coup d'œil au processus d'exécution du code suivant :

var a = 2;
  • Lors de la rencontre avec var a, le compilateur va demandez Scope Si la variable a existe dans la même collection de portées. Si elle existe, le compilateur ignorera la déclaration et poursuivra la compilation ; sinon, il sera demandé à la portée de déclarer une nouvelle variable dans la collection de portée actuelle et de la nommer a

  • Suivant Le compilateur générera le code d'exécution requis pour que le moteur gère l'opération d'affectation de a = 2. Lorsque le moteur tourne, il demandera d'abord au scope s'il y a une variable a dans l'ensemble de scope actuel. Si c'est le cas, le moteur utilisera la variable ; si elle n'existe pas, le moteur continuera à chercher la variable

  • Si le moteur trouve le a variable, il attribuera 2 Donnez-lui ou le moteur renvoie une erreur.

Résumé : L'opération d'affectation d'une variable effectuera deux actions. Premièrement, le compilateur déclarera une variable dans la portée actuelle, puis le moteur d'exécution recherchera la variable dans le champ d'application actuel. scope. Variable, attribuez-lui une valeur si elle peut être trouvée.

Le compilateur génère le code dans la deuxième étape du processus de compilation, et lorsque le moteur l'exécutera, il recherchera la variable a pour voir si elle a été déclarée. Le processus de recherche est assisté par la portée, mais la manière dont le moteur effectue la recherche affectera le résultat final de la recherche.

Dans notre exemple, le moteur effectuera une recherche LHS pour la variable a, et l'autre type de recherche est appelé RHS. "L" et "R" représentent respectivement les côtés gauche et droit d'une opération d'affectation. Les requêtes LHS sont effectuées lorsque la variable apparaît sur le côté gauche de l'affectation, et les requêtes RHS sont effectuées lorsqu'elle apparaît sur le côté droit.

LHS : Essayez de trouver le conteneur de la variable elle-même afin qu'on puisse lui attribuer une valeur ; RHS : Trouvez simplement la valeur d'une variable.
console.log(a);

La référence à a est une référence RHS, car aucune valeur de tâche n'est attribuée à a. Par conséquent, il est nécessaire de trouver et d'obtenir la valeur de a, afin que la valeur puisse être transmise à console.log. (...)

a = 2;

La référence à a ici est une référence LHS, car en fait, nous ne nous soucions pas de la valeur actuelle, nous voulons juste trouver la cible de l'opération d'affectation = 2.

funciton foo(a) {
    console.log(a)
}

foo(2);
  1. L'appel à la fonction foo dans la dernière ligne nécessite une référence RHS à foo, trouver la valeur de foo et me la donner
  2. L'opération implicite a = 2 dans le code est possible C'est facile à oublier, cette opération se produit lorsque 2 est passé en paramètre à la fonction foo, 2 sera affecté au paramètre a, afin d'attribuer une valeur au paramètre a (implicitement), vous devez faire une requête LHS.
  3. Il existe également une référence RHS à a, et la valeur résultante est transmise à console.log(...). console.log(...) lui-même a également besoin d'une référence pour s'exécuter, donc l'objet console sera interrogé pour RHS et la valeur obtenue sera vérifiée pour voir s'il existe une méthode appelée log.

Si la requête RHS ne parvient pas à trouver les variables requises dans toutes les étendues imbriquées, le moteur lancera une exception ReferenceError. Une variable est trouvée en effectuant une requête RHS, mais vous essayez d'effectuer des opérations déraisonnables sur la valeur de cette variable, comme essayer d'appeler une valeur de type non-fonction qui fait référence à une propriété dans une valeur de type null ou indéfini, puis le le moteur lancera une exception TypeError d’un autre type.
Si la variable est introuvable lorsque le moteur exécute la requête LHS, il en créera une dans la portée globale. Cependant, en mode strict, une variable globale n'est pas automatiquement créée, mais une exception ReferenceError est levée

Supplémentaire Plusieurs types d'erreurs JS courants

Un bref résumé est le suivant :

作用域是一套规则,用于确定在哪里找,怎么找到某个变量。如果查找的目的是对变量进行赋值,那么就会使用 LHS查询; 如果目的是获取变量的值,就会使用 RHS 查询;
JavaScript 引擎执行代码前会对其进行编译,这个过程中,像 var a = 2 这样的声明会被分解成两个独立的步骤

  • var a 在其作用域中声明变量,这会在最开始的阶段,也就是代码执行前进行

  • 接下来,a = 2 会查询 (LHS查询)变量 a 并对其进行赋值。

词法作用域

词法作用域是你在写代码时将变量写在哪里来决定的。编译的词法分析阶段基本能够知道全局标识符在哪里以及是如何声明的,从而能够预测在执行过程中如果对他们查找。

有一些方法可以欺骗词法作用域,比如 eval, with, 这两种现在被禁止使用,1是严格模式和非严格模式下表现不同 2是有性能问题, JavaScript引擎在编译阶段会做很多性能优化,而其中很多优化手段都依赖于能够根据代码的词法进行静态分析,并预先确定所有变量和函数的定义位置,才能在执行过程中快速找到识别符,eval, with会改变作用域,所以碰到它们,引擎将无法做优化处理。

全局作用域和函数作用域

全局作用域

  • 在最外层函数和最外层函数外面定义的变量拥有全局作用域
var a = 1;
function foo() {

}

变量a 和函数声明 foo 都是在全局作用域中的。

  • 所有未定义直接赋值的变量自动声明为拥有全局作用域

var a = 1;
function foo() {
    b = 2;
}
foo();
console.log(b); // 2
  • 所有 window 对象的属性拥有全局作用域

函数作用域

函数作用域是指在函数内声明的所有变量在函数体内始终是可见的。外部作用域无法访问函数内部的任何内容。

function foo() {
    var a = 1;
    console.log(a); // 1
}
foo();
console.log(a); // ReferenceError: a is not defined
只有函数的{}构成作用域,对象的{}以及 if(){}都不构成作用域;

变量提升

提升是指声明会被视为存在与其所出现的作用域的整个范围内。

JavaScript编译阶段是找到找到所有声明,并用合适的作用域将他们关联起来(词法作用域核心内容),所以就是包含变量和函数在内的所有声明都会在任何代码被执行前首先被处理。

每个作用域都会进行提升操作。

function foo() {
    var a;
    console.log(a); // undefined
    a = 2;
}
foo();
注意,函数声明会被提升,但是函数表达式不会被提升。

关于 块级作用域和变量提升的内容之前在 从JS底层理解var、let、const这边文章中详细介绍过,这里不再赘述。

块级作用域

我们来看下面这段代码

for(var i = 0; i < 5; i++) {
    setTimeout(() => {
        console.log(i);
    })
}
console.log(`当前的i为${i}`); // 当前的i为5

上面这段代码我们希望是输出 0,1, 2, 3, 4 ,但是实际上输出的是 5,5, 5, 5, 5。我们在 for 循环的头部直接定义了变量 i,通常是因为只想在 for 循环内部的上下文中使用 i,但是实际上 此时的 i 被绑定在外部作用域(函数或全局)中。

,块级作用域是指在指定的块级作用域外无法访问。在ES6之前是没有块级作用域的概念的,ES6引入了 let 和 const。我们可以改写上面的代码,使它按照我们想要的方式运行。

for(let i = 0; i < 5; i++) {
    setTimeout(() => {
        console.log(i);
    })
}
// 0 1 2 3 4
console.log(`当前的i为${i}`); // ReferenceError: i is not defined

此时 for 循环头部的 let 不仅将 i 绑定到了 for 循环的迭代中,事实上将它重新绑定到了循环的每一个迭代中,确保使用上一次循环迭代结束的值重新进行赋值。

let声明附属于一个新的作用域而不是当前的函数作用域(也不属于全局作用域)。但是其行为是一样的,可以总结为:任何声明在某个作用域内的变量,都将附属于这个作用域。
const也是可以用来创建块级作用域变量,但是创建的是固定值。

作用域链

JavaScript是基于词法作用域的语言,通过变量定义的位置就能知道变量的作用域。全局变量在程序中始终都有都定义的。局部变量在声明它的函数体内以及其所嵌套的函数内始终是有定义的。

每一段 JavaScript 代码都有一个与之关联的作用域链(scope chain)。这个作用域链是一个对象列表或者链表。当 JavaScript 需要查找变量 x 的时候(这个过程称为变量解析),它会从链中的第一个变量开始查找,如果这个对象上依然没有一个名为 x 的属性,则会继续查找链上的下一个对象,如果第二个对象依然没有名为 x 的属性,javaScript会继续查找下一个对象,以此类推。如果作用域链上没有任何一个对象包含属性 x, 那么就认为这段代码的作用域链上不存在 x, 并最终抛出一个引用错误 (Reference Error) 异常。

下面作用域中有三个嵌套的作用域。

function foo(a) {
    var b = a * 2;
    function bar(c) {
        console.log(a, b, c)
    }
    bar( b * 3);
}
foo(2);

<img src="https://img.php.cn/upload/image/252/331/635/1622453204885970.png" title="1622453204885970.png" alt="Une analyse approfondie de la portée en JavaScript">

气泡1包含着整个全局作用域,其中只有一个标识符:foo;
气泡2包含着foo所创建的作用域,其中有三个标识符:a、bar 和 b;
气泡3包含着 bar所创建的作用域,其中只有一个标识符:c

执行 console.log(...),并查找 a,b,c三个变量的引用。下面我们来看看查找这几个变量的过程.
它首先从最内部的作用域,也就是 bar(..) 函数的作用域气泡开始找,引擎在这里无法找到 a,因此就会去上一级到所嵌套的 foo(...)的作用域中继续查找。在这里找到了a,因此就使用了这个引用。对b来说也一样,而对 c 来说,引擎在 bar(..) 中就找到了它。

如果 a,c都存在于 bar(...) 内部,console.log(...)就可以直接使用 bar(...) 中的变量,而无需到外面的 foo(..)中查找。作用域会在查找都第一个匹配的标识符时就停止。

在多层的嵌套作用域中可以定义同名的标识符,这叫”遮蔽效应“。

var a = &#39;外部的a&#39;;
function foo() {
    var a = &#39;foo内部的a&#39;;
    console.log(a); // foo内部的a
}
foo();

作用域与执行上下文

JavaScript的执行分为:解释和执行两个阶段

解释阶段

  • 词法分析
  • 语法分析
  • 作用域规则确定

执行阶段

  • 创建执行上下文
  • 执行函数代码
  • 垃圾回收

作用域在函数定义时就已经确定了,而不是在函数调用时确定,但执行上下文是函数执行之前创建的。

总结

  • 作用域就是一套规则,用于确定在哪里找以及怎么找到某个变量。

  • 词法作用域在你写代码的时候就确定了。JavaScript是基于词法作用域的语言,通过变量定义的位置就能知道变量的作用域。ES6引入的let和const声明的变量在块级作用域中。

  • 声明提升是指声明会被视为存在与其所出现的作用域的整个范围内。

  • 查找变量的时候会先从内部的作用域开始查找,如果没找到,就往上一级进行查找,依次类推。

  • 作用域在函数定义时就已经确定了,执行上下文是函数执行之前创建的。

更多编程相关知识,请访问:编程视频!!

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:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer