JavaScript のスコープチェーンの詳細な分析

WBOY
リリース: 2022-11-03 16:02:48
転載
2222 人が閲覧しました

この記事では、JavaScript に関する関連知識を提供します。主にスコープ チェーンの関連コンテンツを紹介します。スコープとは、すべての宣言の ID の収集と維持を担当する一連のルールです。一連のクエリは識別子 (変数) で構成されており、これらの識別子に対する現在実行中のコードのアクセス権を決定するために非常に厳格なルール セットが実装されています。一緒に見てみましょう。皆さんのお役に立てれば幸いです。

JavaScript のスコープチェーンの詳細な分析

[関連する推奨事項: JavaScript ビデオ チュートリアル Web フロントエンド ]

1. とはスコープ? ?

スコープは、宣言されたすべての識別子 (変数) で構成される一連のクエリを収集および維持する一連のルールであり、これらの識別子に対する現在実行中のコードのアクセス権を決定する非常に厳密な一連のルールを適用します。 。

# スコープとは何かをよりわかりやすい言葉で説明すると、次のようになります。

  • #スコープは、まず一連のルールです
  • このルールの目的は、変数を保存し、制限付きで変数を取得する方法です。

完全に適切ではないかもしれない例えを使用すると、国際銀行があり、そこに手持ちのさまざまな国の通貨を預けるということになります。お金を引き出すには、対応する通貨が利用可能な場合にのみ、その通貨を引き出すことを制限する一連のルールがあります。このバンクとそれが策定するルールは、変数のスコープの役割を果たします。

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()
ログイン後にコピー
3 つの定義された関数 foo、bar、baz、および変数 a については、それらのスコープは作成時に定義されています。

したがって、コードが実行されると、bar 関数は最初に渡された変数 a の値を呼び出します。変数 a の値が初めて出力されるとき、最初に変数 a が出力されるかどうかを尋ねます。が独自のスコープで定義されています。定義されている場合は、 a の値があるかどうかが尋ねられ、出力変数 a は 2.

次に、 foo 関数の呼び出しを開始します。 foo の出力変数 a の値。また、

独自のスコープが定義されているかどうかも尋ねられます。変数 a が foo で定義されていない場合、定義されたときのスコープを 検索します。 .変数 a が定義されているかどうかを尋ねます。グローバル スコープによって定義されており、値が存在するため、出力 a は 1 になります。実際、スコープ チェーンはすでに関係していますが、今はそれについては説明しません。

c 関数呼び出し (baz 関数) を入力すると、変数 b の値が baz に出力されます。b は、変数 b が独自のスコープ内で定義されているかどうかを尋ねます。baz が定義されていない場合は、

自体を調べます。定義時のスコープは、変数 b が bar 関数のスコープに定義されているかどうかです。実際には、bar はパラメータで暗黙的に変数 b に定義され、値 1 が割り当てられています。最終出力は 1 です。 これは静的スコープであり、変数や関数の記述位置を確認するだけでスコープを決定できます。

反対は

ダイナミック スコープ

です。JavaScript では、これのみがダイナミック スコープを指します。これについては、後で確認するときに説明します。 JavaScript が動的スコープであると仮定して、上記の例のコード実行プロセスも見てください。最初に

barを呼び出して変数aに渡しますが、初めて変数aの値を出力するときは変数aの取得が完了し、2が出力されます。 foo関数を呼び出すと、自身のスコープには変数aが存在しないので、関数を呼び出した位置の

スコープ

からルックアップすることになりますが、このとき関数barのスコープとなり、したがって、出力 a の値は 2 です。 c 関数が呼び出されるとき、つまり baz 関数が呼び出されるとき、変数 b 自体は存在しないため、呼び出された位置のスコープ

を探します。 is、グローバル スコープ、グローバル効果 変数 b もドメイン内で定義されていない場合、エラーが直接報告されます。

3. スコープの分類

スコープの分類は、上記に従って静的スコープと動的スコープに分けることができます:

静的スコープ
  • 動的スコープ
  • 静的スコープ、つまり字句スコープは、特定の範囲に細分化することもできます。
    • 全局作用域
    • 函数作用域
    • 块级作用域

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

以上がJavaScript のスコープチェーンの詳細な分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:juejin.im
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート