Javascript 很全的this的用法

前言: 名字取得可能有點大,this 關鍵字是 JavaScript 中最複雜的機制之一。筆者也困擾已久,但自從閱讀了《你不知道的Javascript》之後豁然開朗,整理成文。如需更更詳細的解釋,請閱讀《你不知道的Javascript》第二部分第1章第2章。es6

綁定規則

默認綁定

最經常使用的函數調用類型:獨立函數調用。能夠把這條規則看做是沒法應用 其餘規則時的默認規則。app

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

foo() 是直接使用不帶任何修飾的函數引用進行調用的,所以只能使用 默認綁定,沒法應用其餘規則。
ps:不帶任何修飾的函數的描述若是有疑惑的話請不要糾結,繼續看下去就就會明白。框架

須要說明的一點是:若是使用嚴格模式(strict mode),那麼全局對象將沒法使用默認綁定,所以 this 會綁定 到 undefined:函數

function foo() {  
         "use strict";     
        console.log( this.a ); 
    } 
    var a = 2; 
    foo(); // TypeError: this is undefined

隱式綁定

另外一條須要考慮的規則是調用位置是否有上下文對象,或者說是否被某個對象擁有或者包 含,不過這種說法可能會形成一些誤導。oop

function foo() {      
    console.log( this.a ); 
    } 
 
var obj = {      
    a: 2,     
    foo: foo  
    }; 
 
obj.foo(); // 2  此處foo裏的this就是指的.前面的對象

ps:囉嗦兩句,爲何obj.foo()能夠執行,這是一種屬性的引用鏈的寫法,由於obj 和foo 都掛在全局做用域上。若是還不明白再舉一個例子。this

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

對象屬性引用鏈中只有最頂層或者說最後一層會影響調用位置.此處也就是obj2編碼

這裏說一個比較容易出錯的地方:es5

function foo() {      
    console.log( this.a ); 
    } 
var obj = {      
    a: 2,     
    foo: foo  
    }; 
    
var a = "oops, global"; // a 是全局對象的屬性 
 
 setTimeout( obj.foo, 100 ); // "oops, global

JavaScript 環境中內置的 setTimeout() 函數實現和下面的僞代碼相似:prototype

