this關鍵字在c ,java中都提供了這個關鍵字,在剛開始學習時覺得有難度,但是只要理解了,用起來就方便多了,下面透過本篇文章給大家詳解js裡this關鍵字的理解。
關於this,是很多前端面試必考的題目,有時候在網路上看到這些題目,自己試了一下,額,還真的錯了!在實際開發中,也會遇到this 的問題(雖然有些類別庫會幫我們處理),例如在使用一些框架的時候,例如:knockout,有時候不明白為什麼不直接使用this,而要把this 作為參數傳入。
接下來你談談我對它的理解,也作為一個筆記,方便以後參閱。有不對的地方,歡迎指出批評。
1. 不像C#,this一定是指向目前物件。
js的this指向是不確定的,也就是說是可以動態改變的。 call/apply 就是用來改變this指向的函數,這樣設計可以讓程式碼更有彈性,多用性更高。
2. this 一般情況下,都是指向函數的擁有者。
這一點很重要!這一點很重要!這一點很重要!
這也是一道常見的面試題,如下碼:
<script type="text/javascript"> var number = 1; var obj = { number: 2, showNumber: function(){ this.number = 3; (function(){ console.log(this.number); })(); console.log(this.number); } }; obj.showNumber(); </script>
由於showNumber方法的擁有者是obj,所以this.number=3; this 指向的就是 obj 的屬性 number。
同理,第二個 console.log 印刷的也是屬性 number。
為什麼第二點說一般情況下this都是指向函數的擁有者,因為有特殊情況。函數自執行就是特殊情況,在函數自執行裡,this 指向的是:window。所以第一個 console.log 印製的是 window 的屬性 number。
所以再加一點:
3. 在函數自執行裡,this 指向的是 window 物件。
擴展,關於this,還有一個地方比較讓人模糊的是在 dom 事件裡,通常有如下3種情況:
如下:
1. 使用標籤屬性註冊事件,此時this 指向的是 window 物件。
<input id="test" type="button" value="按钮" onClick="test()"/> function test(){alert(this)}
2. 對於1,要讓 this 指向 input,可以將 this 作為參數傳遞。
3. 使用 addEventListener 等註冊。此時this 也是指向 input。
document.getElementById("test").addEventListener("click",test);
在物件導向程式語言中,對於this關鍵字我們是非常熟悉的。例如C 、C#和Java等都提供了這個關鍵字,雖然在開始學習的時候覺得比較難,但只要理解了,用起來是非常方便和意義確定的。 JavaScript也提供了這個this關鍵字,不過用起來就比經典OO語言中要"混亂"的多了。
下面就來看看,在JavaScript中各種this的使用方法有什麼混亂之處?
1.在HTML元素事件屬性中inline方式使用this關鍵字:
// 可以在里面使用this ">division element // 可以在里面使用this ">division element
我們一般比較常用的方法是在此使用:javascirpt: EventHandler(this),這樣的形式。不過這裡其實可以寫任何合法的JavaScript語句,要是高興在此定義個類別也可以(不過將會是個內部類別)。這裡的原理是腳本引擎產生了一個div實例物件的匿名成員方法,而onclick指向這個方法。
2、用DOM方式在事件處理函數中使用this關鍵字:
division element var div = document.getElementById('elmtDiv'); div.attachEvent('onclick', EventHandler); function EventHandler() { // 在此使用this } // --> division element var div = document.getElementById('elmtDiv'); div.attachEvent('onclick', EventHandler); function EventHandler() { // 在此使用this } // -->
這時的EventHandler()方法中的this關鍵字,指示的物件是IE的window物件。這是因為EventHandler只是一個普通的函數,而對於attachEvent後,腳本引擎對它的呼叫和div物件本身沒有任何的關係。同時你可以再看看EventHandler的caller屬性,它是等於null的。如果我們要在這個方法中獲得div物件引用,應該使用:this.event.srcElement。
3、用DHTML方式在事件處理函數中使用this關鍵字:
division element lt;mce:script language="javascript"> var div = document.getElementById('elmtDiv'); div.onclick = function() { // 在此使用this }; / --> division element var div = document.getElementById('elmtDiv'); div.onclick = function() { // 在此使用this }; // -->
這裡的this關鍵字指示的內容是div元素物件實例,在腳本中使用DHTML方式直接為div.onclick賦值一個EventHandler的方法,等於為div物件實例新增一個成員方法。這種方式和第一種方法的差異是,第一種方法是使用HTML方式,而這裡是DHTML方式,後者腳本解析引擎不會再產生匿名方法。
4、類別定義中使用this關鍵字:
function JSClass() { var myName = 'jsclass'; this.m_Name = 'JSClass'; } JSClass.prototype.ToString = function() { alert(myName + ', ' + this.m_Name); }; var jc = new JSClass(); jc.ToString(); function JSClass() { var myName = 'jsclass'; this.m_Name = 'JSClass'; } JSClass.prototype.ToString = function() { alert(myName + ', ' + this.m_Name); }; var jc = new JSClass(); jc.ToString();
这是JavaScript模拟类定义中对this的使用,这个和其它的OO语言中的情况非常的相识。但是这里要求成员属性和方法必须使用this关键字来引用,运行上面的程序会被告知myName未定义。
5、为脚本引擎内部对象添加原形方法中的this关键字:
function.prototype.GetName = function() { var fnName = this.toString(); fnName = fnName.substr(0, fnName.indexOf('(')); fnName = fnName.replace(/^function/, ''); return fnName.replace(/(^\s+)|(\s+$)/g, ''); } function foo(){} alert(foo.GetName()); function.prototype.GetName = function() { var fnName = this.toString(); fnName = fnName.substr(0, fnName.indexOf('(')); fnName = fnName.replace(/^function/, ''); return fnName.replace(/(^\s+)|(\s+$)/g, ''); } function foo(){} alert(foo.GetName());
这里的this指代的是被添加原形的类的实例,和4中类定义有些相似,没有什么太特别的地方。
6、结合2&4,说一个比较迷惑的this关键字使用:
view plaincopy to clipboardprint? function JSClass() { this.m_Text = 'division element'; this.m_Element = document.createElement('DIV'); this.m_Element.innerHTML = this.m_Text; this.m_Element.attachEvent('onclick', this.ToString); } JSClass.prototype.Render = function() { document.body.appendChild(this.m_Element); } JSClass.prototype.ToString = function() { alert(this.m_Text); }; var jc = new JSClass(); jc.Render(); jc.ToString(); function JSClass() { this.m_Text = 'division element'; this.m_Element = document.createElement('DIV'); this.m_Element.innerHTML = this.m_Text; this.m_Element.attachEvent('onclick', this.ToString); } JSClass.prototype.Render = function() { document.body.appendChild(this.m_Element); } JSClass.prototype.ToString = function() { alert(this.m_Text); }; var jc = new JSClass(); jc.Render(); jc.ToString();
我就说说结果,页面运行后会显示:"division element",确定后点击文字"division element",将会显示:"undefined"。
7、CSS的expression表达式中使用this关键字:
height: expression(this.parentElement.height);"> division element height: expression(this.parentElement.height);"> division element
这里的this看作和1中的一样就可以了,它也是指代div元素对象实例本身。
8、函数中的内部函数中使用this关键字:
view plaincopy to clipboardprint? function OuterFoo() { this.Name = 'Outer Name'; function InnerFoo() { var Name = 'Inner Name'; alert(Name + ', ' + this.Name); } return InnerFoo; } OuterFoo()(); function OuterFoo() { this.Name = 'Outer Name'; function InnerFoo() { var Name = 'Inner Name'; alert(Name + ', ' + this.Name); } return InnerFoo; } OuterFoo()();
运行结果显示是:"Inner Name, Outer Name"。按我们在2中的讲解,这里的结果如果是"Inner Name, undefined"似乎更合理些吧?但是正确的结果确实是前者,这是由于JavaScript变量作用域的问题决定的,详细了解推荐参看"原来JScript中的关键字'var'还是有文章的"一文及回复。
归纳起来,JavaScript中的this用法有以下3种(详细用法参原文):
1.在HTML元素事件属性 或 CSS的expression表达式 中inline方式使用this关键字——对应原文的1、7
2.在事件处理函数中使用this关键字——对应原文的2、3
其中可分为两种方式
(1)DOM方式——此种方式的结果是this指向窗口(window)对象
(2)DHTML方式——此种方式的结果是this指向div元素对象实例
3.在类定义中使用this关键字并在其 内部函数 或 成员函数(主要是prototype产生)中使用——对应原文的4、5、8
需要说明的是,在函数也是个对象,因此需要区分 变量定义 和 成员变量定义,如下:
view plaincopy to clipboardprint? var variableName; //变量定义 //作用域:函数定义范围内 //使用方法:直接使用variableName this.varName; //成员变量定义 //作用域:函数对象定义范围内及其成员函数中 //使用方法:this.varName var variableName; //变量定义 //作用域:函数定义范围内 //使用方法:直接使用variableName this.varName; //成员变量定义 //作用域:函数对象定义范围内及其成员函数中 //使用方法:this.varName
以上归纳出的三类this的使用方法中,第一种比较容易理解,这里对原文中第6点提到的程序进行了测试和改进如下,以说明上述后两种使用方法:
view plaincopy to clipboardprint? function JSClass() { var varText = "func variable!"; //函数中的普通变量 this.m_Text = 'func member!'; //函数类的成员变量 this.m_Element = document.createElement('DIV'); //成员变量,创建一个div对象 this.m_Element.innerHTML = varText; //使用函数的普通变量 this.m_Element.attachEvent('onclick', this.ToString); //给这个对象的事件连上处理函数 this.newElement = document.createElement('DIV'); this.newElement.innerHTML = "new element"; this.newElement.m_Text = "new element text!"; //给创建的对象建个成员 this.newElement.onclick = function() { alert(this.m_Text); //指向div对象的成员 }; } JSClass.prototype.Render = function() { document.body.appendChild(this.m_Element); //把div对象挂在窗口上 document.body.appendChild(this.newElement); } JSClass.prototype.ToString = function() { alert(this.m_Text); //指向窗口(window)对象 }; function initialize(){ var jc = new JSClass(); jc.Render(); jc.ToString(); //里面的this指向JSClass类的实例,里面有m_Text成员 } // --> initialize(); // --> function JSClass() { var varText = "func variable!"; //函数中的普通变量 this.m_Text = 'func member!'; //函数类的成员变量 this.m_Element = document.createElement('DIV'); //成员变量,创建一个div对象 this.m_Element.innerHTML = varText; //使用函数的普通变量 this.m_Element.attachEvent('onclick', this.ToString); //给这个对象的事件连上处理函数 this.newElement = document.createElement('DIV'); this.newElement.innerHTML = "new element"; this.newElement.m_Text = "new element text!"; //给创建的对象建个成员 this.newElement.onclick = function() { alert(this.m_Text); //指向div对象的成员 }; } JSClass.prototype.Render = function() { document.body.appendChild(this.m_Element); //把div对象挂在窗口上 document.body.appendChild(this.newElement); } JSClass.prototype.ToString = function() { alert(this.m_Text); //指向窗口(window)对象 }; function initialize(){ var jc = new JSClass(); jc.Render(); jc.ToString(); //里面的this指向JSClass类的实例,里面有m_Text成员 } // --> initialize(); // -->
上面的代码执行结果是:
页面加载时,弹出对话框,输出func member!
页面上显示
func variable! new element
单击func variable时,弹出对话框,显示undefined
——因为这时toString函数里的this指针指向window
单击new element时,弹出对话框显示new element text!
——因为这时toString函数里的this指针指向div元素,而该元素已经定义了m_Text成员(this.newElement.m_Text = "new element text!")