本文是本人學習,張容銘著的《Javascript 設計模式》的筆記與總結。僅供學習交流。
每個類都有3個部分:
1、第一部分是構造函數內的,這是供實例化對象復制用的。
2、第二部分是構造函數外的,直接通過點語法添加,這是供類使用的,實例化對象是訪問不到的。
3、第三部分是類的原型中的,實例化對象是可以通過其原型鏈間接地訪問到,也是為供所有實例化對象所共用的。
一、子類的原型對象————類式繼承
類式繼承:通過子類的原型prototype對父類實例化來實現。
//類式繼承
//聲明父類
function SuperClass(){
this.superValue = true;
}
//為父類添加共有方法
SuperClass.prototype.getSuperValue = function () {
return this.superValue;
};
// 聲明子類
function SubClass(){
this.subValue = false;
}
// 繼承父類
SubClass.prototype = new SuperClass ();
// 為子類添加共有方法
SubClass.prototype.getSubValue = function (){
return this.subValue;
}
上面聲明了2個類,而且第二個類的原型prototype 被賦予了第一個類的實例。
類的原型對象的作用就是為類的原型添加共有方法,但類不能直接訪問這些屬性個方法,必須通過原型prototype來訪問。
var instance = new SubClass();
console.log(instance.getSuperValue() ); // true
console.log(instance.getSubValue()); //false
通過instanceof 來檢測某個對象是否是某個類的實例,或者說某個對象是否繼承了某個類。
console.log(instance instanceof SuperClass); //true
console.log(instance instanceof SubClass); //true
console.log(SubClass instanceof SuperClass); //false
第三個false,說明 ,instanceof 是判斷前面的對象是否是后面類(對象)的實例,它并不表示兩者的繼承。
>> SubClass 繼承 SuperClass 時是通過將superClass的實例賦予給SubClass 的原型prototype,所以說 SubClass.prototype繼承了SuperClass
console.log(SubClass.prototype instanceof SuperClass); //true
>> Object是所有對象的祖先
console.log(instance instanceof Object); //true
類式繼承的缺點
1、一個子類的實例更改子類原型從父類構造函數中繼承來的共有屬性就會直接影響到其他子類。原因:
由于子類通過其原型prototype對父類實例化,繼承了父類。所以說父類中的共有屬性要是引用類型,就會在子類中被所有實例共用。
示例代碼:
function SuperClass () {
this.books = ['Javascript','html','css'];
}
function SubClass () {}
SubClass.prototype = new SuperClass ();
var instance1 = new SubClass();
var instance2 = new SubClass();
console.log(instance2.books); // ['Javascript','html','css'];
instance1.books.push('設計模式');
console.log(instance2.books); // ['Javascript','html','css','設計模式'];
2、在實例化父類的時候也無法對父類構造函數內的屬性進行初始化。原因:
由于子類實現的繼承是靠其原型prototype對父類的實例化實現的,因此在創建父類的時候,是無法向父類傳遞參數的。
二、創建即繼承——構造函數繼承
構造函數式:通過在子類的構造函數環境中執行一次父類的構造函數來實現的。用call(this,變量);
//構造函數式繼承
//聲明父類
function SuperClass (id){
//引用類型共有屬性
this.books = ['JavaScript','html','css'];
//值類型共有屬性
this.id = id;
}
//父類聲明原型方法
SuperClass.prototype.showBooks = function (){
console.log(this.books);
}
//聲明子類
function SubClass (id) {
//繼承父類
SuperClass.call (this, id);
}
//創建第一個子類的實例
var instance1 = new SubClass (10);
var instance2 = new SubClass (11);
instance1.books.push('設計模式');
console.log(instance1.books); // ['JavaScript','html','css','設計模式'];
console.log(instance1.id); //10
console.log(instance2.books); //['JavaScript','html','css'];
console.log(instance2.id); //11
instance1.showBooks (); //TypeError
構造函數式繼承的精華:
SuperClass.call (this, id);
優缺點:
call將子類的變量在父類中執行一遍,由于父類是給this綁定屬性的,所以子類自然也就繼承了父類的共有屬性。而這種類型的繼承沒有涉及原型prototype,所以父的原型方法自然不會被子類繼承。如果想被子類繼承就必須要放在構造函數中。
三、組合繼承 (類式+ 構造式)
組合繼承結合了類式繼承 + 構造函數繼承
1、類式繼承:通過子類的原型prototype對父類實例化來實現。
2、構造函數式:通過在子類的構造函數環境中執行一次父類的構造函數來實現的。用call(this,變量);
//組合式繼承
//聲明父類
function SuperClass(name){
//值類型共有屬性
this.name = name;
//引用類型共有屬性
this.books = ['html'、'css'、'Javascript'];
}
// 父類原型共有方法
SuperClass.prototype.getName = function () {
console.log(this.name);
};
//聲明子類
function SubClass (name, time) {
//構造函數式繼承父類name屬性
SuperClass.call(this.name);
//子類中新增共有屬性
this.time = time;
}
//類式繼承 子類原型繼承父類
SubClass.prototype = new SuperClass();
//子類原型方法
SubClass.prototype.getTime = function (){
console.log(this.time);
}
如上,在子類構造函數中執行父類構造函數,在子類原型上實例化父類就是組合模式。這樣融合了類式繼承和構造函數繼承的優點,并且過濾掉其缺點,你測試看看。
測試代碼:
var instance1 = new SubClass("js book", 2014);
instance1.book.push("設計");
console.log(instance1.books); //["html","css","Javascript","設計模式"]
instance1.getName (); //js book
instance1.getTime (); // 2014
var instance2 = new SubClass ("css book", 2013);
console.log(instance2.books); //["html","css","Javascript"]
instance2.getName (); //css book
instance2.getTime (); //2013
優缺點
子類的實例中更改父類繼承下來的引用類型屬性如books,不會影響到其他實例,并且子類實例化過程中又能將參數傳遞到父類的構造函數中。
四、原型式繼承
//原型式繼承
function inheritObject (o){
//聲明一個過渡函數對象
function F (){ }
//過渡對象的原型繼承父對象
F.prototype = o;
//返回過渡對象的一個實例,該實例的原型繼承了父對象
return new F();
}
這是類式繼承的一個封裝
測試代碼:
var book = {
name : "js book",
alikeBook : ["css book","html book"]
}
var newBook = inheritObject (book);
newBook.name = "ajax book";
newBook.alikeBook.push("xml book");
var otherBook = inheritObject (book);
otherBook.name = "flash book";
otherBook.alikeBook.push("as book");
console.log(newBook.name); //ajax book
console.log(newBook.alikeBook); //["css book","html book","xml book","as book"]
console.log(otherBook.name); //flash book
console.log(otherBook.alikeBook); //["css book","html book","xml book","as book"]
console.log(book.name); // js book
console.log(book.alikeBook); //["css book","html book","xml book","as book"]
優缺點
跟類式繼承一樣,父類對象book中的值類型的屬性被復制,引用類型的屬性被共同用
五、寄生式繼承
// 寄生式繼承
// 聲明基對象
var book = {
name : "js book",
alikeBook : ["css book", "html book"]
}
function createBook (obj){
//通過原型繼承方式創建新對象
var o = new inheritObject(obj);
//拓展新對象
o.getName = function (){
console.log(name);
};
//返回拓展后的新對象
return o;
}
優缺點
寄生式繼承是對原型的第二次封裝,并且在這第二次封裝過程中對繼承的對象進行了拓展,這樣新創建的對象不僅僅有父類中的屬性和方法而且還添加新的屬性和方法。
六、寄生組合式繼承
/**
* 寄生式繼承 繼承原型
* 傳遞參數 subClass 子類
* 傳遞參數 superClass 父類
**/
function inheritPrototype(subClass, superClass){
//復制一份父類的原型副本保存在變量中
var p = inheritObject (superClass.prototype);
//修正因為重寫子類原型導致子類的constructor 屬性被修改;
p.constructor = subClass;
//設置子類的原型
subClass.prototype = p;
}
測試用例
// 定義父類
function SuperClass (name){
this.name = name;
this.colors = ["red","blue","green"];
}
// 定義父類原型方法
SubClass.prototype.getName = function () {
console.log(this.name);
}
// 定義子類
function SubClass (name, time){
// 構造函數式繼承
SuperClass.call (this, name);
// 子類新增屬性
this.time = time;
}
//寄生式繼承父類原型
inheriPrototype(SubClass, SuperClass);
//子類新增原型方法
SubClass.prototype.getTime = function (){
console.log(this.time);
};
// 創建兩個測試方法
var instance1 = new SubClass("js book", 2014);
var instance2 = new SubClass("css book", 2013);
測試代碼
instance1.colors.push("black");
console.log(instance1.colors); //["red","blue","green","black"]
console.log(instance2.colors); //["red","blue","green"]
instance2.getName (); //css book
instance2.getTime (); //2013
七、多繼承
一些面向對象語言中支持多繼承,在javascript中也能實現,但是有些局限性。只有一條原型鏈,理論上不能繼承多個父類。而通過 extend 方法可以做到。
// 單繼承 屬性復制
var extend = function (target, source){
//遍歷源對象中的屬性
for (var property in source){
//將源對象中的屬性復制到目標對象中
target[property] = source[property];
}
//返回目標對象
return target;
}
extend方法是一個淺復制的過程,他只能復制值類型的屬性,對于引用類型的屬性是不行的。
單繼承測試代碼
var book = {
name : 'javascript 設計模式',
alike : ['css','html','javascript']
}
var anotherBook = {
color : 'blue'
}
extend(anotherBook, book);
console.log(anotherBook.name); //Javascript 設計模式
console.log(anotherBook.alike); // ['css','html','javascript']
anotherBook.alike.push('ajax');
anotherBook.name = '設計模式';
console.log(anotherBook.name); // 設計模式
console.log(anotherBook.alike); //['css','html','javascript','ajax']
console.log(book.name); //Javascript 設計模式
console.log(book.alike); // ['css','html','javascript']
多繼承
//多繼承 屬性復制
var mix = function (){
var i = 1, // 從第二個參數起為被繼承的對象
len = arguments.length, //獲取參數長度
target = arguments[0], ///第一個對象為目標對象
arg; //緩存參數對象
// 遍歷被繼承對象
for(; i<len; i++){
// 緩存當前對象
arg = arguments[i];
//遍歷被繼承對象中的屬性
for (var property in arg){
//將被繼承對象中的屬性復制到目標對象中
target[property] =arg[property];
}
}
// 返回目標對象
return target;
}
//將mix綁定到原生對象Object上,所有的對象都可以擁有這個方法。
Object.prototype.mix = function (){
var i = 0, //從第一個參數起為被繼承的對象
len = arguments.length, // 獲取參數長度
arg;
//遍歷被繼承的對象
for(; i<len; i++){
// 緩存當前對象
arg = arguments[i];
//遍歷被繼承對象中的屬性
for (var property in arg){
//將被繼承對象中的屬性復制到目標對象中
this[property] = arg[property];
}
}
}
// 調用
var book1 = {name:'書一',publish:'廣州'};
var book2 = {cover:'http://www.baidu.com',title:'sdfdf'};
var otherBook = {content:'df'};
otherBook.mix(book1,book2);
console.log(JSON.stringify(otherBook)); //{"content":"df","name":"書一","publish":"廣州","cover":"http://www.baidu.com","title":"sdfdf"}
八、多種調用方式————多態
//場景: 一個參數返回平方, 兩個參數相乘,三個參數相加再相乘,三個以上相加
// 多態
function total () {
// 獲取參數
var arg = arguments,
//獲取參數長度
len = arg.length;
switch(len){
//如果沒有參數
case 0:
return 0;
case 1:
return Math.pow(10,arg[0]);
case 2:
return arg[0] * arg[1];
case 3:
return (arg[0] + arg[1])*arg[3];
defult:
var a=0;
for(var i=0; i<len ;i++){
a += arg[i];
}
return a;
}
}
根據不同數量的參數有不同的處理