Heim  >  Artikel  >  Web-Frontend  >  Eine eingehende Analyse des Umfangs in JavaScript

Eine eingehende Analyse des Umfangs in JavaScript

青灯夜游
青灯夜游nach vorne
2021-05-31 17:28:372060Durchsuche

Dieser Artikel vermittelt Ihnen ein detailliertes Verständnis des JavaScript-Bereichs. Es hat einen gewissen Referenzwert. Freunde in Not können sich darauf beziehen. Ich hoffe, es wird für alle hilfreich sein.

Eine eingehende Analyse des Umfangs in JavaScript

Dieser Artikel heißt besser „Notizen“. Der Inhalt stammt aus dem ersten Teil von „JavaScript You Don't Know (Band 1)“, Umfang und Abschluss. Sehr gut erzählt und durchaus sehenswert.

Was ist Scope

Scope ist eine Reihe von Regeln zum Suchen von Variablen nach Namen.

Umfang verstehen

Verstehen Sie zunächst einige grundlegende Konzepte:

  • Engine: Verantwortlich für den Kompilierungs- und Ausführungsprozess des gesamten JavaScript-Programms von Anfang bis Ende.
  • Compiler: Verantwortlich für Syntaxanalyse und Codegenerierung. Dieser Teil ist auch zu sehen Wie JavaScript-Code ausgeführt wird
  • Geltungsbereich: Verantwortlich für das Sammeln und Verwalten einer Reihe von Abfragen, die aus allen deklarierten Bezeichnern (Variablen) bestehen, und die Implementierung eines sehr strengen Regelsatzes, um den Zugriff des aktuellen Ausführungscodes zu bestimmen zu diesen Identifikatoren.

Als nächstes werfen wir einen Blick auf den Ausführungsprozess des folgenden Codes:

var a = 2;
  • Bei der Begegnung mit var a fragt der Compiler scope, ob die Variable a in existiert das gleiche Zielfernrohr in der Mitte. Wenn sie vorhanden ist, ignoriert der Compiler die Deklaration und fährt mit der Kompilierung fort. Andernfalls wird der Bereich aufgefordert, eine neue Variable in der aktuellen Bereichssammlung zu deklarieren und sie als a zu bezeichnen a是否存在于同一个作用域集合中。如果存在,编译器会忽略声明,继续编译;否则,会要求作用域在当前作用域集合中声明一个新的变量,并命名为 a

  • 接下来 编译器 会为 引擎 生成运行时所需的代码,用来处理 a = 2 这个赋值操作。引擎运行时会先问作用域,当前作用域集中是否存在变量a。如果是,引擎就会使用该变量;如果不存在,引擎会继续查找该变量

  • 如果 引擎 找到了a 变量,就会将 2 赋值给它,否则引擎就抛出一个错误。

总结:变量的赋值操作会执行两个动作,首先编译器会在当前作用域中声明一个变量,然后在运行时引擎就会会作用域中查找该变量,如果能够找到就对它赋值。

编译器在编译过程的第二步中生成了代码,引擎执行它时,会通过查找变量 a来判断它是否已声明过。查找的过程中由作用域进行协助,但是引擎执行怎么样的查找,会影响最终的查找结果。

在我们的例子中,引擎会为变量 a 进行 LHS 查询,另外一个查找的类型叫做 RHS。 ”L“ 和 "R" 分别代表一个赋值操作左侧和右侧。当变量出现在赋值操作的左侧时进行 LHS 查询,出现在右侧时进行 RHS 查询。

LHS:试图找到变量的容器本身,从而可以对其赋值;RHS: 就是简单地查找某个变量的值。
console.log(a);

对 a 的引用是一个 RHS 引用,因为这里 a 并没有赋予任务值,相应地需要查找并取得 a 的值,这样才能将值传递给 console.log(...)

a = 2;

这里对 a 的引用是 LHS 引用,因为实际上我们并不关心当前的值是什么,只是想要为 = 2这个赋值操作找到目标。

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

foo(2);
  1. 最后一行 foo 函数的调用需要对 foo 进行 RHS 引用,去找 foo的值,并把它给我
  2. 代码中隐式的 a = 2 操作可能很容易被你忽略掉,这操作发生在 2 被当做参数传递给 foo 函数时,2 会被分配给参数 a,为了给参数 a (隐式地) 分配值,需要进行一次 LHS 查询。
  3. 这里还有对 a 进行的 RHS 引用,并且将得到的值传给了 console.log(...)console.log(...) 本身也需要一个引用才能执行,因此会对 console对象进行 RHS 查询,并且检查得到的值中是否有一个叫做 log generiert den Laufzeitcode, der für die
  4. Engine
