我試圖用簡單、清晰、專業的方式講清楚js繼承這麼一個有點複雜的問題javascript
由於須要複用java
經過複用咱們能夠實現共享。函數
從對象的角度來講,咱們不想重複寫同一段邏輯,因此邏輯須要複用;可是咱們不但願一個對象掌管的變量被其它對象修改。因此變量不能共享。也就是要共享函數,不共享屬性。this
原型鏈能夠用來實現共享,構造函數可讓對象擁有本身的屬性,二者結合的組合的組合繼承
即是最經常使用
的繼承方式。spa
組合繼承中有一個弊端,經過簡單的改進,寄生組合式繼承
能夠實現最有效
的繼承。接下來將展開講解組合繼承
和寄生組合式繼承
。prototype
若是已經瞭解了這些知識,能夠直接跳過設計
function Person () { } Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job="Software Engineer"; Person.prototype.sayName=function () { console.log(this.name); }; var person1=new Person(); person1.sayName();//Nicholas
構造函數(Person
)能夠用來建立實例(person1
),建立的實例和構造函數共同指向一個原型(Person.prototype
),原型的構造器又指向了構造函數。
若是沒有指定原型,則默認的原型爲Object.prototype
。code
new操做符具體幹了什麼呢?對象
建立一個空對象,而且 this 變量引用該對象,同時還繼承了該函數的原型。var obj = {};
blog
屬性和方法被加入到 this 引用的對象中。obj.__proto__ = Base.prototype;
(圖示中用[[Prorotype]]
表明__proto__
)
新建立的對象由 this 所引用,而且最後隱式的返回 this 。Base.call(obj);
屏蔽:
//...上面的代碼,此處省略 var person2=new Person(); person1.name='Mike'; person1.sayName();//Mike person2.sayName();//Nicholas
屬性值沿着原型鏈,遵循就近原則。就近的屬性值會屏蔽上層原型鏈的屬性值
動態追加而非斷開:
誤區:javaScript高級程序設計第三版 中指出了 修改原型時 這樣一種斷開情景
function Person () { } var friend=new Person(); Person.prototype={ constructor:Person, name:'Nicholas', age:29, job:'Software Engineer', sayName:function () { console.log(this.name); } } friend.sayName();//error
friend的原型丟失了,和它斷開了聯繫。
其實如今已經有所改進,運行friend.sayName();
,應該輸出爲:Nicholas
,當修改原型時,語言的設計意圖是什麼?若是你是語言的設計者到底應該爲什麼種輸出?
我認爲最好的效果是確保其穩妥性的同時,又不失靈活是最佳的,在這個例子中,建立完原型關係後又想追加原型中的屬性,我但願他可以追加成功,所以,但願它輸出爲:Nicholas
。
可是,我不但願原型中的屬性隨意修改,先開始的時候我給他賦予了值,也定義了相應的方法使用這個值,若是這個值在後期隨意修改的話,使用這個方法的時候會以爲不知所措。
因此我但願的涉及是原型屬性值能夠追加但定義後值不能隨意修改,以下:
function Person () { } Person.prototype.name = "Greg"; Person.prototype.age = 24; Person.prototype.job="Doctor"; var friend=new Person(); Person.prototype={ constructor:Person, name:'Nicholas', age:29, job:'Software Engineer', sayName:function () { console.log(this.name); } } friend.sayName();//Greg
打印friend.__proto__
:
Object {name: "Greg", age: 24, job: "Doctor"} age:24 job:"Doctor" name:"Greg" sayName:function()
橫向上,每一個原型的屬性 在原型自己被修改時 只是動態追加而不會修改原值;縱向上,原型鏈繼承遵循就近原則。
讓一個構造函數A的原型爲另外一個構造函數B的實例,便造成了A繼承B的原型鏈。
function SuperType() { this.property=true; } SuperType.prototype.getSuperValue=function () { return this.property; }; function SubType() { this.subproperty=false; }; SubType.prototype=new SuperType(); SubType.prototype.getSubValue=function () { return this.subproperty; } var instance=new SubType();
不易理解的地方:subproperty的屬性在實例中而不在SubType構造函數中;property的屬性在 SubType.prototype中而不在SuperType構造函數中。
講解:subproperty和property都是實例屬性,在哪裏建立了實例就出如今哪裏,
優點:函數共享
缺陷:原型鏈體現出了共享的特性,當一個實例改變了其從原型那裏繼承來的引用屬性值時,其它繼承自這個原型屬性的值都將被改變。
function SuperType() { this.colors=['red','blue','green']; } function SubType() { }; SubType.prototype=new SuperType(); var instance1=new SubType(); instance1.colors.push('black'); console.log(instance1.colors);//["red", "blue", "green", "black"] var instance2=new SubType(); console.log(instance2.colors);//["red", "blue", "green", "black"]
function SuperType(name) { this.name=name; } function SubType() { SuperType.call(this,'Nicholas'); this.age=29; } var instance=new SubType(); console.log(instance.name+' '+instance.age);//Nicholas 29
構造函數每一個實例的屬性都藉助構造函數本身生成
優點:每一個實例屬性各自獨立
缺陷:沒法共享函數
組合繼承結合了原型鏈繼承和構造函數繼承的優點:
function SuperType(name) { this.name=name; this.colors=['red','blue','green'] } SuperType.prototype.sayName=function () { console.log(this.name); } function SubType(name,age) { SuperType.call(this,name); this.age=age; } SubType.prototype=new SuperType(); SubType.prototype.sayAge=function () { console.log(this.age); } var instance1=new SubType('Nicholas',29); instance1.colors.push('black'); console.log(instance1.colors); instance1.sayName(); instance1.sayAge(); var instance2=new SubType('Greg',27); console.log(instance2.colors); instance2.sayName(); instance2.sayAge();
組合繼承雖然實現了需求:共享函數,但不共享屬性,但是它是有不足之處:咱們在獨立屬性時只是但願實例有各自的屬性就行了,不須要原型(SubType.prototype)中也存在屬性,這就多餘了。
SubType.prototype存在屬性是由於它對SuperType作了實例化繼承,咱們將實例話繼承換成前拷貝繼承即可以解決問題:
//將組合繼承中的實例化繼承: //SubType.prototype=new SuperType(); //改成淺拷貝繼承: function inheritPrototype(subType,superType){ var prototype=Object.create(superType.prototype);//建立對象 prototype.constructor=subType; subType.prototype=prototype; } inheritPrototype(SubType,SuperType);
Object.create()代碼說明:
function Object.create(o){ function F() {}; F.prototype=o; return new F(); }
javascript高級程序設計 第三版