標籤:輸出 span 錯誤 alt nbsp 複製 href person es2017
於javascript原型鏈的層層遞進尋找規則,以及原型對象(prototype)的共用特性,實現繼承是非常簡單的事情
一、把父類的執行個體對象賦給子類的原型對象(prototype),可以實現繼承
1 function Person(){ 2 this.userName = ‘ghostwu‘; 3 } 4 Person.prototype.showUserName = function(){ 5 return this.userName; 6 } 7 function Teacher (){} 8 Teacher.prototype = new Person(); 9 10 var oT = new Teacher(); 11 console.log( oT.userName ); //ghostwu12 console.log( oT.showUserName() ); //ghostwu
通過把父類(Person)的一個執行個體賦給子類Teacher的原型對象,就可以實現繼承,子類的執行個體就可以訪問到父類的屬性和方法
如果你不會畫這個圖,你需要去看下我的這篇文章:[js高手之路]一步步圖解javascript的原型(prototype)對象,原型鏈
第11行,執行oT.userName, 首先去oT對象上尋找,很明顯oT對象上沒有任何屬性,所以就順著oT的隱式原型__proto__的指向尋找到Teacher.prototype,
發現還是沒有userName這個屬性,繼續沿著Teacher.prototype.__proto__向上尋找,找到了new Person() 這個執行個體上面有個userName,值為ghostwu
所以停止尋找,輸出ghostwu.
第12行,執行oT.showUserName前面的過程同上,但是在new Person()這個執行個體上還是沒有尋找到showUserName這個方法,繼續沿著new Person()的
隱式原型__proto__的指向( Person.prototype )尋找,在Person.prototype上找到了showUserName這個方法,停止尋找,輸出ghostwu.
二、把父類的原型對象(prototype)賦給子類的原型對象(prototype),可以繼承到父類的方法,但是繼承不到父類的屬性
1 function Person(){ 2 this.userName = ‘ghostwu‘; 3 } 4 Person.prototype.showUserName = function(){ 5 return ‘Person::showUserName方法‘; 6 } 7 function Teacher (){} 8 Teacher.prototype = Person.prototype; 9 10 var oT = new Teacher(); 11 console.log( oT.showUserName() ); //ghostwu12 console.log( oT.userName ); //undefined, 沒有繼承到父類的userName
因為Teacher.prototype的隱式原型(__proto__)只指向Person.prototype,所以擷取不到Person執行個體的屬性
三、發生繼承關係後,執行個體與建構函式(類)的關係判斷
還是通過instanceof和isPrototypeOf判斷
1 function Person(){ 2 this.userName = ‘ghostwu‘; 3 } 4 Person.prototype.showUserName = function(){ 5 return this.userName; 6 } 7 function Teacher (){} 8 Teacher.prototype = new Person(); 9 10 var oT = new Teacher();11 console.log( oT instanceof Teacher ); //true12 console.log( oT instanceof Person ); //true13 console.log( oT instanceof Object ); //true14 console.log( Teacher.prototype.isPrototypeOf( oT ) ); //true15 console.log( Person.prototype.isPrototypeOf( oT ) ); //true16 console.log( Object.prototype.isPrototypeOf( oT ) ); //true
四,父類存在的方法和屬性,子類可以覆蓋(重寫),子類沒有的方法和屬性,可以擴充
1 function Person() {} 2 Person.prototype.showUserName = function () { 3 console.log(‘Person::showUserName‘); 4 } 5 function Teacher() { } 6 Teacher.prototype = new Person(); 7 Teacher.prototype.showUserName = function(){ 8 console.log(‘Teacher::showUserName‘); 9 }10 Teacher.prototype.showAge = function(){11 console.log( 22 );12 }13 var oT = new Teacher();14 oT.showUserName(); //Teacher::showUserName15 oT.showAge(); //22
五、重寫原型對象之後,其實就是把原型對象的__proto__的指向發生了改變
原型對象prototype的__proto__的指向發生了改變,會把原本的繼承關係覆蓋(切斷)
1 function Person() {} 2 Person.prototype.showUserName = function () { 3 console.log(‘Person::showUserName‘); 4 } 5 function Teacher() {} 6 Teacher.prototype = new Person(); 7 Teacher.prototype = { 8 showAge : function(){ 9 console.log( 22 );10 }11 }12 var oT = new Teacher();13 oT.showAge(); //2214 oT.showUserName();
上例,第7行,Teacher.prototype重寫了Teacher的原型對象(prototype),原來第6行的原型對象的隱式原型(__proto__)指向就沒有作用了
所以在第14行,oT.showUserName() 就會發生調用錯誤,因為Teacher的原型對象(prototype)的隱式原型(__proto__)不再指向父類(Person)的執行個體,繼承關係被破壞了.
六、在繼承過程中,小心處理執行個體的屬性上參考型別的資料
1 function Person(){ 2 this.skills = [ ‘php‘, ‘javascript‘ ]; 3 } 4 function Teacher (){} 5 Teacher.prototype = new Person(); 6 7 var oT1 = new Teacher(); 8 var oT2 = new Teacher(); 9 oT1.skills.push( ‘linux‘ );10 console.log( oT2.skills ); //php, java, linux
oT1的skills添加了一項linux資料,其他的執行個體都能訪問到,因為其他執行個體中共用了skills資料,skills是一個參考型別
七、借用建構函式
為了消除參考型別影響不同的執行個體,可以借用建構函式,把參考型別的資料複製到每個對象上,就不會相互影響了
1 function Person( uName ){ 2 this.skills = [ ‘php‘, ‘javascript‘ ]; 3 this.userName = uName; 4 } 5 Person.prototype.showUserName = function(){ 6 return this.userName; 7 } 8 function Teacher ( uName ){ 9 Person.call( this, uName );10 }11 var oT1 = new Teacher();12 oT1.skills.push( ‘linux‘ );13 var oT2 = new Teacher();14 console.log( oT2.skills ); //php,javascript15 console.log( oT2.showUserName() );
雖然oT1.skills添加了一項Linux,但是不會影響oT2.skills的資料,通過子類建構函式中call的方式,去借用父類的建構函式,把父類的屬性複製過來,而且還能
傳遞參數,如第8行,但是第15行,方法調用錯誤,因為在構造中只複製了屬性,不會複製到父類原型對象上的方法
八、組合繼承(原型對象+借用建構函式)
經過以上的分析, 單一的原型繼承的缺點有:
1、不能傳遞參數,如,
Teacher.prototype = new Person();
有些人說,小括弧後面可以跟參數啊,沒錯,但是只要跟了參數,子類所有的執行個體屬性,都是跟這個一樣,說白了,還是傳遞不了參數
2、把參考型別放在原型對象上,會在不同執行個體上產生相互影響
單一的借用建構函式的缺點:
1、不能複製到父類的方法
剛好原型對象方式的缺點,借用建構函式可以彌補,借用建構函式的缺點,原型對象方式可以彌補,於是,就產生了一種組合繼承方法:
1 function Person( uName ){ 2 this.skills = [ ‘php‘, ‘javascript‘ ]; 3 this.userName = uName; 4 } 5 Person.prototype.showUserName = function(){ 6 return this.userName; 7 } 8 function Teacher ( uName ){ 9 Person.call( this, uName );10 }11 Teacher.prototype = new Person();12 13 var oT1 = new Teacher( ‘ghostwu‘ );14 oT1.skills.push( ‘linux‘ );15 var oT2 = new Teacher( ‘ghostwu‘ );16 console.log( oT2.skills ); //php,javascript17 console.log( oT2.showUserName() ); //ghostwu
子類執行個體oT2的skills不會受到oT1的影響,子類的執行個體也能調用到父類的方法.
[js高手之路]從原型鏈開始圖解繼承到組合繼承的產生