function setTimeout(fn,delay) { // 等待 delay 毫秒code

fn(); // <-- 調用位置! }

setTimeout的參數fn能夠看作調用時候傳入的函數複製給fn,這個是掛在全局做用域上的(此處的說法不嚴謹,不少時候框架的不一樣此處的this被綁定到哪兒很不肯定).因此,回調函數丟失 this 綁定是很是常見的。es6 的=>很好的解決了這個問題。

顯式綁定

JavaScript 提供的絕大多數函數以及你自 己建立的全部函數均可以使用 call(..) 和 apply(..) 方法。它們會把這個對象綁定到 this,接着在調用函數時指定這個 this。由於你能夠直接指定 this 的綁定對象,所以我 們稱之爲顯式綁定。

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

經過 foo.call(..),咱們能夠在調用 foo 時強制把它的 this 綁定到 obj 上。
若是你傳入了一個原始值(字符串類型、布爾類型或者數字類型)來看成 this 的綁定對 象,這個原始值會被轉換成它的對象形式(也就是 new String(..)、new Boolean(..) 或者 new Number(..))。這一般被稱爲「裝箱」。

ps:硬綁定只能執行一次,以後再綁別的都是綁不上去的。

因爲硬綁定是一種很是經常使用的模式,因此在 ES5 中提供了內置的方法 Function.prototype. bind,它的用法以下:

function foo(something) {      
    console.log( this.a, something );      
    return this.a + something; 
    } 
 var obj = {      
    a:2 
    }; 
    
var bar = foo.bind( obj ); 
 
var b = bar( 3 ); // 2 3  

console.log( b ); // 5

bind(..) 會返回一個硬編碼的新函數,它會把參數設置爲 this 的上下文並調用原始函數。

new綁定

對於js的new 初學者可能會把它和JAVA類的語言混淆
首先咱們從新定義一下 JavaScript 中的「構造函數」 。在 JavaScript 中,構造函數只是一些 使用 new 操做符時被調用的函數。它們並不會屬於某個類,也不會實例化一個類。實際上, 它們甚至都不能說是一種特殊的函數類型,它們只是被 new 操做符調用的普通函數而已。

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

2涉及到原型鏈的知識,本篇可忽視。

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

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

ps:此處可能看得比較費解,請閱讀《你不知道的js》第二部的關於類與原型鏈的章節,仍是比較複雜的,在此就不展開了。此處你能夠理解爲用函數new 了一個新對象,而後綁定到 函數的this上使用。

在 new 中使用硬綁定函數的通常用處

主要目的是預先設置函數的一些參數,這樣在使用 new 進行初始化時就能夠只傳入其他的參數。bind(..) 的功能之一就是能夠把除了第一個 參數(第一個參數用於綁定 this)以外的其餘參數都傳給下層的函數(這種技術稱爲「部 分應用」,是「柯里化」的一種)。舉例來講:

function foo(p1,p2) {          
    this.val = p1 + p2; 
    } 
 
// 之因此使用 null 是由於在本例中咱們並不關心硬綁定的 this 是什麼 
// 反正使用 new 時 this 會被修改 
var bar = foo.bind( null, "p1" ); 
 
var baz = new bar( "p2" );  
 
baz.val; // p1p2

優先級

咱們能夠根據優先級來判斷函數在某個調用位置應用的是哪條規則。能夠按照下面的 順序來進行判斷:
(數值越小優先級越高)

1 函數是否在 new 中調用(new 綁定)?若是是的話 this 綁定的是新建立的對象。

var bar = new foo()

2 函數是否經過 call、apply(顯式綁定)或者硬綁定調用?若是是的話,this 綁定的是 指定的對象。

var bar = foo.call(obj2)

3 函數是否在某個上下文對象中調用(隱式綁定)?若是是的話,this 綁定的是那個上 下文對象。

var bar = obj1.foo()

4 若是都不是的話,使用默認綁定。若是在嚴格模式下,就綁定到 undefined,不然綁定到 全局對象。

var bar = foo()

綁定例外

在某些場景下 this 的綁定行爲會出乎意料,你認爲應當應用其餘綁定規則時,實際上應用 的多是默認綁定規則。

被忽略的this

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

間接引用

另外一個須要注意的是,你有可能(有意或者無心地)建立一個函數的「間接引用」,在這 種狀況下,調用這個函數會應用默認綁定規則。

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()。

1

 軟綁定

用硬綁定以後就沒法使用隱式綁定或者顯式綁定來修改 this。
若是能夠給默認綁定指定一個全局對象和 undefined 之外的值,那就能夠實現和硬綁定相 同的效果,同時保留隱式綁定或者顯式綁定修改 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;      
     }; }

關於=>有話說

箭頭函數並非使用 function 關鍵字定義的,而是使用被稱爲「胖箭頭」的操做符 => 定 義的。箭頭函數不使用 this 的四種標準規則,而是根據外層(函數或者全局)做用域來決 定 this。
以前咱們有說過setTimeout的this綁定丟失的問題es6和es6之前的解決辦法以下,能夠比較着看
es6:

function foo() {      
setTimeout(() => {         // 這裏的 this 在此法上繼承自 foo()         
console.log( this.a ); },100); } 
 
var obj = {      
        a:2 
        }; 
 
foo.call( obj ); // 2

es5

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

ES6 中的箭頭函數並不會使用四條標準的綁定規則,而是根據當前的詞法做用域來決定 this,具體來講,箭頭函數會繼承外層函數調用的 this 綁定(不管 this 綁定到什麼)。這 其實和 ES6 以前代碼中的 self = this 機制同樣。

相關文章
相關標籤/搜索