一文全面解析JS中的this綁定規(guī)則
大家好,我是一名喜歡摸魚的前端程序員,寫過JavaScript的都知道,JS中的this相對來講是比較難以捉摸的,尤其在一些復(fù)雜的場景下的指向總是讓人摸不著頭腦,所以這篇文章我們就來系統(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ī)則,但是實際的情況往往是比較復(fù)雜的,可能涉及到多個綁定一起使用的情況,這個時候我們就需要研究一下不同函數(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)文章!
- JavaScript中this綁定規(guī)則你理解了嗎
- 細說JavaScript中的this指向與綁定規(guī)則
- JavaScript this綁定與this指向問題的解析
- JavaScript?中的?this?綁定規(guī)則詳解
- JavaScript中this的綁定你知道幾種?
- 詳解JavaScript中的this硬綁定
- 一文搞懂JavaScript中的this綁定規(guī)則
- JavaScript中?this?的綁定指向規(guī)則
- 詳解JavaScript的this指向和綁定
- JavaScript this綁定過程深入詳解
- React.js綁定this的5種方法(小結(jié))
- JavaScript調(diào)用模式與this關(guān)鍵字綁定的關(guān)系
- 深入理解JavaScript this綁定規(guī)則
相關(guān)文章
一文讓你徹底弄懂js中undefined和null的區(qū)別
JavaScript是一門動態(tài)類型語言,元素除了表示存在的空值外,還有可能根本就不存在,這就是undefined存在的原因,這篇文章主要給大家介紹了關(guān)于undefined和null區(qū)別的相關(guān)資料,需要的朋友可以參考下2022-03-03

