Heim  >  Artikel  >  Web-Frontend  >  Detaillierte JavaScript-Analyse der Bereichskette

Detaillierte JavaScript-Analyse der Bereichskette

WBOY
WBOYnach vorne
2022-11-03 16:02:482220Durchsuche

Dieser Artikel vermittelt Ihnen relevantes Wissen über JavaScript, das hauptsächlich den relevanten Inhalt der Bereichskette vorstellt. Der Bereich ist ein Regelwerk, das für das Sammeln und Verwalten aller deklarierten Bezeichner (Variablen) verantwortlich ist Eine Reihe von Regeln wird implementiert, um die Zugriffsberechtigungen des aktuell ausgeführten Codes für diese Bezeichner zu bestimmen. Schauen wir uns das gemeinsam an. Ich hoffe, es wird für alle hilfreich sein.

Detaillierte JavaScript-Analyse der Bereichskette

【Verwandte Empfehlungen: JavaScript-Video-Tutorial, Web-Frontend

1.

Scope ist ein Regelwerk, das für das Sammeln und Verwalten einer Reihe von Abfragen verantwortlich ist, die aus allen deklarierten Bezeichnern (Variablen) bestehen, und das Durchsetzen eines sehr strengen Regelwerks, das die Zugriffsrechte des aktuell ausgeführten Codes auf diese Bezeichner bestimmt.

Eine verständlichere Erklärung des Bereichs lautet wie folgt:

  • Der Bereich ist zunächst eine Reihe von Regeln作用域首先是一套规则
  • 该规则的用处是用来存储变量,以及如何有限制的获取变量。

用一个不一定完全恰当的形容来类比作用域就是,存在一个国际银行,你将手里各国的货币存入其中,当你要取出一些钱时,它有一套规则限定你只能在可使用货币的当地才能取出相应的该货币。  这个银行和它制定的规则,就是作用域对于变量的作用。

2. 词法作用域与动态作用域

在JavaScript中,所使用的作用域是词法作用域,也称为静态作用域。它是在编译前就确定的。JavaScript本质上是一门编译型语言,只不过它编译发生的时间节点是在代码执行前的几微秒。不同于其他编译型语言是在构建的过程中编译,所以才看上去更像是解释型语言。

关于编译和解释暂且不论,只需要理解到词法作用域就是静态作用域。 理解静态的含义就是当代码在书写下定义时就已经确定了。也就是人所读到代码中变量和函数被定义在什么范围,该范围就是它们的作用域。

用一个简单的例子来理解:

var a = 1function foo() {  console.log(a)
}function bar(b) {  var a = 2
  console.log(a)  foo()  function baz() {      console.log(b)
  }  return baz
}var c = bar(a)c()

对于定义的三个函数foo, bar, baz以及变量a,它们在书写时作用域就已经定义。

因而在代码执行时, bar函数先调用传入变量a的值, 在第一个输出变量a值时,会先询问自身作用域是否定义过变量a, 定义过则询问是否存在a的值,存在着输出变量a为2.

然后开始调用foo函数, foo中只有输出变量a的值, 同样也会询问自身作用域是否定义过变量a, foo中未定义, 则会往上寻找自身定义时的作用域询问是否定义过变量a, 全局作用域定义过并且存在a值, 因而输出a为1。其实这其中已经涉及到了作用域链,但暂且不议。

之后进入到c函数调用也就是baz函数, baz中输出变量b的值,b会询问自身作用域是否存在定义过变量b, baz未定义, 则往上查找自身定义时的作用域也就是bar函数作用域是否定义过变量b, bar实际隐含在参数中为变量b定义并且赋值为1, 因而最终输出为1。

这就是静态作用域,只需要看变量和函数的书写位置,即可确定它们都作用域范围。

与之相对的是动态作用域, 在JavaScript中涉及到动态作用域的只有this指向,这在之后复习this时会涉及。

假设JavaScript是动态作用域,同样看上述例子里的代码执行过程。

bar先调用并传入变量a, 在第一个输出变量a值时, 完全获取到变量a因而输出2。在调用foo函数时, 由于自身作用域没有变量a, 则会从自身被调用的位置的作用域去往上查找,则此时为函数bar的作用域,因而输出的a值为2。

