JS大法好,JS在手,天下我有,信JS,得永生。
這個系列的教程我一開始是寫在github上的,
但是覺得放到掘金來可以讓更多需要的人看到,
就搬到掘金專欄上啦,
如果覺得本教程對你有幫助,請點這裡去github上給我一顆Star~
教程目錄也在github上哈~
本著對技術負責的態度,任何糾正/疑問,儘管提出,我會及時修正/回答。
一定要把每個例子程式碼都拷貝到你的執行環境中邊看結果邊理解,不然學習效果減半,或者沒效果。
下面開始第三篇:
不得不說的原型與原型鏈:prototype與__proto__
1、紙面意思
啥是原型,啥是原型鏈?
原型:一個屬性,屬性名叫prototype,只有建構函式有,比如Foo.prototype;
原型鏈:一個屬性,屬性名叫__proto__,萬物皆有,鏈狀相連,最後歸宗到Object.prototype上,Object.prototype__proto__值為null;
2、嘗試理解一下這兩個點,記住一定配合控制檯去列印。
訪問一個例項的屬性,JS會先在例項內部尋找,找不到的話,沿著原型鏈繼續找下去。
function Foo(){this.name='我是建構函式Foo'} //一個建構函式Foo var foo = new Foo(); //通過new Foo(),得到例項化的foo console.log(foo.bar); //訪問foo的bar屬性,控制檯列印一下,得到undefined Foo.prototype.bar = '我是bar屬性,在Foo.prototype(Foo的原型)裡'; //給Foo.prototype新增bar屬性 console.log(foo.bar); //再次訪問foo的bar屬性,控制檯列印一下看看。結果驗證了"沿著原型鏈找下去"這句話。 Object.prototype.examAttr = '我是examAttr屬性,在Object.prototype(Object的原型)裡'; //給Object.prototype新增examAttr屬性 console.log(foo.examAttr); //訪問foo的examAttr屬性,控制檯列印一下看看。結果驗證了"一直找到Object.prototype"這句話。複製程式碼
配合之前寫的紙面意思,回味一下上面的程式碼。
每個建構函式都有一個原型,這個原型的constructor屬性就是這個建構函式。
function Foo(){this.name='我是建構函式Foo'} //一個建構函式Foo console.log(Foo.prototype); //列印結果可以看到一個Object物件,即Foo的原型,裡面有一個constructor屬性,屬性值即為Foo函式。 var foo = new Foo(); //例項化 console.log(foo.constructor); //foo中沒有constructor屬性,沿著原型鏈找到Foo的原型(即上面列印的結果),得到Foo原型的constructor屬性值,即Foo函式。複製程式碼
以上程式碼解釋了為什麼通過檢視例項的constructor屬性可以得到例項的建構函式。重點在於“沿著原型鏈找”。
3、從老生常談的JS實現new運算子過程來剖析
//寫一個建構函式,定義其prototype function Foo(name) { this.name = name; } Foo.prototype = { constructor:Foo, //由於重新定義了prototype,我們們把constructor屬性補上。 say: function () { console.log('My name is ' + this.name); } }; //JS實現new的方法generate function generate(Fun,arguments) { var foo = {}; //新建一個空物件 Fun.apply(foo, arguments); //利用apply改變this指向,現在執行Fun時,內部this指向foo空物件,那麼給this.name賦值就變成了給foo.name賦值。 foo.__proto__ = Fun.prototype; //把foo的__proto__屬性指向Fun.prototype。 return foo; } //執行generate,模擬new var foo = generate(Foo,["Terry"]); //相當於 var foo = new Foo("Terry") //驗證例項方法 foo.say(); //檢視foo的建構函式 console.log(foo.constructor);複製程式碼
上面的程式碼完全實現了new運算子的邏輯,所以說,new運算子就是上面這段程式碼的語法糖而已。
下面這張圖想必很多人都很熟悉,我擷取了小上半部分,
不要考慮看不見的導線,只關心f1、Foo、Foo.prototype三者的關係就夠了。
配合下面這張圖,再回頭看一下JS實現new的過程:
理不清沒關係,我們來解析一下:
圖中表示,f1由new Foo而來,而f1的__proto__連線著Foo.prototype,
這說明Foo的例項f1的__proto__(原型鏈)是指向Foo.prototype(原型)的,
你再回頭去看我們用JS實現new所封裝的方法generate,有這麼一句:
foo.__proto__ = Fun.prototype;
那不就是手動把foo(圖中的f1)的原型鏈指向Fun(圖中的Foo)的原型嗎!複製程式碼
4、ES5物件導向中常用的混合模式
在封裝程式碼/外掛的時候,使用物件導向的混合模式來寫,程式碼結構是這樣的:複製程式碼
function MyPlugin(name){
this.name = name; //每個例項都不一樣的屬性,寫在建構函式裡。
}
MyPlugin.prototype = {
publicFun:function(){
console.log('我是公共方法,所有例項共用'); //每個例項都呼叫一樣邏輯的程式碼,封裝成方法寫進建構函式的原型裡
console.log(this.name+'使用了外掛');
}
}
//使用外掛
var myPlugin = new MyPlugin('小明');
myPlugin.publicFun(); //例項並沒有publicFun方法,但是JS從myPlugun.__proto__中找到了public。複製程式碼
通過找“點”大法(前面教程有說過),可以發現publicFun內部的this指向myPlugin,
也就驗證了為什麼1、理解this指向的小技巧中小結裡的第三點:
“明確區分函式是[建構函式]還是[普通函式],[建構函式]內的this指向例項化後的物件;”
這回知道為什麼外掛可以這麼寫,並且例項化後的外掛可以直接呼叫寫在prototype裡的方法了吧?
小結
- 關於原型與原型鏈,沒有什麼“一句話來說”,仔細通讀這篇教程吧,尤其是那張圖。
PS:
歡迎轉載,需要註明原址。
教程之間緊密聯絡,不懂的地方,請好好看下全系列教程目錄,
有沒有你不懂的那個關鍵字在裡面。
如果幫到你,別忘了給我一顆Star~