關於 this 的一篇總結

前言

前排聲明,這真的是寫 this,沒有什麼太多新的東西,就是一個本身對 this 綁定規則的總結,也許後期水平提升會從更深的角度去解釋 JavaScript 中的 this 綁定規則。本文從分別從綁定全局對象和綁定具體對象的角度總結了一下 this 的綁定規則。javascript

綁定全局對象

this 綁定全局對象,分爲兩種狀況:html

  • 直接在全局做用域下使用 this
  • 被調用的函數不知足綁定具體對象的綁定規則

直接在全局做用域下使用 this

這種沒什麼好說的,全局做用域下調用 this 綁定的是全局對象,例如瀏覽器中綁定的是 window 對象。java

console.log(this)
複製代碼

直接拿上面這段代碼在瀏覽器控制檯運行下獲得的就是全局對象。數組

被調用的函數不知足綁定具體對象的綁定規則

直接使用函數名調用函數瀏覽器

function test () {
    console.log(this);  // window
}

test();

var test1 = function() {
    console.log(this);  // window
}

test1();
複製代碼

代碼傳送門app

定義某對象的屬性爲函數,該屬性值被賦值被另外一變量時。dom

var aObj = {
    propertyFn: function() {
        console.log(this.testName);
    },
    testName: "aObj"
};

aObj.propertyFn();  // "aObj"

var aFn = aObj.propertyFn;
aFn();  // undefined
複製代碼

代碼傳送門函數

綁定具體對象

綁定具體對象的狀況較多,可總結爲如下幾種ui

  • 函數做爲對象屬性被獲取並調用
  • 使用 new 關鍵字調用函數
  • 使用 call,apply,bind 調用函數
  • 函數被註冊爲事件監聽器和事件處理器
  • 箭頭函數中的 this

函數做爲對象屬性被獲取並調用

函數做爲對象屬性被獲取並調用時,函數中的 this 綁定的是獲取該屬性的對象。考慮以下代碼this

function test() {
    return this.testName;
}

var testObj = {
    testName: "testObj",
    getTestName: test,
    getTestNameFn: function() {
        return this.testName;
    }
}

console.log(testObj.getTestName()); // "testObj" 函數雖然是在全局做用域下定義的,
                                    // 可是被賦值給了testObj的getTestName屬性,且是被做爲對象的屬性調用的
console.log(testObj.getTestNameFn());   // "testObj"
複製代碼

代碼傳送門

使用 new 關鍵字調用函數

使用 new 關鍵字調用函數時,該函數會生成並返回以個新的對象,函數中的 this 綁定的生成的新對象。

function Test(name) {
    this.name = name;
    console.log(this);
}

var s = new Test("s");  // {name: "s"}
function Test1(name) {
    this.name = name;
    console.log(this);
    return true;
}

var s1 = new Test1("s1");   // {name: "s1"}

function Test2(name) {
    this.name = name;
    console.log(this);
    return {};
}

var s2 = new Test2("s2");   // {name: "s2"}
複製代碼

代碼傳送門

上述結果代表,使用 new 調用函數的時候必定會生成一個新對象,且 this 綁定的就是這個新對象,只不過當你在函數中 return 了非 Object 類型的值時,這個對象不會被賦值給你定義的接收變量,這時接收的變量被賦的是函數中使用 return 返回的值。

使用call ,apply,dind 調用函數

這裏call和apply的做用是相似的,都是函數的實例方法,可爲函數指定 this 綁定的對象,二者區別在於 apply 的第二個參數是數組,該數組中的值會以實參形式被傳遞給調用 apply 的函數,而 call 函數除了第一個參數外的參數均被傳遞給調用 call 的函數。

function test(param1, param2) {
  console.log(this.name, param1 + ", " + param2);
}

var a = {
  name: "a"
};

var b = {
  name: "b"
}

test.call(a, "aParam1", "bParam2");
test.apply(b, ["bParam1", "bParam2"]);
test();
複製代碼

代碼傳送門

bind函數的做用和以上二者與別很大,其做用是將函數中的 this 綁定對象與指定的對象綁定起來,返回一個函數,每次調用返回的函數時,其 this 都是綁定的指定對象。

function test() {
    console.log(this.name);
}

var a = {
    name: "a"
}

var bindTest = test.bind(a);

bindTest(); // "a"

var b = {
    name: "b",
    getName: bindTest
}

b.getName();    // "a"

var c = new bindTest(); // undefined

bindTest.call(b);   // "a"
複製代碼

代碼傳送門

