于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 ); //ghostwu
12 console.log( oT.showUserName() ); //ghostwu
通過把父類(Person)的一個執行個體賦給子類Teacher的原型對象,就可以實作繼承,子類的執行個體就可以通路到父類的屬性和方法
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5yNwIzM0YTNwkTMtgjNxUTN2ITMxcjM4AzNxAjMtITOxMTNy8CX4AzNxAjMvwlM5EzM1IzLcd2bsJ2Lc12bj5ycn9Gbi52YucTMwIzcldWYtl2Lc9CX6MHc0RHaiojIsJye.png)
如果你不會畫這個圖,你需要去看下我的這篇文章:[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() ); //ghostwu
12 console.log( oT.userName ); //undefined, 沒有繼承到父類的userName
因為Teacher.prototype被Person.protoype替換了( 第8行代碼 ),是以,Teacher的prototype屬性就直接指向了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 ); //true
12 console.log( oT instanceof Person ); //true
13 console.log( oT instanceof Object ); //true
14 console.log( Teacher.prototype.isPrototypeOf( oT ) ); //true
15 console.log( Person.prototype.isPrototypeOf( oT ) ); //true
16 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::showUserName
15 oT.showAge(); //22
五、重寫原型對象之後,其實就是把構造函數的原型屬性(prototype)的指向發生了改變
構造函數的原型屬性(prototype)的指向發生了改變,會把原本的繼承關系覆寫(切斷)
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(); //22
14 oT.showUserName();
上例,第7行,Teacher.prototype重寫了Teacher的原型對象(prototype),原來第6行的Teacher構造函數的prototype屬性指向new Person()的關系切斷了
是以在第14行,oT.showUserName() 就會發生調用錯誤,因為Teacher構造函數上的prototype屬性不再指向父類(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,javascript
15 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,javascript
17 console.log( oT2.showUserName() ); //ghostwu
子類執行個體oT2的skills不會受到oT1的影響,子類的執行個體也能調用到父類的方法.
作者:ghostwu, 出處:http://www.cnblogs.com/ghostwu
部落格大多數文章均屬原創,歡迎轉載,且在文章頁面明顯位置給出原文連接配接