一文搞懂JavaScript中的this綁定規則
前言
首先我們來看一個示例。定義一個函數,用三種不同的方式調用,會產生三種不同的結果
function foo(){ console.log(this); } foo() //window let obj ={ name:'sss', foo:foo } obj.foo()//{name:'sss',foo:function} foo.call("abc") // 生成包裝類型對象,String {'abc'}
一個函數調用方式不同this指向也不盡相同,為什麼會出現這麼令人費解的現象呢?是因為在JavaScript中函數的this是動態綁定的(箭頭函數除外)。經過一段時間對this的研究,得出瞭以下幾個結論:
- 函數在調用時,JavaScript會默認給this綁定一個值。
- this的綁定與編寫的位置沒有關系
- this的綁定與調用的方式以及調用的位置有關
- this的綁定是在運行時進行的
this綁定與調用方式和調用位置有關,那麼根據調用方式和調用位置,我們該怎麼確定this指向呢?
最近這段時間閱讀瞭《你不知道的JavaScript》這本書,讓我對js有瞭更深刻的理解,裡面也說到瞭this綁定問題,如果有興趣推薦大傢去看看這本書,這裡我就不過多介紹瞭,下面來聊聊怎麼確定this指向吧,這是我從這本書還有網上的一些資料總結出來的,也是自己的理解,再也不用擔心搞不清this指向瞭,嘿嘿!
this四大綁定規則
一.默認綁定
獨立函數調用的就是默認綁定,簡單說就是函數沒有綁定到某個對象上進行調用,默認返回window(註意:嚴格模式返回undefined)
舉例:
function foo(){ console.log(this); } foo() // 獨立函數調用 let obj ={ name:'sss', bar:function(){ console.log(this); } } let ff = obj.bar ff() // 函數在對象中,但是獨立函數獨立函數調用 // 高階函數 function test(fn){ fn() } test(obj.bar) //相當於fn=obj.bar fn() 所以還是獨立函數調用
結果都是window
二.隱式綁定
在調用位置中,通過對象來發起的調用會返回調用它的那個對象
舉例:
let obj ={ name:'sss', bar:function(){ console.log(this); } } obj.bar() // 通過對象調用,結果是 obj
有一種更微妙但是又更常見的情況發生在傳入回調函數時:
function foo(){ console.log(this); } setTimeout(foo,1000) //Window let btn = document.querySelector('button') btn.addEventListener('click',foo) //button let ar = [1,2,3,4] ar.forEach(item=>console.log(this)) //Window
setTimeout內部綁定的this是window ,btn的this指向按鈕,forEach方法指向Window
三.new綁定
JavaScript中的函數也可以當作一個構造函數來使用,也就是通過new關鍵字
我們先來回憶一下new關鍵字做瞭什麼:
- 創建(構造)一個全新的對象
- 這個新對象執行[[prototype]]連接
- 將這個新對象綁定在this上
- 沒有顯示返回其他對象時,即返回這個新對象
代碼示例:
function person(name) { this.name =name console.log(this); } new person('sss') // person{name:'sss'}
四.顯示綁定
相當於強制綁定,使用call,bind,apply等一系列方法進行強制綁定;
顯示綁定簡單來說就是通過一些方法來進行強制綁定,this就是強制綁定的那個對象;代碼示例:
// 顯示綁定 function foo(){ console.log(this); } let obj ={ name:'sss' } // 此時我們希望this指向obj 如果是正常情況下該這麼指向呢 // 1 obj.foo = foo obj.foo() // obj // 但是我們希望直接綁定 就可以通過call 來進行顯示綁定 foo.call(obj) //obj foo.call("123") //"123" foo.call(123) //123 foo.call(undefine) // 特例:window
call,apply,bind簡單介紹:
相同點:
- 都是改變this指向的;
- 第一個參數都是this要指向的對象;
- 都可以利用後續參數傳參;
區別:
- call和bind的參數是依次傳參,一一對應的;
- 但apply隻有兩個參數,第二個參數為數組;
- call和apply都是對函數進行直接調用,而bind方法返回的仍是一個函數;
綁定規則優先級
知道瞭this的四種綁定規則,那麼,這四種規則該怎麼用呢?
就像運算符有優先級規則一樣,this四種綁定規則也有優先級 ,下面來介紹一下它的優先級規則
直接上結論吧,我們可以按照下面這個順序來判斷this:
- 函數是否在new中調用(new綁定)?如果是的話this綁定的就是新創建的對象;
- 函數是否通過call、apply、bind調用(顯示綁定)?如果是的話,this綁定的是指定的對象;
- 函數是否在某個上下文對象中調用(隱式綁定)?如果是的話,this綁定的是那個上下文對象;
- 如果都不是的話,使用默認綁定,綁定到全局對象(嚴格模式綁定到undefined)
代碼示例:
//優先級 function foo(){ console.log(this); } let obj = { name:'ss', foo:foo } obj.foo.call('nn') // 結果是 包裝對象nn // 顯然顯示大於隱形綁定 new obj.foo() // 結果是foo實例對象 // new綁定大於隱式綁定 // new 和顯示綁定綁定不能一起使用 let f = foo.bind(obj) new f() // 結果依然是foo實例對象 new綁定>顯示綁定 let f2 = foo.bind(obj) f2.call('ss') // 結果是obj對象 bind>call
輸出:
這個判斷順序幾乎適用所有情況,當然也有例外,我們需要註意這些例外:
1.顯示綁定傳入null和undefine
function foo(){ console.log(this); } foo.apply("ss") //"ss" foo.apply(null) //Window foo.apply(undefined) //Window
說明在我們傳入null和undefined時,會忽略顯示綁定,使用默認規則
另外,在嚴格模式下則是會返回基本的數據類型
"use strict" function foo(){ console.log(this); } foo.apply("ss") //"ss" foo.apply(null) //null foo.apply(undefined) //undefine
2.創建一個函數的 間接引用,這種情況也是使用默認綁定規則
function foo(){ console.log(this); } let obj = { foo:foo } (a = obj.foo)() // Window
a =obj.foo 會默認返回foo() 相當於獨立調用,使用默認規則
3.箭頭函數
箭頭函數中是沒有this的,所以它會去不斷去上一層的作用域查找this並使用
面試題
現在你已經掌握瞭如何判斷this指向瞭,來幾個面試題試試手吧
題1
let name = 'Window'; let Person = { name:'person', say:function(){ console.log(this); } }; let ss = Person.say; ss(); // Window Person.say(); // Person (Person.say)(); // Person (b = Person.say)(); // Window
題2
var name = 'window' // {} -> 對象 // {} -> 代碼塊 var person1 = { name: 'person1', foo1: function () { console.log(this.name) }, foo2: () => console.log(this.name), foo3: function () { return function () { console.log(this.name) } }, foo4: function () { // console.log(this) // 第一個表達式this -> person1 // console.log(this) // 第二個表達式this -> person2 // console.log(this) // 第三個表達式this -> person1 return () => { console.log(this.name) } } } var person2 = { name: 'person2' } // 開始題目: person1.foo1(); // 隱式綁定: person1 person1.foo1.call(person2); // 顯式綁定: person2 person1.foo2(); // 上層作用域: window person1.foo2.call(person2); // 上層作用域: window person1.foo3()(); // 默認綁定: window person1.foo3.call(person2)(); // 默認綁定: window person1.foo3().call(person2); // 顯式綁定: person2 person1.foo4()(); // person1 person1.foo4.call(person2)(); // person2 person1.foo4().call(person2); // person1
題3
var name = 'window' /* 1.創建一個空的對象 2.將這個空的對象賦值給this 3.執行函數體中代碼 4.將這個新的對象默認返回 */ function Person(name) { this.name = name this.foo1 = function () { console.log(this.name) }, this.foo2 = () => console.log(this.name), this.foo3 = function () { return function () { console.log(this.name) } }, this.foo4 = function () { return () => { console.log(this.name) } } } // person1/person都是對象(實例instance) var person1 = new Person('person1') var person2 = new Person('person2') // 面試題目: person1.foo1() // 隱式綁定: person1 person1.foo1.call(person2) // 顯式綁定: person2 person1.foo2() // 上層作用域查找: person1 person1.foo2.call(person2) // 上層作用域查找: person1 person1.foo3()() // 默認綁定: window person1.foo3.call(person2)() // 默認綁定: window person1.foo3().call(person2) // 顯式綁定: person2 person1.foo4()() // 上層作用域查找: person1(隱式綁定) person1.foo4.call(person2)() // 上層作用域查找: person2(顯式綁定) person1.foo4().call(person2) // 上層作用域查找: person1(隱式綁定)
題4
var name = 'window' /* 1.創建一個空的對象 2.將這個空的對象賦值給this 3.執行函數體中代碼 4.將這個新的對象默認返回 */ function Person(name) { this.name = name this.obj = { name: 'obj', foo1: function () { return function () { console.log(this.name) } }, foo2: function () { return () => { console.log(this.name) } } } } var person1 = new Person('person1') var person2 = new Person('person2') person1.obj.foo1()() // 默認綁定: window person1.obj.foo1.call(person2)() // 默認綁定: window person1.obj.foo1().call(person2) // 顯式綁定: person2 person1.obj.foo2()() // 上層作用域查找: obj(隱式綁定) person1.obj.foo2.call(person2)() // 上層作用域查找: person2(顯式綁定) person1.obj.foo2().call(person2) // 上層作用域查找: obj(隱式綁定)
以上就是一文搞懂JavaScript中的this綁定規則的詳細內容,更多關於JavaScript this綁定規則的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- JavaScript深入刨析this的指向以及如何修改指向
- Vue自定義指令中無法獲取this的問題及解決
- 關於ES6中的箭頭函數超詳細梳理
- JavaScript中call,apply,bind的區別與實現
- JavaScript中的this例題實戰總結詳析