天天看點

[js高手之路]從原型鍊開始圖解繼承到組合繼承的産生

于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的原型對象,就可以實作繼承,子類的執行個體就可以通路到父類的屬性和方法

[js高手之路]從原型鍊開始圖解繼承到組合繼承的産生

如果你不會畫這個圖,你需要去看下我的這篇文章:[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

部落格大多數文章均屬原創,歡迎轉載,且在文章頁面明顯位置給出原文連接配接

繼續閱讀