一.前言
在JavaScript中this的指向經常困擾著很多人,也折磨著很多人,經常性的會出現下列的問題,想用this的時候發現this指向的物件不是自己想要的,但是顯式的更改又更改不掉,問題的本質還是對this的使用方法沒有真正的理解,這篇文章我們就一次性的將this的使用方式進行總結,一次性解決this的使用問題。
二.this到底指向什麼?
需要注意的是,當我們在進行一個函式呼叫的時候,JavaScript會預設給this繫結一個值,this的繫結和定義的位置沒有關係,this的繫結和呼叫的方式和呼叫的位置有關係,this是在執行時被繫結的,當我們瞭解了這些前置知識後我們就可以開始去探索this的繫結規則了,讓我們開始吧!
三.預設繫結
什麼是預設繫結哪?預設繫結簡單來講就是獨立函式呼叫,獨立函式呼叫我們可以理解為,函式沒有被繫結到其他物件上呼叫,我們可以透過以下的幾個案例來看下什麼樣的情況下是獨立函式呼叫。
function myFun(){ console.log(this); } myFun() // window
另外當一個函式在另外一個函式中進行呼叫的時候也是獨立函式呼叫,this依然指向的是全域性物件。
function myFun(){ console.log(this); } function myFun2(){ myFun() } myFun2() // window
甚至我們給一個函式傳輸一個函式,然後在函式體的內部執行這個函式this的指向依然是當前頂級物件。
function myFun(fn){ fn() } let obj = { name:"wang", age:12, fun:function(){ console.log(this); } } myFun(obj.fun) // window
從上述的內容中我們可以得出一個結論,只要是一個函式被獨立進行呼叫,那麼這個函式就指向當前作用域的頂級物件也就是window。
四.隱式繫結
除了上述直接對函式進行呼叫,還有一種方式是透過物件對函式進行呼叫,也就是在它的呼叫位置中是透過某個物件發起的函式呼叫,我們透過如下幾個案例來了解下隱式繫結的情況。
let obj = { name:"wang", age:12, fun:function(){ console.log(this); } } obj.fun(); // { name: 'wang', age: 12, fun: [Function: fun] }
我們透過執行的結果可以看出來,透過物件進行呼叫this指向的是當前的物件,接下來我們來看下連續使用物件進行呼叫,最終的結果指向的是什麼。
function foo () { console.log(this) } var obj1 = { name: "obj1", foo: foo, } var obj2 = { name: "obj2", obj1: obj1, } obj2.obj1.foo() // { name: 'obj1', foo: [Function: foo] }
我們從上述的內容中可以看出來,當進行物件的連續呼叫的時候,this指向的呼叫次序最近的那個物件。
💡需要主要注意的是,當我們在透過物件呼叫某個方式,但是我們將這段程式碼重新賦值了變數,然後使用這個變數進行呼叫,這個時候this指向的是window。
function Myfn(){ console.log(this); } let obj = { name:"wang", foo:Myfn } let returnFn = obj.foo; returnFn(); // window
五.顯式繫結
我們在上述內容中瞭解了預設繫結和隱式繫結,但是僅僅這些內容不能完全滿足我們的需求,隱式繫結有一個條件,必須在呼叫的物件內部有一個函式的引用,如果沒有這樣的引用,在進行呼叫時,會報找不到該函式錯誤,間接的將this繫結到了這個物件上,在很多時候我們想要按照自己的程式設計的想法去更改this的指向,這個時候隱式繫結就滿足不了我們的需求了,這個時候我們就需要使用顯示繫結來解決這個問題,在JavaScript中,所有的函式都可以呼叫call,apply,call和apply的使用方法僅僅是引數列表不同(如下),當我們在使用bind的時候bind會返回一個繫結函式,我們需要對返回的函式進行呼叫,bind的引數傳遞方式和apply一致。
// call的使用方法 fn.call(this,[argsArray]) // apply的使用方法 fn.apply(this,args1,args2) // bind的使用方法 let resultFn = fn.bind(this,args1,args2) resultFn()
那麼既然我們已經瞭解了顯式繫結的使用,那麼我們就來具體的透過程式碼來使用一下call繫結。
function myFn(){ console.log(this); } myFn.call("wangpeng") // [String: 'wangpeng']
然後我們使用apply來進行繫結。
function myFn(){ console.log(this); } myFn.apply("wangpeng") // [String: 'wangpeng']
bind的使用和上述兩者稍微有點差別。
function myFn () { console.log(this) } let bindFn = myFn.bind("wangpeng") bindFn() // [String: 'wangpeng']
六.new繫結
在JavaScript中函式是可以作為建構函式進行例項化的,也就是使用new關鍵字,在使用new繫結之前我們需要先來看下,當我們在使用new來呼叫函式的時候發生了什麼。
建立一個全新的物件。
這個物件會被執行prototype連線。
這個新物件會繫結到函式呼叫的this上(this繫結在這個步驟完成)
如果函式沒有返回返回其他物件,表示式會返回這個新物件。
function myFn (name) { console.log(this) // myFn {} this.name = name // myFn { name: 'wangpeng' } } let p = new myFn('wangpeng') console.log(p);
七.繫結的優先順序
在上述內容中我們進行this繫結都是單獨進行測試和使用的,但是在實際的開發中有的時候可能是幾種繫結規則在一起的,這個時候我們就要考慮,當不同的規則一起使用的時候它們的優先順序問題。
預設規則的優先順序最低,因為當存在其他規則的時候就會使用其他規則進行繫結。
顯示繫結高於隱式繫結。
let obj = { name:"John", foo:function(){ console.log(this); } } obj.foo.call("wangpeng") // [String: 'wangpeng']
new繫結優先順序高於隱式繫結。
let obj = { name:"John", foo:function(){ console.log(this); } } new obj.foo() // foo {}
new繫結的優先順序高於bind,同時new繫結無法和call,apply一起使用。
function myFn(){ console.log(this); } let resultFn = myFn.bind(); new resultFn() // myFn {}
八.常見內建函式this的指向
雖然我們已經瞭解了this繫結的所有的規則,但是我們在實際的開發中,往往會呼叫JS中內建的函式,或者第三方庫幫我們呼叫,在這種情況下,我們其實也不知道this到底被繫結到了什麼。
定時器函式
setTimeout(function(){ console.log(this) }) // window
按鈕的點選監聽
var box = document.querySelector(".box"); box.onclick = function(){ console.log(this === box) } // box
陣列內建的forEach方法
var names = ["abc","cba","nba"] var obj = {name:"why"} names.forEach(function(item){ console.log(this) // 三次obj },obj)
💡注意在forEach中如果不使用第二個引數指定指向的話,預設指向window
九.this繫結規則之外
情況一:當我們使用顯式繫結的時候傳入null或者undefined,那麼顯式繫結的規則會被忽略,使用預設繫結。
function myFn () { console.log(this) } myFn.call(null) // window myFn.apply(null) // window let resultFn = myFn.bind(null) resultFn() // window
情況二:建立一個函式的間距引用,這種情況下使用預設繫結規則。
function foo () { console.log(this) } var obj1 = { name: "obj1", foo: foo } var obj2 = { name: "obj2" } obj1.foo(); // obj (obj2.foo = obj1.foo)() // window
情況三:箭頭函式中沒有this,所以箭頭函式中的this指向的上層作用域中的this。
let obj = { data: [], getData: function () { setTimeout(() => { console.log(this) }, 1000) } } obj.getData() // obj
十.總結
這篇文章我們學習了JS中this的繫結規則,this的繫結規則大致分為四種,分別是預設繫結,隱式繫結,顯式繫結和new繫結,然後我們講解了在它們在一起使用的時候的優先順序,隱式繫結優先於預設繫結,顯式繫結優先於隱式繫結,new繫結優先於隱式繫結,new繫結優先於bind繫結,除了這些有明確規則的繫結方式之外,我們還需要在平時開發者中總結常見內建函式的繫結,在文章的最後,我們瞭解了在規則之外的三種情況,在日常中使用較多的是箭頭函式,需要牢記,熟練使用~