レキシカルスコープの簡単な紹介は何ですか?
20 に答える
例を通してそれらを理解しています。:)
まず、レキシカル スコープ( static スコープとも呼ばれる) を C ライクな構文で:
void fun()
{
int x = 5;
void fun2()
{
printf("%d", x);
}
}
すべての内部レベルは、その外部レベルにアクセスできます。
Lispの最初の実装で使用される動的スコープと呼ばれる別の方法があり、これも C に似た構文です。
void fun()
{
printf("%d", x);
}
void dummy1()
{
int x = 5;
fun();
}
void dummy2()
{
int x = 10;
fun();
}
ここでは、または、または宣言された関数を呼び出す任意の関数のfun
いずれかにアクセスできます。x
dummy1
dummy2
x
fun
x
dummy1();
5を出力します。
dummy2();
10が出力されます。
最初のものはコンパイル時に推定できるため静的と呼ばれ、2 つ目は外側のスコープが動的で関数のチェーン呼び出しに依存するため動的と呼ばれます。
静的スコープの方が目にやさしいと思います。ほとんどの言語は、Lisp でさえ、最終的にはこの方法になりました (両方を行うことができますよね?)。動的スコープは、呼び出された関数にすべての変数の参照を渡すようなものです。
コンパイラが関数の外側の動的スコープを推測できない理由の例として、最後の例を考えてみましょう。次のように書くと:
if(/* some condition */)
dummy1();
else
dummy2();
呼び出しチェーンは実行時の条件によって異なります。true の場合、呼び出しチェーンは次のようになります。
dummy1 --> fun()
条件が false の場合:
dummy2 --> fun()
fun
どちらの場合も、の外側のスコープは、呼び出し元と呼び出し元の呼び出し元などです。
C言語では、ネストされた関数や動的スコープが許可されていないことに言及してください。
可能な限り短い定義を試してみましょう:
レキシカルスコープは、ネストされた関数で変数名がどのように解決されるかを定義します。親関数が返された場合でも、内部関数には親関数のスコープが含まれます。
それだけです!
レキシカル (別名静的) スコープとは、コードのテキスト コーパス内の位置のみに基づいて変数のスコープを決定することを指します。変数は常にその最上位環境を参照します。動的スコープとの関連で理解するとよいでしょう。
スコープは、関数、変数などを使用できる領域を定義します。たとえば、変数の可用性は、そのコンテキスト内で定義されます。たとえば、それらが定義されている関数、ファイル、またはオブジェクトとしましょう。通常、これらをローカル変数と呼びます。
字句部分とは、ソース コードを読むことでスコープを導き出せることを意味します。
レキシカル スコープは静的スコープとも呼ばれます。
動的スコープは、定義後にどこからでも呼び出しまたは参照できるグローバル変数を定義します。ほとんどのプログラミング言語のグローバル変数はレキシカルスコープですが、グローバル変数と呼ばれることもあります。つまり、変数がこのコンテキストで使用可能であることは、コードを読むことで導き出すことができます。インスタンスまたは定義を見つけるために 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 の匿名メソッド(クロージャー)です。これは、呼び出し関数の変数を知っているためです。そこから呼び出しパスを再帰的にたどらないため、完全に動的ではありません。
@Arak のような人々からの完全な機能を備えた、言語にとらわれない回答が大好きです。ただし、この質問にはJavaScriptのタグが付けられていたので、この言語に固有のメモをいくつか追加したいと思います。
JavaScript では、スコープの選択肢は次のとおりです。
- 現状のまま(範囲調整なし)
- 字句
var _this = this; function callback(){ console.log(_this); }
- バウンド
callback.bind(this)
JavaScriptには実際には動的スコープがないことに注意してください。キーワードを.bind
調整します。これは近いですが、技術的には同じではありません。this
以下は、両方のアプローチを示す例です。コールバックのスコープを決定するたびにこれを行うため、promise、イベント ハンドラーなどに適用されます。
レキシカル
Lexical Scoping
以下は、JavaScript でのコールバックの用語です。
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
}
//...
};
バウンド
スコープを設定する別の方法は、次を使用することFunction.prototype.bind
です。
var downloadManager = {
initialize: function() {
$('.downloadLink').on('click', function () {
this.startDownload();
}.bind(this)); // Create a function object bound to `this`
}
//...
私の知る限り、これらのメソッドは動作的に同等です。
レキシカル スコープとは、ネストされた関数のグループで、内部関数が親スコープの変数やその他のリソースにアクセスできることを意味します。
これは、子の関数が親の実行コンテキストに字句的にバインドされていることを意味します。
レキシカル スコープは、static スコープと呼ばれることもあります。
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';
}
}
}
レキシカル スコープについて気付くことは、それが順方向に機能することです。つまり、子の実行コンテキストから名前にアクセスできるということです。
ただし、親から変数likes
にアクセスすることはできません。
これは、異なる実行コンテキストで同じ名前を持つ変数が、実行スタックの上から下に優先されることも示しています。
最も内側の関数 (実行スタックの最上位のコンテキスト) にある別の変数に似た名前を持つ変数は、より高い優先順位を持ちます。
ソース。
レキシカル スコープ: 関数の外部で宣言された変数はグローバル変数であり、JavaScript プログラムのどこでも表示されます。関数内で宣言された変数には関数スコープがあり、その関数内に表示されるコードにのみ表示されます。
レキシカル スコープとは、関数が、その関数のすぐ周囲のスコープではなく、定義されたコンテキストで変数を検索することを意味します。
詳細が必要な場合は、 Lispで字句スコープがどのように機能するかを調べてください。Common Lisp の Dynamic and Lexical variables でKyle Cronin が選択した回答は、ここの回答よりもはるかに明確です。
偶然にも、私はこれについて Lisp の授業でしか習わなかったのですが、たまたま JavaScript にも当てはまります。
このコードを 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
レキシカル スコープとは、実行スタック内の現在の位置から見える識別子 (変数、関数など) のレキシコンを指します。
- global execution context
- foo
- bar
- function1 execution context
- foo2
- bar2
- function2 execution context
- foo3
- bar3
foo
bar
それらはグローバルであるため、常に利用可能な識別子のレキシコン内にあります。
が実行されると、 、、、およびfunction1
のレキシコンにアクセスできます。foo2
bar2
foo
bar
が実行されると、、、、、およびfunction2
のレキシコンにアクセスできます。foo3
bar3
foo2
bar2
foo
bar
グローバルおよび/または外部関数が内部関数識別子にアクセスできない理由は、その関数の実行がまだ行われておらず、その識別子がメモリに割り当てられていないためです。さらに、その内部コンテキストが実行されると、実行スタックから削除されます。つまり、そのすべての識別子はガベージ コレクションされ、使用できなくなります。
最後に、これが、ネストされた実行コンテキストが常にその先祖の実行コンテキストにアクセスできる理由であり、したがって、より多くの識別子のレキシコンにアクセスできる理由です。
見る:
- https://tylermcginnis.com/ultimate-guide-to-execution-contexts-hoisting-scopes-and-closures-in-javascript/
- https://developer.mozilla.org/en-US/docs/Glossary/Identifier
上記の定義を簡素化するのを手伝ってくれた@ robr3rdに感謝します。