Was genau bedeutet das in JavaScript? Viele Leute werden Ihnen sagen, dass sich dies auf das aktuelle Objekt bezieht. Ist das richtig? Das stimmt in den meisten Fällen. Beispielsweise schreiben wir auf Webseiten häufig JavaScript wie folgt:
<input type="submit" value="提交" onclick="this.value='正在提交数据'" />
Dies bezieht sich hier offensichtlich auf das aktuelle Objekt, nämlich die Schaltfläche „Senden“. Normalerweise ist die Situation, wenn wir dies verwenden, ähnlich. Aber gibt es eine Situation, in der dies nicht der Fall ist?
Sehen Sie sich dieses Beispiel an:
var foo = function() { console.log(this); } foo(); new foo();
Vergleichen Sie die Ausführungsergebnisse von foo() und new foo(). Sie werden feststellen, dass ersteres nicht auf foo selbst, sondern auf das Fensterobjekt der aktuellen Seite verweist, während letzteres tatsächlich auf foo verweist . Warum ist das so?
Tatsächlich handelt es sich hierbei um eine wichtige Funktion von JavaScript, nämlich den sogenannten „Closure“. Das Konzept des Abschlusses ist nicht komplex, aber auch nicht so einfach, dass es in ein oder zwei Sätzen klar erklärt werden kann. Ich werde mich in einem zukünftigen Artikel mit dieser wichtigsten Funktion von Javascript befassen. Was ich Ihnen nun sagen möchte, ist, dass der Umfang in JavaScript aufgrund von Abschlüssen sehr wichtig wird.
Der sogenannte Scope bezieht sich vereinfacht gesagt auf die Umgebung, in der eine Funktion erstellt wird. Der Wert dieser Variablen ist, sofern nicht angegeben, der aktuelle Gültigkeitsbereich der Funktion.
Im vorherigen Beispiel befindet sich die Funktion foo() im globalen Bereich (hier ist das Fensterobjekt), daher ist der Wert davon das aktuelle Fensterobjekt. In Form von new foo() wird tatsächlich eine Kopie von foo() erstellt und Operationen werden an dieser Kopie ausgeführt, daher ist dies hier die Kopie von foo().
Das ist vielleicht etwas abstrakt, schauen wir uns ein praktisches Beispiel an:
<input type="button" id="aButton" value="demo" onclick="" /> <script type="text/javascript"> function demo() { this.value = Math.random(); } </script>
Wenn Sie die Funktion „demo()“ direkt aufrufen, meldet das Programm einen Fehler, da die Funktion „demo“ im Fensterobjekt definiert ist, sodass der Eigentümer (Bereich) von „demo“ „window“ ist und das „this“ von „demo“ ebenfalls „window“ ist. Das Fenster verfügt nicht über ein Wertattribut, daher wurde ein Fehler gemeldet.
Wenn wir eine Kopie dieser Funktion zu einem HTML-Element hinzufügen, indem wir eine Kopie erstellen, wird sein Besitzer zu diesem Element, und dies bezieht sich auch auf dieses Element:
document.getElementById("aButton").onclick = demo;
Dadurch wird das onlick-Attribut von aButton auf eine Kopie von demo() gesetzt, und dies zeigt auch auf aButton.
Sie können sogar verschiedene Kopien der Funktion für mehrere verschiedene HTML-Elemente erstellen. Der Besitzer jeder Kopie ist das entsprechende HTML-Element, und ihre jeweiligen Thiss verweisen auch auf ihre Besitzer, was keine Verwirrung stiften wird.
Wenn Sie jedoch das Onlick-Ereignis eines Elements wie folgt definieren:
<input type="button" id="aButton" value="demo" onclick="demo()" />
Nachdem Sie auf diese Schaltfläche geklickt haben, werden Sie feststellen, dass das Programm erneut einen Fehler meldet – dies zeigt erneut auf das Fenster!
Eigentlich erstellt diese Methode keine Funktion für das Programm, sondern verweist nur auf die Funktion.
Schauen wir uns die Unterschiede genauer an.
Verwenden Sie die Methode, die eine Kopie der Funktion erstellt:
<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>
Die resultierende Ausgabe ist:
function demo() { this.value = Math.random(); }
So verwenden Sie Funktionsreferenzen:
<input type="button" id="aButton" value="demo" onclick="demo()" />
Die resultierende Ausgabe ist:
function onclick() { demo(); }
Auf diese Weise können Sie den Unterschied erkennen. In der Funktionsreferenzmethode ruft das Onclick-Ereignis einfach die Funktion demo() direkt auf, und der Gültigkeitsbereich der Funktion demo() ist immer noch das Fensterobjekt, sodass dieses immer noch auf das Fenster zeigt.
Das wirft eine weitere Frage auf: Da Funktionskopien so einfach zu verwenden sind, warum brauchen wir Funktionsreferenzen? Die Antwort ist Leistung. Jedes Mal, wenn eine Kopie einer Funktion erstellt wird, weist das Programm eine bestimmte Menge Speicher für die Kopie der Funktion zu. In tatsächlichen Anwendungen werden die meisten Funktionen nicht unbedingt aufgerufen, sodass dieser Teil des Speichers verschwendet wird. Bei Verwendung von Funktionsreferenzen weist das Programm nur der Funktion selbst Speicher zu, während Referenzen nur Zeiger zuweisen, was wesentlich effizienter ist. Programmierer, Sparen ist die Hauptsache, eh
Schauen wir uns also eine bessere Lösung an:
<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
Der Prozess der Funktion p1.moveTo.apply(p2,[10,10]) in „JavaScript diesen Entscheidungsbaum“ ist wie folgt:
Wir wissen, dass die beiden Methoden apply und call äußerst leistungsfähig sind. Sie ermöglichen das Wechseln des Kontexts der Funktionsausführung, also des daran gebundenen Objekts. p1.moveTo.apply(p2,[10,10]) ist eigentlich p2.moveTo(10,10). Dann kann p2.moveTo(10,10) wie folgt interpretiert werden:
1) Wird die Funktion p2.moveTo(10,10) mit new aufgerufen? Dies ist offensichtlich nicht der Fall. Gehen Sie zum Zweig „Nein“, d. h. wird die Funktion mit Punkt (.) aufgerufen? ;
2) Die Funktion p2.moveTo(10,10) wird mit dot(.) aufgerufen, das heißt, sie tritt in den „Ja“-Zweig ein, das heißt, dies zeigt hier auf das vorherige Objekt p2 in p2.moveTo( 10,10), also p2.x=10;
Bezüglich des Prozesses der JavaScript-Funktionsausführungsumgebung gibt es eine sehr gute Beschreibung in der IBM Developerworks-Dokumentenbibliothek. Der Auszug lautet wie folgt:
„Eine Funktion in JavaScript kann als gewöhnliche Funktion oder als Methode eines Objekts ausgeführt werden. Dies ist der Hauptgrund, warum dies eine so umfangreiche Bedeutung hat. Wenn eine Funktion ausgeführt wird, wird eine Ausführungsumgebung (ExecutionContext) erstellt.“ Das Verhalten der Funktion erfolgt in dieser Ausführungsumgebung. Beim Erstellen der Ausführungsumgebung erstellt JavaScript zunächst die Argumentvariable, die beim Aufruf der Funktion übergeben wird. Anschließend wird die Gültigkeitskette erstellt und initialisiert Parameterliste der Funktion, der Wert ist der entsprechende Wert in der Argumentvariablen, der formale Parameter wird auf undefiniert initialisiert. Wenn die Funktion interne Funktionen enthält, werden diese internen Funktionen initialisiert Wenn nicht, fahren Sie mit der Initialisierung der in dieser Funktion definierten lokalen Variablen fort. Beachten Sie, dass diese Variablen zu diesem Zeitpunkt auf undefiniert initialisiert sind und ihre Zuweisungsvorgänge erst ausgeführt werden, wenn die Funktion nach der Ausführungsumgebung (ExecutionContext) ausgeführt wird. Dies ist für uns sehr wichtig, um die Rolle von Variablen in JavaScript zu verstehen. Aus Platzgründen werden wir dieser Variablen schließlich keinen Wert zuweisen Oben wird es diesem globalen Objekt, dem aktuellen Objekt usw. zugewiesen. Je nachdem, ob die Funktion aufgerufen wurde (bis zu diesem Punkt), wird ExecutionContext erfolgreich erstellt, die Funktion beginnt zeilenweise auszuführen und die erforderlichen Variablen werden gelesen aus der zuvor erstellten Ausführungsumgebung (ExecutionContext) ”
.
Das Verständnis dieses Absatzes trägt wesentlich zum Verständnis der Javascript-Funktionen bei.