從上面的示例代碼能夠看出,函數和指定對象被綁定後使用 new 關鍵字是綁定失效,在以上示例中綁定函數中的 this 綁定的是一個新建立的對象實例,且該對想的構造函數時test函數。由此也可得出,this 綁定場景同時出現的狀況下 new 的優先級是高於調用 bind 函數的優先級的。

箭頭函數

關於箭頭函數中 this 綁定,MDN 中的說法是箭頭函數是沒有本身的 this 的,其 this 是從其做用域鏈上層做用域繼承而來的。那麼怎麼理解呢?下面上代碼:

let arrowFn = () => {console.log(this === window)};
arrowFn();  // true

let a = {
    name: "a",
    getSelf: arrowFn
};
a.getSelf();    // true

let b = {
    name: "b"
}
arrowFn.call(b);    // true
複製代碼

代碼傳送門

以上代碼是箭頭函數直接在全局做用域下定義的狀況,那麼其做用域鏈上層就是全局做用域,而在瀏覽器中全局做用域 this 綁定的值是 window 。因爲 JavaScript 中的做用域是靜態做用域,那麼箭頭函數在全局做用域中定義時便已經能夠肯定其 this 就是 window 了,並且後面的該箭頭函數做爲對象屬性值被調用,仍是使用 call 顯示指定 this 其 this 均爲改變。而非箭頭函數的畫風是這樣的:

let fn = function() {
    console.log(this === window);
}
fn();   // true
let a = {
    name: "a",
    getSelf: fn
};
a.getSelf();    // false

let b = {
    name: "b"
}
fn.call(b);    // false

複製代碼

代碼傳送門

那麼是否是一旦箭頭函數被定義了,其 this 的綁定就已經被肯定了呢?

let createArrowFn = function() {
    return () => {console.log(this)};
}

let a = {
    name: "a",
    getSelf: createArrowFn
};
let aArrow = a.getSelf();   
aArrow();    // 對象a

let b = {
    name: "b"
}
var bArrow = createArrowFn.call(b);  
bArrow();   // {name: "b"}
複製代碼

代碼傳送門

上面代碼兩次 this 打印的結果是不同的,那麼是否是就推翻了箭頭函數一旦被定義,其 this 就已經肯定了的結論。其實否則,這裏箭頭函數的是上層做用域是createArrowFn這個函數的做用域,這個函數做用域中的 this 會隨着調用場景的不一樣發生發生變化,因此繼承其做用域綁定 this 的箭頭函數中的 this 天然也會發生改變了。其實箭頭函數中的 this 能夠這麼理解,至關於將函數的上層做用域的 this 用一個變量保存下來,而後在其子函數中使用它。

let fn = function() {
    var _this = this;
    return function() {
        console.log(_this);
    }
}

let a = {
    name: "a",
    getSelf: createArrowFn
};
let aArrow = a.getSelf();   
aArrow();    // 對象a

let b = {
    name: "b"
}
var bArrow = createArrowFn.call(b);  
bArrow();   // {name: "b"}
複製代碼

代碼傳送門

在理解箭頭函數中的 this 時只需理解其被定義時所在做用域的 this 綁定的是什麼就能夠了。

事件監聽器和事件處理器中的 this

先說明不論是事件監聽器仍是事件處理器中 this 綁定的都是當前觸發該事件的節點,即綁定了該事件的元素節點。

這裏主要是區分下事件監聽器和事件處理器,事件處理器實際上是指 html 標籤的 on... 屬性定義的函數,好比 onclick="function() {}",固然也能夠在 JavaScript 中去設置該屬性,事件處理器的特色是其只能有一個,由於是 html 標籤屬性因此能夠覆蓋。

事件監聽器是指使用addEventListener函數註冊的事件回調函數,可同時註冊多個。

結論

在討論 JavaScript 中 this 的綁定值時,其實就是幾種狀況:

  1. new 函數時this是新建立的對象
  2. call, apply, bind 指定的對象
  3. 調用該函數的對象
  4. 箭頭函數的 this 取決於其定義時所在做用域的 this 綁定值
  5. 事件監聽器和事件處理器的中的 this 綁定的是綁定函數節點
  6. 上面的狀況都不是時,嚴格模式下的 undefined ,非嚴格模式下的全局變量

以上總結僅僅爲粗淺的不一樣場景下 this 的綁定值的總結,沒有從更深的層次(好比ES標準中的定義)去討論,主要我的時間水平有限,因此大佬請忽略。若有錯誤歡迎各位指正,不勝感激。

相關文章
相關標籤/搜索