JavaScript 系列之 this(一)

這是我參與8月更文挑戰的第11天,活動詳情查看:8月更文挑戰數組

每一個函數的 this 是在調用時被綁定的,徹底取決於函數的調用位置,調用位置就是函數在代碼中被調用的位置(而不是聲明的位置)。markdown

1、綁定規則

this 永遠指向最後調用它的那個對象app

關於 this 的誤解:this 指向函數自身、this 指向函數做用域。ide

  • this 是在運行時綁定的,徹底取決於函數的調用位置
  • 並非在編寫時綁定的,它的上下文取決於函數調用時的各類條件。

⚠️:做用域鏈依據詞法做用域,this 依據動態做用域。函數

1.1 默認綁定

當函數直接使用不帶任何修飾的函數引用進行調用時,則綁定到全局對象或 undefined(嚴格模式時)上oop

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

var a = 2;
foo();//2
複製代碼

1.2 隱式綁定

隱式綁定規則會把函數調用中的 this 綁定到這個上下文對象post

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

var obj2 = {
    a: 42,
    foo: foo
};

var obj1 = {
    a: 2,
    obj2: obj2
}

obj1.obj2.foo();//42
複製代碼

注意:對象屬性引用鏈中只有上一層或者說最後一層在調用位置中起做用ui

1.2.1 隱式丟失

被隱式綁定的函數會丟失綁定對象,就是說會默認綁定,從而把 this 綁定到全局對象或 undefined 上this

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

var obj = {
  a: 42,
  foo: foo
};

var bar = obj.foo; // 函數別名
var a = "oops";

bar(); // "oops"
複製代碼
// 傳入回調函數時
function foo() {    
  console.log(this.a);
}

function doFoo(fn) {
  // fn 其實引用的是 foo
  fn(); // <-- 調用位置
}

var obj = {
  a:42,
  foo: foo
};

var a = "oops";
doFoo(obj.foo);// oops
複製代碼
// setTimeout()也會丟失 this 綁定
function foo() {    
  console.log(this.a);
}

var obj = {
  a:42,
  foo: foo
};

var a = "oops";
setTimeout(obj.foo, 100);
複製代碼

回調函數丟失 this 綁定很是常見編碼

1.3 顯式綁定

利用 call(),apply()

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

var obj = {
  a:2
};

foo.call(obj); //2
複製代碼

區別在於第二個參數,call()是依次輸入參數,而apply()是一個參數數組

1.3.1 硬綁定

首先建立函數 bar(),並在它的內部手動調用了 foo.call(obj),所以強制把 foo 的 this 綁定到了 obj,不管以後如何調用函數 bar,它總會手動在 obj 上調用 foo。這種綁定是一種顯示綁定,稱爲硬綁定。

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

var obj = {
  a:2
};

var bar = function() {
  foo.call(obj);
}

bar(); // 2
setTimeout(bar, 100); // 2
bar.call(window); // 2
複製代碼

典型的應用場景就是建立一個包裹函數,負責接收參數並返回值。

function foo(something) {    
  console.log(this.a, something);
  return this.a + something;
}

var obj = {
  a: 2
}

var bar = function() {    
  return foo.apply(obj, arguments);
};

var b = bar(3); // 2 3
console.log(b);// 5
複製代碼

進而咱們能夠建立一個能夠重複使用的函數:

function foo(something) {    
  console.log(this.a, something);
  return this.a + something;
}

var obj = {
  a: 2
}

function bind(fn, obj) {
  return function() {
    return fn.apply(obj, arguments);
  }
}

var bar = bind(foo, obj);

var b = bar(3); // 2 3
console.log(b);// 5
複製代碼

以上也是 ES5 內置 Function.prototype.bind 的使用方法,bind() 會返回一個硬編碼的新函數,它會把你指定的參數設置爲 this 的上下文並調用原始函數。

1.3.2 API 調用的上下文

第三方庫的許多函數,以及 JavaScript 語言和宿主環境中許多新的內置函數,都提供了一種可選參數,一般被稱爲「上下文」。

function foo(el){
  console.log(el, this.id);
}

var obj = {
  id: 'a'
}

// 調用 foo 時把 this 綁定到 obj
[1, 2, 3].forEach(foo, obj);
// 1 a 2 a 3 a
複製代碼

1.4 new 綁定

實際上並不存在所謂的「構造函數」,只有對於函數的「構造調用」。

function foo(a) {    
  this.a = a;
}

var bar = new foo(2);
console.log(bar.a); // 2
複製代碼

使用 new 來調用 foo 時,咱們會構造一個新對象並把它綁定到 foo() 調用中的 this 上。

1.5 規則補充

1.5.1 優先級

new 綁定 > 顯式綁定 > 隱式綁定 > 默認綁定

1.5.2 綁定例外

如下應用的是默認綁定規則

call(null/undefined)
apply(null, [2,3])
bind(null2)
複製代碼

1.5.3 this 詞法

ES6 中的箭頭函數不會使用四條綁定規則,而是根據當前的詞法做用域來決定 this。

箭頭函數繼承外層函數調用的 this 綁定,這其實和 ES6 以前代碼中的 self = this 機制同樣。

function foo() {
  // 返回一個箭頭函數
  return (a) => {
    // this 繼承自 foo()
    console.log(this.a);
  };
}

var obj1 = {
  a: 2
};
var obj2 = {
  a: 3
};

var bar = foo.call(obj1);
bar.call(obj2); // 2
複製代碼

foo() 內部建立的箭頭函數會捕獲調用時 foo() 的 this。因爲 foo() 的 this 綁定到 obj1,bar(引用箭頭函數)的 this 也會綁定到 obj1,箭頭函數的綁定沒法修改。

箭頭函數實際上是沒有 this 的,這個函數中的 this 只取決於他外面的第一個不是箭頭函數的函數的 this。在下面這個例子中,由於調用 a 符合前面代碼中的第一個狀況,因此 this 是 window。而且 this 一旦綁定了上下文,就不會被任何代碼改變。

function a() {
  return () => {
    return () => {
      console.log(this)
    }        
  }
}

console.log(a()()()) // window
複製代碼
相關文章
相關標籤/搜索