erforderlich ist, um die Zuweisungsoperation von a = 2 durchzuführen. Wenn die Engine läuft, fragt sie zunächst den Bereich, ob die Variable a im aktuellen Bereichssatz vorhanden ist. Wenn dies der Fall ist, verwendet die Engine die Variable. Wenn sie nicht vorhanden ist, sucht die Engine weiterhin nach der Variablen einen Fehler werfen.


Zusammenfassung: Bei der Zuweisungsoperation einer Variablen werden zwei Aktionen ausgeführt: Zuerst deklariert der Compiler eine Variable im aktuellen Bereich, und dann sucht die Engine zur Laufzeit nach der Variablen , es wird verwendet. Der Compiler generiert den Code im zweiten Schritt des Kompilierungsprozesses. Wenn die Engine ihn ausführt, ermittelt sie, ob er deklariert wurde, indem sie nach der Variablen a sucht. Der Suchvorgang wird durch den Umfang unterstützt, aber die Art und Weise, wie die Suchmaschine die Suche durchführt, wirkt sich auf das endgültige Suchergebnis aus.

In unserem Beispiel führt die Engine eine LHS-Abfrage für die Variable a durch, und der andere Abfragetyp heißt RHS. „L“ und „R“ repräsentieren die linke bzw. rechte Seite einer Zuweisungsoperation. LHS-Abfragen werden durchgeführt, wenn die Variable auf der linken Seite der Zuweisung erscheint, und RHS-Abfragen werden durchgeführt, wenn sie auf der rechten Seite erscheint.

🎜LHS: versucht, den Container der Variablen selbst zu finden, damit ihr ein Wert zugewiesen werden kann; RHS: sucht einfach nach dem Wert einer Variablen. 🎜
var a = 1;
function foo() {

}
🎜Der Verweis auf a ist ein RHS-Verweis, da a hier kein Aufgabenwert zugewiesen wird. Dementsprechend müssen Sie den Wert von a finden und erhalten, damit der Wert an console.log(...) übergeben werden kann. )🎜
var a = 1;
function foo() {
    b = 2;
}
foo();
console.log(b); // 2
🎜Hier ist das Richtige Die Referenz von a ist eine LHS-Referenz, da uns der aktuelle Wert eigentlich egal ist, wir wollen nur das Ziel für die Zuweisungsoperation = 2 finden. 🎜
function foo() {
    var a = 1;
    console.log(a); // 1
}
foo();
console.log(a); // ReferenceError: a is not defined
    🎜Der Aufruf der foo-Funktion in der letzten Zeile erfordert einen RHS-Verweis auf foo, finde den Wert von foo und gib ihn mir🎜🎜Die implizite a = 2-Operation im Code kann leicht übersehen werden Sie, dieser Vorgang tritt auf, wenn 2 als Parameter an die Funktion foo übergeben wird. 2 wird der Reihe nach dem Parameter a zugewiesen Um den Parameter a bereitzustellen, wird (implizit) ein Wert zugewiesen, was eine LHS-Abfrage erfordert. 🎜🎜Es gibt auch einen RHS-Verweis auf a, und der erhaltene Wert wird an console.log(...) übergeben. console.log(...) selbst benötigt ebenfalls eine Referenz zur Ausführung, daher führt es eine RHS-Abfrage für das Konsolenobjekt durch und prüft, ob einer der erhaltenen Werte vorhanden ist ​wird als log-Methode bezeichnet. 🎜🎜🎜Wenn die RHS-Abfrage die erforderlichen Variablen nicht in allen verschachtelten Bereichen finden kann, löst die Engine eine ReferenceError-Ausnahme aus. Durch Ausführen einer RHS-Abfrage wird eine Variable gefunden, aber Sie versuchen, unangemessene Operationen mit dem Wert dieser Variablen durchzuführen, z. B. den Versuch, einen Nicht-Funktionstypwert aufzurufen, der auf eine Eigenschaft in einem Null- oder undefinierten Typwert verweist, und dann den Die Engine löst eine TypeError-Ausnahme eines anderen Typs aus. 🎜Wenn die Engine die Variable beim Ausführen der LHS-Abfrage nicht finden kann, erstellt sie eine im globalen Bereich. Im strikten Modus wird jedoch nicht automatisch eine globale Variable erstellt, sondern eine ReferenceError-Ausnahme ausgelöst 🎜🎜Supplementary🎜Mehrere häufige Fehlertypen in JS🎜🎜🎜Eine kurze Zusammenfassung lautet wie folgt:🎜

    作用域是一套规则,用于确定在哪里找,怎么找到某个变量。如果查找的目的是对变量进行赋值,那么就会使用 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="Eine eingehende Analyse des Umfangs in 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声明的变量在块级作用域中。

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

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

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

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

Das obige ist der detaillierte Inhalt vonEine eingehende Analyse des Umfangs in JavaScript. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:segmentfault.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen