web-dev-qa-db-ja.com

字句スコープとは何ですか?

誰かが私に字句スコープの簡単な紹介をお願いします。

602
Subba Rao

私は例を通してそれらを理解しています。 :)

まず、C言語のような構文での語彙スコープ(静的スコープとも呼ばれます):

void fun()
{
    int x = 5;

    void fun2()
    {
        printf("%d", x);
    }
}

すべての内部レベルはその外部レベルにアクセスできます。

LISPの最初の実装で使用されている動的スコープと呼ばれるもう1つの方法があります。これもCのような構文です。

void fun()
{
    printf("%d", x);
}

void dummy1()
{
    int x = 5;

    fun();
}

void dummy2()
{
    int x = 10;

    fun();
}

ここでfunは、dummy1またはdummy2内のx、またはxが宣言されているfunを呼び出す任意の関数内の任意のxにアクセスできます。

dummy1();

5を印刷します。

dummy2();

10を印刷します。

最初のものはコンパイル時に演繹できるので静的と呼ばれ、2番目のものは外部スコープが動的で関数の連鎖呼び出しに依存するので動的と呼ばれます。

私は、静的スコープの方が見やすいと思います。ほとんどの言語は、結局LISPでさえもこのようにしていました(そうすることができます、正しいですか?)動的スコープは、呼び出された関数にすべての変数の参照を渡すのに似ています。

コンパイラが関数の外側の動的スコープを推定できない理由の例として、次のように書いた場合、最後の例を考えてください。

if(/* some condition */)
    dummy1();
else
    dummy2();

コールチェーンは実行時の条件によって異なります。それが本当なら、コールチェーンは次のようになります。

dummy1 --> fun()

条件が偽の場合

dummy2 --> fun()

どちらの場合もfunの外側の範囲は、呼び出し側と呼び出し側の呼び出し側です。

C言語では入れ子関数も動的スコープも許可されていないことを言及してください。

626
AraK

できるだけ短い定義を試してみましょう。

字句スコープは、入れ子関数で変数名を解決する方法を定義します。内部関数に親関数のスコープが含まれている場合でも、親関数が戻りました

それだけです。

239
Pierre Spring
var scope = "I am global";
function whatismyscope(){
   var scope = "I am just a local";
   function func() {return scope;}
   return func;
}

whatismyscope()()

上記のコードは "私はただの地元の人です"を返します。 「私はグローバルです」とは返されません。なぜなら、関数func()は、関数whatismyscopeの範囲内で、isが最初に定義された場所をカウントするからです。

呼び出されているもの(グローバルスコープ/他の関数内からでも)から気にする必要はありません。そのため、グローバルスコープの値が表示されなくなります。

これは字句スコープと呼ばれ、「関数は定義時に有効だったスコープチェーンを使って実行されます」 - JavaScript Definition Guideによると。

字句スコープは非常に強力な概念です。

お役に立てれば..:)

46
kta

スコープは、関数、変数などが利用可能な領域を定義します。たとえば、変数の使用可能性はそのコンテキスト内で定義されます。たとえば、関数、ファイル、またはオブジェクトが定義されているとします。通常、これらのローカル変数を呼び出します。

字句部分は、ソースコードを読むことでスコープを導き出すことができるということです。

字句スコープは静的スコープとも呼ばれます。

動的スコープは、定義後にどこからでも呼び出しまたは参照できるグローバル変数を定義します。ほとんどのプログラム言語の大域変数は字句解析の範囲にありますが、時には大域変数と呼ばれます。これは、この文脈で変数が利用可能であることをコードを読むことから導き出すことができることを意味します。インスタンス化または定義を見つけるには、uses句またはincludes句に従う必要があるかもしれませんが、コード/コンパイラはこの場所の変数について知っています。

これに対して、動的スコープでは、最初にローカル関数を検索し、次にローカル関数を呼び出した関数を検索し、次にその関数を呼び出した関数を検索し、以下同様に呼び出しスタックを上に移動します。 「動的」とは、特定の関数が呼び出されるたびに呼び出しスタックが異なる可能性があるため、呼び出し元が変わると関数が異なる変数にヒットする可能性があるという点で、変更を意味します。 ( こちら を参照)

動的スコープの興味深い例を見るためにはここで を見てください

詳細については、 こちら および こちら を参照してください。

Delphi/Object Pascalのいくつかの例

Delphiには字句解析範囲があります。

unit Main;
uses aUnit;  // makes available all variables in interface section of aUnit

interface

  var aGlobal: string; // global in the scope of all units that use Main;
  type 
    TmyClass = class
      strict private aPrivateVar: Integer; // only known by objects of this class type
                                    // lexical: within class definition, 
                                    // reserved Word private   
      public aPublicVar: double;    // known to everyboday that has access to a 
                                    // object of this class type
    end;

implementation

  var aLocalGlobal: string; // known to all functions following 
                            // the definition in this unit    

end.

動的スコープに最も近いDelphiは、RegisterClass()/ GetClass()関数ペアです。その使用についてはここで を参照してください

あるクラスを登録するためにRegisterClass([TmyClass])が呼び出される時間はコードを読み取ることでは予測できず(ユーザーが呼び出すボタンクリックメソッドで呼び出される)、GetClass( 'TmyClass')を呼び出すコードは次のようになります。結果かどうか。 RegisterClass()の呼び出しは、GetClass()を使用するユニットの字句の範囲内にある必要はありません。

動的スコープのもう1つの可能性は、Delphi 2009の 匿名メソッド (クロージャ)です。呼び出し関数の変数を知っているからです。それはそこからの呼び出しパスを再帰的にはたどらないので、完全に動的ではありません。

38

レキシカル(AKA静的)スコープとは、コードのテキストコーパス内での位置にのみ基づいて変数のスコープを決定することです。変数は常にその最上位レベルの環境を参照します。動的スコープに対する の関係でそれを理解するのは良いことです。

36
Evan Meagher

私は@Arakのような人々からの、機能豊富で言語にとらわれない答えが大好きです。この質問はJavaScriptとタグ付けされているので、私はこの言語に非常に特有のいくつかのメモを追加したいと思います。

Javascriptでは、スコープの選択は次のとおりです。

  • そのまま(範囲調整なし)
  • 字句var _this = this; function callback(){ console.log(_this); }
  • バウンドcallback.bind(this)

注目に値するのは、JavaScript には動的スコープが実際にはない ということです。 .bindthisキーワードを調整します、そしてそれは近いですが、技術的に同じではありません。

これは両方のアプローチを示す例です。これは、約束、イベントハンドラなどに適用されるので、コールバックをスコープする方法を決定するたびに行います。

字句解析

JavaScriptでコールバックのLexical Scopingを呼ぶかもしれません:

var downloadManager = {
  initialize: function() {
    var _this = this; // Set up `_this` for lexical access
    $('.downloadLink').on('click', function () {
      _this.startDownload();
    });
  },
  startDownload: function(){
    this.thinking = true;
    // request the file from the server and bind more callbacks for when it returns success or failure
  }
  //...
};

バウンド

スコープを設定するもう1つの方法は、 Function.prototype.bind を使うことです。

var downloadManager = {
  initialize: function() {
    $('.downloadLink').on('click', function () {
      this.startDownload();
    }.bind(this)); // create a function object bound to `this`
  }
//...

私の知る限りでは、これらの方法は行動的に同等です。

31
SimplGy

IBM はそれを次のように定義します。

宣言が適用されるプログラムまたはセグメント単位の部分。ルーチン内で宣言された識別子は、そのルーチン内およびすべてのネストされたルーチン内で認識されています。ネストされたルーチンが同じ名前の項目を宣言した場合、外側の項目はネストされたルーチンでは使用できません。

例1

function x() {
    /*
    Variable 'a' is only available to function 'x' and function 'y'.
    In other words the area defined by 'x' is the lexical scope of
    variable 'a'
    */
    var a = "I am a";

    function y() {
        console.log( a )
    }
    y();

}
// outputs 'I am a'
x();

例2

function x() {

    var a = "I am a";

    function y() {
         /*
         If a nested routine declares an item with the same name,
         the outer item is not available in the nested routine.
         */
        var a = 'I am inner a';
        console.log( a )
    }
    y();

}
// outputs 'I am inner a'
x();
12
Robert Rocha

字句スコープ:関数の外側で宣言された変数はグローバル変数であり、JavaScriptプログラムの至る所で見られます。関数の内部で宣言された変数は関数の有効範囲を持ち、その関数の内部に現れるコードに対してのみ可視です。

11
Stan

LexicalとDynamic Sc​​opingをめぐる会話の中で欠けている重要な部分があります:生涯スコープ変数の/ - またはwhen変数にアクセスできる)のわかりやすい説明。

