JavaScript 面向物件之二 —— 函式上下文(this的指向)
本系列文章根據《愛前端邵山歡老師深入淺出的js面向物件》視訊整理歸納
函式上下文
在 JavaScript 中,函式的上下文是有規律可循的,基本可以分為以下幾項:
規律一:函式用圓括號呼叫,函式上下文是 window 物件。
如下,函式 function fun(){} 的上下文是什麼,即 this 的指向是誰, 不是根據它如何定義,而是根據如何呼叫得出的,在這裡fun() 是通過函式名加圓括號直接呼叫的,所以此時函式的上下文,即 this 指向就是 window 物件。
function fun(){
var a = 1;
console.log(this .a);
}
var a = 2;
fun(); // 輸出為 2
而我們知道,所有的全域性變數都是 window 物件的屬性(函式裡面的區域性變數,不是 window 的屬性,不是任何東西的屬性,就是一個變數。),所以在這裡最後輸出 2。
規律二:函式如果作為一個物件的方法,物件使用點方法進行呼叫,那麼函式的上下文就是這個物件。
如下,定義一個函式 fun,並將其作為新建物件 obj 的一個屬性 c,通過 物件.函式() 的方法進行呼叫:
function fun(){
var a = 1;
console.log(this.a);
}
var obj = {
'a' = 2,
'b' = 3,
'c' = fun
};
obj.c(); // 輸出為 2
此時函式的上下文為 obj 物件,即函式裡的 this 指向這個物件 obj,所以輸出為 2。
規律三:函式是事件處理函式,那麼函式的上下文就是觸發這個事件的物件。
如下,我們建立三個 div,給其設定背景色,定義一個函式 fun,將其作為三個 DOM 元素的事件處理函式,使點選不同的 div 時,相應 div 背景色發生變化。
HTML 部分
<div id="box1"></div>
<div id="box2"></div>
<div id="box3"></div>
CSS 部分
div {
width: 50px;
height: 50px;
background-color: red;
margin: 10px 0;
}
JavaScript 部分
function fun(){
this.style.backgroundColor = 'blue';
}
document.getElementById('box1').onclick = fun;
document.getElementById('box2').onclick = fun;
document.getElementById('box3').onclick = fun;
在這裡函式不會執行,直到我們點選了某一個 div 標籤,此時點選誰,函式上下文就是誰,this 就指向誰。
規律四:函式被定時器呼叫時,上下文是 window 物件。
如下,定義函式 fun,在定時器 setInterval 中呼叫:
funcyion fun(){
console.log(this.a);
}
var a = 1;
setInterval(fun,1000); // 每隔一秒輸出一次 1
因為函式被定時器呼叫,上下文是 window 物件,所以輸出為 1。
我們經常會因為對於被定時器呼叫的函式 this 指向理解有誤,而犯如下的錯誤:
點選 div,使之2秒後變為藍色:
var box = getElementById('box');
box.onclick = function(){
setTimeout(function(){
this.style.backgroundColor = 'blue';
},2000);
}
上述程式碼在點選 div 2秒後會報錯,因為我們理所當然的認為 this 指向觸發事件處理函式的物件,但其實在這裡,函式是在定時器內呼叫的,所以函式的上下文是 window 物件,即這裡的 this 指向 window 物件,而 window 物件沒有背景顏色這個屬性,所以會報錯。
而我們要實現最初想達成的效果,可以通過在定時器外面的事件處理函式中,把 this 存為區域性變數 that 來備份上下文,因為在這裡 this 就是 box 這個元素。
var box = document.getElementById('box');
box.onclick = function (){
var that = this;
setTimeout(function(){
that.style.backgroundColor = 'blue';
},2000)
}
規律五:陣列中存放的函式,被陣列索引呼叫,函式上下文就是這個陣列。
如下,定義一個函式 fun,使之成為陣列的一項:
function fun(){
console.log(this.length);
}
var arr = [fun,1,2];
arr[0](); // 3
在這裡函式是從陣列中索引並加圓括號進行呼叫的,所以最終呼叫者可以認為是這個陣列,所以上下文就是這個陣列,即 this 的指向也是這個陣列。
綜合練習:
function fun1(c,d,e,f,g){
console.log(this.length);
}
function fun2(a,b){
arguments[0](4,5,6,7,8);
}
fun2(fun1,1,2,3);
輸出為 4
解析:
涉及知識點一: arguments.callee
在函式內部,我們無法通過 this 得到函式自身,必須使用arguments.callee。
function fun(){
console.log(arguments.callee === fun);
}
fun(); // 輸出 true
涉及知識點二:函式的長度是什麼
函式的長度是形參列表的長度(即函式定義時寫在 fun 圓括號內的字母個數),與實參長度無關(即函式呼叫時圓括號內的引數個數),
即 arguments.callee.length = 形參個數
而 arguments.length = 實參個數
function fun(a,b,c,d,e){ // 此處的 a,b,c,d,e 是形參
console.log(arguments.callee.length); // 5
console.log(arguments.length); // 6
}
fun(1,2,3,4,5,6); // 輸出 5 6
// 此處的 1,2,3,4,5,6 為實參
此時再回頭看我們的練習題, fun1 作為 fun2 的實參進行呼叫,而 fun2 中的 arguments[0] 就是 fun2,所以函式的最終呼叫,是 arguments 物件進行方括號索引得到 fun1 ,然後加圓括號執行呼叫,而 arguments 是一個物件,可以參照我們總結的規律五得出,fun1 函式中的 this 指向 arguments 物件,那麼最終我們所要輸出的就是 arguments.length,它的值則為fun2(fun1,1,2,3)的實參個數,所以最終輸出為 4
如果我們將 fun1 函式變成:
function fun1(){
console.log(this.callee.length);
}
那麼結果將輸出 arguments.callee.length,即 fun2 的形參個數 2