1、prototype屬性的特色:
java
一、定義在prototype中的方法是"實例方法",必須是new出來的實例,才能調用prototype中的方法,相同的方法能夠被不一樣的實例調用,互不干擾:json
function Human (name) { this.name = name; } Human.prototype = { sayHi:function () { console.log('Hi! I\'m '+this.name+'.'); } } var tom = new Human('Tom'); tom.sayHi();// Hi! I'm Tom. var perter = new Human('Perter'); perter.sayHi();// Hi! I'm Perter.
能夠作個小調整,將打招呼的的方法sayHi()移到構造函數中,一實例化就執行,沒必要單獨調用:函數
function Human (name) { this.name = name; this.sayHi() } Human.prototype = { sayHi:function () { console.log('Hi! I\'m '+this.name+'.'); } } var tom = new Human('Tom');// Hi! I'm Tom. var perter = new Human('Perter');// Hi! I'm Perter.
自問:可是,這樣跟在window全局做用域寫個sayHi方法,傳不一樣值調用有何區別?:this
function sayHi (name) { console.log("Hi! I'm "+name+'.'); } sayHi('Tom');// Hi! I'm Tom. sayHi('Perter');// Hi! I'm Perter.
自答:全局的function對象適合封裝簡單的小塊邏輯,若是是較爲複雜的邏輯,都寫在一個function對象中會使邏輯更趨複雜,難以維護,通常會拆分紅多個function,以便於管理和維護:spa
function Human (name) { this.name = name; this.sayHi() } Human.prototype = { sayHi:function () { console.log('Hi! I\'m '+this.name+'.'); }, work:function () { console.log("For live,I must work."); }, eat:function () { console.log("For live,I must eat."); } sex:function () { console.log("Sex is an instinct"); } }
上例的Human類比開始多定義了3個方法,prototype實現的是,對已經拆分的邏輯模塊(sayHi,work,eat,sex),再封裝後的複用,這是一個簡單的function對象難以作到的。prototype
二、其餘屬性定義的方法都是"靜態方法",只能在統一不變的"類"的層級調用。code
三、 獨立的json對象也是封裝邏輯的很好容器,但由於是引用類型,在相同的文檔上下文中,複用會出現後者參數覆蓋前者的狀況。orm
2、使用prototype的注意事項:對象
一、若是使用xx.prototype={}的方式封裝邏輯,會影響"constructor"值,因此使用後注意重置"constructor"爲當前的類。方便之後判斷一個實例的構造類是誰。繼承
function Human (name) { this.name = name; } var a = new Human('Peter'); console.log(a.constructor);
上面代碼的輸出結果是:
咱們獲得了實例a的構造類是Human!
可是使用xx.prototype={}的方式封裝邏輯後:
function Human (name) { this.name = name; } Human.prototype = { sayHi:function () { console.log("Hi! I'm "+this.name+'.'); } } var a = new Human('Peter'); console.log(a.constructor);
輸出結果是:
咱們已經再也不知道實例a的構造類是誰?因此,應該重置"constructor"爲當前的類:
function Human (name) { this.name = name; } Human.prototype = { sayHi:function () { console.log("Hi! I'm "+this.name+'.'); } } Human.prototype.constructor = Human; var a = new Human('Peter'); console.log(a.constructor);
輸出結果又是:
二、在構建繼承父類的子類時,子類會調用父類的構造函數,生成實例,再賦值給子類的prototype。也就是子類還沒調用,子類繼承自父類的構造方法已經執行了。
// 定義"人"類 function Human () { alert("hello world!"); } Human.prototype = { sayHi:function () { console.log("hello world!"); } } Human.prototype.constructor = Human; // 定義"女人"類 function Woman (name) {} // "女人"類繼承"人"類 Woman.prototype = new Human(); // 定義"女人"類的特有方法 Woman.prototype.bear = function () { console.log("I can bear baies."); } Woman.prototype.constructor = Woman;
上述代碼只是定義了一個Human類,而後Woman子類繼承Human父類,尚未實例化,就執行了
alert("hello world!");
不只不合理,當父類構造函數含有破壞性代碼,或者要依賴特定狀態時,還會引起其餘錯誤。
因此,一般咱們須要單獨封裝一個實現繼承的方法,而不是僅僅把子類的prototype賦值爲父類的實例。
3、繼承方法的封裝步驟:
3.1.構造一個新類,該類具備一個空的構造函數,將該類的prototype賦值爲父類的prototype,而後將該類的實例賦值給子類的prototype:
function inheritance(){}; inheritance.prototype = superClass.prototype;
subClass.prototype = new inheritance();
構造函數爲空了,子類再來繼承時,也就不會出現還沒實例化,代碼已經開始執行的狀況了。
3.2.重置恢復子類的constructor:
subClass.prototype.constructor = subClass;
便於對實例追根溯源。
3.3.子類自定義屬性(baseConstructor)保存對父類的構造函數的引用:
subClass.baseConstructor = superClass;
構造函數只是在過分的「inheritance」類置空了,它的引用仍然會經過子類的自定義屬性(baseConstructor)保留,方便在須要的時候調用。
3.4.若是父類自定義屬性(__super__)包含對祖父類的引用,那麼這個屬性要賦值給父類prototype下的同名屬性:
if(superClass.__super__){ superClass.prototype.__super__ = superClass.__super__; }
只要保存在prototype屬性中的方法才能被後代繼承,這樣作方便子類對祖先類的方法的重寫。
3.5.子類自定義屬性(__super__)保存對父類的prototype的引用:
subClass.__super__ = superClass.prototype;
方便子類對父類的方法的重寫。
完整的代碼:
// 實現繼承的方法 function extend (subClass,superClass) { // 建立一個新的類,該類具備一個空的構造函數,並具備父類的成員 function inheritance(){}; inheritance.prototype = superClass.prototype; // 將子類的prototype屬性設置爲不帶構造函數的父類新實例 subClass.prototype = new inheritance(); subClass.prototype.constructor = subClass;// 重置恢復constructor屬性 subClass.baseConstructor = superClass;// 用自定義屬性保存父級的構造函數 // 容許多重繼承 if(superClass.__super__){ superClass.prototype.__super__ = superClass.__super__; } subClass.__super__ = superClass.prototype;// 保持對父類原型的引用,方便子類重寫 }
,