JavaScript スコープとスコープ チェーンの解析 (例付き)

不言
リリース: 2019-03-15 14:31:51
転載
2006 人が閲覧しました

この記事は JavaScript のスコープとスコープ チェーンの分析に関する内容です (例付き)。一定の参考価値があります。困っている友人は参考にしてください。お役に立てば幸いです。

JavaScript にはスコープと呼ばれる機能があります。多くの初心者開発者にとってスコープの概念は理解しにくいものですが、この記事ではスコープとスコープ チェーンについてできるだけわかりやすく説明することに努めます。

スコープ

1. スコープとは

スコープとは、ランタイム コードの特定の部分における変数、関数、およびオブジェクトの可視性です。言い換えれば、スコープはコード ブロック内の変数やその他のリソースの可視性を決定します。この 2 つの文は理解しにくいかもしれません。まず例を見てみましょう:

function outFun2() {
    var inVariable = "内层变量2";
}
outFun2();//要先执行这个函数,否则根本不知道里面是啥
console.log(inVariable); // Uncaught ReferenceError: inVariable is not defined
ログイン後にコピー

上の例から、スコープの概念が理解できます。変数 inVariable はグローバル スコープで宣言されていません。したがって、グローバル スコープ内にあるため、値を取得するとエラーが報告されます。これは次のように理解できます: スコープは独立した領域であるため、変数が漏洩したり公開されたりすることはありません。つまり、 スコープの最大の用途は変数を分離することであり、異なるスコープにある同じ名前の変数は競合しません。

ES6 より前の JavaScript にはブロックレベルのスコープがなく、グローバル スコープと関数スコープのみでした。 ES6 の登場により、新しいコマンド let と const に反映できる「ブロックレベルのスコープ」が提供されます。

2. グローバル スコープと関数スコープ

コード内のどこからでもアクセスできるオブジェクトにはグローバル スコープがあります。一般に、次の状況にはグローバル スコープがあります。

    最も外側の関数と最も外側の関数の外側で定義された変数にはグローバル スコープがあります
var outVariable = "我是最外层变量"; //最外层变量
function outFun() { //最外层函数
    var inVariable = "内层变量";
    function innerFun() { //内层函数
        console.log(inVariable);
    }
    innerFun();
}
console.log(outVariable); //我是最外层变量
outFun(); //内层变量
console.log(inVariable); //inVariable is not defined
innerFun(); //innerFun is not defined
ログイン後にコピー
#未定義で値が直接割り当てられていないすべての変数は、自動的に所有グローバル スコープとして宣言されます
  • function outFun2() {
        variable = "未定义直接赋值的变量";
        var inVariable2 = "内层变量2";
    }
    outFun2();//要先执行这个函数,否则根本不知道里面是啥
    console.log(variable); //未定义直接赋值的变量
    console.log(inVariable2); //inVariable2 is not defined
    ログイン後にコピー
ウィンドウ オブジェクトのすべてのプロパティにはグローバル スコープがあります
  • 一般に、ウィンドウ オブジェクトの組み込みプロパティにはグローバル スコープがあります (例: window.name、window.location、window)。 .トップなど

グローバル スコープには欠点があります。多くの行の JS コードを記述し、変数定義が関数に含まれていない場合、それらはすべてグローバル スコープ内にあることになります。これにより、グローバル名前空間が汚染され、名前の競合が容易に発生します。

// 张三写的代码中
var data = {a: 100}

// 李四写的代码中
var data = {x: true}
ログイン後にコピー

これが、jQuery や Zepto などのライブラリのソース コードが

(function(){....})()

に配置される理由です。内部に配置されたすべての変数は漏洩または公開されず、外部に汚染されず、他のライブラリや JS スクリプトに影響を与えないためです。これは関数のスコープの現れです。 関数スコープは関数内で宣言された変数を指します。グローバル スコープとは対照的に、ローカル スコープは通常、固定コード フラグメント内 (最も一般的には関数内) 内でのみアクセスできます。

