有不少初學的小夥伴在調用函數給對象進行賦值的時候常常會出現一些關於this的錯誤,例如this找不到啊,或者沒報錯卻沒有生效啊之類的問題,即使是一些入門級的同窗在遇到這些問題時,也只是經過不斷的嘗試使用var _this = this
、.call()
等方法去實現效果,最後雖然達到了想要的效果,可是卻並無明白問題所在,也懶得去仔細研究,那麼今天我就來帶你們一塊兒看看js中this的廬山真面目es6
「舒適提示:內容較多,建議點贊收藏後閱讀」數組
❝當一個函數被調用時,會建立一個活動記錄(有時候也稱爲執行上下文)。這個記錄會包 含函數在哪裏被調用(調用棧)、函數的調用方法、傳入的參數等信息。 this 就是記錄的 其中一個屬性,會在函數執行的過程當中用到。markdown
——《你不知道的JavaScript上卷》app
❞
上面是《你不知道的JavaScript》一書中對於this的解釋,可能看起來很難理解,但咱們不妨只看第一句話就能夠了,「當一個函數被調用時,會建立一個活動記錄」,這句話也就說明了,函數在定義時是沒有this的,this只在函數被調用時產生,咱們也暫且能夠粗略的理解爲,this就是表明調用這個函數的對象,這句話在大多數狀況下是行得通的,那什麼狀況下不能這麼理解呢,下面咱們就來看看this的綁定規則函數
默認綁定就是直接調用函數,這也是最多見的一種狀況,咱們能夠看看下面一段代碼oop
function foo() {
var a = 2 foo.a = 3 console.log(this) // Window console.log(this.a) // 4 } var a = 4 foo() 複製代碼
*你們能夠直接複製上述代碼,而後打開控制欄,粘貼到console裏運行一下ui
上述的結果能夠看到,咱們直接運行函數時候,函數裏的this是直接綁定到Window上的,其實按我我的的理解是,全部的全局變量都是默認綁定到Window上的,全局變量a,用Window.a也同樣能夠獲取到,因此此處的直接調用函數 `foo()`咱們也能夠理解爲Window.foo(),因此默認綁定依然遵循咱們上述得出的結論:**this就是表明調用這個函數的對象**注意,在嚴格模式下,直接調用函數,this並不會被綁定到window上,而是被綁定到undefined上。this
隱式綁定咱們能夠簡單的理解爲,當函數被調用時被一個對象所包裹或擁有,或者能夠理解爲,該函數定義在某對象的一個屬性下面或被對象的的一個屬性所引用。看文字比較枯澀,直接看代碼spa
function foo() {
var a = 2 foo.a = 3 console.log(this) console.log(this.a) } var a = 4 var obj = { a: 5, foo: foo, // 此處函數foo()被引用給了對象obj的foo屬性 } obj.foo() // 打印結果爲 // {a: 5, foo: ƒ} // 5 複製代碼
像上述方式,當函數做爲對象的一個屬性被調用時,咱們則能夠稱之爲隱形綁定,這種狀況也一樣適應於咱們上面所說的,誰調用,this就是誰的說法。我的理解,這之因此叫作隱式綁定就是由於咱們沒有給this指定綁定對象,而this自動綁定到了所屬的上下文對象中[也就是所處的對象中]。code
對象屬性引用鏈中只有最頂層或者說最後一層會影響調用位置。舉例來講:
function foo() {
console.log(this.a) } var obj2 = { a: 42, foo: foo, } var obj1 = { a: 2, obj2: obj2, } obj1.obj2.foo() // 42 複製代碼
一個最多見的 this 綁定問題就是被隱式綁定的函數會丟失綁定對象,也就是說它會應用默認綁定,從而把 this 綁定到全局對象或者 undefined 上,取決因而否是嚴格模式。
function foo() {
console.log(this.a) } var obj = { a: 2, foo: foo, } var bar = obj.foo // 函數別名! var a = 'oops, global' // a 是全局對象的屬性 bar() // "oops, global" 複製代碼
雖然 bar 是 obj.foo 的一個引用,可是實際上,它引用的是 foo 函數自己,所以此時的 bar() 實際上是一個不帶任何修飾的函數調用,所以應用了默認綁定。若是不能理解這句話的話,咱們能夠直接這麼理解
var bar = boj.foo = foo var() 至關於 foo() 複製代碼
另外一種很是類似的狀況可能會令咱們感到迷惑,可是道理是同樣的,代碼以下
function foo() {
console.log(this.a) } var obj = { a: 2, foo: foo, } var a = 'oops, global' // a 是全局對象的屬性 setTimeout(obj.foo, 100) // "oops, global" 複製代碼
JavaScript 環境中內置的 setTimeout() 函數實現和下面的僞代碼相似:
function setTimeout(fn, delay) {
// 等待 delay 毫秒 fn() // <-- 調用位置! } 複製代碼
這樣咱們就好理解多了,在這裏obj.foo也就是foo做爲回調函數被傳入,執行的時候依然執行的是foo(),一樣適用於默認綁定的原則
咱們經常使用的.apply()
、.call()
、.bind()
這種改變函數this指向的方法,由於咱們能夠直接指定 this 的綁定對象,所以咱們稱之爲顯式綁定。 這三個函數都用來改變this的指向,下面咱們就簡單說一下這三個函數的用法和區別
function foo() {
console.log('我叫:' + this.name + ',我今年' + this.age) } var obj = { name: '小豪', age: '18', } foo.apply(obj) // 我叫:小豪,我今年18 foo.call(obj) // 我叫:小豪,我今年18 foo.bind(obj)() // 我叫:小豪,我今年18 複製代碼
能夠看出,三個函數接受的第一個參數都爲要綁定的對象,用法上並沒有區別,只不過.bind()方法返回的是一個函數,咱們須要加一個括號去調用它。
function foo(from, to) {
console.log('我叫:' + this.name + ',我今年' + this.age + ',來自' + from + ',要去' + to) } var obj = { name: '小豪', age: '18', } foo.apply(obj, ['河北','北京']) // 我叫:小豪,我今年18,來自河北,要去北京 foo.call(obj, '河北' , '北京') // 我叫:小豪,我今年18,來自河北,要去北京 foo.bind(obj, '河北' , '北京')() // 我叫:小豪,我今年18,來自河北,要去北京 複製代碼
以上能夠看出,call和bind的傳參方式是同樣的,只要用逗號隔開就能夠了,而apply則必須把全部的參數都放到一個數組裏。
「注意,此類函數不可疊加使用,如foo.call(obj1).call(obj2)是錯誤的行爲,foo.call(obj1).apply(obj2)也是」
function foo(a) {
this.a = a } var bar = new foo(2) console.log(bar.a) 複製代碼
使用new進行的this綁定將始終被綁定到建立時候賦值的對象bar上,後續也沒法再修改他的this綁定。
關於用new調用函數後是如何執行的,《你不知道的JavaScript》一書中是這麼說的
❝使用 new 來調用函數,或者說發生構造函數調用時,會自動執行下面的操做。
❞
- 建立(或者說構造)一個全新的對象。
- 這個新對象會被執行 [[ 原型 ]] 鏈接。
- 這個新對象會綁定到函數調用的 this 。
- 若是函數沒有返回其餘對象,那麼 new 表達式中的函數調用會自動返回這個新對象。
咱們如今關心的是第 1 步、第 3 步、第 4 步,因此暫時跳過第 2 步,因爲構造函數我也不是太懂,這裏先不提,感興趣的同窗能夠本身去搜索,咱們只要知道new返回的是一個對象就能夠了。不信打印一下代碼
function foo(a) {
this.a = a } var bar = new foo(2) console.log(typeOf(bar)) // object 複製代碼
這裏咱們重點關注一下第四句話:「若是函數沒有返回其餘對象,那麼 new 表達式中的函數調用會自動返回這個新對象」,正常的咱們在這一小節開頭已經看過了,那下面咱們就來看看不正常的:當函數返回了其餘對象
function foo(a) {
this.a = a return { b: 5 } } var bar = new foo(2) console.log(bar) // {b: 5} console.log(bar.a) // undefined 複製代碼
看,這時候bar就等於函數裏新返回的對象了,因此說,若是你在函數裏返回了新的對象,那麼 new 表達式中的函數調用會自動返回這個咱們手動建立的新對象,不然將返回自動建立的那個對象,注意當函數返回了非對象和undefind、null時,bar依然等於咱們建立的那個實例
毫無疑問,確定是默認綁定的優先級最低,由於它會在你不進行其餘全部綁定的時候纔會選擇默認綁定。 其次的優先級爲
❝new > 顯示綁定(apply、call、bind) > 隱式綁定(對象調用)> 默認綁定
❞
咱們以前介紹的四條規則已經能夠包含全部正常的函數。可是 ES6 中介紹了一種沒法使用這些規則的特殊函數類型:箭頭函數。
箭頭函數並非使用 function 關鍵字定義的,而是使用被稱爲「胖箭頭」的操做符 => 定 義的。箭頭函數不使用 this 的四種標準規則,而是根據外層(函數或者全局)做用域來決 定 this 。
function foo() {
// 返回一個箭頭函數 return () => { //this 繼承自 foo() console.log(this.a) } } var obj1 = { a: 2, } var obj2 = { a: 3, } var bar = foo.call(obj1) bar.call(obj2) // 2, 不是 3 ! 複製代碼
以上代碼能夠看出,箭頭函數的this指向取決於它所在做用域的this,而且箭頭函數沒法被改變this指向,不管是用call仍是new。
其實箭頭函數和咱們以前使用的一種方法幾乎同樣,那就是用一個變量暫存this
function foo() {
var self = this // lexical capture of this setTimeout(function () { console.log(self.a) }, 100) } var obj = { a: 2, } foo.call(obj) // 2 複製代碼
看,在這種狀況下,函數裏的this指向也是取決於它所在的做用域,不一樣的是,箭頭函數的this指向取決於它所在的最近的做用域,而用變量暫存this的方法可讓它指向任意做用域的this(前提是能夠訪問到那個變量)
因此,在不考慮es6的兼容問題或者已經作好兼容處理的狀況下,咱們不妨使用箭頭函數代替咱們之前var _this = this
的寫法,這樣寫既簡單也更美觀。
function foo() {
var self = this // lexical capture of this setTimeout(function () { console.log(self.a) }, 100) } var obj = { a: 2, } foo.call(obj) // 2 // 改寫爲 function foo() { setTimeout(() => { console.log(this.a) }, 100) } var obj = { a: 2, } foo.call(obj) // 2 複製代碼
不過,在《你不知道的JavaScript》一書中,編者建議,若是你在編寫代碼的過程當中有使用上面四種綁定規則的話,須要儘可能避免使用var _this = this
和箭頭函數,由於在同一個函數或者同一個程序中混 合使用這兩種風格一般會使代碼更難維護,而且可能也會更難編寫
❝做者基礎較爲薄弱,文章爲本人蔘考相關技術博客和技術書籍所總結,因此可能並不會徹底準確,有些地方爲了通俗易懂也沒有使用專業詞彙,可能會出現用詞錯誤的狀況,但願各位同仁能夠多加指正,共同進步,謝謝。
❞
❝本文大部份內容參考自書籍《你不知道的JavaScript》上卷,若是想了解完整內容,請閱讀書籍。