1. 程式人生 > 程式設計 >JS中this的4種繫結規則詳解

JS中this的4種繫結規則詳解

JS中this是什麼

理解this之前, 先糾正一個觀點,this 既不指向函式自身,也不指函式的詞法作用域。

如果僅通過this的英文解釋,太容易產生誤導了。

它實際是在函式被呼叫時才發生的繫結,也就是說this具體指向什麼,取決於你是怎麼呼叫的函式。也就是說誰呼叫的this,this就指向誰

JS中this說明

ES6中的箭頭函式採用的是詞法作用域。

為什麼要使用this:使API設計得更簡潔且易於複用。

this即不指向自身,也不指向函式的詞法作用域。

this的指向只取決於函式的呼叫方式

this繫結規則

new > 顯示繫結 > 隱式繫結 > 預設繫結

預設繫結

當獨立函式呼叫時,不管是否在呼叫棧中,this都指向全域性物件(瀏覽器中為window)

嚴格模式下,不能將全域性物件用於預設繫結。

var a = 2;
function foo(){
  console.log(this.a);
}
function bar(){
  var a = 5;
  foo();
}
bar(); // 2

隱式繫結

當函式引用有上下文物件時,隱式繫結規則會把函式呼叫中的this繫結到這個上下文物件。

物件屬性引用鏈中只有最後一層在呼叫位置中起作用。

要求:物件內部必須包含一個指向函式的屬性,該物件可通過這個屬性間接引用函式。

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

隱式丟失

function foo() {
  console.log( this.a );
}
var obj = {
  a: 2,foo: foo
};
var bar = obj.foo; // 這裡bar將引用foo函式本身,所以不帶有函式物件的上下文
var a = "oops,global"; // a是全域性物件的屬性
bar(); // "oops,global"

和回撥函式的情況下(引數傳遞時的隱式賦值)

function foo() {
  console.log( this.a );
}
function doFoo(fn) {
  // 引數傳遞時,相當於fn = obj.foo,就和上個例子一樣了
  fn(); // <-- call-site!
}
var obj = {
  a: 2,foo: foo
};
var a = "oops,global"; // `a` also property on global object
doFoo( obj.foo ); // "oops,global"

顯式繫結

採用call()和apply(),通過傳入一個物件(若為基本型別,會被封裝函式轉為物件—裝箱),將this繫結到該物件。

硬繫結

function foo() {
  console.log( this.a );
}
var obj = {
  a: 2
};
var bar = function() {
  foo.call( obj );
};
bar(); // 2
setTimeout( bar,100 ); // 2
// 硬繫結後bar無論怎麼呼叫,都不會影響foo函式的this繫結
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 ); // 將obj物件硬編碼進去
};
var b = bar( 3 ); // 2 3
console.log( b ); // 5

即將內部函式用apply硬繫結到某個物件,無論怎麼呼叫這個包裹函式,都不會影響內部函式的this。

bind輔助函式如下:

function foo(something) {
  console.log( this.a,something );
  return this.a + something;
}
// simple `bind` helper
function bind(fn,obj) {
  return function() {
    return fn.apply( obj,arguments ); // 利用引數將obj傳入進去
  };
}
var obj = {
  a: 2
};
var bar = bind( foo,obj ); // bind( foo,obj )會返回一個包裹函式
var b = bar( 3 ); // 2 3
console.log( b ); // 5

總結:上述包裹函式,想要包裹其他函式,只能一個一個重複寫,硬編碼的方式導致不能被重用,當某種功能需要多次重複使用時,將其抽象出來,成為函式。

new繫結

任何函式都可能被用作建構函式,當函式被new操作符“構造呼叫”時,會執行下面操作:

1. 建立一個新物件(若該函式不是JS內建的,則建立一個新的Object物件);

2. 將this繫結到這個物件;

3. 執行建構函式中的程式碼(為這個新物件新增屬性);

4. 若函式沒有返回其他物件,則自動返回這個新物件;若函式有return返回的是非物件,則還是自動返回這個新物件,即覆蓋那個非物件。

function foo(a) {
  this.a = a;
}
var bar = new foo( 2 );
console.log( bar.a ); // 2

補充說明

間接引用

function foo() {
  console.log( this.a );
}
var a = 2;
var o = { a: 3,foo: foo };
var p = { a: 4 };
o.foo(); // 3
(p.foo = o.foo)(); // 2,由於p.foo = o.foo的返回值是目標函式的引用,所以呼叫位置是foo(),而不是p.foo()或o.foo()

箭頭函式:不使用這四個this規則,根據詞法作用域來決定this。

function foo() {
  // 返回一個箭頭函式
  return (a) => {
    // `this` here is lexically adopted from `foo()`
    console.log( this.a );
  };
}
var obj1 = {
  a: 2
};
var obj2 = {
  a: 3
};
// foo()不是箭頭函式,他的this被繫結到obj1
var bar = foo.call( obj1 ); // foo.call( obj1 )返回箭頭函式,所以bar為箭頭函式
bar.call( obj2 ); // 2! 箭頭函式的this無法被修改,new也不行

如下為和箭頭函式一樣的模式:

function foo() {
  var self = this; // lexical capture of `this`
  setTimeout( function(){
    console.log( self.a );
  },100 );
}
var obj = {
  a: 2
};

更多關於JS中this物件的相關文章大家可以點選下面的相關連結