總結的文章略長,甚點。
知識點預熱
Object類型
在ECMAScript中(就像在Java中的 java.lang.Object 對象一樣), Object 類型是所有它的實例的基礎, Object 類型所具有的任何屬性和方法也同樣可被更具體的對象所用。
JavaScript主要是通過原型鏈實現了面向對象中的實現繼承(區分接口繼承和實現繼承),所以每當構造一個實例對象時便繼承了 Object.prototype 上的方法,但這種原型鏈式的繼承並不是復制方法的副本,而是引用(指向)式的繼承。
person.hasOwnProperty('constructor');// false Object.prototype.hasOwnProperty('constructor');// true
//Number,String,Boolean類型返回新的字符串,其實是在包裝類的實例上調用toString或toLocaleString var number=10; number.toString();// "10" var str='xx'; var strnew=str.toString();// "xx" str==strnew;// true 兩個string基本類型的字符串比較內容而已所以為true str+='add';// "xxadd" 修改str指向的內容,進一步確定toString()返回的是副本並不是str的引用 strnew;// "xx" var bol=true; bol.toString();// "true" //引用類型構造函數及其實例對象調用toString/toLocaleString返回 Object.toString();// "function Object() { [native code] }" var person=new Object(); person.toString();// "[object Object]" Array.toString();// "function Array() { [native code] }" var a=new Array(); a.toString();// "" 即調用數組每一項的toString()方法,然後拼接成字符串 Functiom.toString();// "function Function() { [native code] }" new Function('console.log(1)').toString();//"function anonymous() {console.log(1)}" Boolean.toString();// "function Boolean() { [native code] }" new Boolean(true).toString();// "true" String.toString();// "function String() { [native code] }" new String('xx').toString();// "xx" Number.toString();// "function Number() { [native code] }" new Number(10).toString();// "10"
//基本類型Number,String,Boolean var num=1; var numnew=num.valueOf(); num+=2;// 3 numnew;// 1 var str='xx'; str.valueOf(); //"xx" var bol=true; bol.valueOf();// true; //包裝類的實例 new Number().valueOf();// 0 new String().valueOf();// "" new Boolean().valueOf();// false //其他引用類型返回自身的引用 Object.valueOf()==Object;// true var o=new Object(); o.valueOf()==o;// true
創建Object實例:不論用哪種方式效果是一樣的
var o=new Object(); o.name="xx"; o.say=function(){ console.log('hi') } //如果不傳參,可以省略圓括號,但不推薦 var o=new Object;
var o={ name:'xx', say:function(){ console.log('hi'); } }
ECMAScript中表達式上下文的定義:該上下文期待的一個值(表達式)。在這個例子中,左邊的花括號 ({) 表示對象字面量的開始 ,因為它出現在表達式上下文中。賦值操作符表示後面是一個值,所以左花括號在這裡表示一個表達式的開始。
ECMAScript中語句上下文的定義:同樣的花括號,例如跟在if條件語句後面,則表示一個語句塊的開始。
在通過對象字面量定義的對象時,實際上不會調用 Object 構造函數。此話出自JavaScript高級程序設計第三版,不明白不調用 Object 構造函數那是通過什麼幕後形式創建的對象,js引擎?
屬性名可以使用字符串定義,也可以像本代碼一樣簡寫,JavasScript會自動轉化為字符串。
for(var i in o){ console.log(typeof i);//string }
對象字面量也是向函數傳遞大量可選的參數的首選方式。
function displayInfo(args){ var output=""; for(var i in args){ if(args.hasOwnProperty(i)){ if(typeof args[i]=='function'){ output+=args[i](); } else{ output+=args[i]+' '; } } } console.log(output); }; displayInfo({ name:'xx', say:function(){ return 'hi'; } });
方括號語法的主要優點是通過可以通過變量來訪問屬性,如果屬性名中包含會導致語法錯誤的字符,或者屬性名使用的是關鍵字或保留字,也可以使用方括號表示法訪問。將要訪問的屬性以字符串形式放在方括號中。
ECMA-262把對象定義為
無序屬性的集合,其屬性可以包含基本值,對象,函數。相當於說對象是一組沒有特定順序的值。對象的每個屬性或方法都有一個名字,而每個名字都映射到一個值,可以把ECMAScript中的對象想象成散列表,無非就是一組名值對,其中的值可以是數據或函數。每個對象都是基於一個引用類型創建的,這個引用類型可以是原生類型也可以是自定義類型。
創建對象:雖然 Object 構造函數或對象字面量都可以用來創建單個對象,但這些方法明顯有缺點:使用同一個接口創建很多對象,會產生大量重復的代碼。所以產生如下七種模式。
1.工廠模式:抽象了創建具體對象的過程,考慮到在ECMAScript中無法創建類,所以就用函數封裝代碼實現特定功能。實質就是用函數封裝以特定接口來創建對象的細節(細節是指構造對象實例的方式)。實際想想在軟件工程領域中我們經常這樣做,將一個功能封裝起來只給外界提供接口。
function createPerson(name,age,job){ var o=new Object(); o.name=name; o.age=age; o.job=job; o.say=function(){ console.log(this.name); }; return o; } var person1=createPerson('xx',20,'student'); var person2=createPerson('mm',22,'student'); person1==person2;// false
有點感覺了,像java/C++中類裡面的構造函數有沒有,可以返回同功能的不同的多個實例。
缺點:沒有解決對象識別問題(即怎樣知道一個對象的類型),雖然你可以用代碼試探實例是那種類型(比如可以用 person1.__proto__ 的方法),但卻沒法直觀地知道 person1 和 person2 到底是哪種類型的實例。
2.構造函數模式:ECMAScript中的原生構造函數可以用來創建特定類型的對象,此外也可以創建自定義構造函數,從而定義自定義對象類型的屬性和方法。
function Person(name,age,job){ this.name=name; this.age=age; this.job=job; this.say=function(){ console.log(this.name); }; } var p1=new Person('xx',20,'student'); var p2=new Person('mm',22,'student'); p1==p2;// false p1.constructor==Person;//truePerson 中的代碼與工廠模式中 createPerson 中的代碼相比:
p1 和 p2 這兩個對象都有一個 constructor (構造器)屬性(javascript高程上這麼說其實並不准確, constructor 其實並不是 p1 和 p2 自身有的屬性而是通過原型鏈繼承來的屬性), constructor 指向 Person 。
這裡所創建的 p1 和 p2 既是 Object 的實例,又是 Person 的實例,原因是原型鏈上繼承關系,後面有說。 p1 instanceof Object;// true
p1 instanceof Person;// true
創建自定義的構造函數意味著將來可以將它的實例標識為一種特定的類型,明確的知道實例的類型這正是構造函數模式勝於工廠模式的地方。
在另一個對象的作用域中調用:在某個特殊對象的作用域中調用 Person 函數。
var o=new Object(); Person.call(o,'xx',20,'student'); o.say();// xx
缺點: say 這個方法要在每個實例上都創建一遍, p1.say==p2.say;// false p1和p2上的say方法雖然內容一樣但卻是完全不同的兩個 Function 類型實例, this.say=function(){ console.log(this.name);}; 相當於 this.say=new Function("console.log(this.name)"); 以這種方式創建函數,會導致不同的作用域鏈和標識符解析(變量,函數,屬性,參數的名字),但創建 Function 新實例的機制仍然是相同的。
解決方案:鑒於 say 函數對於 p1 和 p2 完成的功能一樣,那麼可以不用在執行代碼前就把該函數綁定到特定對象上面,通過把完成特定功能的函數單獨拿出來,而讓實例對象的 "say" 屬性指內存堆裡同一個函數來解決問題。這樣 p1 和 p2 就共享了在全局作用域中定義的同一個函數。
function Person(name,age,job){ this.name=name; this.age=age; this.job=job; this.say=say; }; function say(){ console.log(this.name); } var p1=new Person('xx',20,'student'); var p2=new Person('mm',20,'student');
缺點:
3.原型模式:JavaScript中每個函數都有一個 prototype (原型)屬性,這個屬性是一個指針,指向一個對象。 prototype 是這個屬性的名字,這個指針的內容就是該函數原型屬性對象在堆內存中的地址。這個原型對象的作用就是包含可以由特定類型的所有實例共享的屬性和方法。也可以這麼理解, prototype 就是通過調用構造函數而創建的那個對象實例的原型對象。使用原型好處就是可以讓所有對象實例共享它所包含的屬性和方法。即不必在構造函數中定義對象實例的信息,而是可以將這些信息添加到原型對象中。
function Person(){} Person.prototype.name='xx'; Person.prototype.age=29; Person.prototype.job="student"; Person.prototype.say=function(){ console.log(this.name); }; var p1=new Person(); p1.name;// 'xx' var p2=new Person(); p1==p2;// false p1.say==p2.say;// true
//字面量創建對象屬性 var p={ name:'xx', say:function(){} } Object.getOwnPropertyDescriptor(p,'name');// Object {value: "xx", writable: true, enumerable: true, configurable: true} //直接在對象上添加屬性 var o=new Object(); o.name='xx';// "xx" Object.getOwnPropertyDescriptor(o,'name');// Object {value: "xx", writable: true, enumerable: true, configurable: true} //Object.definedProperty定義屬性 var p={}; Object.defineProperty(p,'name',{ value:'xx' });// Object {name: "xx"} 一旦定義某一屬性後就不能通過這種方式再次定義該屬性,因為此時writable默認為false,除非當時顯式聲明為true Object.getOwnPropertyDescriptor(p,name);// Object {value: "xx", writable: false, enumerable: false, configurable: false}
var o={ toString:function(){ console.log('我是實例上的toString'); } }; for(var i in o){ console.log(i); };// toString
注:上面的覆寫 toString 代碼會在IE早期版本中出現bug,不會打印出 toString ,因為IE認為原型的 toString 方法被打上了值為 false 的 [[Enumerable]] 標記,因此會跳過該屬性。
解決方案:
1.改變原型上方法的可枚舉性
Object.defineProperty(Object.prototype,"valueOf",{enumerable:true }); Object.defineProperty(o,"valueOf",{enumerable:true }); Object.getOwnPropertyDescriptor(Object.prototype,'valueOf');// Object {writable: true, enumerable: true, configurable: true} for(var i in o){ console.log(o[i]); }
2.如果你想得到所有實例屬性而不論它們是否都可枚舉,使用 Object.getOwnPropertyNames() ,返回類型為 "[object Object]" 類數組:
Object.getOwnPropertyNames(Person.prototype);// ["constructor", "name", "age", "job", "say"] Object.getOwnPropertyNames(Object.prototype);/* ["constructor", "toString", "toLocaleString", "valueOf", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "__defineGetter__", "__lookupGetter__", "__defineSetter__", "__lookupSetter__", "__proto__"] */
ECMAScript5中 Object.keys() 方法可以取得對象上所有可枚舉的實例屬性,這樣就不用 for-in 和 hasOwnProperty 篩選判斷了:參數為對象,返回一個包含所有可枚舉屬性的字符串數組
Object.keys(Person.prototype);// ["name", "age", "job", "say"] p1.sex='女'; Object.keys(p1);// ["sex"]
更簡單的原型語法:為了更好的封裝原型的功能,將原型上定義的屬性和方法用對象字面量重寫整個原型
Person.prototype={ name:'xx', age:22, say:function(){console.log(this.name)} }
但這樣做法會使原型上的 constructor 屬性不見了, Object.getOwnPropertyNames(Person.prototype);// ["name", "age", "say"] ,為什麼呢?因為這種方式是在重定義原型屬性的內容(即讓 prototype 指向了別處對象),而之前的方式是在默認的原型屬性上添加新屬性而已。我們知道,每創建一個函數,就會同時創建它的 prototype 對象,這個對象也就會自動獲得 constructor 屬性,但現在我們讓 prototype 指向了別處新對象,原來帶 constructor 的那個對象在內存中就沒人引用了,如果也沒有實例對象的引用情況下就等待垃圾處理機制的回收。有意思的是雖說 Person.prototype 沒有 construcor 屬性了,但是再次訪問 Person.prototype.constructor;// Object() { [native code] } 什麼鬼?不是說沒有這個屬性不能訪問到了嗎?原來現在的 constructor 其實是在自己對象上搜索不到的屬性,便順著原型鏈繼承自 Object.prototype.constructor 來的。此時已經不能用 constructor 來判斷實例對象的類型了。
Person.prototype.hasOwnProperty('constructor');// false Object.prototype.hasOwnProperty('constructor');// true
如果需要 constructor ,可以特意將 constructor 設回適當的值。
Person.prototype={ name:'xx', age:22, say:function(){console.log(this.name);}, constructor:Person }
但是這樣會將它的 [[Enumerable]] 特性被設置為 true 。當然再加一步,通過 Object.defineProperty() 將特性重新賦為 false 。
function Person(){} Person.prototype={ constructor:Person, name:'xx', hobby:['a','b'] }; var p1=new Person(); var p2=new Person(); p1.hobby.push('c'); p1.hobby;// ['a','b','c'] p2.hobby;// ['a','b','c'] p1.hobby===p2.hobby
4.組合使用構造函數模式和原型模式:構造函數模式用於定義實例上的屬性,原型模式定義方法和共享的屬性。這樣每個實例都會有自己的一份實例屬性的副本,但同時又共享著方法的引用,節省了內存。
function Person(name,age){ this.name=name; this.age=age; this.hobby=['a','b']; } Person.prototype={ constructor:Person, say:function(){ console.log(this.name); } } var p1=new Person('xx',20); var p2=new Person('mm',20); p1.hobby.push('c'); p2.hobby;// ['a','b'];
5.動態原型模式(在構造函數中初始化原型):動態原型是把所有信息都封裝在了構造函數中,這樣構造函數和原型就不獨立開了,符合了OO語言中功能在一塊的習慣。通過在構造函數中初始化原型(僅在必要的情況下)又保持了同時使用構造函數和原型的優點。換句話說可以通過檢查某個應該存在的方法是否有效來決定是否需要初始化原型。
function Person(name,age){ this.name=name; this.age=age; if(typeof this.say!='function'){ //或用instanceof判斷 Person.prototype.say=function(){ console.log(this.name); } } } var f=new Person("xx",20); f.say();// 'xx'
這段代碼只會初次調用構造函數時才會執行,此後原型已經完成初始化,需要再做什麼改變了。 if 語句檢查的可以是初始化之後應該存在的任何屬性或方法。不必用一大堆 if 語句檢查每個屬性和每個方法,只要檢查其中一個就好。
缺點:不能用對象字面量的形式重寫原型,因為實例先於修改原型創建,執行 new 時,調用構造函數,為實例添加一個指向默認原型的 [[prototype]] ,然後再初始化,所以是在修改原型之前。
6.寄生構造函數模式:工廠模式和構造函數模式的結合。用一個函數封裝創建創建對象的代碼然後返回新創建的對象。除了使用 new 操作符並把使用的包裝函數叫構造函數外,這個模式和工廠模式其實一樣。這裡涉及到JavaScript中構造函數的返回值
function Person(name,age){ var o=new Object(); o.name=name; o.age=age; o.say=function(){ console.log(this.name); } return o; } var p=new Person('xx',20); p.say();
應用:假設想創建一個具有額外方法的特殊數組。由於不能直接修改Array構造函數,因此可以使用這個模式
function SpecialArray(){ var values=new Array(); values.push.apply(values,arguments);//初始化數組 values.toPipedString=function(){ return values.join('|'); }; return values; } var hobbits=new SpecialArray('a','b','c'); hobbits.toPipedString();// "a|b|c"
關於寄生構造函數模式,說明一點,返回的對象與構造函數或者與構造函數的原型屬性之間沒有關系。即構造函數返回的對象與在構造函數外部創建的對象沒什麼區別,為此不能依賴instanceof操作符來確定對象類型。此種模式不推薦。
7.穩妥構造函數模式:
穩妥對象:沒有公共屬性,而且其方法也不引用 this 的對象。穩妥對象最適合在一些安全的環境中(這些環境會禁止使用 this 和 new )或者防止數據被其他應用程序改動時使用。
穩妥構造函數遵循與寄生構造函數類似的模式,有兩點不同:一是創建對象的實例方法不引用 this ,二是不使用 new 操作符調用構造函數。
function Person(name,age){ var o=new Object(); //在這裡定義私有變量和函數 o.say=function(){ console.log(name); } return o; } var p=Person('xx',20); p.say();// 'xx'
注意這種模式創建的對象中,除了使用say方法外沒有別的方法可以訪問其數據成員。即使有其他代碼會給這個對象添加方法或數據成員,但也不可能有別的方法訪問傳入到構造函數中的原始數據。使用穩妥構造函數模式創建的對象與構造函數之間也沒有什麼關系,因此也不能用 instanceof 判斷類型。
繼承
許多OO語言都支持兩種繼承方式,接口繼承和實現繼承。接口繼承只繼承方法名,實現繼承繼承實際的方法。由於函數沒有簽名,在ECMAMScript中無法實現接口繼承。ECMAScript只支持實現繼承,而且其實現繼承主要是依靠原型鏈來實現的。
1.原型鏈:作為實現繼承的主要方法。基本思想是利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。假如讓原型對象等於另一個類型的實例,此時的原型對象將包含一個指向另一個原型的指針,相應地,另一個原型中也包含著一個指向另一個構造函數的指針。假如另一個原型又是另一個類型的實例,那麼上述關系依然成立,如此層層遞進,就構成了實例與原型的鏈條。
function SuperType(){ this.property=true; } SuperType.prototype.getSuperValue=function(){ return this.property; } function SuberType(){ this.subproperty=false; } SuberType.prototype=new SuperType(); SuberType.prototype.getSubValue=function(){ return this.subproperty; } var instance=new SuberType(); instance.getSuperValue();// true
調用 insatnce.getSuperValue() 會經歷:搜索實例;搜索 SubType.prototype ;搜索 SuperType.prototype ,最後一步才會找到該方法。
instance instanceof Object;// true instance instanceof SuperType;// true instance instanceof SuberType;// trueisPrototypeOf()方法:只要是原型鏈中出現過的原型,都可以說是該原型鏈所派生的實例的原型,因此isPrototypeOf()也會返回true。
Object.prototype.isPrototypeOf(instance);// true SuperType.prototype.isPrototypeOf(instance);// true SuberType.prototype.isPrototypeOf(instance);// true
2.借用構造函數:解決了原型中包含引用類型值所帶來的問題。即在子類型的構造函數中調用超類型的構造函數(看來也是借鑒了Java中的 super() ),至於是怎麼實現的?JavaScript中函數只不過是在特定環境中執行代碼的對象,因此通過 apply 和 call 方法可以在將來新創建的對象上執行構造函數。
//父類 function SuperType(){ this.colors=["a","b"]; } //子類 function SuberType(){ SuperType.call(this); } var instance1=new SuberType(); instance1.colors.push('c');// 3 colors值為["a","b","c"]; var instance2=new SuberType(); instance2.colors;// ["a","b"];
通過使用 call 方法或 apply 方法,我們實際是在(未來將要)新創建的 SuberType 實例的環境下調用 SuperType 構造函數,畢竟每當 new 一個實例的時候是先將構造函數的作用域賦給新對象( this 指向確定)。這樣一來,就會在新的 SuberType 對象上執行 SuperType 函數中定義的所有對象初始化代碼。這樣, SuberType 的每個實例就都會有自己的 colors 屬性的副本了。
優點:可以在子類的構造函數中向超類構造函數傳遞參數(這點Java的 super() 傳遞參數也可做到)
function SuperType(name){ this.name=name; } function SuberType(age){ //繼承了SuperType還傳遞了參數 SuperType.call(this,"xx"); //實例的其他屬性 this.age=age; } var instance=new SuberType(20); instance.name;// "xx" instance.age;// 20
為了確保 SuperType 構造函數不會重寫子類的屬性,所以在子類中定義的屬性寫在調用的後面。
缺點:如果僅僅使用構造函數來完成繼承,那麼也無法避免構造函數模式中存在的問題,即方法都在構造函數中定義,就沒有函數的復用了。在超類原型中定義的方法,對子類型而言也是不可見的,結果所有類型就只能使用構造函數模式。考慮到這些,借用構造函數的技術也很少單獨使用。
3.組合繼承:原型鏈和構造函數的技術結合起來,使用原型鏈實現實現對原型屬性和方法的繼承,借用構造函數實現對實例屬性的繼承。
//父類 function SuperType(name){ this.name=name; this.colors=["a","b"]; } SuperType.prototype.sayName=function(){ console.log(this.name); } //子類 function SuberType(name,age){ SuperType.call(this,name); this.age=age; } SuberType.prototype=new SuperType();
SuberType.prototype.constructor=SuberType; SuberType.prototype.sayAge=function(){ console.log(this.age); }
var a1=new SuberType('xx',20);
a1.colors.push('c'); a1.colors;// ["a","b","c"] a1.sayAge();// 20; a1.sayName();// xx
這種方式 instanceof 和 isPrototypeOf 同樣可用。但注意到 SuberType.prototype 有個 name 和 colors 的無用屬性。
缺點:會調用兩次超類的構造函數,一次是在創建子類原型的時候,另一次是在子類構造函數內部的 call 或 apply 。子類型最終會包含超類型對象的全部實例屬性,但我們不得不在調用子類型構造函數時重寫這些屬性。解決辦法是寄生組合繼承。
4.原型式繼承:這種方法並沒有使用嚴格意義上的構造函數,而是借助原型可以基於已有的對象創建新對象,同時還不必因此創建自定義類型。
function object(o){ function F(){} F.prototype=o; return new F(); }
先創建一個臨時性的構造函數,然後將傳入的對象作為這個構造函數的原型,返回了臨時類型的一個新實例。
var person={ name:"xx", friends:["aa","bb","cc"] }; var p1=object(person); p1.name;// "xx" 原型繼承 p1.name="xixi"; p1.friends.push("dd"); person.friends;// ["aa", "bb", "cc", "dd"] var p2=object(person);// 再次調用object(),雖然是重新執行了F.prototype但是o參數仍指向原來的person p2.name="xuxu"; p2.friends.push("ee"); person.friends;// ["aa", "bb", "cc", "dd", "ee"]
ECMA5新增的 Object.create() 規范化了原型式繼承,兩個參數,一個用作新對象原型的對象,(可選的)為一個新對象定義額外屬性的對象。在傳入一個參數情況下, Object.create() 和 object() 沒什麼區別。
var person={ name:"xx", friends:["aa","bb","cc"] }; var p1=Object.create(person); p1.name="xixi"; p1.friends.push("dd"); var p2=Object.create(person); p2.name="xuxu"; p2.friends.push("ee"); person.friends;// ["aa", "bb", "cc", "dd", "ee"]
Object.create() 方法的第二個參數與 Object.defineProperties() 方法的第二個參數格式相同,每個屬性都是通過自己的描述符定義的。以這種方式指定的任何屬性都會覆蓋原型對象上的同名屬性。
var person={ name:'xx', friends:["aa","bb","cc"] }; var p1=Object.create(person,{ name:{ value:"xixi" } }); p1.name;// "xixi" person.name;// "xx"
在沒有必要創建構造函數,而只是想讓一個對象與另一個對象保持類似情況下,原型式繼承是完全可以的。不過缺點還是在的,比如包含引用類型值得屬性始終都是共享的。
5.寄生式繼承:是一種與原型式繼承緊密相關的思路。寄生式繼承的思路與寄生構造函數和工廠模式類似,即創建一個僅用於封裝繼承過程的函數,該函數在內部以某種方式增強對象,最後再像是它做了所有工作一樣返回對象。
function createAnother(original){ var clone=object(original); //調用函數創建一個新對象 clone.say=function(){ //增強這個對象 console.log("hi"); } return clone; }
var person={ name:'xx', friends:["aa","bb","cc"] } var p1=createAnother(person); p1.name;// "xx" p1.friends;// ["aa", "bb", "cc"] p1.say();// hi
在主要考慮對象而不是自定義類型的構造函數情況下,寄生式繼承也是一種有用方法,而且 object() 函數也不是必須的,任何能夠返回新對象的函數都適用此模式。
缺點:由於是增強對象給對象添加函數,所以不能函數復用。這點與構造函數模式相似。
6.寄生組合式繼承:通過構造函數繼承屬性,原型鏈的混成繼承方法。說白了就是不必為了指定子類型的原型而調用超類型的構造函數而造成子類型原型上出現一些用不到的屬性,我們所要的無非就是超類型原型的一個副本實現原型鏈之間的繼承關系而已。那麼為何不考慮結合寄生式繼承因為寄生式繼承可以為一個對象指定原型啊。這樣使用寄生式繼承來繼承超類型的原型,然後再將返回的結果指定給子類原型。
function inheritPrototype(suberType,superType){ var prototype=object(superType.prototype);// 創建對象 prototype.constructor=suberType;// 增強對象 suberType.prototype=prototype;// 指定對象 }
function SuperType(name){ this.name=name; this.colors=["aa","bb","cc"]; } SuperType.prototype.say=function(){ console.log(this.name); } function SuberType(age,name){ SuperType.call(this,name); this.age=age; } inheritPrototype(SuberType,SuperType); SuberType.prototype.say=function(){ console.log(this.age); }
只調用一次父類構造函數不僅提高了效率,這樣做還能正常地使用 instanceof 和 isPrototypeOf 。寄生組合式繼承是引用類型最理想的繼承范式。YUI的 YAHOO.lang.extend 就用到了寄生組合繼承(https://yui.github.io/yui2/docs/yui_2.3.0/docs/Lang.js.html)
參考 :《JavaScript高級程序設計》