筆記-你不知道的JS-this

1 this的概念

this 在任何狀況下都不指向函數的詞法做用域。做用域「對象」沒法經過 JavaScript代碼訪問,它存在於 JavaScript 引擎內部。數組

this 是在運行時進行綁定的,並非在編寫時綁定,它的上下文取決於函數調用時的各類條件。this 的綁定和函數聲明的位置沒有任何關係,只取決於函數的調用方式。app

當一個函數被調用時,會建立一個活動記錄(有時候也稱爲執行上下文)。這個記錄會包含函數在哪裏被調用(調用棧)、函數的調用方法、傳入的參數等信息。this 就是記錄的其中一個屬性,會在函數執行的過程當中用到。函數

2 this的綁定

函數的執行過程當中調用位置如何決定 this 的綁定對象,綁定規則:oop

1 默認綁定this

function foo(){
  console.log(a);
}
var a=2;
foo();

foo() 是直接使用不帶任何修飾的函數引用進行調用的,所以只能使用默認綁定,沒法應用其餘規則。若是使用嚴格模式(strict mode),那麼全局對象將沒法使用默認綁定,所以 this 會綁定
到 undefined。prototype

2 隱式綁定
調用位置是否有上下文對象,或者說是否被某個對象擁有或者包含。當 foo() 被調用時,它的落腳點確實指向 obj 對象。當函數引用有上下文對象時,隱式綁定規則會把函數調用中的 this 綁定到這個上下文對象。由於調用 foo() 時 this 被綁定到 obj,所以 this.a 和 obj.a 是同樣的。對象屬性引用鏈中只有最頂層或者說最後一層會影響調用位置。code

function fooo(){
  conosle.log(this.a);
}
var obj={
  a:2,
  foo:foo
};
var obj2={
  a:22,
  obj: obj
}
obj.foo();  // 2
obj2.obj.foo();  // 2

隱式丟失
雖然 bar 是 obj.foo 的一個引用,可是實際上,它引用的是 foo 函數自己,所以此時的bar() 實際上是一個不帶任何修飾的函數調用,所以應用了默認綁定。對象

當函數被做爲參數傳遞時(如:回調函數),會丟失this,由於參數傳遞其實就是一種隱式賦值繼承

function foo() { 
  console.log( this.a );
}
var obj = { 
  a: 2,
  foo: foo
};
var bar = obj.foo; // 函數別名!
var a = "oops, global"; // a 是全局對象的屬性 
bar(); // "oops, global"

3 顯式綁定
JavaScript 提供的絕大多數函數以及你本身建立的全部函數均可以使用 call(..) 和 apply(..) 方法。這兩個方法是如何工做的呢?它們的第一個參數是一個對象,它們會把這個對象綁定到this,接着在調用函數時指定這個 this。由於你能夠直接指定 this 的綁定對象,所以咱們稱之爲顯式綁定。ip

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

若是你傳入了一個原始值(字符串類型、布爾類型或者數字類型)來看成 this 的綁定對象,這個原始值會被轉換成它的對象形式(也就是new String(..)、new Boolean(..)或者new Number(..))。這一般被稱爲「裝箱」。

4 new 綁定
使用 new 來調用函數,或者說發生構造函數調用時,會自動執行下面的操做。

  1. 建立(或者說構造)一個全新的對象。
  2. 這個新對象會被執行[[原型]]鏈接。
  3. 這個新對象會綁定到函數調用的this。
  4. 若是函數沒有返回其餘對象,那麼new表達式中的函數調用會自動返回這個新對象。
function foo(a) { 
  this.a = a;
}
var bar = new foo(2); 
console.log( bar.a ); // 2

使用 new 來調用 foo(..) 時,咱們會構造一個新對象並把它綁定到 foo(..) 調用中的 this上。new 是最後一種能夠影響函數調用時 this 綁定行爲的方法,咱們稱之爲 new 綁定。

3 判斷this綁定


注意:若是你把 null 或者 undefined 做爲 this 的綁定對象傳入 call、apply 或者 bind,這些值在調用時會被忽略,實際應用的是默認綁定規則。

那麼什麼狀況下你會傳入 null 呢?一種很是常見的作法是使用 apply(..) 來「展開」一個數組,並看成參數傳入一個函數。相似地,bind(..) 能夠對參數進行柯里化(預先設置一些參數),這種方法有時很是有用:

function foo(a,b) {
console.log( "a:" + a + ", b:" + b );
}
// 把數組「展開」成參數
foo.apply( null, [2, 3] ); // a:2, b:3
// 使用 bind(..) 進行柯里化
var bar = foo.bind( null, 2 ); 
bar( 3 ); // a:2, b:3

軟綁定會對指定的函數進行封裝,首先檢查調用時的 this,若是 this 綁定到全局對象或者 undefined,那就把指定的默認對象 obj 綁定到 this,不然不會修改 this。

if (!Function.prototype.softBind) { 
  Function.prototype.softBind = function(obj) {
    var fn = this;
    // 捕獲全部 curried 參數
    var curried = [].slice.call( arguments, 1 ); var bound = function() {
      return fn.apply(
      (!this || this === (window || global)) ?
        obj : this
        curried.concat.apply( curried, arguments )
      ); 
    };
    bound.prototype = Object.create( fn.prototype );
    return bound; };
}

4 箭頭函數

箭頭函數並非使用 function 關鍵字定義的,而是使用被稱爲「胖箭頭」的操做符 => 定義的。箭頭函數不使用 this 的四種標準規則,而是根據外層(函數或者全局)做用域來決定 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, 不是 3 !

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

相關文章
相關標籤/搜索