c函数调用也就是baz函数调用时,也同样是自身不存在变量b,去寻找自身被调用的位置的作用域,也就是全局作用域,全局作用域中同样未定义过变量b, 则直接报错。

3. 作用域的分类

作用域的分类可以按照上述所说的静动分为:

  • 静态作用域
  • 动态作用域
  • Der Zweck dieser Regel besteht darin, Variablen zu speichern und wie Variablen abgerufen werden mit Einschränkungen.

Um eine Analogie zu verwenden, die möglicherweise nicht ganz angemessen ist, um den Umfang zu beschreiben: Es gibt eine internationale Bank, in die Sie die Währungen verschiedener Länder einzahlen, wenn Sie etwas Geld abheben möchten verfügt über eine Reihe von Regeln. Sie können die entsprechende Währung nur dort abheben, wo die Währung verfügbar ist. Diese Bank und die von ihr formulierten Regeln spielen die Rolle des Geltungsbereichs für Variablen. 🎜

2. Lexikalischer Bereich und dynamischer Bereich🎜🎜In JavaScript ist der verwendete Bereich der lexikalische Bereich, auch statischer Bereich genannt. Es wird vor der Kompilierung festgelegt. JavaScript ist im Wesentlichen eine kompilierte Sprache, die Kompilierung erfolgt jedoch einige Mikrosekunden vor der Ausführung des Codes. Im Gegensatz zu anderen kompilierten Sprachen wird sie während des Erstellungsprozesses kompiliert, sodass sie eher wie eine interpretierte Sprache aussieht. 🎜🎜 Unabhängig von der Kompilierung und Interpretation müssen Sie nur verstehen, dass der lexikalische Bereich ein statischer Bereich ist. Verstehen Sie, dass die Bedeutung von „statisch“ darin besteht, dass bestimmt wird, wann der Code geschrieben und definiert wird. Das heißt, der Umfang, in dem Variablen und Funktionen im Code definiert sind, den die Leute lesen, ist ihr Geltungsbereich. 🎜🎜Verwenden Sie zum Verständnis ein einfaches Beispiel: 🎜
var name = 'xavier'function foo() {  var name = 'parker'
  var age = 18

  function bar() {    var name = 'coin'
    return age
  }  return bar()
}foo()console.log(age) // 报错
🎜Für die drei definierten Funktionen foo, bar, baz und Variable a wurden ihre Bereiche beim Schreiben definiert. 🎜🎜Wenn der Code ausgeführt wird, ruft die Bar-Funktion also zunächst den Wert der übergebenen Variablen a auf. Wenn der Wert der Variablen a zum ersten Mal ausgegeben wird, fragt sie zunächst, ob die Variable a in ihr definiert wurde eigener Bereich. Wenn es definiert wurde, wird gefragt, ob der Wert von a vorhanden ist. Es gibt eine Ausgabevariable a, die 2 ist. 🎜🎜 Dann rufen Sie die Funktion foo auf. Es gibt nur den Wert der Ausgabevariablen a foo, und auch fragt seinen eigenen Bereich, ob die Variable a definiert wurde, aber sie ist nicht in foo definiert, dann sucht es nach dem Bereich, als er definiert wurde und fragen Sie, ob die Variable a definiert wurde. Der globale Bereich wurde definiert und ein Wert existiert, sodass die Ausgabe a 1 ist. Tatsächlich ist die Scope-Kette bereits beteiligt, aber darauf wollen wir vorerst nicht eingehen. 🎜🎜Dann geben Sie den c-Funktionsaufruf ein, bei dem es sich um die baz-Funktion handelt. Baz fragt, ob die Variable b in ihrem eigenen Bereich definiert ist Funktion von , wenn es definiert ist, ob die Variable b im Funktionsumfang bar definiert wurde, tatsächlich implizit für die Variable b im Parameter definiert ist und ihr der Wert 1 zugewiesen ist Die Ausgabe ist 1. 🎜🎜Dies ist ein statischer Bereich. Sie müssen sich nur die Schreibposition von Variablen und Funktionen ansehen, um deren Umfang zu bestimmen. 🎜🎜Das Gegenteil ist <code>dynamischer Bereich. In JavaScript ist das Einzige, was den dynamischen Bereich betrifft, dieser Zeiger, der später behandelt wird. 🎜🎜Angenommen, JavaScript ist ein dynamischer Bereich, schauen Sie sich den Codeausführungsprozess im obigen Beispiel an. 🎜🎜bar wird zuerst aufgerufen und in der Variablen a übergeben. Wenn der Wert der Variablen a zum ersten Mal ausgegeben wird, wird die Variable a vollständig erhalten und 2 ausgegeben. Da sich beim Aufrufen der foo-Funktion keine Variable a in ihrem eigenen Bereich befindet, wird der Bereich der aufgerufenen Position aufgerufen. Zu diesem Zeitpunkt ist dies der Bereich der Funktion bar, also ist die Ausgabe Der Wert von a ist 2. 🎜🎜Wenn die c-Funktion aufgerufen wird, das heißt, wenn die baz-Funktion aufgerufen wird, gibt es auch keine Variable b selbst, daher sucht sie nach dem Bereich der Position, an der sie aufgerufen wird, also Das Gleiche gilt für den globalen Gültigkeitsbereich. Wenn die Variable b nicht definiert wurde, wird direkt ein Fehler gemeldet. 🎜