function doSomething(){
    var blogName="浪里行舟";
    function innerSay(){
        alert(blogName);
    }
    innerSay();
}
alert(blogName); //脚本错误
innerSay(); //脚本错误
ログイン後にコピー

スコープは階層的です。内部スコープは外部スコープの変数にアクセスできますが、その逆はできません。例を見てみましょう。スコープのメタファーとしてバブルを使用すると理解しやすいかもしれません:

#最終出力結果は 2、4、12JavaScript スコープとスコープ チェーンの解析 (例付き)

バブル 1 はグローバル スコープであり、識別子は foo です;

    バブル 2 はスコープ foo であり、識別子は a、bar、b です;
  • バブル 3 はスコープ bar 、識別子のみ c.
  • 注目に値します:
  • if および switch 条件ステートメントや for および while ループ ステートメントなどのブロック ステートメント (中括弧 "{}" で囲まれたステートメント) は、関数とは異なり、新しいスコープ
が作成されます。ブロック ステートメント内で定義された変数は、すでに存在するスコープ内に残ります。

if (true) {
    // 'if' 条件语句块不会创建一个新的作用域
    var name = 'Hammad'; // name 依然在全局作用域中
}
console.log(name); // logs 'Hammad'
ログイン後にコピー
JS の初心者は変数ホイスティングに慣れるまでに時間がかかることが多く、この独特の動作を理解していないと バグが発生する可能性があります。このため、ES6 では変数のライフサイクルをより制御しやすくするためにブロックレベルのスコープが導入されました。

3. ブロック レベルのスコープ

ブロック レベルのスコープは、新しいコマンド let および const を使用して宣言できます。宣言された変数には、指定されたブロックのスコープ外ではアクセスできません。ブロック レベルのスコープは、次の状況で作成されます。

関数内

    コード ブロック内 (中括弧のペアで囲まれています)
  1. let 宣言の構文は var の構文と一致します。基本的に変数宣言には var の代わりに let を使用できますが、変数のスコープが現在のコード ブロックに制限されます。ブロックレベルのスコープには次の特徴があります。

宣言された変数はコード ブロックの先頭に昇格されません

  • let/const 宣言はコード ブロックの先頭に昇格されません現在のコード ブロックは先頭にあるため、変数をコード ブロック全体内で使用できるようにするには、let/const 宣言を先頭に手動で配置する必要があります。
function getValue(condition) {
if (condition) {
let value = "blue";
return value;
} else {
// value 在此处不可用
return null;
}
// value 在此处不可用
}
ログイン後にコピー

