第一次翻譯,翻譯的很差,已經再盡全力s去翻譯了,若是哪裏看不明點,請出門左轉下邊原文地址javascript
英文原文點擊這裏java
javascript中最使人困惑的東西就是this關鍵字,它在每一個函數做用域中都會自動定義的一個特殊的標識符,可是,它折磨着每個javascript開發者,哪怕是有經驗的。git
任何科技的充分進步,都和魔法沒什麼區別程序員
javascript的this機制事實上並非先進的,可是開發者常常按照他們本身的觀點把this解釋的複雜和混亂。毫無疑問這是由於缺乏深入的理解。github
既然this機制讓開發者甚至是經驗豐富的程序員感到困惑。那爲何會是有用的,thia弊大於利嗎?在此以前,咱們先跳過how的問題,去審查一下why的問題。ide
讓咱們來講明使用this的動機和實用性。函數
function identify() { return this.name.toUpperCase(); } function speak() { var greeting = "Hello, I'm " + identify.call( this ); console.log( greeting ); } var me = { name: "Kyle" }; var you = { name: "Reader" }; identify.call( me ); // KYLE identify.call( you ); // READER speak.call( me ); // Hello, I'm KYLE speak.call( you ); // Hello, I'm READER
若是看不明白這個代碼塊,不要着急!咱們很快會解釋,把這些問題放到一邊,咱們先來看關於why的問題。學習
這個代碼塊容許identify()和speak()兩個函數分別被兩個對象me和you重用,而不是爲每一個對象單獨寫一個版本的函數。this
經過依賴this。你能夠用一種更明確的方式在環境對象中使用兩個方法。prototype
function identify(context) { return context.name.toUpperCase(); } function speak(context) { var greeting = "Hello, I'm " + identify( context ); console.log( greeting ); } identify( you ); // READER speak( me ); // Hello, I'm KYLE
this機制提供一種更加優雅的方式去傳遞一個對象引用,從而實現更加清楚的API設計和更簡單的重用。
你用的模式越複雜,你就越能清晰的看到,傳遞上下文的時候一個顯示的參數要比傳遞一個this上下文更加麻煩。當咱們查看對象和原型的時候,你將能看到一個可以自動引用正確的函數集合上下文對象的實用性。
咱們很快會開始解釋this其實是如何工做的,可是首先要糾正一下錯誤的觀念。
第一個常常被解釋成困惑的是this指代函數自己,至少這卻是一個合理的解釋。
爲何你會須要在函數內部引用函數自身,最可能的緣由是遞歸(在函數內部調用函數自身)或者事件處理的時候當第一次調用時解綁事件。
開發者最新的js機制是把函數當成一個對象同樣來引用(js裏全部的函數都是對象),這樣會致使須要在函數互相調用之間存儲狀態(屬性值)。固然這種機制可行的,也有一些有限的用處。這本書剩下的部分將會闡述許多其餘的模式,以便更好的存儲函數對象以外的狀態。
可是等一會,咱們將會探索一種模式,來講明this是如何不讓函數得到自身的引用,就像咱們以前假設的同樣。
考慮下邊的代碼,咱們嘗試去跟蹤一下foo函數被調用了多少次。
function foo(num) { console.log( "foo: " + num ); // keep track of how many times `foo` is called this.count++; } foo.count = 0; var i; for (i=0; i<10; i++) { if (i > 5) { foo( i ); } } // foo: 6 // foo: 7 // foo: 8 // foo: 9 // how many times was `foo` called? console.log( foo.count ); // 0 -- WTF?
foo.count依然是0,即便通過了四次console以後咱們能清晰的看到foo事實上調用了四次,這個讓人失望的源頭在於對this是什麼的解釋太字面。
當代碼執行到foo.count = 0的時候,確實,它給foo函數對象添加了一個屬性count,可是this.count只是在函數內部引用,this並非指向全部其餘的函數對象,即便屬性名稱相同,由於this所在的對象不同,因此困惑就產生了。
一個有責任的開發者應該問到這一點:若是我增長一個count屬性可是這個屬性並非我想要的,那我是否增長了。事實上,若是開發者更深的挖掘,她會發現她建立了一個變量count,這個變量當前的值是NAN,一旦她注意到這個奇怪的結果,她將有一系列的疑問,全局對象是什麼,爲何這個值是NAN而不是數值。(在foo中打印count,會顯示出NAN)。
有責任的開發者不該該在這一點中止而是應當深刻挖掘爲何這個引用的表現沒有想預想的那樣,去回答這個棘手可是很重要的問題。許多開發者簡單的避免這個問題,而且採起一些其餘的解決辦法,好比建立另外一個對象去存儲count這個屬性:
function foo(num) { console.log( "foo: " + num ); // keep track of how many times `foo` is called data.count++; } var data = { count: 0 }; var i; for (i=0; i<10; i++) { if (i > 5) { foo( i ); } } // foo: 6 // foo: 7 // foo: 8 // foo: 9 // how many times was `foo` called? console.log( data.count ); // 4
雖然這個確實解決了問題,可是不幸的是它忽略了真正的問題,不能理解this究竟是什麼以及她是如何工做的,而是回到了一個更加熟悉的溫馨環境,詞法做用域(靜態做用域)。
詞法做用域是一個完美而有用的機制,我不會以任何方式輕視它,可是,不斷的猜想如何使用this,可是一般會帶來錯誤,退回到詞法做用域去解決問題並非一個好緣由。
爲了在一個函數對象引用自身,this有着顯而易見的不足,你一般須要經過指向詞法標識符(變量)去引用函數對象。
function foo() { foo.count = 4; // `foo` refers to itself } setTimeout( function(){ // anonymous function (no name), cannot // refer to itself }, 10 );
在第一個方法中,稱爲命名函數,foo是一個引用,能夠被用來在函數內部引用函數自己。
可是第二個例子,這個沒有名稱標識的函數是經過setTimeout執行回調(因此叫匿名函數),因此,這個時候沒有合適的方法經過函數名引用函數對象自身。
上邊是舊的用法,如今已經棄用,一個函數中的arguments.callee引用指向當前執行的函數的函數對象。this引用是從匿名函數內部引用自身的惟一方法,不過,最好的方法是避免使用匿名函數,至少在那些須要引用自身的時候,使用命名函數或者表達式。而arguments.callee已經被棄用,不建議使用。
因此,另一個解決方法解決咱們運行的例子是咱們在每一個地方用foo這個標識符做爲函數對象的引用,而不是this。
function foo(num) { console.log( "foo: " + num ); // keep track of how many times `foo` is called foo.count++; } foo.count = 0; var i; for (i=0; i<10; i++) { if (i > 5) { foo( i ); } } // foo: 6 // foo: 7 // foo: 8 // foo: 9 // how many times was `foo` called? console.log( foo.count ); // 4
然而這個方法依然側重於this的實際理解,而且依賴於foo的詞法做用域中的變量。
還有另一種方法更關注this在foo函數對象中實際指向的問題。
function foo(num) { console.log( "foo: " + num ); // keep track of how many times `foo` is called // Note: `this` IS actually `foo` now, based on // how `foo` is called (see below) this.count++; } foo.count = 0; var i; for (i=0; i<10; i++) { if (i > 5) { // using `call(..)`, we ensure the `this` // points at the function object (`foo`) itself foo.call( foo, i ); } } // foo: 6 // foo: 7 // foo: 8 // foo: 9 // how many times was `foo` called? console.log( foo.count ); // 4
避免用this,咱們擁抱這種方法,咱們將會稍微解釋一下這個技術是如何更完整的工做的,因此,若是你仍然有一點困惑,彆着急。
第二個常見的錯誤觀點是認爲this的意思是以某種形式指代函數做用域,這是一個讓人困惑的問題,由於在必定意義上這是有道理的,可是另外一方面,這是很讓人誤導的。
要明確的是,在任何狀況下,this並非指代函數的詞法做用域,在內部,做用域是一個相似能夠訪問每一個標識符屬性的對象,可是這個做用域對象在js代碼中是不能訪問的,他是引擎內部執行的一部分。
下邊代碼嘗試去跨越代碼的邊界去使用this隱式的指向函數做用域。
function foo() { var a = 2; this.bar(); } function bar() { console.log( this.a ); } foo(); //undefined
這個代碼塊裏有不止一個錯誤,彷佛它可能得出想要的結果,這個你看到的代碼。。。(原文這裏嘲諷了這塊代碼)。
首先,你想經過this.bar()來引用bar方法,當運行起來無疑會出事故,咱們要儘快解釋爲什麼報錯,調用bar()最天然的方式是省略this,只對標識符進行詞法引用。
然而,開發者嘗試使用this的目的是在foo和bar兩個函數之間創造一個橋樑,是的bar能夠訪問到foo內部做用域中的變量,可是並無這樣的橋樑,你不能用this引用去查找詞法做用域下的一些東西,這是不可能的。
每當你感受你想嘗試把慈父做用域的查找和this混合的時候,記得,這樣的橋是不存在的。
拋開那些不正確的解釋,如今咱們把注意力,轉移到this機制是如何工做的。
咱們以前說過,this是運行的時候綁定而不是聲明的時候綁定,它是基於函數調用狀況下的上下文,this綁定和函數生命的位置沒有關係,而是與函數的調用方式有關係。
當一個函數被調用,將會建立一個激活記錄,也就是所謂的執行上下文。該記錄包含了函數調用的位置信息(堆棧),以及函數是如何被調用的,還有參數是如何傳遞的等等,這個記錄的其中一個屬性是this引用,將會在函數執行的期間被使用。
在下一章,咱們要學習去找一個函數的調用點去肯定在函數執行的時候是如何綁定this的。
this綁定對於沒有花時間去搞懂這個機制具體如何工做的js開發者來講是一個恆定不斷的困難。來自stackoverfolw的回答者的猜想,試錯和盲目的複製粘貼並非利用這種機制的有效方法。
學習this,首先要去了解this不是什麼,儘管任何的假設或者誤解均可能致使你。。。this既不是函數自己的引用,也不是函數詞法做用域的引用。
this其實是函數調用時進行的綁定,它的引用綁定徹底由函數的調用位置所決定。