[譯] Javascript 中多樣的 this

Javascript 中多樣的 this

本文將盡可能解釋清楚 JavaScript 中最基礎的部分之一:執行上下文(execution context)。若是你常常使用 JS 框架,那理解 this 更是錦上添花。但若是你想更加認真地對待編程的話,理解上下文無疑是很是重要的。javascript

咱們能夠像日常說話同樣來使用 this。例如:我會說「我媽很不爽,這(this)太糟糕了」,而不會說「我媽很不爽,我媽很不爽這件事太糟糕了」。理解了 this 的上下文,纔會理解咱們爲何以爲很糟糕。html

如今試着把這個例子與編程語言聯繫起來。在 Javascript 中,咱們將 this 做爲一個快捷方式,一個引用。它指向其所在上下文的某個對象或變量。前端

如今這麼說可能會讓人不解,不過很快你就能理解它們了。java

全局上下文

若是你和某人聊天,在剛開始對話、沒有作介紹、沒有任何上下文時,他對你說:「這(this)太糟糕了」,你會怎麼想?大多數狀況人們會試圖將「這(this)」與周圍的事物、最近發生的事情聯繫起來。react

對於瀏覽器來講也是如此。成千上萬的開發者在沒有上下文的狀況下使用了 this。咱們可憐的瀏覽器只能將 this 指向一個全局對象(大多數狀況下是 window)。android

var a = 15;
console.log(this.a);
// => 15
console.log(window.a);
// => 15複製代碼

[以上代碼需在瀏覽器中執行]ios

函數外部的任何地方都爲全局上下文,this 始終指向全局上下文(window 對象)。git

函數上下文

以真實世界來類比,函數上下文能夠當作句子的上下文。「我媽很不爽,這(this)很不妙。」咱們都知道這句話中的 this 是什麼意思。其它句子中一樣能夠使用 this,可是因爲其處於所處上下文不一樣於是意思全然不一樣。例如,「風暴來襲,這(this)太糟糕了。」github

JavaScript 的上下文與對象有關,它取決於函數被執行時所在的對象。所以 this 會指向被執行函數所在的對象。編程

var a = 20;

function gx () {
    return this;
}

function fx () {
    return this.a;
}

function fy () {
    return window.a;
}

console.log(gx() === window);
// => True
console.log(fx());
// => 20
console.log(fy());
// => 20複製代碼

this 由函數被調用的方式決定。如你所見,上面的全部函數都是在全局上下文中被調用。

var o = {
  prop: 37,
  f: function() {
    return this.prop;
  }
};

console.log(o.f());
// => 37複製代碼

當一個函數是做爲某個對象的方法被調用時,它的 this 指向的就是這個方法所在的對象。

function fx () {
    return this;
}

var obj = {
    method: function () {
        return this;
    }
};

var x_obj = {
    y_obj: {
        method: function () {
            return this;
        }
    }
};

console.log(fx() === window);
// => True — 咱們仍處於全局上下文中。
console.log(obj.method() === window);
// => False — 函數做爲一個對象的方法被調用。
console.log(obj.method() === obj);
// => True — 函數做爲一個對象的方法被調用。
console.log(x_obj.y_obj.method() === x_obj)
// => False — 函數做爲 y_obj 對象的方法被調用,所以 `this` 指向的是 y_obj 的上下文。複製代碼

例 4

function f2 () {
  'use strict'; 
  return this;
}

console.log(f2() === undefined);
// => True複製代碼

在嚴格模式下,全局做用域的函數在全局做用域被調用時,thisundefined

例 5

function fx () {
    return this;
}

var obj = {
    method: fx
};

console.log(obj.method() === window);
// => False
console.log(obj.method() === obj);
// => True複製代碼

與前面的例子同樣,不管函數是如何被定義的,在這兒它都是做爲一個對象方法被調用。

例 6

var obj = {
    method: function () {
        return this;
    }
};

var sec_obj = {
    method: obj.method
};

console.log(sec_obj.method() === obj);
// => False
console.log(sec_obj.method() === sec_obj);
// => True複製代碼

this 是動態的,它能夠由一個對象指向另外一個對象。

