上一節《javascript基礎–對象(Object)封裝》講了如何造人,這一節將會總結怎么理清人與人之間的血緣關系,即繼承關系。對象繼承簡單地分為兩類,構造函數繼承和非構造函數繼承。所謂構造函數繼承就是利用this綁定對象的模式創建的對象之間的繼承關系;而非構造函數繼承就是利用對象字面量創建的對象之間的繼承關系。上代碼:
//Person父對象利用this綁定,子對象Tom,Linda也同樣采用這種模式創建function Person(name,sex){ this.name = name; this.sex = sex;}Person.PRototype.say = function(){ console.log(this.name +' is ' + this.sex);}//創建的Tom,Linda去繼承Person父對象,此種繼承成為構造函數繼承//Person采用對象字面量創建,子對象Tom,Linda也同樣采用這種模式創建var Person = { Pname: 'Person', Psex: 'Person sex is male or female', say: function(){ console.log('haha'); }}//創建的Tom,Linda去繼承Person父對象,此種繼承成為非構造函數繼承首先確定父對象,即引言內的父對象,copy下來
function Person(name,sex){ this.name = name; this.sex = sex;}Person.prototype.say = function(){ console.log(this.name +' is ' + this.sex);}我們的任務是創建Tom和Linda去繼承Person,我們先創建子對象模型
//Tom構造函數function Tom(name,sex){}Tom.prototype = {}var T = new Tom('Tom','male');//Linda構造函數function Linda(name,sex){}Linda.prototype = {}var L = new Linda('Linda','female');分析:父對象Person中屬性和方法分為兩部分,name和sex屬性是在構造函數Person上,而say方法是在Person.prototype上。所以要繼承Person所有屬性和方法,分為兩步,即繼承構造函數上的屬性和繼承原型對象上的方法。
對象冒充,是利用call或者apply方法,將父對象的屬性和方法綁定在子對象上,從而完成構造函數上屬性和方法的繼承,但是person原型對象上的say方法不會繼承。這里簡單介紹一下call和apply
//call定義A.call(B,Object);//釋義:調用A對象的方法,以B對象替換當前的A對象,其中參數是Object//apply定義A.apply(B,array);//釋義:調用A對象的方法,以B對象替換當前的A對象,其中參數必須是Array類型//so call和apply區別就在參數的形式上不同現在來完善Tom和Linda構造函數
//Tom構造函數function Tom(name,sex){ Person.apply(this,arguments);}Tom.prototype = {}var T = new Tom('Tom','male');//Linda構造函數function Linda(name,sex){ Person.apply(this,arguments);}Linda.prototype = {}var L = new Linda('Linda','female');測試:
//可見Tom和Linda成功繼承了Person構造函數上的屬性,Person原型對象屬性怎么繼承呢?T.name;//'Tom'T.sex;//'male'T.say;//undefinedL.name;//'Linda'L.sex;//'female'L.say;//undefined原型繼承,顧名思義肯定要在子對象原型上做文章,首先想到的是直接繼承Person.protype,就像這樣
//Tom構造函數function Tom(name,sex){ Person.apply(this,arguments);}Tom.prototype = Person.prototype;var T = new Tom('Tom','male');//Linda構造函數function Linda(name,sex){ Person.apply(this,arguments);}Linda.prototype = Person.prototype;var L = new Linda('Linda','female');測試:
//say被繼承了,完了??沒完T.say();//Tom is maleL.say();//Linda is female我們為Tom原型對象上添加一個run方法,Linda原型對象上不添加,期望結果是T.run返回function,L.run返回undefined
Tom.prototype.run = function(){ console.log('Tom running');}//測試T.run();//'Tom running'L.run();//'Tom running'Person.prototype.run();//'Tom running'//可見修改Tom的原型對象也同樣修改了Person.prototype原因是,prototype是一個地址,子對象原型繼承的是地址,而非數值。在C語言中,指針就是這樣一個概念。那怎么避免呢?既然地址不行,那就用值,不就ok了。所以讓子對象原型繼承父對象實例是一種方式。
//Tom構造函數function Tom(name,sex){ Person.apply(this,arguments);}//想想封裝函數的原型鏈,任何一個prototype對象都有一個constructor屬性,指向它的構造函數。如果沒有"Tom.prototype = new Person();"這一行,Tom.prototype.constructor是指向Tom的;加了這一行以后,Tom.prototype.constructor指向Person。所以我們必須手動糾正Tom.prototype = new Person();Tom.prototype.constructor = Tom;var T = new Tom('Tom','male');//Linda構造函數function Linda(name,sex){ Person.apply(this,arguments);}//同理Linda.prototype = new Person();Linda.prototype.constructor = Linda;var L = new Linda('Linda','female');Tom.prototype.run = function(){ console.log('Tom running');}測試:
//測試結果表明,我們成功了T.say();//'Tom is male'L.say();//'Linda is female'T.run();//'Tom running'L.run();//undefinedPerson.prototype.run();//undefined除了利用繼承值以外,還有別的方法嗎?肯定有,那就是利用空對象作為中介,讓子對象的原型繼承空對象,空對象繼承父對象原型,這樣間接實現繼承,而且避免了直接繼承prototype的缺點,給子對象添加自身額外的方法時只會改變空對象,而不會改變父對象,這樣就達到和繼承值一樣的目的。
var Empty = function(){};Empty.prototype = Person.prototype;Tom.prototype = new Empty();Tom.prototype.constructor = Tom;將此方法進行封裝成extend方法
function extend(Child, Parent) { var F = function(){}; F.prototype = Parent.prototype; Child.prototype = new F(); Child.prototype.constructor = Child; //為了保證繼承函數的完整性,增添一個通向父對象原型的接口 Child.uber = Parent.prototype;}現在將Tom和Linda構造函數改變
//Tom構造函數function Tom(name,sex){ Person.apply(this,arguments);}//調用extend函數extend(Tom,Person);var T = new Tom('Tom','male');//Linda構造函數function Linda(name,sex){ Person.apply(this,arguments);}//同理extend(Linda,Person);var L = new Linda('Linda','female');Tom.prototype.run = function(){ console.log('Tom running');}測試:
//成功了T.name;//'Tom'T.say();//'Tom is male'T.run;//functionL.name;//'Linda'L.say();//'Linda is female'L.run;//undefinedPerson.prototype.run;//undefined上面講了為了繼承原型上的屬性和方法,我們使用了原型繼承。這里有一個新思路,就是拷貝繼承,即將父對象原型上的屬性和方法直接拷貝到子對象上,從而實現繼承。這樣也避免了改變子對象原型的時候不會改變父對象原型,因為拷貝的是值而不是地址。
function extend2(Child, Parent) { var p = Parent.prototype; var c = Child.prototype; for (var i in p) { c[i] = p[i]; } //為了保證繼承函數的完整性,增添一個通向父對象原型的接口 c.uber = p;}現在改變Tom,Linda構造函數
//Tom構造函數function Tom(name,sex){ Person.apply(this,arguments);}//調用extend2函數extend2(Tom,Person);var T = new Tom('Tom','male');//Linda構造函數function Linda(name,sex){ Person.apply(this,arguments);}//同理extend2(Linda,Person);var L = new Linda('Linda','female');Tom.prototype.run = function(){ console.log('Tom running');}測試,不出意外,結果一樣
//意料之中,成功了T.name;//'Tom'T.say();//'Tom is male'T.run;//functionL.name;//'Linda'L.say();//'Linda is female'L.run;//undefinedPerson.prototype.run;//undefined還是先確定父對象,即引言內的父對象,copy下來
var Person = { Pname: 'Person', Psex: 'Person sex is male or female', say: function(){ console.log('haha'); }}我們的任務是創建Tom和Linda去繼承Person,我們先創建子對象模型
var Tom = {};var Linda = {};分析:這里要注意,這兩個對象都是普通對象,不是構造函數,無法使用構造函數方法實現”繼承”。
json格式的發明人Douglas Crockford,提出了一個object()函數
function object(o){ function F(){}; F.prototype = o; return new F();}添加Tom和Linda對象私有方法
Tom = object(Person);Tom.name = 'Tom';Linda = object(Person);Linda.name = 'Linda';測試:
Tom.Pname;//'Person'Tom.Psex;//'Person sex is male or female'Tom.say;//functionTom.name;//'Tom'Linda.Pname;//'Person'Linda.Psex;//'Person sex is male or female'Linda.say;//functionLinda.name;//'Linda'和構造函數的實現思路一樣,拷貝也是實現繼承的一種方式
function extendCopy(p) { var c = {}; for (var i in p) { c[i] = p[i]; } //為了保證繼承函數的完整性,增添一個通向父對象的接口 c.uber = p; return c;}改變Tom, Linda對象
Tom = extendCopy(Person);Tom.name = 'Tom';Linda = extendCopy(Person);Linda.name = 'Linda';測試:
//同樣成功了Tom.Pname;//'Person'Tom.Psex;//'Person sex is male or female'Tom.say;//functionTom.name;//'Tom'Linda.Pname;//'Person'Linda.Psex;//'Person sex is male or female'Linda.say;//functionLinda.name;//'Linda'為什么叫做淺拷貝呢,現在假設父對象中有一個屬性是數組或者對象,為父對象增加一個屬性color數組
Person.color = [yellow,white,black];//Tom繼承PersonTom = extendCopy(Person);//測試Tom.color;//[yellow,white,black]//現在我們為Tom.colorTom.color.push('blue');//測試Tom.color;//[yellow,white,black,blue]Person.color;//[yellow,white,black,blue]//???為什么改變子對象,父對象也跟著改變了呢?//原因是數組或對象是地址,相當于拷貝了地址,所以會出現這種情況。為了避免這種情況,也就有了深拷貝深拷貝是在前拷貝的基礎上增添了對父對象屬性的判斷,判斷是否為地址,如果是,則遞歸拷貝函數
function deepCopy(p, c) { var c = c || {}; for (var i in p) { if (typeof p[i] === 'object') { c[i] = (p[i].constructor === Array) ? [] : {}; deepCopy(p[i], c[i]); } else { c[i] = p[i]; } } return c;}改變Tom和Linda對象
Tom = deepCopy(Person,Tom);Tom.name = 'Tom';Linda = deepCopy(Person,Tom);Linda.name = 'Linda';測試:
Person.color = [yellow,white,black];//Tom繼承PersonTom = extendCopy(Person);//測試Tom.color;//[yellow,white,black]//現在我們為Tom.colorTom.color.push('blue');//測試Tom.color;//[yellow,white,black,blue]//父對象沒有改變Person.color;//[yellow,white,black]jQuery使用的深拷貝
對象的繼承到這里就結束了,其實也蠻簡單的。針對兩種情況,構造函數繼承其實就是對象冒充,原型,拷貝三種方式;非構造函數就是object()和淺深拷貝。只要深入理解,記憶起來還是蠻easy
新聞熱點
疑難解答