字句スコープの簡単な紹介とは何ですか?
字句スコープの簡単な紹介とは何ですか?
回答:
例を通してそれらを理解します。:)
まず、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を印刷します
1つ目はコンパイル時に推定できるため静的と呼ばれ、2つ目は外部スコープが動的で関数のチェーン呼び出しに依存するため動的と呼ばれます。
静的スコープの方が見やすいと思います。ほとんどの言語は、Lispも含めて、最終的にこのようになりました(両方を実行できますか?)。動的スコープは、呼び出された関数にすべての変数の参照を渡すようなものです。
コンパイラーが関数の外部動的スコープを推定できない理由の例として、最後の例を考えてみましょう。このようなものを書くと:
if(/* some condition */)
dummy1();
else
dummy2();
呼び出しチェーンは、実行時の条件によって異なります。trueの場合、呼び出しチェーンは次のようになります。
dummy1 --> fun()
条件が偽の場合:
dummy2 --> fun()
fun
どちらの場合も、の外側のスコープは、呼び出し元と呼び出し元の呼び出し元の合計です。
C言語ではネストされた関数や動的スコープは許可されていません。
JavaScript
ます。したがって、これは受け入れられた回答としてマークされるべきではないと思います。特にJSの語彙範囲は異なります
for
ループ内が典型的な問題です。JavaScriptの字句スコープは、ES6 let
またはconst
が使用されていない限り、関数レベルのみです。
可能な限り短い定義を試してみましょう:
字句スコープは、ネストされた関数での変数名の解決方法を定義します。内部関数には、親関数が返された場合でも、親関数のスコープが含まれます。
それだけです!
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定義ガイドに従っています。
字句スコープは非常に強力な概念です。
お役に立てれば..:)
字句(静的)スコープとは、コードのテキストコーパス内での位置のみに基づいて変数のスコープを決定することを指します。変数は常にトップレベルの環境を参照します。動的スコープとの関連でそれを理解するのは良いことです。
スコープは、関数、変数などが利用できる領域を定義します。たとえば、変数の可用性は、そのコンテキスト内で定義されます。たとえば、関数、ファイル、またはオブジェクトが定義されます。これらは、通常、これらのローカル変数と呼ばれます。
字句部分は、ソースコードの読み取りからスコープを派生できることを意味します。
字句スコープは静的スコープとも呼ばれます。
動的スコープは、定義された後、どこからでも呼び出しまたは参照できるグローバル変数を定義します。ほとんどのProgrammin言語のグローバル変数はレキシカルスコープのものですが、グローバル変数と呼ばれることもあります。つまり、変数をこのコンテキストで使用できるということは、コードを読み取ることから導き出すことができます。インスタンス化または定義を見つけるには、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`
}
//...
これらの方法は、私の知る限り、行動的に同等です。
bind
してもスコープには影響しません。
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();
字句スコープとは、ネストされた関数のグループでは、内部関数が親スコープの変数やその他のリソースにアクセスできることを意味します。つまり、子関数はその親の実行コンテキストに字句的にバインドされます。字句スコープは、静的スコープとも呼ばれます。
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
はその親からアクセスできません。
これは、異なる実行コンテキストで同じ名前の変数が実行スタックの上から下に優先されることも示しています。最も内側の関数(実行スタックの最上位のコンテキスト)にある、別の変数と同様の名前を持つ変数は、優先順位が高くなります。
単純な言語では、字句スコープはスコープ外で定義された変数であるか、スコープ内で上位スコープが自動的に使用できるため、そこに渡す必要はありません。
例:
let str="JavaScript";
const myFun = () => {
console.log(str);
}
myFun();
//出力:JavaScript
bind
ます。それらがあれば、bind
もう必要ありません。この変更の詳細については、stackoverflow.com
語彙的および動的スコープに関する会話には欠落している重要な部分があります。スコープ付き変数の存続期間の簡単な説明-または変数にいつアクセスできるかです。
動的スコーピングは、「グローバル」スコーピングにごくわずかしか対応していません。これは、私たちが伝統的に考えている方法です(この2つを比較した理由は、すでに言及されているためです。また、リンクされた記事の説明は特に好きではありません)); おそらく、リンクされた記事によれば、「... [it]はグローバルスコープ変数の代わりとして役立つ」とはいえ、おそらくグローバルとダイナミックを比較しない方がいいでしょう。
では、わかりやすい英語で、2つのスコープメカニズムの重要な違いは何ですか?
字句スコープは、上記の回答全体で非常によく定義されています。字句スコープ変数は、それが定義された関数のローカルレベルで使用できます。
ただし、これはOPの焦点ではないため、動的スコープはそれほど注目されていません。注目されたことは、おそらくもう少し必要であることを意味します(他の回答に対する批判ではなく、「ああ、その答えにより、もう少しあるといいのですが」)。それで、もう少しです:
動的スコープとは、関数呼び出しの存続期間中、または関数の実行中に、大きなプログラムから変数にアクセスできることを意味します。本当に、ウィキペディアは実際に2つの違いを説明することで素晴らしい仕事をします。わかりにくくしないように、動的スコープについて説明するテキストを次に示します。
... [I] n動的スコープ(または動的スコープ)、変数名のスコープが特定の関数の場合、そのスコープは関数が実行されている期間です。関数の実行中は変数名が存在します、その変数にバインドされていますが、関数が戻った後、変数名は存在しません。
字句スコープとは、関数がその周囲のスコープではなく、定義されたコンテキストで変数を検索することを意味します。
詳細が必要な場合は、Lispで字句スコープがどのように機能するかを確認してください。Common Lispの動的変数と語彙変数でカイルクローニンが選択した回答は、ここでの回答よりもはるかに明確です。
偶然にも私は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
JavaScriptの字句スコープとは、関数の外部で定義された変数が、変数宣言の後に定義された別の関数の内部でアクセスできることを意味します。しかし、その反対は真実ではありません。関数内で定義された変数には、その関数の外からはアクセスできません。
この概念は、JavaScriptのクロージャーで頻繁に使用されます。
以下のコードがあるとしましょう。
var x = 2;
var add = function() {
var y = 1;
return x + y;
};
これで、add()を呼び出すと、3が出力されます。
したがって、add()関数は、x
メソッド関数addの前に定義されているグローバル変数にアクセスしています。これは、JavaScriptの字句スコープのために呼び出されます。
add()
関数が所定のコードスニペットの直後に呼び出された場合、3も出力されます。字句スコープとは、関数がローカルコンテキストの外部のグローバル変数にアクセスできることを意味するだけではありません。したがって、サンプルコードは実際に字句スコープの意味を示すのに役立ちません。コードで字句スコープを示すには、実際には反例または少なくともコードの他の可能な解釈の説明が必要です。
字句スコープとは、実行スタックの現在の位置から見える識別子(変数、関数など)の語彙集のことです。
- 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
。
グローバル関数や外部関数が内部関数識別子にアクセスできないのは、その関数の実行がまだ発生していないため、その識別子がメモリに割り当てられていないためです。さらに、その内部コンテキストが実行されると、実行スタックから削除されます。つまり、そのコンテキストのすべての識別子がガベージコレクションされ、使用できなくなります。
最後に、ネストされた実行コンテキストが常にその祖先の実行コンテキストにアクセスできるのはこのためです。したがって、識別子のより大きなレキシコンにアクセスできます。
見る:
上記の定義を単純化するのに役立つ@ robr3rdに特に感謝します。
ここでは、この問題について別の角度から説明します。一歩下がって、解釈のより大きなフレームワーク(プログラムの実行)におけるスコーピングの役割を調べます。言い換えれば、ある言語のインタープリター(またはコンパイラー)を構築していて、プログラムとそれに何らかの入力が与えられた場合に、出力を計算する責任があると想像してください。
解釈には次の3つを追跡することが含まれます。
状態-つまり、ヒープとスタック上の変数と参照メモリの場所。
その状態での操作-つまり、プログラムのすべてのコード行
環境所与た動作が実行さ-つまり、投影状態動作に。
インタープリターは、プログラムのコードの最初の行から開始し、その環境を計算し、その環境で行を実行して、プログラムの状態への影響をキャプチャします。次に、プログラムの制御フローに従って次のコード行を実行し、プログラムが終了するまでプロセスを繰り返します。
操作の環境を計算する方法は、プログラミング言語で定義された一連の正式なルールによるものです。「バインディング」という用語は、プログラムの全体的な状態から環境内の値へのマッピングを表すために頻繁に使用されます。「全体的な状態」とは、グローバルな状態を意味するのではなく、実行の任意の時点で到達可能なすべての定義の合計を意味することに注意してください)。
これは、スコーピング問題が定義されるフレームワークです。次に、オプションの次の部分に進みます。
これは、動的スコープの要点であり、コードが実行される環境は、実行コンテキストによって定義されたプログラムの状態にバインドされます。
言い換えると、字句スコープを使用すると、コードが認識する環境は、ブロックや関数など、言語で明示的に定義されたスコープに関連付けられた状態にバインドされます。
古代の質問ですが、これが私の見解です。
字句(静的)スコープとは、ソースコード内の変数のスコープを指します。
JavaScriptのような言語では、関数を渡したり、さまざまなオブジェクトにアタッチしたり、再アタッチしたりできますが、そのスコープは、その時点で関数を呼び出しているユーザーに依存しますが、そうではありません。スコープをそのように変更すると動的スコープになり、JavaScriptはthis
オブジェクト参照を使用する場合を除いてそれを行いません。
ポイントを説明するには:
var a='apple';
function doit() {
var a='aardvark';
return function() {
alert(a);
}
}
var test=doit();
test();
この例では、変数a
はグローバルに定義されていますが、doit()
関数内でシャドウされています。この関数は別の関数を返します。この関数は、ご覧のように、a
自身のスコープ外の変数に依存しています。
これを実行すると、使用される値は aardvark
、ではないapple
、それはの範囲にあるもののれ、test()
機能、元の関数のレキシカルスコープではありません。つまり、使用されるスコープは、ソースコードに表示されるスコープであり、関数が実際に使用されるスコープではありません。
この事実は、迷惑な結果をもたらす可能性があります。たとえば、関数を個別に整理し、イベントハンドラーなどで時間が来たらそれらを使用する方が簡単だと判断する場合があります。
var a='apple',b='banana';
function init() {
var a='aardvark',b='bandicoot';
document.querySelector('button#a').onclick=function(event) {
alert(a);
}
document.querySelector('button#b').onclick=doB;
}
function doB(event) {
alert(b);
}
init();
<button id="a">A</button>
<button id="b">B</button>
このコードサンプルは、それぞれ1つずつ実行します。字句スコープのため、ボタンがA
は内部変数を使用しは使用B
しないます。ネスト関数は、思ったよりも多くなる可能性があります。
ちなみに、どちらの例でも、含まれている関数関数がコースを実行しても、内側のレキシカルスコープの変数が存続していることに気付くでしょう。これはクロージャと呼ばれ、外部関数が終了した場合でも、ネストされた関数が外部変数にアクセスすることを指します。JavaScriptは、これらの変数が不要になったかどうかを判断するのに十分スマートである必要があり、不要な場合はガベージコレクションを実行できます。
私は通常例で学びます、そしてここに少し何かがあります:
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();
このトピックは組み込みbind
関数と強く関連しており、ECMAScript 6 Arrow Functionsで導入されています。本当に面倒でした。なぜなら、使用したいすべての新しい「クラス」(実際には関数)メソッドについてbind
、スコープにアクセスするためにこれを行わなければならなかったからです。
デフォルトでは、JavaScript this
は関数のスコープを設定しません(コンテキストをに設定しませんthis
)。デフォルトでは、必要なコンテキストを明示的に指定する必要があります。
矢印の機能は自動的に、いわゆるますレキシカルスコープ(その包含ブロック内の変数の定義へのアクセス権を持っています)。アロー関数を使用するthis
と、アロー関数が最初に定義された場所に自動的にバインドされ、このアロー関数のコンテキストはそれを含むブロックです。
以下の最も単純な例で実際にどのように機能するかを確認してください。
矢印関数の前(デフォルトでは字句スコープなし):
const programming = {
language: "JavaScript",
getLanguage: function() {
return this.language;
}
}
const globalScope = programming.getLanguage;
console.log(globalScope()); // Output: undefined
const localScope = programming.getLanguage.bind(programming);
console.log(localScope()); // Output: "JavaScript"
矢印関数を使用(デフォルトでは字句スコープ):
const programming = {
language: "JavaScript",
getLanguage: function() {
return this.language;
}
}
const arrowFunction = () => {
console.log(programming.getLanguage());
}
arrowFunction(); // Output: "JavaScript"