《你不知道的javascript(中)》this和對象模型(一)

在翻閱《你不知道的javascript》這一套書的中上卷目錄之后,發現書中針對閉包、對象、原型、語法、異步、回調等等既基礎又重要的
javascript知識有著針對性的闡述,于是決定對這套書的中上卷進行學習。上卷和中卷各講述了兩大部分知識,分別是:作用域與閉包、
this和對象原型、類型和語法、異步和性能。本文是對this和對象原型的學習總結。

this,同樣是js語法中一個超級bug的詞匯,我表示每次看代碼的時候,一看到this就頭大,因為老是搞不準它綁定的對象會是誰。在《你不知道的javascript(上)》一書中,對this的綁定分了四種情況,并且進行了優先級劃分,同樣是對每種情形使用案例來講解,如果只是知道this綁定的規則,還是挺容易的,困難的是能夠實際應用到代碼中。

對于this的綁定機制,關鍵點是找到調用包含this語法函數的調用位置,然后根據調用位置來確定this綁定的對象。先上結論:

  • this一共有4中綁定方式:默認綁定,隱式綁定,顯式綁定,new綁定。其中綁定優先級是:new綁定>顯式綁定>隱式綁定>默認綁定。也就是說上面情況存在并列或以上時,按照優先級來確定this綁定的規則;
  • 默認綁定:在獨立函數調用的情況下(也就是調用語句沒有上下文對象),使用默認綁定規則,及this綁定全局作用域。需要注意的是,嚴格模式下并不存在默認綁定(this會綁定到undefind),“嚴格模式下”指的是包含this語句的函數體使用嚴格模式,如果是調用位置的作用域使用的嚴格模式,則不影響默認綁定。
  • 隱式綁定:當包含this的函數倍調用的語句中存在上下文對象時,this綁定其上下文對象,注意只綁定上一層,例如obj1.obj2.foo(),假設foo()中含有this語句,this綁定的是obj2,而不是obj1。
  • 顯示綁定:使用call()以及apply()兩個語句來調用包含this的函數時,this被綁定到兩個語句中指定的對象之上。(此處需要知道兩個語句的用法
  • new綁定:使用new來調用包含this的函數作為對象,來初始化另一個對象時,this被綁定到這個初始化的對象上面。

以上,其實仔細研究確實很容易明白的,但是具體起來,就會衍生出很多例外和實際應用了,例如顯式綁定中的硬綁定。

1.默認綁定

  最簡單的一種情況,時刻注意函數體中是否使用了嚴格模式。此外,請看以下代碼:

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

盡管調用位置在另一個函數體內,但是沒有上下文對象,this綁定的還是全局對象。

2.隱式綁定

  也是一種很簡單的情形,不過,會存在一種this丟失的情況。究其原因,主要是因為對函數的賦值,傳遞的實際是其引用位置,最終參數的指向都是堆中的同一個位置(js中對內存劃分兩個區域,堆和棧。堆用于存放引用類型的值,既對象等;棧用于存放基本類型的值,以及對引用類型值的引用指針

  如下代碼:

function foo(){
  console.log(this.a);
}
var obj = {
  a:2;
  foo:foo
}
var bar = obj.foo;//此處的傳參是將foo的引用位置直接傳給了bar;
var a = "oops,global";
bar();//"oops,global"

因為直接傳遞了引用位置,所以bar()調用foo()就和obj不存在關系了,則此時直接判斷bar(),屬于默認綁定。

3.顯式綁定

  顯式綁定的規則已在上面敘述,注意顯式綁定中存在的一種硬綁定情況(一般的顯示綁定1依舊會出現this丟失,所以出現硬綁定概念,既不會出現this丟失),如下代碼:

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

無論如何調用bar,this綁定的都是obj。硬綁定的引用就是可以創建一個重復利用的輔助函數,ES5中內置的方法bind()原理就是如此。在一些筆試題中會出現使用原生js模擬bind()的題,建議可以記一下如下代碼:

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

注意這段代碼和上段代碼的區別,區別正是使用bind()的意義。其區別是將foo和obj作為參數傳給bind(),那么任何一個包含有this的函數作為參數傳遞給bind(),其this都會綁定到另一個參數obj來執行(在這種情況下,綁定對象和包含this函數兩者就都可以變化了)。這就是bind()的意義。這段代碼和ES5內置的bind()還存在差別,內置bind()的實際寫法是foo.bind(obj)。

4.new綁定

  首先需要清楚new的使用意義,使用new來調用函數,會自動執行下面操作:

  • 創建(或者構造)一個全新對象;
  • 這個新對象會被執行[[Prototype]]連接;
  • 新對象會綁定到函數調用的this;
  • 如果函數沒有返回其他對象,那么new表達式中的函數調用會自動返回這個新對象。

如上,已經清楚解釋了this會綁定到誰。

搞清楚this綁定的四種規則,以及優先級,應該說對一般的函數使用及判定已經夠用了,此外還需要注意ES6中的箭頭函數,不適用于以上四種規則,
其綁定是根據外層作用域來決定this,且綁定無法被修改。在書中還提到了柯里化、軟綁定等一些概念,如有興趣,可以自行查書進行學習。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容