動的スコーピングは、私たちが伝統的に考える「グローバル」スコーピングに非常におおまかにしか対応しません(2つの間の比較を提起するのは、それが既に 言及された であるからです。特に linked 記事の説明のように);リンクされた記事によれば、「グローバルにスコープされた変数の代わりとして役立つということになっていますが」、グローバルとダイナミックを比較しないことがおそらく最善です。

それで、平易な英語で、2つのスコープメカニズムの間の重要な違いは何ですか?

レキシカルスコープは、上記の回答全体を通して非常によく定義されています。レキシカルスコープ変数は、それが定義されている関数のローカルレベルで使用可能、またはアクセス可能です。

しかし、それはOPの焦点では​​ないので、動的スコープはあまり注目されておらず、注目されていることはおそらくもう少し必要であることを意味します(それは他の回答に対する批判ではなく、むしろ「ああ」その答えは私たちがもう少しあることを望みました ")。だから、ここでもう少しです:

動的スコープとは、関数呼び出しの有効期間中、または関数の実行中に、変数が大きいプログラムにアクセス可能であることを意味します。実際、ウィキペディアは実際には2つの間の 違いの説明 を使用してNiceの仕事をしています。わかりにくくしないように、動的スコープを説明するテキストは次のとおりです。

... [I] n動的スコープ(または動的スコープ)。変数名のスコープが特定の関数の場合、そのスコープはその関数が実行されている期間です。関数の実行中は変数名が存在します。 、およびその変数にバインドされていますが、関数が戻った後、変数名が存在しません。

4
Thomas

字句スコープとは、関数が定義されているコンテキスト内で変数を検索するのであって、そのすぐ周囲のスコープ内では検索されないことを意味します。

さらに詳細が必要な場合は、LISPで字句スコープがどのように機能するかを調べてください。 Common LISPの 動的変数および字句変数 でKyle Croninが選んだ答えは、ここでの答えよりもはるかに明確です。

偶然にも私はLISPクラスでこれについて学んだだけで、それはJSでも同様に当てはまります。

私はこのコードをchromeのコンソールで実行しました。

// javascript               equivalent LISP
var x = 5;                //(setf x 5)
console.debug(x);         //(print x)
function print_x(){       //(defun print-x () 
    console.debug(x);     //    (print x)
}                         //)
(function(){              //(let  
    var x = 10;           //    ((x 10))
    console.debug(x);     //    (print x)
    print_x();            //    (print-x)
})();                     //)

出力:

5
10
5 
3
ratiotile

Javascriptの字句スコープは、関数の外側で定義された変数が、変数宣言の後に定義された別の関数の内側からアクセスできることを意味します。しかし、その逆は成り立ちません。関数内で定義された変数はその関数外ではアクセスできません。

この概念は、Javascriptのクロージャでよく使用されています。

以下のコードがあるとしましょう。

var x = 2;
var add = function() {
var y = 1;
return x + y;
};

さて、あなたがadd()を呼び出すと - > 3と表示されます。

したがって、add()関数は、メソッドfunction addの前に定義されているグローバル変数xにアクセスしています。これは、JavaScriptの字句スコープのために呼び出されます。

2
Praveen Kishor

ここで、この問題について別の見方をします。私たちが一歩後退して、より広い解釈の枠組み(プログラムの実行)におけるスコープの役割を見ることによって得ることができるということです。言い換えれば、あなたが言語のためのインタプリタ(またはコンパイラ)を構築していて、プログラムとそれへの何らかの入力を与えられて、出力を計算する責任があると想像してください。

解釈は3つのことを追跡することを含みます:

1)状態 - すなわち、ヒープおよびスタック上の変数および参照されたメモリ位置。

2)その状態での操作 - つまりあなたのプログラムのコードのすべての行

3)与えられた作戦が動く環境 - すなわち作戦への国家の投射。

インタプリタはプログラム内のコードの最初の行から開始し、その環境を計算し、その環境でその行を実行して、プログラムの状態に対するその影響を取得します。その後、プログラムの制御フローに従って次のコード行を実行し、プログラムが終了するまでこのプロセスを繰り返します。

どのような操作に対しても環境を計算する方法は、プログラミング言語によって定義された正式な一連の規則を使用することです。 「バインディング」という用語は、プログラムの全体的な状態から環境内の値へのマッピングを記述するためによく使用されます。 「全体の状態」とは、グローバルな状態ではなく、実行の任意の時点で到達可能なすべての定義の合計を意味します。

