JavaScript中this詳解
都說 JavaScript 是一種很靈活的語言,這其實(shí)也可以說它是一個(gè)混亂的語言。它把函數(shù)式編程和面向?qū)ο缶幊挑酆弦黄?,再加上?dòng)態(tài)語言特性,簡(jiǎn)直強(qiáng)大無比(其實(shí)是不能和C++比的,^_^ )。
這里的主題是 this ,不扯遠(yuǎn)了。this 本身原本很簡(jiǎn)單,總是指向類的當(dāng)前實(shí)例,this 不能賦值。這前提是說 this 不能脫離 類/對(duì)象 來說,也就是說 this 是面向?qū)ο笳Z言里常見的一個(gè)關(guān)鍵字。說的極端點(diǎn),如果你編寫的 JS 采用函數(shù)式寫法,而不是面向?qū)ο笫剑闼械拇a里 this 會(huì)少很多,甚至沒有。記住這一點(diǎn),當(dāng)你使用 this 時(shí),你應(yīng)該是在使用對(duì)象/類 方式開發(fā),否則 this 只是函數(shù)調(diào)用時(shí)的副作用。
JS 里的 this
在 function 內(nèi)部被創(chuàng)建
指向調(diào)用時(shí)所在函數(shù)所綁定的對(duì)象(拗口)
this 不能被賦值,但可以被 call/apply 改變
以前用 this 時(shí)經(jīng)常擔(dān)心,不踏實(shí),你不知道它到底指向誰? 這里把它所有用到的地方列出
this 和構(gòu)造器
this 和對(duì)象
this 和函數(shù)
全局環(huán)境的 this
this 和 DOM/事件
this 可以被 call/apply 改變
ES5 中新增的 bind 和 this
ES6 箭頭函數(shù)(arrow function) 和 this
1. this 和構(gòu)造器
this 本身就是類定義時(shí)構(gòu)造器里需要用到的,和構(gòu)造器在一起再自然不過。
/** * 頁簽 * * @class Tab * @param nav {string} 頁簽標(biāo)題的class * @param content {string} 頁面內(nèi)容的class * */ function Tab(nav, content) { this.nav = nav this.content = content } Tab.prototype.getNav = function() { return this.nav; }; Tab.prototype.setNav = function(nav) { this.nav = nav; }; Tab.prototype.add = function() { };
按照 JavaScript 的習(xí)慣, this 應(yīng)該掛屬性/字段,方法都應(yīng)該放在原型上。
2. this 和對(duì)象
JS 中的對(duì)象不用類也可以創(chuàng)建,有人可能奇怪,類是對(duì)象的模板,對(duì)象都是從模板里 copy 出來的,沒有類怎么創(chuàng)建對(duì)象? JS 的確可以,并且你完全可以寫上萬行功能代碼而不用寫一個(gè)類。話說 OOP 里說的是面向?qū)ο缶幊?,也沒說面向類編程,是吧 ^_^ 。
var tab = { nav: '', content: '', getNav: function() { return this.nav; }, setNav: function(n) { this.nav = n; } }
3. this 和函數(shù)
首先,this 和獨(dú)立的函數(shù)放在一起是沒有意義的,前面也提到過 this 應(yīng)該是和面向?qū)ο笙嚓P(guān)的。純粹的函數(shù)只是一個(gè)低級(jí)別的抽象,封裝和復(fù)用。如下
function showMsg() { alert(this.message) } showMsg() // undefined
定義 showMsg,然后以函數(shù)方式調(diào)用,this.message 是 undefined。因此堅(jiān)決杜絕在 純函數(shù)內(nèi)使用 this,但有時(shí)候會(huì)這么寫,調(diào)用方式使用 call/apply
function showMsg() { alert(this.message) } var m1 = { message: '輸入的電話號(hào)碼不正確' } var m2 = { message: '輸入的身份證號(hào)不正確' } showMsg.call(m1) // '輸入的電話號(hào)碼不正確' showMsg.call(m2) // '輸入的身份證號(hào)不正確'
用這種方式可以節(jié)省一些代碼量,比如當(dāng)兩個(gè) 類/對(duì)象 有一共相似的方法時(shí),不必寫兩份,只要定義一個(gè),然后將其綁定在各自的原型和對(duì)象上。這時(shí)候其實(shí)你還是在使用對(duì)象或類(方式1/2),只是間接使用罷了。
4. 全局環(huán)境的 this
前面提到 this 是 “指向調(diào)用時(shí)所在函數(shù)所綁定的對(duì)象”, 這句話拗口但絕對(duì)正確,沒有一個(gè)多余的字。全局環(huán)境中有不同的宿主對(duì)象,瀏覽器環(huán)境中是 window, node 環(huán)境中是 global。這里重點(diǎn)說下瀏覽器環(huán)境中的 this。
瀏覽器環(huán)境中非函數(shù)內(nèi) this 指向 window
alert(window=== this) // true
因此你會(huì)看很很多開源 JS lib 這么寫
(function() {
// ...
})(this);
或這樣寫
(function() {
// ...
}).call(this);
比如 underscore 和 requirejs,大意是把全局變量 window 傳入匿名函數(shù)內(nèi)緩存起來,避免直接訪問。至于為啥要緩存,這跟 JS 作用域鏈有關(guān)系,讀取越外層的標(biāo)識(shí)符性能會(huì)越差。請(qǐng)自行查閱相關(guān)知識(shí),再說就扯遠(yuǎn)了。
瀏覽器中比較坑人,非函數(shù)內(nèi)直接使用 var 聲明的變量默認(rèn)為全局變量,且默認(rèn)掛在 window 上作為屬性。
var andy = '劉德華' alert(andy === window.andy) // true alert(andy === this.andy) // true alert(window.andy === this.andy) // true
因?yàn)檫@個(gè)特性,有些筆試題如
var x = 10; function func() { alert(this.x) } var obj = { x: 20, fn: function() { alert(this.x) } } var fn = obj.fn func() // 10 fn() // 10
沒錯(cuò),最終輸出的都是全局的 10。永遠(yuǎn)記住這一點(diǎn):判斷 this 指向誰,看執(zhí)行時(shí)而非定義時(shí),只要函數(shù)(function)沒有綁定在對(duì)象上調(diào)用,它的 this 就是 window。
5. this 和 DOM/事件
W3C 把 DOM 實(shí)現(xiàn)成了各種節(jié)點(diǎn),節(jié)點(diǎn)嵌套一起形成 DOM tree。節(jié)點(diǎn)有不同類型,如文本節(jié)點(diǎn),元素節(jié)點(diǎn)等10多種。元素節(jié)點(diǎn)又分成了很多,對(duì)寫HTML的人來說便是很熟悉的標(biāo)簽(Tag),如 div,ul,label 等。 看 W3C 的 API 文檔,會(huì)發(fā)現(xiàn)它完全是按照面向?qū)ο蠓绞綄?shí)現(xiàn)的各種 API,有 interface,extends 等。如
看到了吧,這是用 Java 寫的,既然是用面向?qū)ο蠓绞綄?shí)現(xiàn)的API,一定有類/對(duì)象(廢話^_^),有 類/對(duì)象,則一定有 this (別忘了這篇文章的中心主題)。所有的 HTML tag 類命名如 HTMLXXXElement,如
HTMLDivElement
HTMLLabelElement
HTMLInputElement
...
前面說過 this 是指向當(dāng)前類的實(shí)例對(duì)象,對(duì)于這些 tag 類來說,不看其源碼也知它們的很多方法內(nèi)部用到的 this 是指向自己的。 有了這個(gè)結(jié)論,寫HTML和JS時(shí), this 就清晰了很多。
示例A
<!-- this 指向 div --> <div onclick="alert(this)"></div>
示例B
<div id="nav"></div> <script> nav.onclick = function() { alert(this) // 指向div#nav } </script>
示例C
$('#nav').on('click', function() { alert(this) // 指向 nav })
以上三個(gè)示例可以看到,在給元素節(jié)點(diǎn)添加事件的時(shí)候,其響應(yīng)函數(shù)(handler)執(zhí)行時(shí)的 this 都指向 Element 節(jié)點(diǎn)自身。jQuery 也保持了和標(biāo)準(zhǔn)一致,但卻讓人迷惑,按 “this 指向調(diào)用時(shí)所在函數(shù)所綁定的對(duì)象” 這個(gè)定義,jQuery 事件 handler 里的 this,應(yīng)該指向 jQuery 對(duì)象,而非 DOM 節(jié)點(diǎn)。因此你會(huì)發(fā)現(xiàn)在用 jQuery 時(shí),經(jīng)常需要把事件 handler 里的 element 在用 $ 包裹下變成 jQuery 對(duì)象后再去操作。比如
$('#nav').on('click', function() { var $el = $(this) // 再次轉(zhuǎn)為 jQuery 對(duì)象,如果 this 直接為 jQuery 對(duì)象更好 $el.attr('data-x', x) $el.attr('data-x', x) })
有人可能有如下的疑問
<div id="nav" onclick="getId()">ddd</div> <script> function getId() { alert(this.id) } </script>
點(diǎn)擊 div 后,為什么 id 是 undefined,不說是指向的 當(dāng)前元素 div 嗎? 如果記住了前面提到的一句話,就很清楚為啥是 undefined,把這句話再貼出來。
判斷 this 指向誰,看執(zhí)行時(shí)而非定義時(shí),只要函數(shù)(function)沒有綁定在對(duì)象上調(diào)用,它的 this 就是 window
這里函數(shù) getId 調(diào)用時(shí)沒有綁定在任何對(duì)象上,可以理解成這種結(jié)構(gòu)
div.onclick = function() { getId() }
getId 所處匿名函數(shù)里的 this 是 div,但 getId 自身內(nèi)的 this 則不是了。 當(dāng)然 ES5 嚴(yán)格模式下還是有個(gè)坑。
6. this 可以被 call/apply 改變
call/apply 是函數(shù)調(diào)用的另外兩種方式,兩者的第一個(gè)參數(shù)都可以改變函數(shù)的上下文 this。call/apply 是 JS 里動(dòng)態(tài)語言特性的表征。動(dòng)態(tài)語言通俗的定義
程序在運(yùn)行時(shí)可以改變其結(jié)構(gòu),新的函數(shù)可以被引進(jìn),已有的函數(shù)可以被刪除,即程序在運(yùn)行時(shí)可以發(fā)生結(jié)構(gòu)上的變化
通常有以下幾點(diǎn)特征表示它為動(dòng)態(tài)語言
動(dòng)態(tài)的數(shù)據(jù)類型
動(dòng)態(tài)的函數(shù)執(zhí)行
動(dòng)態(tài)的方法重寫
動(dòng)態(tài)語言多從世界第二門語言 LISP 發(fā)展而來,如死去的 SmallTalk/VB,目前還活著的 Perl/Python, 以及還流行的 Ruby/JavaScript。JS 里動(dòng)態(tài)數(shù)據(jù)類型的體現(xiàn)便是弱類型,執(zhí)行的時(shí)候才去分析標(biāo)識(shí)符的類型。函數(shù)動(dòng)態(tài)執(zhí)行體現(xiàn)為 eval,call/aply。方法重寫則體現(xiàn)在原型重寫。不扯遠(yuǎn),這里重點(diǎn)說下 call/apply 對(duì) this 的影響。
var m1 = { message: 'This is A' } var m2 = { message: 'This is B' } function showMsg() { alert(this.message) } showMsg() // undefined showMsg.call(m1) // 'This is A' showMsg.call(m2) // 'This is B'
可以看到單獨(dú)調(diào)用 showMsg 返回的是 undefined,只有將它綁定到具有 message 屬性的對(duì)象上執(zhí)行時(shí)才有意義。發(fā)揮想象力延伸下,如果把一些通用函數(shù)寫好,可以任意綁定在多個(gè)類的原型上,這樣動(dòng)態(tài)的給類添加了一些方法,還節(jié)省了代碼。這是一種強(qiáng)大的功能,也是動(dòng)態(tài)語言的強(qiáng)表現(xiàn)力的體現(xiàn)。
經(jīng)常會(huì)聽到轉(zhuǎn)向 Ruby 或 Python 的人提到“編程的樂趣”,這種樂趣是源自動(dòng)態(tài)語言更接近人的思維(而不是機(jī)器思維),更符合業(yè)務(wù)流程而不是項(xiàng)目實(shí)現(xiàn)流程。同樣一個(gè)功能,動(dòng)態(tài)語言可以用更小的代碼量來實(shí)現(xiàn)。動(dòng)態(tài)語言對(duì)程序員生產(chǎn)力的提高,是其大行其道的主要原因。
性能方面,動(dòng)態(tài)語言沒有太大的優(yōu)勢(shì),但動(dòng)態(tài)語言的理念是:優(yōu)化人的時(shí)間而不是機(jī)器的時(shí)間。提高開發(fā)者的生產(chǎn)力,寧肯犧牲部分的程序性能或者購(gòu)買更高配置的硬件。隨著IT業(yè)的不斷發(fā)展和摩爾定律的作用,硬件相對(duì)于人件一直在貶值,這個(gè)理念便有了合理的現(xiàn)實(shí)基礎(chǔ)。
JS 里的 call/apply 在任何一個(gè)流行的 lib 里都會(huì)用到,但幾乎就是兩個(gè)作用
配合寫類工具實(shí)現(xiàn)OOP,如 mootools, ClassJS, class.js,
修復(fù)DOM事件里的 this,如 jQuery, events.js
關(guān)于 call 和 apply 復(fù)用:利用apply和arguments復(fù)用方法
關(guān)于 call 和 apply 的性能問題參考: 冗余換性能-從Backbone的triggerEvents說開了去
7. ES5 中新增的 bind 和 this
上面 6 里提到 call/apply 在 JS 里體現(xiàn)動(dòng)態(tài)語言特性及動(dòng)態(tài)語言的流行原因,其在 JS 用途如此廣泛。ES5發(fā)布時(shí)將其采納,提了一個(gè)更高級(jí)的方法 bind。
var modal = { message: 'This is A' } function showMsg() { alert(this.message) } var otherShowMsg = showMsg.bind(modal) otherShowMsg() // 'This is A'
因?yàn)槭荅S5才加的,低版本的IE不支持,可以修復(fù)下Function.prototype。bind 只是 call/apply 的高級(jí)版,其它沒什么特殊的。
8. ES6 箭頭函數(shù)(arrow function) 和 this
ES6 在今年的 6月18日 正式發(fā)布(恰京東店慶日同一天,^_^),它帶來的另一種類型的函數(shù) - 箭頭函數(shù)。箭頭函數(shù)的一個(gè)重要特征就是顛覆了上面的一句話,再貼一次
判斷 this 指向誰,看執(zhí)行時(shí)而非定義時(shí),只要函數(shù)(function)沒有綁定在對(duì)象上調(diào)用,它的 this 就是 window
是的,前面一直用這句話來判斷 this 的指向,在箭頭函數(shù)里前面半句就失效了。箭頭函數(shù)的特征就是,定義在哪,this 就指向那。即箭頭函數(shù)定義在一個(gè)對(duì)象里,那箭頭函數(shù)里的 this 就指向該對(duì)象。如下
var book = { author: 'John Resig', init: function() { document.onclick = ev => { alert(this.author) ; // 這里的 this 不是 document 了 } } }; book.init()
對(duì)象 book 里有一個(gè)屬性 author, 有一個(gè) init 方法, 給 document 添加了一個(gè)點(diǎn)擊事件,如果是傳統(tǒng)的函數(shù),我們知道 this 指向應(yīng)該是 document,但箭頭函數(shù)會(huì)指向當(dāng)前對(duì)象 book。
箭頭函數(shù)讓 JS 回歸自然和簡(jiǎn)單,函數(shù)定義在哪它 this 就指向哪,定義在對(duì)象里它指向該對(duì)象,定義在類的原型上,指向該類的實(shí)例,這樣更容易理解。
總結(jié):
函數(shù)的上下文 this 是 JS 里不太好理解的,在于 JS 函數(shù)自身有多種用途。目的是實(shí)現(xiàn)各種語言范型(面向?qū)ο?,函?shù)式,動(dòng)態(tài))。this 本質(zhì)是和面向?qū)ο舐?lián)系的,和寫類,對(duì)象關(guān)聯(lián)一起的, 和“函數(shù)式”沒有關(guān)系的。如果你采用過程式函數(shù)式開發(fā),完全不會(huì)用到一個(gè) this。 但在瀏覽器端開發(fā)時(shí)卻無可避免的會(huì)用到 this,這是因?yàn)闉g覽器對(duì)象模型(DOM)本身采用面向?qū)ο蠓绞介_發(fā),Tag 實(shí)現(xiàn)為一個(gè)個(gè)的類,類的方法自然會(huì)引用類的其它方法,引用方式必然是用 this。當(dāng)你給DOM對(duì)象添加事件時(shí),回調(diào)函數(shù)里引用該對(duì)象就只能用 this 了。
明白了么?
相信看完全文以后,this不再是坑~
- js中的this關(guān)鍵字詳解
- javascript this用法小結(jié)
- JS中的this變量的使用介紹
- javascript中this的四種用法
- 改變javascript函數(shù)內(nèi)部this指針指向的三種方法
- 關(guān)于js里的this關(guān)鍵字的理解
- Javascript this關(guān)鍵字使用分析
- vue項(xiàng)目中在外部js文件中直接調(diào)用vue實(shí)例的方法比如說this
- JS函數(shù)this的用法實(shí)例分析
- Javascript this指針
- JavaScript中的this基本問題實(shí)例小結(jié)
相關(guān)文章
用Javascript做flash做的事..才完成的一個(gè)類.Auntion Action var 0.1
用Javascript做flash做的事..才完成的一個(gè)類.Auntion Action var 0.1...2007-02-02阻止子元素繼承父元素事件具體思路及實(shí)現(xiàn)
想要阻止點(diǎn)擊#p_cont區(qū)域時(shí)觸發(fā)a事件,需要在#p_cont區(qū)域內(nèi)加入阻止事件冒泡的代碼,具體實(shí)現(xiàn)祥看本文,希望對(duì)你有所幫助2013-05-05IE的事件傳遞-event.cancelBubble示例介紹
關(guān)于event.cancelBubble,Bubble就是一個(gè)事件可以從子節(jié)點(diǎn)向父節(jié)點(diǎn)傳遞,下面有個(gè)不錯(cuò)的示例,大家可以感受下2014-01-01js事件驅(qū)動(dòng)機(jī)制 瀏覽器兼容處理方法
下面小編就為大家?guī)硪黄猨s事件驅(qū)動(dòng)機(jī)制 瀏覽器兼容處理方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-07-07Ionic3 UI組件之a(chǎn)utocomplete詳解
這篇文章主要為大家詳細(xì)介紹了Ionic3 UI組件之a(chǎn)utocomplete的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06用javascript獲取textarea中的光標(biāo)位置
Javascript一向以他的靈活隨意而著稱,這也使得它的功能可以非常的強(qiáng)大,而由于沒有比較好的調(diào)試工具,又使得它使用起來困難重重,尤其使對(duì)于一些初學(xué)者,更是感覺到無從下手。今天探討的問題是用javascript獲取textarea中光標(biāo)的位置。2008-05-05