难缠的this
在深入了解JavaScript中this關鍵字之前,有必要先退一步,看一下為什么this關鍵字很重要。this允許復用函數時使用不同的上下文。換句話說,this關鍵字允許在調用函數或方法時決定哪個對象才是焦點。之后討論的所有東西都是基于這個理念。我們希望能夠在不同的上下文或在不同的對象中復用函數或方法。
我們關注的第一件事是如何判斷this關鍵字的引用。當你試圖回答這個問題時,需要問自己的第一個也是最重要的問題是“這個函數在哪里被調用”。判斷this引用什么的唯一方法就是看使用this關鍵字的這個方法在哪里被調用。
一 隱式綁定
請記住,判斷this指向要看使用this關鍵字的這個方法在哪里被調用。假如有下面一個對象
const obj = {title: 'test',fn(): {console.dir(this.title)} } 復制代碼如果想調用fn方法,就要
obj.fn() 復制代碼這就把我們帶到隱式綁定規則的主要關鍵點。為了判斷 this 關鍵字的引用,函數被調用時先看一看點號左側。如果有“點”就查看點左側的對象,這個對象就是 this 的引用。 在上面的例子中,obj 在“點號左側”意味著 this 引用了 obj 對象。所以就好像 在 fn 方法的內部 JavaScript 解釋器把 this 變成了 obj。 再看如下例子
const obj = {title: 'test',fn() {console.dir(this.title)},sub: {title: 'test2',fn() {console.dir(this.title)}} } obj.fn() // test obj.sub.fn() // test2 復制代碼每當判斷 this 的引用時,都需要查看調用過程,并確認“點的左側”是什么。第一個調用,obj 在點左側意味著 this 將引用 obj。第二次調用中,sub 在點的左側意味著 this 引用 sub。 所以大多數情況下,檢查使用this方法的點的左側是什么。如果沒有點呢,繼續往下看。
二 顯示綁定
如果fn只是一個獨立的函數,如下
fn() {console.dir(this.title) } const obj = {title: 'test' } 復制代碼為了判斷this的引用必須先查看這個函數的調用位置,那怎樣才能讓fn調用的時候將this指向this?我們并不能再像上面那樣簡單的使用obj.fn(),因為obj并沒有fn方法。但是在js中,每個函數都有一個方法call,正好解決這個問題。
"call" 是每個函數都有的方法,它允許在調用函數時為函數指定上下文
所以,可以用下面的方式調用fn方法
fn.call(obj) 復制代碼call是每個函數都有的屬性,并且傳遞給它的第一個參數會作為函數調用時的上下文。也就是說,this會指向傳遞給call的第一個參數
這就是顯示綁定的基礎,因為我們明確的使用call指定了this的引用。 現在將fn改動一下
fn(name1, name2){console.dir(`${this.title} is ${name1} and ${name2}`) } 復制代碼此時使用call方法就要如下
fn.call(obj, '張三', '李四') 復制代碼使用call方法需要將參數一個一個的傳遞進去,參數過多,就會越麻煩。此時apply方法就可以解決。
apply和call本質相同,但不是一個個傳遞參數,可以用數組傳參且apply會在函數中為你自動展開。
const arr = ['張三', '李四'] fn.apply(obj, arr) 復制代碼除了call和apply可以顯示綁定this外,還有bind方法也可以
bind和call調用方式完全相同,不同的是bind不會立即調用函數,而是返回一個能以后調用的新函數
const newFn = fn.bind(obj, '張三', '李四') newFn() 復制代碼傳入的不是對象: 如果傳入了一個原始值(字符串,布爾類型,數字類型),來當做this的綁定對象,這個原始值轉換成它的對象形式。 如果把null或者undefined作為this的綁定對象傳入call/apply/bind,這些值會在調用時被忽略,實際應用的是默認綁定規則
三 new 綁定
function fn(a) {this.a = a } const bar = new fn(2) console.log(bar.a) // 2 復制代碼用new操作符創建對象時會發生如下步驟:
所以,new fn(2)就相當于
const newObj = new Object() newObj.a = a 復制代碼還有一種特殊情況,就是當構造函數通過 "return" 返回的是一個對象的時候,此次運算的最終結果返回的就是這個對象,而不是新創建的對象,因此 this 在這種情況下并沒有什么用。注意,返回函數也是返回新的對象,函數對象
function fn(a) {this.a = areturn {} } const bar = new fn(2) console.log(bar.a) // undefined 復制代碼四 window 綁定
fn() {console.dir(this.title) } const obj = {title: 'test' } fn() // undefined 復制代碼當點的左側沒有任何東西,也沒有通過call、apply、bind方法調用,也沒有new關鍵字,this就會指向了window。
window.title = '全局' fn() {console.dir(this.title) } fn() // 全局 復制代碼在ES5添加的嚴格模式中,this不會指向window對象,而是保持為undefined
五 四種綁定的優先規則
顯式綁定 > 隱式綁定 > 默認綁定 new綁定 > 隱式綁定 > 默認綁定
六 丟失的this
在某些情況下會丟失 this 的指向,此時,我們就需要借助 call、apply 和 bind 來改變 this 的指向問題。 示例一,當fn方法作為obj對象的屬性調用時,this指向obj對象,當另外一個變量引用fn方法時,因為它作為普通函數調用,所以this指向window對象
const obj = {title: 'test',fn: function() {console.dir(this.title)} } obj.fn() // test const getTitle = obj.fn getTitle() // undefined 復制代碼這種方式實際就是函數調用時,并沒有上下文對象,只是對函數的引用,同樣的問題,還發生在傳入回調函數中
test(obj.fn) // 傳入函數的引用,調用時也是沒有上下文 復制代碼示例二,即使在函數內部定義的函數,如果它作為普通對象調用,this同樣指向window對象
const obj = {title: 'test',name: '張三',fn: function() {console.dir(this.title)function getName(){console.dir(this.name)}getName()} } obj.fn() // test// undefined 復制代碼七 練習
var xuuu = 123 function test() {var xuuu = 456this.aa = 6666 return function() {console.log(xuuu)console.log(this.aa)console.log(this.xuuu) }var sdf=new test() sdf() // 456, undefined, 123 test()() // 456,6666,123 復制代碼結論:第一種情況的中this.aa指向sdf,return中this指向全局對象window;第二種情況中兩次this都指向window
八 箭頭函數的this
在以往的函數中,this 有各種各樣的指向(隱式綁定,顯示綁定,new 綁定, window 綁定......),雖然靈活方便,但由于不能在定義函數時而直到實際調用時才能知道 this 指向,很容易給開發者帶來諸多困擾。比如下面的代碼
function User() {this.name = 'John'setTimeout(function greet() {console.log(`Hello, my name is ${this.name}`) // Hello, my name is console.log(this) // window}, 1000) }const user = new User() 復制代碼如果想把greet里面的this指向user對象該怎么辦呢? 1 使用閉包
function User(){const self = thisthis.name = 'John'setTimeout(function greet() {console.log(`Hello, my name is ${self.name}`) // Hello, my name is Johnconsole.log(self) // User {name: "John"}}, 1000) }const user = new User() 復制代碼** 2 使用顯示綁定bind **
function User() {this.name = 'John';setTimeout(function greet() {console.log(`Hello, my name is ${this.name}`); // Hello, my name is Johnconsole.log(this); // User {name: "John"}}.bind(this)(), 1000); }const user = new User(); 復制代碼** 利用 setTimeout 的可以傳更多參數的特性 **
function User() {this.name = 'John';setTimeout(function greet(self) {console.log(`Hello, my name is ${self.name}`); // Hello, my name isconsole.log(self); // window}, 1000, this); }const user = new User(); 復制代碼** 箭頭函數如何解決 **
function User() {this.name = 'John';setTimeout(() => {console.log(`Hello, my name is ${this.name}`); // Hello, my name is Johnconsole.log(this); // User {name: "John"}}, 1000); }const user = new User(); 復制代碼**箭頭函數在自己的作用域內不綁定 this,即沒有自己的 this,**如果要使用 this ,就會指向定義時所在的作用域的 this 值。在上面的代碼中即指向 User 函數的 this,而 User 函數通過 new 綁定,所以 this 實際指向 user 對象
function foo() {return () => {console.log(this.a);}; } let obj1 = {a: 2 }; let obj2 = {a: 22 }; let bar = foo.call(obj1); // foo this指向obj1 bar.call(obj2); // 輸出2 這里執行箭頭函數 并試圖綁定this指向到obj2 但并不成功 復制代碼結論: 1、箭頭函數沒有自己的this,它的this繼承于它外面第一個不是箭頭函數的函數的this指向。 2、箭頭函數的 this 一旦綁定了上下文,就不會被任何代碼改變 3、箭頭函數使用call、apply、bind時,會自動忽略掉第一個參數 4、嚴格模式并不影響箭頭函數自己的this
九 最后的圖片
總結
- 上一篇: Python面向对象2-类和构造方法
- 下一篇: 爬虫基本原理讲解