一文全面解析JS中的this綁定規(guī)則
大家好,我是一名喜歡摸魚的前端程序員,寫過JavaScript的都知道,JS中的this
相對來講是比較難以捉摸的,尤其在一些復雜的場景下的指向總是讓人摸不著頭腦,所以這篇文章我們就來系統(tǒng)的學習和研究一下this
的綁定規(guī)則,并且會在文章的最后列出了四道關(guān)于this
綁定的題目,有興趣的小伙伴可以自己試著做一下,相信在你耐心看完這些內(nèi)容之后,你會對this
有一個更加全面且深刻的理解。
一.直接調(diào)用(獨立函數(shù)調(diào)用/全局調(diào)用)
直接調(diào)用顧名思義就是直接進行函數(shù)的調(diào)用,怎么直接調(diào)用哪? 我們常見的直接調(diào)用分為兩種情況,如下:
function foo () { console.log(this) } //進行函數(shù)調(diào)用 foo()
這是最常見的一種情況,這種情況this在非嚴格模式的情況下指向的是全局對象,這種調(diào)用方式,我們稱之為獨立函數(shù)調(diào)用,或者稱之為全局調(diào)用。
還有另外一種情況是在對象中定義,然后賦值給一個變量,然后單獨對這個賦值后的函數(shù)進行調(diào)用,依然是獨立函數(shù)調(diào)用或者稱之為全局調(diào)用,非嚴格模式下指向的是window。
let obj = { name: "zpj", foo: function () { console.log(this) } } let bar = obj.foo bar()
這種寫法,依然屬于獨立函數(shù)調(diào)用,指向的依然是window或者說全局對象。
注意:獨立函數(shù)調(diào)用(全局調(diào)用)this的指向在嚴格模式下并不指向window而是指向undefined所以我們在使用的時候千萬不要使用this來代替window,而是直接使用window。
二.對象綁定
通過對象綁定比較容易理解,因為我們在平時會經(jīng)常用到,當我們在如下的這種方式進行的時候會指向調(diào)用它的對象obj;
let obj={ name:"aaa", foo:function(){ console.log(this); } } obj.foo() // { name: 'aaa', foo: [Function: foo] }
三.new綁定
在講解new綁定之前,我們先來看下當我們在new一個對象的時候到底做了什么事情。
- 創(chuàng)建新的空對象。
- 將this指向這個空對象。
- 指向函數(shù)體中的代碼。
- 沒有顯示返回空對象的時候默認返回這個對象。
從第二步我們知道,當我們進行new操作的時候會將this綁定到實例化的這個對象上面。
function foo (name) { this.name = name console.log(this.name) } let bar = new foo("zzz") console.log(bar) // zzz // foo { name: 'zzz' }
通過上述的代碼我們可以看到當我們進行對象實例化的時候函數(shù)內(nèi)部的this指向的是實例化的這個對象。
四.this指向綁定事件的元素
<ul id="color-list"> <li>red</li> <li>yellow</li> <li>blue</li> <li>green</li> <li>black</li> <li>white</li> </ul>
// this 是綁定事件的元素 // target 是觸發(fā)事件的元素 和 srcElememnt 等價 let colorList = document.getElementById("color-list"); colorList.addEventListener("click", function (event) { console.log('this:', this); console.log('target:', event.target); console.log('srcElement:', event.srcElement); })
有些時候我們會遇到一些困擾,比如在div節(jié)點的事件函數(shù)內(nèi)部,有一個局部的 callback 方法,該方法被作為普通函數(shù)調(diào)用時,callback 內(nèi)部的this是指向全局對象 window的.
<div id="div1">我是一個div</div>
window.id = 'window'; document.getElementById('div1').onclick = function(){ console.log(this.id); // div1 const callback = function(){ console.log(this.id); // 因為是普通函數(shù)調(diào)用,所以 this 指向 window } callback(); }
此時有一種簡單的解決方案,可以用一個變量保存 div節(jié)點的引用,如下:
window.id = 'window'; document.getElementById('div1').onclick = function(){ console.log(this.id); // div1 const that = this; // 保存當前 this 的指向 const callback = function(){ console.log(that.id); // div1 } callback(); }
五.顯式綁定(call/apply)
有的時候this的指向并不能如我們所愿,這個時候我們需要手動去更改this的指向,來滿足我們的需求,其實在JavaScript中給我們提供了能夠更改this的綁定,首先我們看下call和apply。
function foo(name,age){ console.log(this); console.log(name,age); } const obj = { name:"zs", age:12, } foo.call(obj,'ls',30) // { name: 'zs', age: 12 } // ls 30
通過call函數(shù)我們可以看到我們可以手動的將this綁定到我們新定義的對象上面來,并且通過call單個傳參的方式將參數(shù)傳遞給了這個函數(shù),實現(xiàn)了函數(shù)的調(diào)用和this的綁定。
function foo (name, age) { console.log(this) console.log(name, age) } const obj = { name: "zs", age: 12, } foo.apply(obj, ['nnn', 45]) // { name: 'zs', age: 12 } // ls 30
我們會發(fā)現(xiàn)我們使用apply的方式進行綁定的修改結(jié)果依然如此,差別在于他們的傳參方式不同,call是單個的方式進行傳參的,而apply是通過數(shù)組的方式傳參的。
六.bind函數(shù)的顯式綁定
bind的綁定和call和apply的使用有些差別,使用bind會生成一個新的函數(shù),這個新函數(shù)我們稱之為BF綁定函數(shù),我們需要手動對這個函數(shù)進行調(diào)用。
function foo(){ console.log("foo",this) } let obj = { name:"why" } let bar = foo.bind(obj) bar()
七.內(nèi)置函數(shù)的調(diào)用綁定思考
我們在開發(fā)中會用到很多內(nèi)置的函數(shù),比如定時器setTimeout 這個時候我們需要靠經(jīng)驗來判斷當前的this指向因為有些東西根本不是我們來調(diào)用的,而是函數(shù)內(nèi)部調(diào)用的,我們根本不知道他們做了什么,這些內(nèi)容需要我們自己總結(jié)一下,內(nèi)容如下。
- 定時器內(nèi)部的函數(shù)this指向window
setTimeout(function () { console.log(this, "1") }, 500) setTimeout(() => { console.log(this, "2") }, 500)
- 按鈕的點擊事件,指向事件發(fā)起的對象,也就是綁定事件的元素。
let box = document.querySelector(".test") box.onclick = function () { console.log(this) }
- forEach中的this也是指向window
const array = [1, 2, 3, 4, 5, 6] array.forEach(item => { console.log(this) })
八.綁定優(yōu)先級的比較
我們前面了解的都是一些獨立的規(guī)則,但是實際的情況往往是比較復雜的,可能涉及到多個綁定一起使用的情況,這個時候我們就需要研究一下不同函數(shù)之間調(diào)用的優(yōu)先級。
- 直接調(diào)用(默認綁定)的優(yōu)先級是最低的。
- 顯式綁定優(yōu)先級高于對象綁定(隱式綁定)。
function foo () { console.log(this.name) } let obj = { name: "zzz", bar: foo } obj.bar.call({ name: "aaa" }) // aaa
- new綁定優(yōu)先級高于對象綁定(隱式綁定)的優(yōu)先級
function foo (name) { this.name = name console.log(this.name) } let obj = { name: "zzz", bar: foo } new obj.bar("ccc") // ccc
- new綁定不能和call與apply一起使用,new綁定的優(yōu)先級比bind高。
function foo (name) { this.name = name console.log(this.name) } let bar = foo.bind("zzz") new bar("ccc") // ccc
- bind和apply的優(yōu)先級,bind的優(yōu)先級更高,也高于call因為call和apply使用方法一樣。
function foo () { console.log(this) } let bar = foo.bind("zzz") bar.call("aaa") // zzz
九.綁定規(guī)則之外
其實在上述的綁定規(guī)則之外還有許多我們有時候按照規(guī)則難以理解的情況,我們來總結(jié)下有哪些情況。
- 在顯式綁定當中如果傳入nullundefined這個綁定會被忽略,使用默認規(guī)則,嚴格模式能夠綁定。
function foo () { console.log(this) } foo.apply(null) foo.apply(undefined) // window // window
- 創(chuàng)建一個函數(shù)的間接引用,使用函數(shù)的默認綁定規(guī)則,指向window(了解)
var obj1 = { name:"obj1", foo:function(){ console.log("foo",this) } } var obj2={ name:"obj2" }; (obj2.foo = obj1.foo)() // window
十.箭頭函數(shù)的使用
我們之前使用函數(shù)的方式是這樣的。
// 普通函數(shù)方式 function foo1(){} // 函數(shù)表達式方式 let foo2 = function(){}
箭頭函數(shù)的寫法
// 箭頭函數(shù)的完整寫法 // 1.()函數(shù)的參數(shù) // 2.{}函數(shù)體 let foo3 = (name,age)=>{ console.log(name); console.log(age); }
箭頭函數(shù)與普通函數(shù)的區(qū)別:箭頭函數(shù)中沒有this
和arguments
并且不能作為構(gòu)造函數(shù)使用。
箭頭函數(shù)的優(yōu)化方式:
- 當一個參數(shù)的時候可以省略函數(shù)參數(shù)的小括號。
let name = ["abc","bca","nba"]; name.forEach(item=>{ console.log(item); })
- 如果函數(shù)體中只有一行執(zhí)行代碼
{}
可以省略,但是一行代碼中不能帶return
;
let name = ["a","b","c"]; name.filter(item=> console.log(item))
- 如果函數(shù)體中只有一行代碼,那么這行代碼的返回值會作為整個函數(shù)的返回值。
let name = ["a","b","c"]; name.filter(item=>item==='a')
- 如果默認返回值是一個對象,那么這個對象必須加小括號
let arr = ()=>({name:"zzz",age:12})
箭頭函數(shù)使用案例:使用所有nums
的所有平方和的值。
let nums = [20,30,11,15,111]
/* *1.首先調(diào)用filter函數(shù)然后返回 *2.然后調(diào)用map函數(shù)然后返回 *3.然后調(diào)用reduce計算后返回。 */ let nums = [20, 30, 11, 15, 111] let num = nums.filter(item => item % 2 === 0) .map(item => item * item) .reduce((pre, cur) => pre + cur, 0) console.log(num) // 1300
十一.箭頭函數(shù)中的this
箭頭函數(shù)中是沒有this
的,所以this
如果在箭頭函數(shù)中的this
就是上級作用域的this
。
let bar =()=>{ console.log(this) } // window
因為沒有this
的原因,箭頭函數(shù)中使用顯式綁定也是無法綁定過去的。
let bar =()=>{ console.log(this) } bar.call({name:"zzz"}) // window
我們再來看下一個案例來明白下this
的查找規(guī)則。
let obj = { name: "obj", foo: function () { let bar = () => { console.log(this) } return bar } } let fn = obj.foo() fn.call("bbb") // obj
- 首先調(diào)用
foo
函數(shù)返回bar
定義的函數(shù),箭頭函數(shù)沒有this
。 - 根據(jù)作用域的查找規(guī)則,會向上層找
this
,foo函數(shù)中是有作用域的。 - 使用
call
函數(shù)是無法更改掉this
的。 - 所以打印出來
this
的指向是obj
函數(shù)。
十二.this常見面試題解析
oconst o1 = { text: 'o1', fn: function () { return this.text; } } const o2 = { text: 'o2', fn: function () { return o1.fn(); } } const o3 = { text: 'o3', fn: function () { var fn = o1.fn; return fn(); } } console.log(o1.fn()); // o1 console.log(o2.fn()); // o1 console.log(o3.fn()); // undefined
解析:首先o1.fn
是一個對象調(diào)用,所以this指向的是這個對象o1
,o2.fn()
單調(diào)用的時候返回的是o1.fn
因此指向的仍然是o1
,第三個o1.fn
進行了重新賦值然后調(diào)用,獨立函數(shù)調(diào)用指向undefined
。
var name = "window"; var person = { name:"person", sayName:function(){ console.log(this.name); } } function sayName(){ var sss = person.sayName; sss(); // window person.sayName(); // person (person.sayName)(); // person 等價于 person.sayName() (b=person.sayName)(); // window } sayName();
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(){ 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 獨立函數(shù)調(diào)用 person1.foo3.call(person2)() // window // 默認調(diào)用 person1.foo3().call(person2) // person2 person1.foo4()() // person1 person1.foo4.call(person2)() // person2 person1.foo4().call(person2) // person1
var name = "window" 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) } } } 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(perosn2)() // window person1.foo3().call(person2) // person2 person1.foo4()() // person1 person1.foo4.call(person2)() // person2 person1.foo4().call(person2) // person1
var name = "window" function Person(name){ this.name = name this.obj = { name:'obj', foo: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 獨立函數(shù)調(diào)用 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
以上就是一文全面解析JS中的this綁定規(guī)則的詳細內(nèi)容,更多關(guān)于JS this綁定規(guī)則的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
基于javascript實現(xiàn)tab選項卡切換特效調(diào)試筆記
這篇文章主要介紹了基于javascript實現(xiàn)tab選項卡切換特效調(diào)試筆記,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-03-03Bootstrap jquery.twbsPagination.js動態(tài)頁碼分頁實例代碼
這篇文章主要介紹了Bootstrap jquery.twbsPagination.js動態(tài)頁碼分頁實例代碼,需要的朋友可以參考下2017-02-02JavaScript實現(xiàn)左右下拉框動態(tài)增刪示例
本篇文章主要介紹了JavaScript實現(xiàn)左右下拉框動態(tài)增刪示例,可以對下拉框進行刪除和增加,非常具有實用價值,需要的朋友可以參考下。2017-03-03關(guān)于js中e.preventDefault()的具體使用
本文主要介紹了關(guān)于js中e.preventDefault()的具體使用,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-04-04