重複した宣言は禁止されています

  • コード ブロック内で識別子が定義されている場合、このコード ブロック内の let 宣言に同じ識別子を使用すると、スローされるエラー。例えば:###
    var count = 30;
    let count = 40; // Uncaught SyntaxError: Identifier 'count' has already been declared
    ログイン後にコピー

    在本例中, count 变量被声明了两次:一次使用 var ,另一次使用 let 。因为 let 不能在同一作用域内重复声明一个已有标识符,此处的 let 声明就会抛出错误。但如果在嵌套的作用域内使用 let 声明一个同名的新变量,则不会抛出错误。

    var count = 30;
    // 不会抛出错误
    if (condition) {
    let count = 40;
    // 其他代码
    }
    ログイン後にコピー
    • 循环中的绑定块作用域的妙用

    开发者可能最希望实现for循环的块级作用域了,因为可以把声明的计数器变量限制在循环内,例如,以下代码在 JS 经常见到:

    <button>测试1</button>
    <button>测试2</button>
    <button>测试3</button>
    <script>
       var btns = document.getElementsByTagName(&#39;button&#39;)
        for (var i = 0; i < btns.length; i++) {
          btns[i].onclick = function () {
            console.log(&#39;第&#39; + (i + 1) + &#39;个&#39;)
          }
        }
    </script>
    ログイン後にコピー

    我们要实现这样的一个需求: 点击某个按钮, 提示"点击的是第n个按钮",此处我们先不考虑事件代理,万万没想到,点击任意一个按钮,后台都是弹出“第四个”,这是因为i是全局变量,执行到点击事件时,此时i的值为3。那该如何修改,最简单的是用let声明i

     for (let i = 0; i <h2>作用域链</h2><h3>1.什么是自由变量</h3><p>首先认识一下什么叫做 <strong>自由变量</strong> 。如下代码中,<code>console.log(a)</code>要得到a变量,但是在当前的作用域中没有定义a(可对比一下b)。当前作用域没有定义的变量,这成为 自由变量 。自由变量的值如何得到 —— 向父级作用域寻找(注意:这种说法并不严谨,下文会重点解释)。</p><pre class="brush:php;toolbar:false">var a = 100
    function fn() {
        var b = 200
        console.log(a) // 这里的a在这里就是一个自由变量
        console.log(b)
    }
    fn()
    ログイン後にコピー

    2.什么是作用域链

    如果父级也没呢?再一层一层向上寻找,直到找到全局作用域还是没找到,就宣布放弃。这种一层一层的关系,就是 作用域链 。

    var a = 100
    function F1() {
        var b = 200
        function F2() {
            var c = 300
            console.log(a) // 自由变量,顺作用域链向父作用域找
            console.log(b) // 自由变量,顺作用域链向父作用域找
            console.log(c) // 本作用域的变量
        }
        F2()
    }
    F1()
    ログイン後にコピー

    3.关于自由变量的取值

    关于自由变量的值,上文提到要到父作用域中取,其实有时候这种解释会产生歧义。

    var x = 10
    function fn() {
      console.log(x)
    }
    function show(f) {
      var x = 20
      (function() {
        f() //10,而不是20
      })()
    }
    show(fn)
    ログイン後にコピー

    在fn函数中,取自由变量x的值时,要到哪个作用域中取?——要到创建fn函数的那个作用域中取,无论fn函数将在哪里调用

    所以,不要在用以上说法了。相比而言,用这句话描述会更加贴切:**要到创建这个函数的那个域”。
    作用域中取值,这里强调的是“创建”,而不是“调用”**,切记切记——其实这就是所谓的"静态作用域"

    var a = 10
    function fn() {
      var b = 20
      function bar() {
        console.log(a + b) //30
      }
      return bar
    }
    var x = fn(),
      b = 200
    x() //bar()
    ログイン後にコピー

    fn()返回的是bar函数,赋值给x。执行x(),即执行bar函数代码。取b的值时,直接在fn作用域取出。取a的值时,试图在fn作用域取,但是取不到,只能转向创建fn的那个作用域中去查找,结果找到了,所以最后的结果是30

    作用域与执行上下文

    许多开发人员经常混淆作用域和执行上下文的概念,误认为它们是相同的概念,但事实并非如此。

    我们知道JavaScript属于解释型语言,JavaScript的执行分为:解释和执行两个阶段,这两个阶段所做的事并不一样:

    解释阶段:

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

    执行阶段:

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

    JavaScript解释阶段便会确定作用域规则,因此作用域在函数定义时就已经确定了,而不是在函数调用时确定,但是执行上下文是函数执行之前创建的。执行上下文最明显的就是this的指向是执行时确定的。而作用域访问的变量是编写代码的结构确定的。

    作用域和执行上下文之间最大的区别是:
    执行上下文在运行时确定,随时可能改变;作用域在定义时就确定,并且不会改变

    一个作用域下可能包含若干个上下文环境。有可能从来没有过上下文环境(函数从来就没有被调用过);有可能有过,现在函数被调用完毕后,上下文环境被销毁了;有可能同时存在一个或多个(闭包)。同一个作用域下,不同的调用会产生不同的执行上下文环境,继而产生不同的变量的值

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

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