JavaScript之this

南波發表於2018-10-21

本文共 1065 字,讀完只需 4 分鐘

概述

在 JAVA 中,this 的概念很透徹,就是指向當前物件(方法和屬性的持有者),在編譯的時候就能確定 this 指代,而由於 JavaScript 中 this 是動態繫結,或稱為執行期繫結的,在絕大多數情況下,函式的呼叫方式決定了 this 的值,所以在 JS 中不能在定義時決定地定義 this 是哪個上下文物件。

this 的指向只和函式的呼叫位置(方式)有關,和函式宣告的位置無關。

本文就從 this 應用場景呼叫方式的角度,解析 this 的用法。

首先,要知道,this 的作用是什麼?

函式體內部,指代函式當前的執行上下文。

一、全域性上下文

在全域性上下文中,this 指向全域性物件,在瀏覽器中指向 window,在 NodeJs 中指向 global。

var a = "全域性";
console.log(this)  // 瀏覽器:window
console.log(this)  // nodejs:global
console.log(this.a)  // "全域性"
複製程式碼

這是 JS 中的預設繫結,在全域性上下文中,this 會預設繫結到 全域性物件。

二、函式宣告

在非嚴格模式下,未加關鍵字 var 宣告的變數,會成為全域性物件下的屬性,所以,此時 在嚴格模式下,this 將保持他進入執行上下文時的值,所以下面的 this 將會預設為 undefined。

function foo() {
    "use strict";
    console.log(this);
}

foo();  // undefined 嚴格模式
複製程式碼

上述也是 JS 中 this 的預設繫結,在函式定義中,函式中的 this 會預設繫結到全域性物件或者 undefined。

當函式作為物件裡的方法被呼叫時,函式中 this 是呼叫該函式的物件。

function foo() {
    console.log("foo");
}

var obj = {
    this.bar = "bar";
    this.foo = foo;
}

obj.foo(); // "foo"
複製程式碼

在呼叫位置,是 obj 物件呼叫了 foo 函式,此時 this 就指向了 obj 物件。

function foo() {
    console.log(this.bar);
}

var obj2 =  {
    bar: "2",
    foo: foo
}

var obj1 = {
    bar: "1",
    obj2: obj2
}

obj1.obj2.foo()  // "2";
複製程式碼

這是 JS 中 this 的隱式繫結,this 會隱式繫結到呼叫的最近一層上下文物件。

三、call & apply

其實在函式中,函式正確的呼叫應該是用 call, apply 函式的形式。

function foo() {
    console.log(this.bar);
}

let obj = {
    bar: "1",
    foo: foo
}

obj.foo();  // `.` 點呼叫其實是語法糖

foo.call(obj)  // 1 正確的姿勢
foo.apply(obj) // 1
複製程式碼

這是 JS 中 this 的顯式繫結,函式通過從 call 和 apply 函式可以顯式指定函式的呼叫物件。

ES5 提供了一個 bind()方法:

let obj2 = {
    a: 2
}

let foo = function () {
    console.log(this.a);
}

let bar = foo.bind(obj2);
bar() // 2;
複製程式碼

就是 JS 中 this 的硬繫結,bind() 函式會返回一個指定了呼叫物件的函式,返回的函式的被指定了 this 且 this 無法改變。

四、建構函式

當函式加上關鍵字 new 後,函式會變成一個建構函式,此時建構函式中的 this 指向即將建立的物件例項,同時物件例項會關聯到建構函式的原型物件 prototype 上。

function Foo() {
    this.a = 3;
}

var obj = new Foo();

obj.a // 3
複製程式碼

五、箭頭函式

ES6 中,提供了函式定義的語法糖,讓定義函式變得更簡潔(沒有 arguments, 沒有原型)。同時上面四條繫結規則在箭頭函式中不適用,使得 this 的查詢更可控。

// 一般函式:
var a = 1;
var obj = {
  a: 2,
  say: function() {
    console.log(this.a)
  }
}
obj.say();  // console.log 列印值為 2

// 箭頭函式
var a = 1;
var obj = {
  a: 2,
  say: () => {
    console.log(this.a)
  }
}
obj.say(); // 1   !!!
複製程式碼

由於箭頭函式定義時, obj {} 不是執行上下文,say 變數引用的箭頭函式,其 this 是父級執行上下文,也就是全域性上下文,所以 this.a 就是 全域性變數 a: 1;

在箭頭函式中,this 與封閉詞法上下文的 this 保持一致,this 被永久繫結到了它最近一層非箭頭函式的 this,而與函式的呼叫位置無關。

總結

在絕大多數情況下,函式的呼叫方式決定了 this 的值,但最終都指向了呼叫了它的那個上下文物件。

歡迎關注我的個人公眾號“謝南波”,專注分享原創文章。

JavaScript之this

掘金專欄 JavaScript 系列文章

  1. JavaScript之變數及作用域
  2. JavaScript之宣告提升
  3. JavaScript之執行上下文
  4. JavaScript之變數物件
  5. JavaScript原型與原型鏈
  6. JavaScript之作用域鏈
  7. JavaScript之閉包
  8. JavaScript之this
  9. JavaScript之arguments
  10. JavaScript之按值傳遞
  11. JavaScript之例題中徹底理解this
  12. JavaScript專題之模擬實現call和apply

相關文章