JavaScript ではこれは正確には何を意味しますか?多くの人は、これは現在のオブジェクトを指していると言うでしょう。これは正しいですか?ほとんどの場合、それが当てはまります。たとえば、Web ページでは次のような JavaScript をよく書きます:
<input type="submit" value="提交" onclick="this.value='正在提交数据'" />
これは明らかに現在のオブジェクト、つまり送信ボタンを指します。通常、これを使用するときの状況はこれに似ています。しかし、そうでない状況はあるのでしょうか?
この例を見てください:
var foo = function() { console.log(this); } foo(); new foo();
foo() と new foo() の実行結果を比較すると、前者では foo 自体を指しているのではなく、現在のページの window オブジェクトを指しているのに対し、後者は実際に foo を指していることがわかります。 。これはなぜでしょうか?
実際、これには JavaScript の重要な機能、いわゆる「クロージャ」が関係しています。クロージャの概念は複雑ではありませんが、1 つまたは 2 つの文で明確に説明できるほど単純でもありません。 JavaScript のこの最も重要な機能については、今後の記事で詳しく説明します。ここで、私が伝えたいのは、JavaScript のスコープはクロージャのために非常に重要になるということです。
いわゆるスコープとは、簡単に言えば、関数が作成される環境を指します。この変数の値が指定されていない場合、関数の現在のスコープになります。
前の例では、 foo() 関数はグローバル スコープ (ここでは window オブジェクト) 内にあるため、 this の値は現在の window オブジェクトになります。 new foo() の形式では、実際には foo() のコピーが作成され、このコピーに対して操作が実行されるため、これが foo() のコピーになります。
これは少し抽象的かもしれません。実際の例を見てみましょう:
<input type="button" id="aButton" value="demo" onclick="" /> <script type="text/javascript"> function demo() { this.value = Math.random(); } </script>
demo() 関数を直接呼び出すと、プログラムはエラーを報告します。これは、demo 関数が window オブジェクトで定義されているため、demo の所有者 (スコープ) が window であり、demo の this も window であるためです。ウィンドウには value 属性がないため、エラーが報告されました。
コピーを作成してこの関数のコピーを HTML 要素に追加すると、その所有者がこの要素になり、これもこの要素を参照します:
document.getElementById("aButton").onclick = demo;
これは、aButton の onlick 属性を demo() のコピーに設定し、これも aButton を指します。
複数の異なる HTML 要素に対して関数の異なるコピーを作成することもできます。各コピーの所有者は対応する HTML 要素であり、それぞれの this も所有者を指すため、混乱は生じません。
ただし、要素の onlick イベントを次のように定義すると、
<input type="button" id="aButton" value="demo" onclick="demo()" />
このボタンをクリックすると、プログラムが再度エラーを報告することがわかります。これは再びウィンドウを示しています。
実際には、このメソッドはプログラムに関数を作成するのではなく、関数を参照するだけです。
違いを詳しく見てみましょう。
関数のコピーを作成するメソッドを使用します:
<input type="button" id="aButton" value="demo" /> <script type="text/javascript"> var button = document.getElementById("aButton"); function demo() { this.value = Math.random(); } button.onclick= demo; alert(button.onclick); </script>
結果の出力は次のとおりです:
function demo() { this.value = Math.random(); }
関数参照の使用方法:
<input type="button" id="aButton" value="demo" onclick="demo()" />
結果の出力は次のとおりです:
function onclick() { demo(); }
こうすると違いがわかります。関数参照メソッドでは、onclick イベントは、demo() 関数を直接呼び出すだけであり、demo() 関数のスコープは依然として window オブジェクトであるため、これは引き続きウィンドウを指します。
これにより、別の疑問が生じます。関数のコピーは非常に簡単に使用できるのに、なぜ関数参照が必要なのでしょうか?答えはパフォーマンスです。関数のコピーが作成されるたびに、プログラムは関数のコピーに一定量のメモリを割り当てます。実際のアプリケーションでは、ほとんどの関数は必ずしも呼び出されるとは限らないため、メモリのこの部分は無駄になります。関数参照を使用すると、プログラムは関数自体にメモリのみを割り当てますが、参照ではポインターのみが割り当てられるため、はるかに効率的です。プログラマーの皆さん、節約が一番大事ですね
それでは、より良い解決策を見てみましょう:
<script type="text/javascript"> function demo(obj) { obj.value = Math.random(); } </script> <input type="button" value="demo" onclick="demo(this)" /> <input type="button" value="demo" onclick="demo(this)" /> <input type="button" value="demo" onclick="demo(this)" />
这样,效率和需求就都能兼顾了。
this的指向
JavaScript由于其在运行期进行绑定的特性,JavaScript 中的 this 可以是全局对象、当前对象或者任意对象,这完全取决于函数的调用方式。JavaScript 中函数的调用有以下几种方式:作为对象方法调用,作为函数调用,作为构造函数调用,和使用 apply 或 call 调用。常言道,字不如表,表不如图。为了让人更好的理解JavaScript this 到底指向什么?下面用一张图来进行解释:
上图我称之为”JavaScript this决策树“(非严格模式下)。下面通过例子来说明这个图如何来帮助我们对this进行判断:
var point = { x : 0, y : 0, moveTo : function(x, y) { this.x = this.x + x; this.y = this.y + y; } }; //决策树解释:point.moveTo(1,1)函数不是new进行调用,进入否决策, //是用dot(.)进行调用,则指向.moveTo之前的调用对象,即point point.moveTo(1,1); //this 绑定到当前对象,即point对象
point.moveTo()函数在 “JavaScript this决策树“中进行判定的过程是这样的:
1)point.moveTo函数调用是用new进行调用的么?这个明显不是,进入“否”分支,即函数是否用dot(.)进行调用?;
2)point.moveTo函数是用dot(.)进行调用的,即进入“是”分支,即这里的this指向point.moveTo中.之前的对象point;
图解point.moveTo函数的this指向什么的解析图如下图所示:
再举例,看下面的代码:
function func(x) { this.x = x; } func(5); //this是全局对象window,x为全局变量 //决策树解析:func()函数是用new进行调用的么?为否,进入func()函数是用dot进行调用的么?为否,则 this指向全局对象window x;//x => 5
func()函数在 “JavaScript this决策树“中进行判定的过程是这样的:
1)func(5)函数调用是用new进行调用的么?这个明显不是,进入“否”分支,即函数是否用dot(.)进行调用?;
2)func(5)函数不是用dot(.)进行调用的,即进入“否”分支,即这里的this指向全局变量window,那么this.x实际上就是window.x;
图解func函数的this指向什么的解析图如下图所示:
针对作为函数直接调用的方式,下面看一个复杂的例子:
var point = { x : 0, y : 0, moveTo : function(x, y) { // 内部函数 var moveX = function(x) { this.x = x;//this 指向什么?window }; // 内部函数 var moveY = function(y) { this.y = y;//this 指向什么?window }; moveX(x); moveY(y); } }; point.moveTo(1,1); point.x; //=>0 point.y; //=>0 x; //=>1 y; //=>1
point.moveTo(1,1)函数实际内部调用的是moveX()和moveY()函数, moveX()函数内部的this在 “JavaScript this决策树“中进行判定的过程是这样的:
1)moveX(1)函数调用是用new进行调用的么?这个明显不是,进入“否”分支,即函数是否用dot(.)进行调用?;
2)moveX(1)函数不是用dot(.)进行调用的,即进入“否”分支,即这里的this指向全局变量window,那么this.x实际上就是window.x;
下面看一下作为构造函数调用的例子:
function Point(x,y){ this.x = x; // this ? this.y = y; // this ? } var np=new Point(1,1); np.x;//1 var p=Point(2,2); p.x;//error, p是一个空对象undefined window.x;//2
Point(1,1)函数在var np=new Point(1,1)中的this在 “JavaScript this决策树“中进行判定的过程是这样的:
1)var np=new Point(1,1)调用是用new进行调用的么?这个明显是,进入“是”分支,即this指向np;
2)那么this.x=1,即np.x=1;
Point(2,2)函数在var p= Point(2,2)中的this在 “JavaScript this决策树“中进行判定的过程是这样的:
1)var p= Point(2,2)调用是用new进行调用的么?这个明显不是,进入“否”分支,即函数是否用dot(.)进行调用?;
2)Point(2,2)函数不是用dot(.)进行调用的?判定为否,即进入“否”分支,即这里的this指向全局变量window,那么this.x实际上就是window.x;
3)this.x=2即window.x=2.
最后看一下函数用call 和apply进行调用的例子:
function Point(x, y){ this.x = x; this.y = y; this.moveTo = function(x, y){ this.x = x; this.y = y; } } var p1 = new Point(0, 0); var p2 = {x: 0, y: 0}; p1.moveTo.apply(p2, [10, 10]);//apply实际上为p2.moveTo(10,10) p2.x//10
「JavaScript this 決定木」の p1.moveTo.apply(p2,[10,10]) 関数の処理は次のとおりです。
apply と call の 2 つのメソッドは、関数実行のコンテキスト、つまりこれにバインドされているオブジェクトを切り替えることができることが非常に強力であることがわかります。 p1.moveTo.apply(p2,[10,10]) は実際には p2.moveTo(10,10) です。 p2.moveTo(10,10) は次のように解釈できます:
1) p2.moveTo(10,10) 関数は new を使用して呼び出されていますか?これは明らかに当てはまりません。「いいえ」分岐に進みます。つまり、関数は dot(.) で呼び出されますか? ;
2) p2.moveTo(10,10) 関数は dot(.) で呼び出されます。つまり、「yes」分岐に入ります。つまり、ここでは、これは p2.moveTo( 10,10) なので、p2.x=10;
JavaScript 関数の実行環境のプロセスについては、IBM Developerworks ドキュメント ライブラリに非常に優れた説明があります。その抜粋は次のとおりです。
「JavaScript の関数は、通常の関数またはオブジェクトのメソッドとして実行できます。これが、関数が実行されるときに実行環境 (ExecutionContext) が作成される主な理由です。関数の動作は、この実行環境で発生します。JavaScript は、関数呼び出し時に渡されるパラメーターを含む引数変数を最初に作成し、それを最初に初期化します。関数のパラメータ リストの値は、引数変数の対応する値です。関数に内部関数が含まれている場合、仮パラメータは未定義に初期化されます。この関数で定義されたローカル変数については、この時点で未定義に初期化され、実行環境 (ExecutionContext) の後に関数が実行されるまでその代入操作は実行されないことに注意してください。これは、JavaScript における変数の役割を理解する上で非常に重要です。スペースの観点から、このトピックについては説明しません。最後に、この変数に値を割り当てます。上記では、関数呼び出しメソッド (この時点まで) が正常に作成され、関数が 1 行ずつ実行を開始し、必要な変数が読み取られることに応じて、このグローバル オブジェクト、現在のオブジェクトなどに割り当てられます。先ほど構築した実行環境(ExecutionContext)”
から。
この段落を理解することは、JavaScript 関数を理解する上で大いに役立ちます。