Maison  >  Article  >  interface Web  >  Analyse détaillée JavaScript de la chaîne de portée

Analyse détaillée JavaScript de la chaîne de portée

WBOY
WBOYavant
2022-11-03 16:02:482208parcourir

Cet article vous apporte des connaissances pertinentes sur JavaScript, qui présente principalement le contenu pertinent de la chaîne de portée. Scope est un ensemble de règles responsables de la collecte et de la maintenance de tous les identifiants déclarés (variables). un ensemble de règles sont implémentées pour déterminer les autorisations d'accès du code en cours d'exécution à ces identifiants ; examinons-les ensemble, j'espère que cela sera utile à tout le monde.

Analyse détaillée JavaScript de la chaîne de portée

【Recommandations associées : Tutoriel vidéo JavaScript, front-end Web

1.

Scope est un ensemble de règles chargées de collecter et de maintenir un ensemble de requêtes composé de tous les identifiants (variables) déclarés et d'appliquer un ensemble de règles très strictes qui déterminent les droits d'accès du code en cours d'exécution à ces identifiants.

Expliquer ce qu'est la portée en termes plus compréhensibles est :

  • La portée est d'abord un ensemble de règles作用域首先是一套规则
  • 该规则的用处是用来存储变量,以及如何有限制的获取变量。

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

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. 作用域的分类

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

  • 静态作用域
  • 动态作用域
  • Le but de cette règle est de stocker des variables et comment obtenir des variables. avec des restrictions.

Pour utiliser une analogie qui n'est peut-être pas tout à fait appropriée pour décrire la portée, il existe une banque internationale dans laquelle vous déposez les devises de différents pays lorsque vous souhaitez retirer de l'argent. a un ensemble de règles. Vous ne pouvez retirer la devise correspondante que là où la devise est disponible. Cette banque et les règles qu'elle formule jouent le rôle de périmètre sur les variables. 🎜

2. Portée lexicale et portée dynamique🎜🎜En JavaScript, la portée utilisée est la portée lexicale, également appelée portée statique. Il est déterminé avant la compilation. JavaScript est essentiellement un langage compilé, mais sa compilation a lieu quelques microsecondes avant l'exécution du code. Contrairement aux autres langages compilés, il est compilé pendant le processus de construction, il ressemble donc davantage à un langage interprété. 🎜🎜 Indépendamment de la compilation et de l'interprétation, il vous suffit de comprendre que la portée lexicale est une portée statique. Comprendre la signification de statique est que il est déterminé lorsque le code est écrit et défini. Autrement dit, la portée dans laquelle les variables et les fonctions sont définies dans le code que les gens lisent est leur portée. 🎜🎜Utilisez un exemple simple pour comprendre : 🎜
var name = 'xavier'function foo() {  var name = 'parker'
  var age = 18

  function bar() {    var name = 'coin'
    return age
  }  return bar()
}foo()console.log(age) // 报错
🎜Pour les trois fonctions définies foo, bar, baz et variable a, leurs portées ont été définies lors de l'écriture. 🎜🎜Ainsi, lorsque le code est exécuté, la fonction bar appelle d'abord la valeur de la variable a transmise. Lorsque la valeur de la variable a est sortie pour la première fois, elle demandera d'abord si la variable a a été définie dans son propre portée. S'il a été défini, il demandera si la valeur de a existe, il y a une variable de sortie a qui est 2.🎜🎜 Commencez ensuite à appeler la fonction foo. Il n'y a que la valeur de la variable de sortie a dans. foo. De plus, demandera à sa propre portée si la variable a a été définie, mais elle n'est pas définie dans foo , puis il recherchera la portée lorsqu'elle a été définiecode> et demandez si la variable a a été définie. La portée globale a été définie et une valeur existe, donc la sortie a est 1. En fait, la chaîne de portée est déjà impliquée, mais n’en discutons pas pour l’instant. 🎜🎜Ensuite, entrez l'appel de la fonction c, qui est la fonction baz. La valeur de la variable b est affichée dans baz b et demandera si la variable b est définie dans sa propre portée. Si baz n'est pas défini, recherchez le. fonction de <code> lorsqu'il est défini. Domaine signifie si la variable b a été définie dans la portée de la fonction bar est en fait implicitement définie pour la variable b dans le paramètre et affectée d'une valeur de 1, donc la valeur finale. la sortie est 1. 🎜🎜Il s'agit d'une portée statique. Il vous suffit de regarder la position d'écriture des variables et des fonctions pour déterminer leur portée. 🎜🎜Le contraire est la portée dynamique En JavaScript, la seule chose qui implique la portée dynamique est ce pointeur, qui sera abordé lors de notre examen ultérieur. 🎜🎜En supposant que JavaScript est une portée dynamique, examinez le processus d'exécution de code dans l'exemple ci-dessus. 🎜🎜bar est d'abord appelé et passé dans la variable a. Lorsque la valeur de la variable a est sortie pour la première fois, la variable a est complètement obtenue et 2 est sortie. Lors de l'appel de la fonction foo, puisqu'il n'y a pas de variable a dans sa propre portée, elle recherchera depuis la portée de la position où elle est appelée. À ce stade, c'est la portée de la barre de fonctions. , donc la sortie La valeur de a est 2. 🎜🎜Lorsque la fonction c est appelée, c'est-à-dire lorsque la fonction baz est appelée, il n'y a pas non plus de variable b elle-même, donc elle recherche la portée de la position où elle est appelée, qui est la portée globale. Il en va de même dans la portée globale. Si la variable b n'a pas été définie, une erreur sera signalée directement. 🎜

3. Classification des scopes 🎜🎜La classification des scopes peut être divisée en statiques et dynamiques comme mentionné ci-dessus : 🎜🎜🎜Portée statique🎜 🎜 Portée dynamique🎜🎜🎜En portée statique, c'est-à-dire en portée lexicale, elle peut également être subdivisée selon une certaine plage en : 🎜
  • 全局作用域
  • 函数作用域
  • 块级作用域

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前端

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