この記事ではこの問題についてのみ説明します。読者が JavaScript の「これは何ですか」という質問に正しく答えることができれば、著者として、このような記事を書く価値があると感じます。
次の 1 つの文を覚えておく必要があります。これは常に、関数が実行されているオブジェクトを指します。関数が作成されたオブジェクトではなく。つまり、電話をかける人が誰を指しているのかということです。 覚えておいてください…
この記事では、 の 3 つの状況でこのオブジェクトがどこにあるかを分析します。
1. 通常の関数の
これがどこにあるとしても、関数が実行されているときにその場所を見つけることが最優先です。
var name="全局"; function getName(){ var name="局部"; return this.name; }; alert(getName());
これがグローバル環境の関数 getName に出現する場合、関数 getName の実行時の場所は
alert(getName());
明らかに、関数 getName が配置されているオブジェクトはグローバル オブジェクト、つまり window であるため、このオブジェクトのホームは window 内にある必要があります。このとき、this は window オブジェクトを指しているため、getName によって返される this.name は実際には window.name であるため、アラートは「グローバル」として出力されます。
では、これが地球環境の働きではなく局所環境の働きで現れる場合、どこに該当するのでしょうか?
var name="全局"; var xpg={ name:"局部", getName:function(){ return this.name; } }; alert(xpg.getName());
これが配置されている関数 getName はグローバル環境ではなく、xpg 環境にあります。これがどこにあっても、関数の実行中にその場所を見つける必要があります。現時点で実行中の関数 getName の位置
alert(xpg.getName());
明らかに、関数 getName が配置されているオブジェクトは xpg であるため、this のホームは xpg 内にある必要があります。つまり、getName によって返される this.name は実際には xpg.name です。そのため、アラートは「部分的」として表示されます。
統合する例を示します:
var someone = { name: "Bob", showName: function(){ alert(this.name); } }; var other = { name: "Tom", showName: someone.showName } other.showName(); //Tom
this キーワードはsomeone.showNameで宣言されていますが、実行時はother.showNameなので、これはother.showName関数の現在のオブジェクト(other)を指しているため、最終的なアラートはother.nameになります。
2. これで終わります
クロージャも問題の種です。この記事では、当面はこれについて詳しく説明しません。つまり、いわゆるクロージャとは、関数内に別の関数を作成することであり、内部関数は外部変数にアクセスします。
放蕩息子と悪党の結末が混ざっており、決して平和が存在しないことを示しています。
var name="全局"; var xpg={ name:"局部", getName:function(){ return function(){ return this.name; }; } }; alert(xpg.getName()());
現時点では、これは明らかに問題です。実際には getName 関数の匿名関数内にあり、匿名関数が変数名を呼び出してクロージャを形成しています。つまり、this はクロージャ内にあります。
これがどこにあっても、関数の実行中にその場所を見つける必要があります。このとき、関数getNameの実行時位置では判断できず、無名関数の実行時位置で判断します。
function (){ return this.name; };
明らかに、匿名関数が配置されているオブジェクトは window なので、this のホームは window 内にある必要があり、匿名関数によって返される this.name は実際には window.name なので、アラートから出てくるのは"グローバル"!
それでは、これを XPG でクロージャに作成するにはどうすればよいでしょうか? —これをキャッシュします
var name="全局"; var xpg={ name:"局部", getName:function(){ var that=this; return function(){ return that.name; }; } }; alert(xpg.getName()());
getName 関数で that=this を定義します。このとき、getName 関数は
で実行されています。alert(xpg.getName());
次に、これは xpg オブジェクトを指すため、これも xpg オブジェクトを指します。 that.name がクロージャの匿名関数で返される場合、このときに返される that.name は実際には xpg.name であるため、「ローカル」に警告を与えることができます。
3. 新しいキーワードは新しいオブジェクト
を作成します。new キーワードの後のコンストラクター内のこれは、コンストラクターで構築された新しいオブジェクトを指します:
function Person(__name){ this.name = __name; //这个this指向用该构造函数构造的新对象,这个例子是Bob对象 } Person.prototype.show = function(){ alert(this.name); //this 指向Person,this.name = Person.name; } var Bob = new Person("Bob"); Bob.show(); //Bob
4. これを呼び出して適用します
JavaScript でこれを管理できるのは、call と apply だけです。
電話して応募するのはこの子の両親のようなものです。彼らはこの子を住まわせた場所ならどこにでも住むでしょう、そして彼らは従わなければなりません!パラメータがない場合、現在のオブジェクトは window
var name="全局"; var xpg={ name:"局部" }; function getName(){ alert(this.name); } getName(xpg); getName.call(xpg); getName.call();
これは関数 getName 内のどこにありますか。これがどこにあっても、関数の実行中にその場所を見つける必要があります。現時点で実行中の関数 getName の位置
getName(xpg);
明らかに、関数 getName が配置されているオブジェクトは window であるため、this のホームは window 内にある必要があります。つまり、getName によって返される this.name は実際には window.name です。そのため、アラートは「グローバル」として表示されます。
それでは、電話して応募する番です。なぜなら、これは彼らの命令を聞かなければならないからです!
getName.call(xpg);
このうち、callはthisのホームがxpgオブジェクトであることを指定しているため、thisはxpgのみに強制的に定着するので、このときthis.nameがxpgオブジェクトを指しており、実際にはthis.nameがxpg.nameなのでアラートが出ます。 「ローカル」として!
5. eval でこれを実行します
eval 関数の場合、実行時に現在のオブジェクトが指定されていないように見えますが、関数が実行されるときのスコープは現在のスコープであり、同等であるため、実際には this はウィンドウを指していません。その行の中にコードを入力します。次の例は、この問題を示しています:
var name = "window"; var Bob = { name: "Bob", showName: function(){ eval("alert(this.name)"); } }; Bob.showName(); //Bob
6、没有明确的当前对象时的this
当没有明确的执行时的当前对象时,this指向全局对象window。
例如对于全局变量引用的函数上我们有:
var name = "Tom"; var Bob = { name: "Bob", show: function(){ alert(this.name); } } var show = Bob.show; show(); //Tom
你可能也能理解成show是window对象下的方法,所以执行时的当前对象时window。但局部变量引用的函数上,却无法这么解释:
var name = "window"; var Bob = { name: "Bob", showName: function(){ alert(this.name); } }; var Tom = { name: "Tom", showName: function(){ var fun = Bob.showName; fun(); } }; Tom.showName(); //window
在浏览器中setTimeout、setInterval和匿名函数执行时的当前对象是全局对象window,这条我们可以看成是上一条的一个特殊情况。
var name = "Bob"; var nameObj ={ name : "Tom", showName : function(){ alert(this.name); }, waitShowName : function(){ setTimeout(this.showName, 1000); } }; nameObj.waitShowName();
所以在运行this.showName的时候,this指向了window,所以最后显示了window.name。
7、dom事件中的this
(1)你可以直接在dom元素中使用
<input id="btnTest" type="button" value="提交" onclick="alert(this.value))" />
分析:对于dom元素的一个onclick(或其他如onblur等)属性,它为所属的html元素所拥有,直接在它触发的函数里写this,this应该指向该html元素。
(2)给dom元素注册js函数
a、不正确的方式
<script type="text/javascript"> function thisTest(){ alert(this.value); // 弹出undefined, this在这里指向?? } </script> <input id="btnTest" type="button" value="提交" onclick="thisTest()" />
分析:onclick事件直接调用thisTest函数,程序就会弹出undefined。因为thisTest函数是在window对象中定义的,
所以thisTest的拥有者(作用域)是window,thisTest的this也是window。而window是没有value属性的,所以就报错了。
b、正确的方式
<input id="btnTest" type="button" value="提交" /> <script type="text/javascript"> function thisTest(){ alert(this.value); } document.getElementById("btnTest").onclick=thisTest; //给button的onclick事件注册一个函数 </script>
分析:在前面的示例中,thisTest函数定义在全局作用域(这里就是window对象),所以this指代的是当前的window对象。而通过document.getElementById(“btnTest”).onclick=thisTest;这样的形式,其实是将btnTest的onclick属性设置为thisTest函数的一个副本,在btnTest的onclick属性的函数作用域内,this归btnTest所有,this也就指向了btnTest。其实如果有多个dom元素要注册该事件,我们可以利用不同的dom元素id,用下面的方式实现:
document.getElementById("domID").onclick=thisTest; //给button的onclick事件注册一个函数。
因为多个不同的HTML元素虽然创建了不同的函数副本,但每个副本的拥有者都是相对应的HTML元素,各自的this也都指向它们的拥有者,不会造成混乱。
为了验证上述说法,我们改进一下代码,让button直接弹出它们对应的触发函数:
<input id="btnTest1" type="button" value="提交1" onclick="thisTest()" /> <input id="btnTest2" type="button" value="提交2" /> <script type="text/javascript"> function thisTest(){ this.value="提交中"; } var btn=document.getElementById("btnTest1"); alert(btn.onclick); //第一个按钮函数 var btnOther=document.getElementById("btnTest2"); btnOther.onclick=thisTest; alert(btnOther.onclick); //第二个按钮函数 </script> 其弹出的结果是: //第一个按钮 function onclick(){ thisTest() } //第二个按钮 function thisTest(){ this.value="提交中"; }
从上面的结果你一定理解的更透彻了。
By the way,每新建一个函数的副本,程序就会为这个函数副本分配一定的内存。而实际应用中,大多数函数并不一定会被调用,于是这部分内存就被白白浪费了。所以我们通常都这么写:
<input id="btnTest1" type="button" value="提交1" onclick="thisTest(this)" /> <input id="btnTest2" type="button" value="提交2" onclick="thisTest(this)" /> <input id="btnTest3" type="button" value="提交3" onclick="thisTest(this)" /> <input id="btnTest4" type="button" value="提交4" onclick="thisTest(this)" /> <script type="text/javascript"> function thisTest(obj){ alert(obj.value); } </script>
这是因为我们使用了函数引用的方式,程序就只会给函数的本体分配内存,而引用只分配指针。这样写一个函数,调用的地方给它分配一个(指针)引用,这样效率就高很多。当然,如果你觉得这样注册事件不能兼容多种浏览器,可以写下面的注册事件的通用脚本:
//js事件 添加 EventUtil.addEvent(dom元素,事件名称,事件触发的函数名) 移除EventUtil.removeEvent(dom元素,事件名称,事件触发的函数名) var EventUtil = new eventManager(); //js事件通用管理器 dom元素 添加或者移除事件 function eventManager() { //添加事件 //oDomElement:dom元素,如按钮,文本,document等; ****** oEventType:事件名称(如:click,如果是ie浏览器,自动将click转换为onclick);****** oFunc:事件触发的函数名 this.addEvent = function(oDomElement, oEventType, oFunc) { //ie if (oDomElement.attachEvent) { oDomElement.attachEvent("on" + oEventType, oFunc); } //ff,opera,safari等 else if (oDomElement.addEventListener) { oDomElement.addEventListener(oEventType, oFunc, false); } //其他 else { oDomElement["on" + oEventType] = oFunc; } } this.removeEvent = function(oDomElement, oEventType, oFunc) { //ie if (oDomElement.detachEvent) { oDomElement.detachEvent("on" + oEventType, oFunc); } //ff,opera,safari等 else if (oDomElement.removeEventListener) { oDomElement.removeEventListener(oEventType, oFunc, false); } //其他 else { oDomElement["on" + oEventType] = null; } } }
正像注释写的那样,要注册dom元素事件,用EventUtil.addEvent(dom元素,事件名称,事件触发的函数名)即可, 移除时可以这样写:EventUtil.removeEvent(dom元素,事件名称,事件触发的函数名),这是题外话,不说了。
以上就是本文的全部内容,希望通过这篇文章大家更加了解javascript的this关键字,大家共同进步。