3. Klassifizierung von Bereichen 🎜🎜Die Klassifizierung von Bereichen kann wie oben erwähnt in statische und dynamische unterteilt werden: 🎜🎜🎜Statischer Bereich🎜 🎜 Dynamischer Bereich🎜🎜🎜Im statischen Bereich, also im lexikalischen Bereich, kann er auch nach einem bestimmten Bereich unterteilt werden in: 🎜
  • 全局作用域
  • 函数作用域
  • 块级作用域

3.1 全局作用域

全局作用域可以理解为所有作用域的祖先作用域, 它包含了所有作用域在其中。也就是最大的范围。反向理解就是除了函数作用域和被{}花括号包裹起来的作用域之外,都属于全局作用域

3.2 函数作用域

之所以在全局作用域外还需要函数作用域,主要是有几个原因:

  • 可以存在一个更小的范围存放自身内部的变量和函数,外部无法访问
  • 由于外部无法访问,所以相当于隐藏了内部细节,仅提供输入和输出,符合最小暴露原则
  • 同时不同的函数作用域可以各自命名相同的变量和函数,而不产生命名冲突
  • 函数作用域可以嵌套函数作用域,就像俄罗斯套娃一样可以一层套一层,最终形成了作用域链

用一个例子来展示:

var name = 'xavier'function foo() {  var name = 'parker'
  var age = 18

  function bar() {    var name = 'coin'
    return age
  }  return bar()
}foo()console.log(age) // 报错

当代码执行时, 最终会报错表示age查找不到。 因为变量age是在foo函数中定义, 属于foo函数作用域中, 验证了第一点外部无法访问内部。

而当代码只执行到foo函数调用时, 其实foo函数有执行过程, 最终是返回了bar函数的调用,返回的结果应该是18。 在对于编写代码的人来说,其实只需要理解一个函数的作用是什么, 然后给一个需要的输入,最后得出一个预期所想的输出,而不需要在意函数内部到底是怎么编写的。验证了第二点只需要最小暴露原则。

在这代码中, 对name变量定义过三次, 但每次都在各自的作用域中而不会产生覆盖的结果。在那个作用域里调用,该作用域就会返回相应的值。这验证了第三点规避命名冲突。

最终bar函数是在foo函数内部定义的,foo函数获取不到bar内部的变量和函数,但是bar函数可以通过作用域链获取到其父作用域也就是foo里的变量与函数。这验证了第四点。

3.3 块级作用域

块级作用域在ES6之后才开始普及,对于是var声明的变量是无效的,仅对let和const声明的变量有效。以{}包裹的代码块就会形成块级作用域, 例如if语句, try/catch语句,while/for语句。但声明对象不属于。