例 7

var shop = {
  fruit: "Apple",
  sellMe: function() {
    console.log("this ", this.fruit);
// => this Apple
    console.log("shop ", shop.fruit);
// => shop Apple
  }
}

shop.sellMe()複製代碼

咱們既能經過 shop 對象也能經過 this 來訪問 fruit 屬性。

例 8

var Foo = function () {
    this.bar = "baz"; 
};

var foo = new Foo();

console.log(foo.bar); 
// => baz
console.log(window.bar);
// => undefined複製代碼

如今狀況不一樣了。new 操做符建立了一個對象的實例。所以函數的上下文設置爲這個被建立的對象實例。

Call、apply、bind

依舊以真實世界舉例:「這(this)太糟糕了,由於我媽開始不爽了。」

這三個方法可讓咱們在任何期許的上下文中執行函數。讓咱們舉幾個例子看看它們的用法:

例 1

var bar = "xo xo";

var foo = {
    bar: "lorem ipsum"
};

function test () {
    return this.bar;
}

console.log(test());
// => xo xo — 咱們在全局上下文中調用了 test 函數。
console.log(test.call(foo)); 
// => lorem ipsum — 經過使用 `call`,咱們在 foo 對象的上下文中調用了 test 函數。
console.log(test.apply(foo));
// => lorem ipsum — 經過使用 `apply`,咱們在 foo 對象的上下文中調用了 test 函數。複製代碼

這兩種方法都能讓你在任何須要的上下文中執行函數。

apply 可讓你在調用函數時將參數以不定長數組的形式傳入,而 call 則須要你明確參數。

例 2

var a = 5;

function test () {
    return this.a;
}

var bound = test.bind(document);

console.log(bound()); 
// => undefined — 在 document 對象中沒有 a 這個變量。
console.log(bound.call(window)); 
// => undefined — 在 document 對象中沒有 a 這個變量。在這個狀況中,call 不能改變上下文。

var sec_bound = test.bind({a: 15})

console.log(sec_bound())
// => 15 — 咱們建立了一個新對象 {a:15},並在此上下文中調用了 test 函數。複製代碼

bind 方法返回的函數的下上文會被永久改變。
在使用 bind 以後,其上下文就固定了,不管你再使用 call、apply 或者 bind 都沒法再改變其上下文。

箭頭函數(ES6)

箭頭函數是 ES6 中的一個新語法。它是一個很是方便的工具,不過你須要知道,在箭頭函數中的上下文與普通函數中的上下文的定義是不一樣的。讓咱們舉例看看。

例 1

var foo = (() => this);
console.log(foo() === window); 
// => True複製代碼

當咱們使用箭頭函數時,this 會保留其封閉範圍的上下文。

例 2

var obj = {method: () => this};

var sec_obj = {
  method: function() {
    return this;
  }
};

console.log(obj.method() === obj);
// => False
console.log(obj.method() === window);
// => True
console.log(sec_obj.method() === sec_obj);
// => True複製代碼

請注意箭頭函數與普通函數的不一樣點。在這個例子中使用箭頭函數時,咱們仍然處於 window 上下文中。
咱們能夠這麼看:

x => this.y equals function (x) { return this.y }.bind(this)

能夠將箭頭函數看作其始終 bind 了函數外層上下文的 this,所以不能將它做爲構造函數使用。下面的例子也說明了其不一樣之處。

例 3

var a = "global";

var obj = {
 method: function () {
   return {
     a: "inside method",
     normal: function() {
       return this.a;
     },
     arrowFunction: () => this.a
   };
 },
 a: "inside obj"
};

console.log(obj.method().normal());
// => inside method
console.log(obj.method().arrowFunction());
// => inside obj複製代碼

當你瞭解了函數中動態(dynamic) this 與詞法(lexical)this ,在定義新函數的時候請三思。若是函數將做爲一個方法被調用,那麼使用動態 this;若是它做爲一個子程序(subroutine)被調用,則使用詞法 this

譯註:瞭解動態做用域與詞法做用域可閱讀此文章

相關閱讀


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOSReact前端後端產品設計 等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索