注意:本文章是我的《You Don’t Know JS》的讀書筆記。
在看backbone
源碼的時候看到這麼一小段,看上去很小,其實忽略了也沒有太大理解的問題。可是不知道爲何,我以爲內心很難受。因此我以爲必定要真正解決這個問題。這個問題就是原型。
就是下面這段代碼:git
var Surrogate = function(){ this.constructor = child; }; Surrogate.prototype = parent.prototype; child.prototype = new Surrogate;
看懂了這一段簡單的代碼了嗎?github
其實就着backbone
的註釋,理解徹底沒有問題。可是以後我不當心深究了一下,忽然發現本身對於這一個問題的理解還很不透徹。很慚愧,寫了好一陣子的JavaScript
,可是一直都是以實用主義的角度來寫的。不少時候真的把JavaScript
當成傳統有class
的語言來寫了。可是JavaScript
是一門很「狡猾」的語言,由於它的不少關鍵字彷彿都在透露本身是傳統面嚮對象語言,instance
,constructor
,new
等等。然而事實上並非。解決這個問題的方法就是讀書,我選了《You Don’t Know JS》。我快速過了相關的那一本,被這本書深深震撼到。本文是我的的學習筆記,也但願能幫助到你。chrome
this
是函數執行的時候,內部產生的一個內部對象。在狀況複雜的時候,this的指向會比較難把握。
this的指向經常不明,在書中主要列舉了兩種狀況的混淆和四條規則,掌握了這二者,this就會比較明晰了。(還有不少很複雜的狀況須要細緻的分析)閉包
1,this
不是函數對象自己。函數自己也是一個對象,可是給這個對象添加屬性並不能影響this
。除非foo.call(foo, i)
,這句代碼的含義是:在foo對象上調用foo函數,並傳遞參數i
。這是一個特殊狀況。
2,this
不是函數內部的做用域。所以在函數內部申明的變量若是不加其餘操做,和this
根本不會有任何關係。app
這四條規則只有一箇中心,就是call-site
。本質上就是指this
決定於調用的對象而不是在哪裏聲明。
1,直接調用函數。函數內部this
會指向全局。this
此時爲window
。閉包調用亦是如此。
2,內部綁定調用。如obj.foo()
。此時foo
中的this
指的就是obj
對象。注意obj.foo()
,無論前面是否是還有其餘調用,可是都不會改變this
此時爲obj
。
3,直接綁定調用。主要是用了call
和apply
來調用。除此以外還有先bind
在調用的方式,這種方式很是強。直接綁定調用的this
就是指定的那個參數的對象。
4,new
方式調用。var bar = new foo()
這一句代碼的做用之一就是把foo
函數中的this
所有賦到bar
上做爲新的bar
對象的屬性。ide
1,通常來講直接綁定調用會比內部綁定調用有更高優先級。
2,new
在必定狀況下能「覆蓋」bind
調用。用這個特性能夠實現函數currying
化。(函數傳入多個參數,在bind的時候傳入固定的變量)函數
function foo(val) { this.val = val; } var obj = {}; var bar = foo.bind(obj); bar("p1"); var baz = new bar("p2") console.log(baz.val); // p2
JavaScript
沒有類,也不能建立實例。可是語法上確實很是很是面向對象,讓人誤解。new
就是總被誤解。學習
var bar = new foo();
下面詳述這一句代碼執行的效果。這很是重要。
1,執行foo()
函數。是「構造方式調用」。注意,JavaScript
裏面並無真正的構造函數。
2,鏈接foo.prototype
和新對象bar
的[[prototype]]
。(至關於把foo.prototype
總體搬到(引用)bar.__proto__
裏面)
3,把foo
中的內部對象this
的屬性給bar
。
4,foo
若是有返回對象就返回那個對象,沒有就自動返回一個新的對象。對象特徵如2,3所述。
事實上,new
作得事太多,不少時候使人討厭。
注意:bar
和foo
函數對象自己的屬性沒有關係。this
在經典的教材上,JavaScript
的「繼承」經常使用下面的代碼:spa
Bar.prototype = new Foo();
這句代碼很經典,可是並很差。它當然可以完成原型鏈的鏈接,可是也產生了不少沒必要要的麻煩。Bar.prototype
上會帶上Foo
的this
,反作用大。所以書上建議是這樣作來完成繼承的:
Bar.prototype = Object.create(Foo.prototype);
A = Object.create(B)
的做用是把B
對象的屬性添加到A
的__proto__
上面,而後A
的[[prototype]]
與B
的鏈接。有個比較「直觀」的說法,就是把B
的「屬性」,添加到A.__proto__
上面,而後把B.__proto__
也添加進去(原型鏈延長的不嚴謹說法)。試着在chrome
裏面console
一下,就會發現此時的A
只有一個__proto__
屬性,裏面是B
的屬性和B.__proto__
。這樣就很好地完成了「繼承」,並且也沒有反作用了。下面提供一個降級寫法(原理很簡單。按照以前的幾條規則能夠很輕易地讀懂):
// Object.create if(!Object.create) { Object.create = function(o) { function F(){}; F.prototype = o; return new F(); } }
還須要注意的是constructor
屬性。若是對象是函數,這個屬性會自動出如今函數的prototype
裏面,但有時候會被覆蓋。
prototype
是函數對象的一個屬性。其實能夠理解成比較特殊的一個屬性,一個對象。這個屬性在用構造器方式調用(new
)的時候就用來鏈接新對象。鏈接到最後就是Object.prototype
,這是每個對象(不包括null
)中__proto__
的盡頭,原型鏈的盡頭。能夠想象,對象的聲明是new Object()
,那麼新的對象原型鏈天然就會有Object.prototype
了。若是想擺脫這個Object.prototype
,能夠用Object.create(null)
。
常常有這種狀況,調用一個對象的屬性的時候,若是在自己找不到就會沿着原型鏈找。直觀而不嚴謹地說,就是從__proto__
一直找下去。這也揭露了a instanceof foo
的本質。就是沿着原型鏈找,看看在a
的原型鏈上有沒有foo.prototype
的存在,有就返回true
。instanceof
這個英文很誤導,由於JavaScript
裏面根本上就算不上有實例這樣的東西。
做者對於JavaScript
的面向對象有一個很是驚豔的解決方案,在那以前先看看之前的方案是怎麼作的。
function Foo(who) { this.me = who; } Foo.prototype.identify = function() { return "I am " + this.me; }; function Bar(who) { Foo.call( this, who ); } Bar.prototype = Object.create( Foo.prototype ); Bar.prototype.speak = function() { alert( "Hello, " + this.identify() + "." ); }; var b1 = new Bar( "b1" ); var b2 = new Bar( "b2" ); b1.speak(); b2.speak();
一圖勝千言:
做者給出的解決方案OLOO
(objects-linked-to-other-objects
),沒有麻煩的new
,沒有虛僞的constructor
,沒有混淆視線的call
, apply
, bind
,原型鏈鏈接再也不赤裸裸。感受真心很棒(注意一點,JavaScript
對象的方法實際上是不是「屬於」一個對象值得商榷,由於所謂方法其實和這個對象關係並無想象中大):
var Foo = { init: function(who) { this.me = who; }, identify: function() { return "I am " + this.me; } }; var Bar = Object.create( Foo ); Bar.speak = function() { alert( "Hello, " + this.identify() + "." ); }; var b1 = Object.create( Bar ); b1.init( "b1" ); var b2 = Object.create( Bar ); b2.init( "b2" ); b1.speak(); b2.speak();
仍然是一圖勝千言:
最後來說講這個:
var Surrogate = function(){ this.constructor = child; }; Surrogate.prototype = parent.prototype; child.prototype = new Surrogate;
很好懂了吧。構造形式調用Surrogate
函數,把this
上的屬性交給child.prototype,這裏是constructor
;把Surrogate.prototype
的內容搬到child.prototype.__proto__
上面。而Surrogate.prototype
引用自parent.prototype
。因此child
是這樣的一個函數:以child
爲構造函數,parent.prototype
爲原型。
這個問題是老生常談的問題了,可是我以爲對於每個寫JavaScript
的人來講,這個問題是永遠避不開的。最後再次推薦《You Don’t Know JS》這一套開源書。能夠絕不誇張地說,這是自紅寶書和犀牛書以後講JavaScript
的最好書了。深度深得恰到好處,語言和例子清晰簡明,我還要繼續學習。
有關 this & Object Prototypes
,書的內容遠遠比我這篇文章豐富精彩,去讀吧。(我也要復反覆讀)
若是有任何錯誤請輕噴,相互學習~