let obj = {  a: 1,   // 这个区域不叫做块级作用域}  

if (true) {  // 这个区域属于块级作用域
  var foo = 1
  let bar = 2}console.log(foo)  // 1console.log(bar)  // 报错

用一个大致的类比来形容全局作用域,函数作用域和块级作用域。一个家中所有的范围就称为全局作用域,而家中的各个房间里的范围则是函数作用域, 甚至可能主卧中还配套有单独的卫生间的范围也属于函数作用域,拥有的半开放式厨房则是块级作用域。

假设你要在家中寻找自己的猫,当它在客厅中,也就是全局作用域里,你可以立马找到。但如果猫在房间里,而没发出声音。你在客厅中是无法判断它在哪里,也就是无法找到它。这就是函数作用域。但是如果它在半开放式厨房里,由于未完全封闭,它是能跑出来的,所以你还是能找得到它。 反之你在房间里,如果它也在,那么可以直接找到。但如果你在房间而它在客厅中,则你可以选择开门去客厅寻找,一样也能找到。

4. 执行上下文和作用域的关系

上述的过程过于理论化,因而现在通过对于实质的情况也就是内存中的情况来讨论。

之前上一篇说过在ES3中执行上下文都有三大内容:

  • 变量对象
  • 作用域链
  • this

实际在内存中,对于全局作用域来说,它所涵盖的范围就是全局对象GO。因为全局对象保存了所有关于全局作用域中的变量和方法。

而对于函数来说,当函数被调用时所创建出的函数执行上下文里的活动对象AO所涵盖的范围就是函数作用域, 并且函数本身存在有一个内部属性[[scope]], 它是用来保存其父作用域的,而父作用域实际上也是另一个变量对象。

对于块级代码来说,就不能用ES3这套来解释,而是用ES6中词法环境和变量环境来解释。块级代码会创建出块级执行上下文,但块级执行上下文里只存在词法环境,不存在变量环境,因而这词法环境里的环境记录就是块级作用域。

相同的解释对于全局和函数也一样,对于ES6中,它们执行上下文里的词法环境和变量环境的环境记录涵盖的范围就是它们的作用域。

用一段代码来更好的理解:

var a = 'a'function foo() {    let b = 'b'
    console.log(c)
}if (true) {    let a = 'c'
    var c = 'c'
    console.log(a)
}foo()console.log(a)

对于这段代码刚编译完准备开始执行,也就是代码创建时,此刻执行上下文栈和内存中的图为:

当开始进行到if语句时,会创建块级执行上下文,并执行完if语句时执行上下文栈和内存图为:

当if语句执行完后, 就会被弹出栈,销毁块级执行上下文。然后开始调用foo函数,创建函数执行上下文,此时执行栈和内存图为:

当foo执行时,变量b被赋值为'b',同时输出c时会在自身环境记录中寻找,但未找到,因而往上通过自身父作用域,也就是全局作用域的环境记录中寻找,找到c的值为'c',输出'c'。

5. 作用域链

通过上文阐述的各个知识点,作用域链就很好理解了,在ES3中就是执行上下文里其变量对象VO + 自身父作用域,然后每个执行上下文依次串联出一条链路所形成的就是作用域链。

而在ES6中就是执行上下文里的词法环境里的环境记录+外部环境引用。外部环境引用依次串联也会形成一条链路,也属于作用域链。

它的作用在于变量的查找路径。当代码执行时,遇到一个变量就会通过作用域链不断回溯,直到找到该值又或者是到了全局作用域这顶层还是不存在,则会报错。

以及之后关于闭包的产生,也是由于作用域链的存在所导致的。这会在之后的复习里涉及到。

6. 一些练习

6.1 自己设计一道简单的练习题

var a = 10let b = 20const c = {  d: 30}function foo() {  console.log(a)  let e = 50
  return b + e
  a = 40}function bar() {  console.log(f)  var f = 60
  let a = 70
  console.log(f)  return a + c.d}if (a <= 30) {  console.log(a)  let b = foo()  console.log(b)
} 

console.log(b)
c.d = bar()console.log(a)console.log(c.d)

【相关推荐:JavaScript视频教程web前端

Das obige ist der detaillierte Inhalt vonDetaillierte JavaScript-Analyse der Bereichskette. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

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