- 說明:該文屬于 大前端全棧架構白寶書專欄,目前階段免費,如需要項目實戰或者是體系化資源,文末名片加V!
- 作者:哈哥撩編程,十余年工作經驗, 從事過全棧研發、產品經理等工作,目前在公司擔任研發部門CTO。
- 榮譽:2022年度博客之星Top4、2023年度超級個體得主、谷歌與亞馬遜開發者大會特約speaker、全棧領域優質創作者。
- 🏆 白寶書系列
- 🏅 啟示錄 - 攻城獅的自我修養
- 🏅 Python全棧白寶書
- 🏅 ChatGPT實踐指南白寶書
- 🏅 產品思維訓練白寶書
- 🏅 全域運營實戰白寶書
- 🏅 大前端全棧架構白寶書

文章目錄
- ?認識上下文
- ?上下文規則
- 🌟上下文規則1
- 🌟上下文規則2
- 🌟上下文規則3
- 🌟上下文規則4
- 🌟上下文規則5
- 🌟上下文規則6
?認識上下文
小時候學習做閱讀理解時,老師經常會強調,注意上下文。比如有一個句子:這是一個好習慣,我們應該堅持。如果不結合上文的意思,根本不知道“這”指的是什么。如果結合上文,比如,隨手關燈,這是一個好習慣,我們應該堅持。我們就知道此時的“這”指的是“隨手關燈”,那么整個句子的語義就好理解了。
在函數中,也經常需要結合上下文來編寫和理解代碼。
函數中可以使用
this
,表示函數的上下文
與中文中的“這”類似,函數中的this具體指代什么必須
通過調用函數時的“前言后語”來判斷
下面看一個例子:
- 第一種調用方式:對象打點調用自己的方法:
var xiaomumu = {name: '小沐沐',age: 2,sex: '男',hobbies: ['秋千', '滑梯'],sayHello: function () {console.log('Hello, 我是' + this.name + ',我今年' + this.age + '歲了');}
};
xiaomumu.sayHello(); // Hello, 我是小沐沐,我今年2歲了
- 第二種調用方式:對象的方法被賦值給一個全局變量,全局變量加圓括號調用:
var xiaomumu = {name: '小沐沐',age: 2,sex: '男',hobbies: ['秋千', '滑梯'],sayHello: function () {console.log('Hello, 我是' + this.name + '我今年' + this.age + '歲了');}
};var sayHello = xiaomumu.sayHello; // 將這個屬性存入了一個全局變量(變量的名稱可以和屬性名相同,方便理解)
sayHello(); //Hello, 我是undefined我今年undefined歲了
這兩種調用方式,this指代的對象分別是:
- 第一種,對象打點調用方式,函數中的this指代這個打點的對象
- 第二種,圓括號直接調用函數,函數中的this指代window對象
其實可以這么理解,第二種中,因為全局變量都是window的屬性,相當于用window.sayHello()的方式調用了這個函數。所以this指代的就是window對象。
所以我們一定要記住:只有函數被調用時,我們才能知道this指代的是什么函數的上下文由函數的調用方式
決定,同一個函數,用不同的形式調用它,則函數的上下文不同
?上下文規則
我們知道,不同的調用方式,函數的上下文就不同,那么我們怎么去判斷函數的上下文呢?這就要學習各種不同的函數調用規則。
🌟上下文規則1
規則1:對象打點調用它的方法函數,則函數的上下文是這個打點的對象
下面看幾個案例,練習一下:
題目一: 下面代碼的運行結果是什么?
function fn() {console.log(this.a + this.b);
}
var obj = {a: 11,b: 22,fn: fn
};obj.fn();
運行結果:33
題目二: 下面代碼的運行結果是什么?
var obj1 = {a: 1,b: 2,fn: function () {console.log(this.a + this.b);}
};var obj2 = {a: 3,b: 4,fn: obj1.fn
};obj2.fn();
運行結果: 7
一定要記住,誰打點調用函數,函數中的this指代的就是誰
題目三: 下面代碼的運行結果是什么?
function outer() {var a = 11;var b = 22;return {a: 33,b: 44,fn: function () {console.log(this.a + this.b);}};
}outer().fn();
運行結果:77
上面的代碼中,outer()返回了一個對象,相當于還是一個對象打點調用了函數,所以適用于對象打點調用函數,函數的上下文就是這個對象的規則。
題目四: 下面代碼的運行結果是什么?
function fun() {console.log(this.a + this.b);
}var obj = {a: 1,b: 2,c: [{a: 3,b: 4,c: fun}]
};var a = 5;
obj.c[0].c();
運行結果:7
上面的代碼中,最終調用函數的是obj.c[0]
這個對象,所以函數中的this指代的就是obj.c[0]
🌟上下文規則2
規則2:圓括號直接調用函數,則函數的上下文是window對象
題目一: 下面代碼的運行結果是什么?
var obj1 = {a: 1,b: 2,fn: function () {console.log(this.a + this.b);}
};
var a = 3;
var b = 4;var fn = obj1.fn;
fn();
運行結果:7
題目二: 下面代碼的運行結果是什么?
function fun() {return this.a + this.b;
}
var a = 1;
var b = 2;
var obj = {a: 3,b: fun(),fun: fun
};
var result = obj.fun();
console.log(result);
運行結果:6,題目分析如下:
這是一道非常正規的面試題,大家一定要學會分析其中的代碼邏輯
🌟上下文規則3
規則3:數組(類數組對象)枚舉出函數進行調用,上下文是這個數組(類數組對象)
數組[下標]()
類數組對象:所有鍵名為自然數序列(從0開始),且有length屬性的對象
arguments對象是最常見的類數組對象,它是函數的實參列表
題目一: 下面代碼的運行結果是什么?
var arr = ['A', 'B', 'C', function () {console.log(this[0]);
}];arr[3]();
運行結果: “A”
上面的代碼適用規則3,this指代的就是arr這個數組
題目二: 下面代碼的運行結果是什么?
function fun() {arguments[3]();
}fun('A', 'B', 'C', function () {console.log(this[1]);
});
運行結果:“B”。我們可以打印一下arguments,可以看到arguments是一個類數組對象,里面其實就是fun()函數被調用時,傳入的實參:
🌟上下文規則4
規則4:IIFE中的函數,上下文是window對象。(IIFE在以前的文中提到過,是立即可執行函數。)
(function (){
? //立即可執行函數
})()
題目一: 下面代碼的運行結果是什么?
var a = 1;
var obj = {a: 2,fun: (function () {var a = this.a;return function () {console.log(a + this.a);}})()
};
obj.fun();
運行結果:3。這是一個大廠的面試題,題目分析如下:
🌟上下文規則5
規則5:定時器、延時器調用函數,上下文是window對象
setInterval(函數,時間);
setTimeout(函數,時間);
題目一: 下面代碼的運行結果是什么?
var obj = {a: 1,b: 2,fun: function () {console.log(this.a + this.b);}
};
var a = 3;
var b = 4;setTimeout(obj.fun, 2000);
運行結果:7。因為用延時器調用的函數,適用規則5,this指代的就是window對象,即this.a
和this.b
的值分別是3,4
題目二: 下面代碼的運行結果是什么?
var obj = {a: 1,b: 2,fun: function () {console.log(this.a + this.b);}
};
var a = 3;
var b = 4;setTimeout(function () {obj.fun(); //適用規則1
}, 2000);
運行結果:3。這里要注意,延時器不是直接調用obj里的fun函數,而是一個匿名函數,這個匿名函數里又調用了obj.fun(),所以此時的this應指代的是obj這個對象,即this.a
和this.b
的值分別是1,2
🌟上下文規則6
規則6:事件處理函數的上下文是綁定事件的DOM元素
DOM元素.onclick = function() {
};
題目一: 點擊哪個盒子,哪個盒子就變紅,要求使用同一個事件處理函數實現(不能用事件委托)
<html lang="en">
<head><style>div {width: 200px;height: 200px;float: left;border: 1px solid #000;margin-right: 10px;}</style>
</head>
<body><div id="box1"></div><div id="box2"></div><div id="box3"></div><script>function setColorToRed(o) {this.style.backgroundColor = 'red';}var box1 = document.getElementById('box1');var box2 = document.getElementById('box2');var box3 = document.getElementById('box3');box1.onclick = function () {setColorToRed(box1);}box1.onclick = setColorToRed;box2.onclick = setColorToRed;box3.onclick = setColorToRed;</script>
</body>
</html>
運行結果:點擊哪個盒子,哪個盒子就變紅。這里的this指代的就是綁定事件的DOM元素;注意區分this和e.target的不同
題目二: 點擊哪個盒子,哪個盒子在2000毫秒后就變紅,要求使用同一個事件處理函數實現(不能用事件委托)
題目分析:
這個題目其實就是上個題目+一個延時器,但是需要注意的是加上延時器后,this指代的可能會不一樣,所以需要使用一個程序猿非常常用的技術:備份上下文
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><style>div {width: 200px;height: 200px;float: left;border: 1px solid #000;margin-right: 10px;}</style>
</head>
<body><div id="box1"></div><div id="box2"></div><div id="box3"></div><script>function setColorToRed(o) {//備份上下文var self = this; // 通常使用self來備份上下文,也有使用that或_this的,這個技術非常的常用!setTimeout(() => {self.style.backgroundColor = 'red';}, 2000);}var box1 = document.getElementById('box1');var box2 = document.getElementById('box2');var box3 = document.getElementById('box3');box1.onclick = function () {setColorToRed(box1);}box1.onclick = setColorToRed;box2.onclick = setColorToRed;box3.onclick = setColorToRed;</script>
</body>
</html>
注意:程序員在書寫this的時候通常會看一下這個this到底指代什么,需不需要備份