1. 程式人生 > >JavaScript 面向物件之二 —— 函式上下文(this的指向)

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