繼承
子類的原型對(duì)象--繼承
// 類式繼承
// 聲明父類
function SupperClass(){
this.superValue = true
}
// 為父類添加公有方法
SupperClass.prototype.getSuperValue = function(){
return this.superValue;
}
// 聲明類
function SubClass(){
this.subValue = false;
}
// 繼承父類
SubClass.prototype = new SuperClass()
// 為子類添加公有方法
SubClass.prototype.getValue = function(){
return this.subValue
}
var instance = new SubClass()
instance.getSuperValue() // true
instance.getSubValue() // false
這里聲明類兩個(gè)類, 而且第二個(gè)類的原型prototype被賦予了第一個(gè)類的實(shí)例
類的原型對(duì)象的作用就是為類的原型添加共有方法,但類不能直接訪問這些屬性和方法,必須通過原型prototype來訪問而我們實(shí)例化一個(gè)父類的時(shí)候,新創(chuàng)建的對(duì)象復(fù)制了父類的構(gòu)造函數(shù)內(nèi)的屬性與方法并且將原形proro指向了父類的原型對(duì)象,這樣就擁有了父類的原型對(duì)象上的屬性與方法,并且這個(gè)新創(chuàng)建的對(duì)象可直接訪問到父類原型對(duì)象的屬性與方法.如果我們將這個(gè)新創(chuàng)建的對(duì)象賦值給子類的原型, 那么子類的原型就可以訪問到父類的原型屬性和方法.
我們可以通過instanceof來檢測(cè)某個(gè)對(duì)象是否是某個(gè)類的實(shí)例,或者說某個(gè)對(duì)象是否繼承了某個(gè)類.這樣就可以判斷對(duì)象與類之間的繼承關(guān)系了.
instanceof是通過判斷對(duì)象的prototype鏈來確定這個(gè)對(duì)象是否是某個(gè)類的實(shí)例,而不關(guān)心對(duì)象與類的自身結(jié)構(gòu)
console.log(instance instanceof SuperClass); //true
console.log(instance instanceof SubClass); //true
console.log(SubClass instanceof SuperClass); //false
instanceof 是判斷前面的對(duì)象是否是后面的類(對(duì)象)的實(shí)例, 它并不表示兩者的繼承.
我們?cè)趯?shí)現(xiàn)subClass繼承superClass時(shí)是通過將superClass的實(shí)例賦值給subClass的原型prorotype,所以SubClass.prototype繼承了superClass
console.log(SubClass.prototype instanceof SuperClass) //true
所創(chuàng)建的所有對(duì)象都是Object的實(shí)例
console.log(instance instanceof Object) // true
這種類繼承還有2個(gè)缺點(diǎn).
一: 由于子類通過原型prototype對(duì)父類實(shí)例化,繼承了父類.所以說父類中的共有屬性要是引用類型,就會(huì)在子類中被所有實(shí)例共有, 因此一個(gè)子類的實(shí)例更改子類原型從父類構(gòu)造函數(shù)中繼承的共有屬性就會(huì)影響到其它子類.
function SuperClass(){
this.books = ['js', 'html', 'css']
}
function SubClass(){}
SubClass.prototype = new SuperClass()
var instance1 = new SubClass();
var instance2 = new SubClass();
console.log(instance2.books); // ['js', 'html', 'css']
instance1.books.push('設(shè)計(jì)模式')
console.log(instance2.books); // ['js', 'html', 'css', '設(shè)計(jì)模式']
instance1 改變了instance2的值
二:由于子類實(shí)現(xiàn)的繼承是靠原型prototype對(duì)父類的實(shí)例化實(shí)現(xiàn)的, 因此在創(chuàng)建父類的時(shí)候,是無法向父類傳遞參數(shù)的, 因?yàn)樵趯?shí)例化父類的時(shí)候也無法對(duì)父類否找函數(shù)內(nèi)的屬性進(jìn)行初始化.
構(gòu)造函數(shù)繼承
// 構(gòu)造函數(shù)式繼承
// 聲明父類
function SuperClass(id){
// 引用類型共有屬性
this.books = ['js', 'css', 'html'];
// 值類型共有屬性
this.id = id;
}
// 父類聲明原型方法
SupperClass.prototype.showBooks = function(){
console.log(this.books)
}
// 聲明子類
function subClass(id){
// 繼承父類
SuperClass.call(this, id)
}
// 創(chuàng)建第一個(gè)子類的實(shí)例
var instance1 = new subClass(10)
// 創(chuàng)建第二個(gè)子類的實(shí)例
var instance2 = new subClass(1);
instance1.books.push('設(shè)計(jì)模式')
console.log(instance1.books) // ['js', 'css', 'html', '設(shè)計(jì)模式']
console.log(instance1.id) // 10
console.log(instance1.books) // ['js', 'css', 'html']
console.log(instance1.id) // 1
instance1.showBooks() // TypeErroe
SuperClass.call(this, id),call這個(gè)方法可以更改函數(shù)的作用環(huán)境,因此在子類中,對(duì)superClass調(diào)用這個(gè)方法就是將子類匯總的變量在父類中執(zhí)行了一遍,由于父類中是給this綁定屬性的,因此子類自然也就繼承了父類的共有屬性.由于這種類型的繼承沒有涉及原型prototype,所以父類的原型方法自然不會(huì)倍子類繼承, 而如果想要被子類繼承就必須放在構(gòu)造函數(shù)中,這樣創(chuàng)建出的每個(gè)實(shí)例都會(huì)單獨(dú)擁有一份而不是共用,這樣就違背了代碼復(fù)用的原則.綜合這兩種模式的優(yōu)點(diǎn),后來就有了組合繼承
組合繼承
類式繼承是用過子類的原型prototype對(duì)父類實(shí)例化來實(shí)現(xiàn)的,構(gòu)造函數(shù)式繼承是通過在子類的構(gòu)造函數(shù)作用環(huán)境中執(zhí)行一次父類的構(gòu)造函數(shù)來實(shí)現(xiàn)的,所以只要在繼承中同時(shí)做到這兩點(diǎn)即可.
// 組合式繼承
// 聲明父類
function SuperClass(name){
// 值類型共有屬性
this.name = name;
// 應(yīng)用類型共有屬性
this.books = ['js', 'css', 'html'];
}
// 父類原型共有方法
SuperClass.prototype.getName = function(){console.log(this.name)}
// 聲明子類
function SubClass(name, time){
// 構(gòu)造函數(shù)式繼承父類name屬性
SuperClass.call(this, name)
// 子類中新增共有屬性
this.time = time;
}
// 類式繼承 子類原型繼承父類
SubClass.prototype = new SupperClass();
// 子類原型方法
SubClass.prototype.getTime = function(){console.log(this.time)}
在子類構(gòu)造函數(shù)中執(zhí)行父類構(gòu)造函數(shù),在子類原型上實(shí)例化父類就是組合模式,這樣就融合了類式繼承的優(yōu)點(diǎn)
var instance1 = new SubClass('js book', 2014)
instance1.books.push('設(shè)計(jì)模式')
console.log(instance1.books); // ['js', 'css', 'html', '設(shè)計(jì)模式']
instance1.getName() // js book
instance1.getTiem() // 2014
var instance2 = new SubClass('css book', 2011);
console.log(instance2.books) // ['js', 'css', 'html']
instance2.getName() // css book
instance2.getTime() // 2011
這種繼承模式在使用構(gòu)造函數(shù)繼承時(shí)執(zhí)行了一遍父類的構(gòu)造函數(shù), 而在實(shí)現(xiàn)子類原型的類式繼承時(shí)又調(diào)用了一遍父類構(gòu)造函數(shù).因此父類構(gòu)造函數(shù)調(diào)用了兩遍.
潔凈的繼承者--原型式繼承
借助原型prototype可以根據(jù)已有的對(duì)象創(chuàng)建一個(gè)新的對(duì)象, 同時(shí)不必創(chuàng)建新的自定義對(duì)象類型
// 原型是繼承
function inheritObject(o){
// 聲明一個(gè)過渡函數(shù)對(duì)象
function F () {}
// 過渡對(duì)象的原型繼承父對(duì)象
F.prototype = o;
// 返回過度對(duì)象的一個(gè)實(shí)例, 該實(shí)例的原型繼承了父對(duì)象
return new F();
}
它是對(duì)類式繼承的一個(gè)封裝,過渡對(duì)象就相當(dāng)于類式繼承中的子類,只不過在原型中作為一個(gè)過渡對(duì)象出現(xiàn)的, 目的是為了創(chuàng)建要返回的新的實(shí)例化對(duì)象,由于F過渡類的構(gòu)造函數(shù)中無內(nèi)容, 開銷比較小, 使用起來也比較方便.
var book = {
name: 'js book';
alikeBook: ['css', 'js']
}
var newBook = inheritObject(book);
newBook.name = 'ajax book';
newBook.alikeBook.push('as book')
var otherBook = inheritObject(book);
newBook.name = 'flash book';
newBook.alikeBook.push('xml book')
console.log(newBook.name); // ajax book
console.log(newBook.alikeBook); // ['css'. 'js', 'as book', 'xml book']
console.log(otherBook.name); // flash book
console.log(otherBook.alikeBook); // ['css'. 'js', 'as book', 'xml book']
console.log(book.name); // js book
console.log(book.alikeBook); // ['css'. 'js', 'as book', 'xml book']
寄生式繼承
function inheritObject(o){
function F(){}
F.prototype = o;
return new F();
}
// 寄生式繼承
// 聲明基對(duì)象
var book = {
name: "js book",
alikeBook: ["css", "js", "html"]
};
function createBook(obj){
// 通過原型繼承方式創(chuàng)建新對(duì)象
var o = new inheritObject(obj)
o.getName = function(){
console.log(name)
};
// 返回拓展后的新對(duì)象
return o;
}
寄生式繼承就是對(duì)原型繼承的第二次封裝,并且在這第二次封裝中對(duì)繼承的對(duì)象進(jìn)行拓展券勺,這樣創(chuàng)建的對(duì)象不僅僅有父類中的屬性和方法而且還添加新的屬性和方法
寄生大概值得就是像寄生蟲一樣寄托于某個(gè)對(duì)象內(nèi)部生長(zhǎng)绪钥。當(dāng)然寄生式繼承這種增強(qiáng)新創(chuàng)建對(duì)象的繼承思想也是寄托于原型繼承模式
終極繼承者--寄生組合式繼承
組合式繼承將類式繼承同構(gòu)造函數(shù)繼承組合使用,但是這種方式有一個(gè)問題关炼,就是子類不是父類的實(shí)例程腹,而子類的原型是父類的實(shí)例,所以才有了寄生組合式繼承
寄生式寄生式繼承儒拂, 寄生式繼承依托于原型繼承寸潦, 原型繼承又與類繼承相像色鸳,另一種繼承模式式構(gòu)造函數(shù)繼承, 子類不是父類實(shí)例的問題是由于類是繼承引起的
/**
* 寄生式繼承 類繼承
* 傳遞參數(shù) subClass 子類
* 傳遞參數(shù) superClass 父類
**/
function inheritObject(o){
var o = new F();
F.prototype = o;
return new F();
}
function inheritPrototype(subClass, superClass){
// 復(fù)制一份父類的原型副本保存在變量中
var p = inheritObject(superClass.prototype);
// 修正因?yàn)橹貙懽宇愒蛯?dǎo)致子類的constructor屬性被修改
p.constructor = subClass;
// 設(shè)置子類的原型
subClass.prototype = p;
}
組合式繼承中见转,通過構(gòu)造函數(shù)繼承的屬性和方法是沒問題的命雀, 所以這里我們組要理解通過寄生式繼承重新繼承父類的原型。我們需要繼承的僅僅是父類的原型池户,不再需要調(diào)用父類的構(gòu)造函數(shù)咏雌,換句話說,在構(gòu)造函數(shù)繼承中我們已經(jīng)調(diào)用了父類的構(gòu)造函數(shù)校焦,因此我們需要的就是父類的原型對(duì)象的一個(gè)副本赊抖,而這個(gè)副本我們通過原型繼承便可得到,但是這么直接賦值給子類會(huì)有問題寨典,因?yàn)楦割愒蛯?duì)象復(fù)制得到的復(fù)制對(duì)象p中的constructor指向的不是subClass子類對(duì)象氛雪,因此寄生式繼承中要對(duì)復(fù)制對(duì)象p做一次增強(qiáng),修復(fù)其constructor屬性指向不正確的問題耸成,最后將得到的復(fù)制對(duì)象p復(fù)制給子類的原型报亩,這樣子類的原型就繼承了父類的原型并且沒有執(zhí)行父類的構(gòu)造函數(shù)
// 定義父類
function SuperClass(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
// 定義父類原型方法
SuperClass.prototype.getName = function (){
console.log(this.name);
};
// 定義子類
function SubClass(name, time){
// 構(gòu)造函數(shù)式繼承
SuperClass.call(this, name)
// 子類新增屬性
this.time = time;
}
// 寄生式繼承父類原型
inheritPrototype(SubClass, SuperClass);
// 子類新增原型方法
SubClass.prototype.getTime = function(){
console.log(this.time);
};
// 創(chuàng)建兩個(gè)測(cè)試方法
var instance1 = new SubClass("js book", 2014)
var instance2 = new SubClass("css book", 2015)
console.log(instance1.colors); // ["red", "blue", "green"];
console.log(instance2.colors); // ["red", "blue", "green"];
instance2.getName(); // 'css book'
instance2.getTime(); // '2015'
多繼承
在JS中繼承是依賴于原型prototype鏈實(shí)現(xiàn)的, 只有一條原型鏈井氢, 所以理論不能繼承多個(gè)父類
extend
// 單繼承 屬性賦值
var extend = function(target, source){
// 遍歷源對(duì)象的屬性
for(var property in source){
// 將源對(duì)象中的屬性復(fù)制到目標(biāo)對(duì)象中
targer[property] = source[property]
}
// 返回目標(biāo)對(duì)象
return target;
}
var book = {
name: 'js',
alike: ['css', 'js', 'html']
}
var anotherBook = {
color: 'red'
}
extend(anotherBook, book)
console.log(anotherBook.name) // js
console.log(anotherBook.alike) // ['css', 'js', 'html']
anotherBook.alike.push('ajax');
anotherBook.name = '設(shè)計(jì)模式';
console.log(anotherBook.name); //設(shè)計(jì)模式
console.log(anotherBook.alike); // ['css', 'js', 'html', 'ajax']
console.log(book.name); // js
console.log(book.alike); // ['css', 'js', 'html', 'ajax']
多繼承
// 多繼承 屬性復(fù)制
var mix = fuction(){
var i = 1,
len = arguments.length,
target = arguments[0],
arg;
// 遍歷被繼承的對(duì)象
for (; i < len; i++){
// 緩存當(dāng)前對(duì)象
arg = argument[i];
// 遍歷被緩存的對(duì)象中的屬性
for (var property in arg){
// 將被緩存對(duì)象中的屬性復(fù)制到新目標(biāo)中
target[property] = arg[property]
}
}
return target;
}
綁定到Object上
Object.prototype.mix = function(){
var i = 0,
len = arguments.length,
arg;
// 遍歷被繼承的對(duì)象
for (; i < len; i++){
// 緩存當(dāng)前對(duì)象
arg = argument[i];
// 遍歷被緩存的對(duì)象中的屬性
for (var property in arg){
// 將被緩存對(duì)象中的屬性復(fù)制到新目標(biāo)中
this[property] = arg[property]
}
}
}
otherBook.mix(book1, book2);
多態(tài)
多態(tài)是同一個(gè)方法多用調(diào)用方式
// 多態(tài)
// 定義一個(gè)方法弦追, 如果不傳參數(shù)返回10, 如果傳一個(gè)參數(shù)返回10+參數(shù) 如果傳兩個(gè)參數(shù)就返回兩個(gè)參數(shù)相加的結(jié)果
function add(){
// 獲取參數(shù)
var arg = arguments,
len = arg.length;
switch(len){
case 0:
return 10;
case 1:
return 10 + arg[0];
case 2:
return arg[0] + arg[1];
}
}