これはスコープ問題が定義されている枠組みです。さて、私たちの選択肢は何でしょうか。

  • インタプリタの実装者としては、環境をプログラムの状態にできるだけ近づけることによってタスクを単純化することができます。したがって、1行のコードの環境は、直前の行が代入、関数呼び出し、関数からの戻りのいずれであったかに関係なく、その操作の効果が適用された直前のコード行の環境によって単純に定義されます。またはwhileループなどの制御構造。

これは動的スコープの要旨で、コードが実行される環境はその実行コンテキストで定義されているプログラムの状態に依存します。

  • Or、あなたは自分の言語を使っているプログラマを考え、変数がとり得る値を追跡するという彼または彼女の仕事を単純化することができます。過去の実行の全体性について結果を推論するには、多すぎる経路と多すぎる複雑さがあります。 Lexical Scopingは現在の環境を状態{で定義されたの部分に制限することでこれを助けます。スコープとその親(すなわち現在のクロックを囲むブロック、または現在の関数を呼び出した関数)。

言い換えれば、Lexical Scopeを使用すると、コードで認識される環境は、ブロック内など、言語で明示的に定義されたスコープに関連付けられた状態にバインドされます。機能。

0
er0

単純な言語では、字句スコープはあなたのスコープの外側で定義された変数であるか、または上位スコープはあなたのスコープの内側で自動的に利用可能です。

例:

let str="JavaScript";

const myFun = () => {
    console.log(str);
}

myFun();

//出力:JavaScript

0
Sky

私は通常例で学びます、これはちょっとしたことです:

const lives = 0;

function catCircus () {
    this.lives = 1;
    const lives = 2;

    const cat1 = {
        lives: 5,
        jumps: () => {
            console.log(this.lives);
        }
    };
    cat1.jumps(); // 1
    console.log(cat1); // { lives: 5, jumps: [Function: jumps] }

    const cat2 = {
        lives: 5,
        jumps: () => {
            console.log(lives);
        }
    };
    cat2.jumps(); // 2
    console.log(cat2); // { lives: 5, jumps: [Function: jumps] }

    const cat3 = {
        lives: 5,
        jumps: () => {
            const lives = 3;
            console.log(lives);
        }
    };
    cat3.jumps(); // 3
    console.log(cat3); // { lives: 5, jumps: [Function: jumps] }

    const cat4 = {
        lives: 5,
        jumps: function () {
            console.log(lives);
        }
    };
    cat4.jumps(); // 2
    console.log(cat4); // { lives: 5, jumps: [Function: jumps] }

    const cat5 = {
        lives: 5,
        jumps: function () {
            var lives = 4;
            console.log(lives);
        }
    };
    cat5.jumps(); // 4
    console.log(cat5); // { lives: 5, jumps: [Function: jumps] }

    const cat6 = {
        lives: 5,
        jumps: function () {
            console.log(this.lives);
        }
    };
    cat6.jumps(); // 5
    console.log(cat6); // { lives: 5, jumps: [Function: jumps] }

    const cat7 = {
        lives: 5,
        jumps: function thrownOutOfWindow () {
            console.log(this.lives);
        }
    };
    cat7.jumps(); // 5
    console.log(cat7); // { lives: 5, jumps: [Function: thrownOutOfWindow] }
}

catCircus();
0
Karl Morrison

字句スコープとは、入れ子になった関数グループでは、内部関数がその親スコープの変数やその他のリソースにアクセスできることを意味します。これは、子関数がその親の実行コンテキストに字句的に結び付けられていることを意味します。字句スコープは静的スコープとも呼ばれます。

function grandfather() {
    var name = 'Hammad';
    // likes is not accessible here
    function parent() {
        // name is accessible here
        // likes is not accessible here
        function child() {
            // Innermost level of the scope chain
            // name is also accessible here
            var likes = 'Coding';
        }
    }
}

字句スコープについて気づくことは、それが前方に機能するということです。つまり、nameはその子の実行コンテキストからアクセスできます。しかし、それはその親には逆行しません。つまり、変数likeはその親からはアクセスできません。これは、異なる実行コンテキストで同じ名前を持つ変数が実行スタックの上から下に優先されることも示しています。最も内側の関数(実行スタックの最上位のコンテキスト)内の、別の変数と同様の名前を持つ変数は、優先順位が高くなります。

これは からここに取られることに注意してください

0
Shivendra Gupta