因爲工做須要項目中要用prototype框架,因此這幾天搗鼓了一下,研究了一下prototype 建立對象和類以及繼承的一些源碼,其實早在好久之前就接觸prototype,而後直接看源碼, 看着太蛋疼,裏面的牽連太多太深, 繞來繞去,腦殼都繞暈了, 因此索性直接看prototype官方教程裏面Class.create()這個函數的使用方式,瞭解了它的使用方式之後咱們再來一步一步的反推它是怎麼實現的,這裏注意一下我不是直接講的prototype源碼實現這一塊的內容,說穿了源碼我看着也暈, 上面都說了東西太多牽連太深。好了, 廢話就很少說了,咱們直接進入主題javascript
咱們先來看一段基於prototype來建立對象以及繼承的一段代碼java
//建立一個動物類 var Animal = Class.create({ //初始化構造函數 initialize: function(name, type) { this.name = name; this.type = type; }, //動物類的方法 speak: function() { alert("my name is "+name); } });
上面這段代碼描述一個基於prototype寫的動物類, 不得不佩服在面向對象這一塊prototype設計的很是整潔和優雅,既然建立了一個類,下面咱們來使用這個,若是有過java背景或者面向對象高級語言的同窗是否是以爲調用方式有點類似,這樣就體現出javascript在實現面向對象這一塊仍是頗有優點的。json
//下面咱們來使用這個動物類 var animal = new Animal('小白', 'dog'); animal.speak();
可是如今問題來了,光能建立類就不能對剛剛建立的類進行擴展或者繼承嘛, 答案是確定的,要否則怎麼說是大名鼎鼎的prototype嘛,好, 下面咱們就來實現繼承這個Animal類數組
//建立人類,繼承自動物類 var Person = Class.create(Animal, { initialize: function($super, name, type) { $super(name, type); }, work: function() { alert(this.name+"今天工做了一成天,雖然有點累但很開心"); } });
好到這裏面咱們已經看到prototype的強大之處了吧,那如今咱們來講說這種方式建立類的一些特性,以便咱們在後面來實現這樣寫面向對象作一些鋪墊,那到底有什麼一些特性或者特色呢閉包
1.是經過Class.create()這個函數來建立類的,接受一個或者兩個參數,這個函數重載了的app
2.這個函數返回一個類,這個類又能夠經過new的方式給爲咱們建立對象框架
3.每一個類都有一個initialize方法,這個是一個構造器能夠用於初始化構造對象函數
4.若是Class.create(base, child)方法重載了兩個參數,那麼base就是基類,child類是繼承自base類的this
上面說了prototype建立對象時的一些基本特色,下面咱們就要來詳細分析一下prototype建立究竟是怎麼來實現的,首先聲明我不是看prototype框架源碼來分析,我是根據這些寫對象的一些特性來進行的,咱們都知道若是聲明一個函數,這個函數就是一個構造器,能夠把它new出來的,看看下面的代碼思路會更清晰spa
//這樣也能建立一個類,至於它與prototype的優劣只有本身體會了 function Animal(name, type) { this.name = name; this.type = type; this.speak = function() { alert("my name is"+this.name); }; } var animal = Animal("小黃", "dog"); animal.speak();
如今你們知道若是要採用new的方式確定要有構造器,說白了就是一個函數,那如今咱們知道Class.create()這個方法返回的是一個函數了吧,可是這究竟是個怎麼樣的函數呢,接着在進行挖掘,首先咱們來分析當傳入一個參數也就是建立一個類沒有繼承的時候是這樣一種狀況
var Animal = Class.create({ //初始化構造函數 initialize: function(name, type) { this.name = name; this.type = type; }, //動物類的方法 speak: function() { alert("my name is "+name); } });
其實就是傳入的了json參數,裏面放了兩個函數,一個構造函數和一個成員函數,剛剛咱們講了這個Class.create()方法返回的是一個函數,傳入的又是一個json, 那咱們確定要將json裏面的函數複製到返回出來的這個函數裏面,請看下面代碼
var Class = { create: function(object) { var fn = function() {}; fn.prototype = object; return fn; } }; //好了, 如今咱們能夠用上面的這個Class.create()方法來建立一個類 var Animal = Class.create({ initialize: function(name) { this.name = name; }, speak: function() { alert("my name is "+this.name); } }); //下面我來new一下建立這個動物類 var animal = new Animal(); animal.initialize("dog"); animal.speak();
上面這段代碼是否是和prototype建立對象有點不同,細心的同窗會發現咱們在new Animal()這個動物類的時候沒有參數, 構造參數是經過animal.initialize()來初始化的,這樣就不對了, prototype不是經過構造函數的時候就直接調用initialize()函數來進行初始化的嗎,沒錯,因此咱們要將上面的代碼作一些小小的修改,上代碼吧
var Class = { create: function(object) { var fn = function() { this.initialize.apply(this, arguments); }; fn.prototype = object; return fn; } }; var Animal = Class.create({ initialize: function(name) { this.name = name; }, speak: function() { alert("my name is "+this.name); } }); //下面我來new一下建立這個動物類 var animal = new Animal("dog"); animal.speak();
到這裏你們應該能看清楚究竟是哪兒作了改動吧,this.initialize.apply(this, arguments); 就是在這兒作了手腳,fn這個函數在建立類的時候是會被返回的,因此在new這個被返回的函數裏面能夠作初始換的一些東西,就是調用咱們的initialize()函數來進行初始化了, 這裏聲明一些哈,若是沒有對javascript, this, 閉包,做用域,原型鏈方面的知識理解就很是痛苦了,不過我仍是會大概說一下,this.initialize.apply(this, arguments);咱們對這句話進行分解, this 其實指的是{init:functdion(), speak:function() {}}這個傳進去的對象,apply(this, arguments), apply是將當前函數的上下文改變也就是把this改變,arguments就是一個函數運行時的參數,這樣說可能有點同窗不是很明白,若是實在看不明白javascript基礎知識須要補補課了。如今咱們能實現向prototype的語法同樣來建立一個了類, 但僅僅是建立一個類,若是須要繼承怎麼辦呢, 就像這樣Class.create(base, object); 這個object要繼承base, 你們都知道若是在繼承的話確定是要將base裏面的成員和方法copy到object裏面來,就這麼簡單嗎, 咱們來仔細想一下吧,應該有如下幾點
首先將基類的成員copy到子類裏面
a. 若是子類的成員變量和積累的同名,會覆蓋基類的
b.子類的成員方法和基類的同名會覆蓋積累的
c.能不能子類和基類的成員方法進行重載呢,答案是確定的
d.手動控制調用基類的構造方法進行初始化會更靈活
以上這些就是prototype建立類的一些特性, 可能還不完善,歡迎你們討論。 下面咱們就用代碼具體的實現,我會盡可能用多的註釋來標註方便你們理解和傳播
var Class = { /* *功能:建立一個類 *參數: * 1.base 表示基類,但必定要是functdion類型的 * 2.object 表示要建立的子類,json數據格式類型的 */ create: function(base, object) { var fn = null; //這個判斷主要是建立有沒有繼承的類 //通常狀況其實就是檢測create()方法是一個參數仍是兩個參數 if(typeof base == "function") { //這個就是建立要返回的函數 fn = function() { var args = []; //這個判斷是檢測子類initialize($super, ...)方法裏面參數 //若是有$super這個參數就是傳了基類的構造函數能夠調用,反之就沒有 if(object.initialize.length > arguments.length) { //這裏就是把基類的構造函數加入到args函數裏面 //這裏你們能夠仔細體會一下這兒爲何要用一個閉包,還有 //apply(fn.prototype, arguments)方法裏面兩個參數的意義 args.push(function() { base.prototype.initialize.apply(fn.prototype, arguments); }); } //將構造函數裏面的參數加入到這個args數組 for(var i = 0 ; i < arguments.length ; i++) { args.push(arguments[i]); } //執行構造函數的初始化參數 this.initialize.apply(this, args); }; //先將積累裏面的成員變量和方法copy到返回函數fn裏面 for(var property in base.prototype) { if(property != "initialize") { fn.prototype[property] = base.prototype[property]; } } //在將子類裏面成員變量和方法copy到返回函數fn裏面 //注意這裏爲何要先拷貝基類在拷貝子類,緣由是 //基類和子類的成員變量和方法重名的話優先的是子類的 for(var property in object) { fn.prototype[property] = object[property]; } } //這個是沒有繼承的狀況就很是簡單了, 上面有講到, //這裏就再也不細細敘述了 else { fn = function() { this.initialize.apply(this, arguments); } fn.prototype = base; } return fn; } } //下面就能夠像prototype語法同樣寫出優雅的代碼, //這段是prototype官方Class.create()APi的源碼實例 //建立一個動物類 var Animal = Class.create({ initialize: function(name, sound) { this.name = name; this.sound = sound; }, speak: function() { alert(this.name+"says:"+this.sound+"!"); } }); //實例化調用 var dog = new Animal('gogo', "wangwang"); dog.speak(); //建立一我的類,繼承自動物類 var Snake = Class.create(Animal, { initialize: function($super, name) { //alert($super); $super(name, 'hisssssssssssssss'); }, say: function() { alert("my name is "+this.name); } }); //實例化調用 var ringneck = new Snake("Ringneck"); ringneck.speak(); ringneck.say();
說到這裏prototype的 Class.create()方法的內核源碼基本上就告一段落了,固然還有一些其餘的方式,也各有千秋,你們能夠看一下
var Class = { create: function() { return function() { this.initialize.apply(this, arguments); } } }; var Animal = Class.create(); Animal.prototype = { initialize: function(name) { this.name = name; }, say: function() { alert("my name is "+this.name); } };