JavaScript 之this關鍵字

首發地址: https://github.com/jeuino/Blo...

前言

本篇文章將介紹 JavaScript 執行上下文中,最後一個重要的屬性 —— this。javascript

this

《JavaScript 之做用域與做用域鏈》中,介紹了詞法做用域與動態做用域,JavaScript 是基於詞法做用域的,而 this 的行爲卻與動態做用域相似,this 的指向是基於調用棧的。html

this 是在運行時進行綁定的,而不是在編寫時綁定,它的指向取決於函數是怎麼被引用的。java

this 綁定的類型能夠分爲如下幾類:git

  • 隱式綁定
  • 顯示綁定
  • new 綁定

咱們直接經過例子來看 this 是如何綁定的。(多例預警,部分示例參考書籍《你不知道的JavaScript》github

隱式綁定

eg1.1:數組

function foo() {
    console.log(this.a);
}
var a = 1;
foo(); // 1

輸出:1
緣由
Imageapp

若是你不清楚變量和函數在內存中是如何存儲的,請閱讀 《JavaScript 以內存空間》

經過 foo() 調用函數時:解釋器首先會在棧中找到 foo 函數的引用地址,而後根據引用地址到堆中找到函數並執行。這種直接在詞法做用域中查找函數引用並調用的方式,this 會指向全局對象 window。函數

注意:在嚴格模式下,this 的值爲 undefined。此處的嚴格模式是指,函數體處於嚴格模式下,this 的值爲 undefined。不然 this 會被綁定到 Window 對象。ui

eg1.2:鞏固例,輸出結果的緣由自行分析呦this

function foo2() {
    console.log(this.a);
}
function foo() {
    var a = 'foo';
    foo2();
}
var a = 1;
foo(); // 1

eg2.1:

function foo() {
    console.log(this.a);
}
var obj = {
    a: 1,
    foo: foo
};
var a = 'global'
obj.foo(); // 1

輸出:1
緣由
Image  2

經過 obj.foo() 調用函數時:解釋器首先會在棧中找到 obj 對象的引用地址,而後根據引用地址到堆中找到原始對象,而後再根據 obj.foo 屬性值(屬性值爲 foo 函數引用地址)在堆中找到函數並執行。

foo 函數是經過對象 obj 來引用的,也就是說,它的調用位置是 obj 對象,因此 this 指向 obj 對象。

eg2.2:鞏固例,輸出結果的緣由自行分析呦

function foo() {
    console.log(this.a);
}
var obj1 = {
    foo: foo,
    a: 1
};
var obj2 = {
    obj1: obj1,
    a: 2
};
obj2.obj1.foo(); // 1

eg3.1:

function foo() {
    console.log(this.a);
}
var obj = {
    foo: foo,
    a: 1
};
var a = 'global';
var bar = obj.foo; 
bar(); // 'global'

輸出:'global'
緣由
Image  3

我想你們經過上圖應該就已經明白爲何輸出的是'global'了。(參考 eg1.1

eg3.2:鞏固例,輸出結果的緣由自行分析呦

function foo() {
    console.log(this.a);
}
function doFoo(fn) {
    fn();
}
var obj = {
    foo: foo,
    a: 1
};
var a = 'global';
doFoo(obj.foo); // 'global'

總結:

隱式綁定有兩種形式:

  • 直接在詞法做用域中引用函數 -> this 指向 window 對象
  • 經過對象屬性間接引用函數 -> this 指向這個對象

顯示綁定

顯示綁定 this 的幾種方法:

  • call(...) 方法
  • apply(...) 方法
  • bind(...) 方法 (ES5)

call/apply 方法

舉例說明:

function foo() {
    console.log(this.a);
}
var obj = {
    a: 1
};
foo.call(obj); // 1
// or
foo.apply(obj); // 1

經過 call apply 方法,能夠在調用 foo 時,強制將它的 this 綁定到 obj 上。

call apply 方法的功能是同樣的,它們的區別在於傳遞參數的格式不一樣。 call 逐個傳遞單個參數( foo.call(obj,1,2,3)), apply一次性傳遞一個參數數組( foo.apply(obj,[1,2,3]))。

若是 call 和 apply 中傳入的第一個參數是原始值(eg: 'str', true, 1),那麼這個原始值會被轉換成它的對象形式(也就是 new String()、new Boolean()、new Number() ..),這個過程一般被稱爲 「裝箱」 操做。

bind 方法

使用 bind(...) 方法,會返回一個指定 this 後的函數,該函數可重複使用。

舉例說明:

function foo() {
    console.log(this.a);
}
var obj = {
    a: 1
};

var bar = foo.bind(obj);

bar(); // 1

特殊狀況

若是將 null 或者 undefined 做爲 this 的綁定對象傳入到 call/apply/bind 中,此時 this 指向 window。

new 綁定

使用 new 操做符來調用函數時,也就是咱們常常說的發生構造函數調用時,會進行如下操做:

  • 建立一個全新的對象;
  • 這個新對象會被執行[[Prototype]]鏈接;
  • 這個新對象會被綁定到調用的函數的 this 上;
  • 若是函數沒有返回其餘對象,那麼 new 表達式中的函數調用會自動返回這個新對象;
function Foo(a) {
    this.a = a;
}

var obj = new Foo(1);

console.log(obj.a); // 1

簡單來講,就是使用 new 操做符來調用函數 Foo 時,會建立一個新的對象並把它綁定到 Foo 函數執行上下文中的 this 上。

優先級

以上四條 this 綁定規則的優先級以下:

  1. new 綁定
  2. 顯示綁定 call/apply/bind
  3. 隱式綁定:經過對象屬性間接調用函數
  4. 隱式綁定:直接在詞法做用域中調用函數,這種綁定方式也被稱爲默認綁定

咱們再來思考一個例子:

function foo() {
    console.log(this.a);
}
var a = 1;
var obj = {
    a: 'obj',
    foo: foo
};
var bar = {
    a: 'bar'
};

(bar.foo = obj.foo)(); // 1

賦值表達式 bar.foo = obj.foo 的返回值是 obj.foo 的值,也就是目標函數 foo 的引用。所以,調用位置等價於 foo() ,因此這裏應用的是默認綁定,輸出結果爲 1。

下一篇

在每一個執行上下文中,都包括三個重要的屬性:

  • 變量對象(Variable Object,VO)
  • 做用域鏈(Scope Chain)
  • this指向

截止到此篇文章,執行上下文中的三個屬性就都介紹完啦。下篇文章將開始介紹執行上下文的生命週期,敬請期待。

參考:

JavaScript 的 this 原理 《你不知道的 JavaScript》上
相關文章
相關標籤/搜索