如下內容均基於本人對《JavaScript高級程序設計》第三版6.3小節的理解app
function Animal(name) { var name = name; //'私有(受保護)'成員,只容許在父類的構造函數中賦值 this.food = undefined; //'公有'成員 //引用類型的成員 this.birthday = { year: undefined }; //構造函數中的方法,打印一些基本信息 this.greeting = function() { console.log('Hi, my name is {' + name + '} I like eat {' + this.food + '} and my birth year is {' + this.birthday.year + '}'); }; //原型中的方法,將構造函數中的非函數成員以JSON格式打印 Animal.prototype.briefInfo = function() { var brief = { name: name, food: this.food, birthday: this.birthday }; console.log(JSON.stringify(brief)); }; }
實現方式:子類的原型指向父類的實例函數
子類:測試
function Dog() {} Dog.prototype = new Animal(); //原型指向父類的實例
測試:this
var dog1 = new Dog(); dog1.food = 'shit'; dog1.birthday.year = 2015; var dog2 = new Dog(); dog2.food = 'bones'; dog2.birthday.year = 2016; dog1.greeting(); //console: Hi, my name is {undefined} I like eat {shit} and my birth year is {2016} dog2.greeting(); //console: Hi, my name is {undefined} I like eat {bones} and my birth year is {2016} dog1.briefInfo(); //console: {"food":"shit","birthday":{"year":2016}} dog2.briefInfo(); //console: {"food":"bones","birthday":{"year":2016}} //以上, //birthday是引用類型的屬性,因此dog1的birthday.year被dog2覆蓋了; //沒法給dog1和dog2的name賦值
存在的問題:prototype
引用類型的對象會被子類的全部實例共享(1.1)
設計
沒法在建立子類的實例時,給父類的構造函數傳遞參數(1.2)
code
實現方式:在子類的構造函數中利用call(或者apply)方法執行父類構造函數(問題1.2解決)
,將執行對象設爲子類的this,至關於把父類構造函數中的成員拷貝了一份到子類(問題1.1解決)
對象
子類繼承
function Dog(name) { Animal.call(this, name); }
測試ip
var dog1 = new Dog('tom'); dog1.food = 'shit'; dog1.birthday.year = 2015; var dog2 = new Dog('mike'); dog2.food = 'bones'; dog2.birthday.year = 2016; dog1.greeting(); //console: Hi, my name is {tom} I like eat {shit} and my birth year is {2015} dog2.greeting(); //console: Hi, my name is {mike} I like eat {bones} and my birth year is {2016} //briefInfo是父類原型中的屬性,並無被繼承,如下語句會報錯 dog1.briefInfo(); //error: dog1.briefInfo is not a function dog2.briefInfo(); //error: dog2.briefInfo is not a function
存在的問題:
父類原型中定義的屬性沒法被繼承
綜合方式一方式二,一種很明顯的方式呼之欲出了:
實現方式:結合原型鏈繼承和經典繼承
子類
function Dog(name) { Animal.call(this, name); //經典繼承 } Dog.prototype = new Animal(); //原型鏈繼承
測試
var dog1 = new Dog('tom'); dog1.food = 'shit'; dog1.birthday.year = 2015; var dog2 = new Dog('mike'); dog2.food = 'bones'; dog2.birthday.year = 2016; dog1.greeting(); //console: Hi, my name is {tom} I like eat {shit} and my birth year is {2015} dog2.greeting(); //console: Hi, my name is {mike} I like eat {bones} and my birth year is {2016} dog1.briefInfo(); //console: {"name":"tom","food":"shit","birthday":{"year":2015}} dog2.briefInfo(); //console: {"name":"mike","food":"bones","birthday":{"year":2016}} //終於獲得了預期的結果,算是較好的實現了繼承。 //可是,並不完美
存在的問題
父類的構造函數被調用了兩次
爲了引出下一個繼承方式,先將函數的繼承放在一邊,看一下js中對象的繼承
(摘抄原文)
1. 原型式繼承
var person = { name: 'martin' firend: ['bob', 'steven'] }; function object(o) { function F() {}; F.prototype = o; return new F(); } var anotherPerson = object(person); //antherPerson繼承了person2. 寄生式繼承
function createAnother(original) { var clone = object(original); //原型式繼承定義的方法 //擴展對象 clone.sayHi = function() { console.log('hi'); } return clone; }
回到正題,接下來介紹一顆 「銀彈」
實現方式:
利用經典繼承拷貝父類構造中的屬性到子類
利用原型式繼承建立一個繼承父類原型的對象
將該對象的constructor屬性指向子類的構造函數(寄生式繼承:擴展對象)
將子類的prototype指向該對象
子類
function Dog(name) { Animal.call(this, name); } function F() {} var supProto = Animal.prototype; F.prototype = supProto; var subProto = new F(); subProto.constructor = Dog; Dog.prototype = subProto;
測試
var dog1 = new Dog('tom'); dog1.food = 'shit'; dog1.birthday.year = 2015; var dog2 = new Dog('mike'); dog2.food = 'bones'; dog2.birthday.year = 2016; dog1.greeting(); //console: Hi, my name is {tom} I like eat {shit} and my birth year is {2015} dog2.greeting(); //console: Hi, my name is {mike} I like eat {bones} and my birth year is {2016} dog1.briefInfo(); //console: {"name":"tom","food":"shit","birthday":{"year":2015}} dog2.briefInfo(); //console: {"name":"mike","food":"bones","birthday":{"year":2016}}