上一片文章,給你們分享了對象的建立和函數的建立及優缺點。還不知道的小夥伴能夠去面向對象,搞定對象這節先了解一下,回過頭再來看這篇文章。javascript
什麼是原型鏈以及書中介紹了好多種繼承方法,優缺點是什麼!java
當談到繼承時,JavaScript 只有一種結構:對象。每一個實例對象( object )都有一個私有屬性(稱之爲 proto )指向它的構造函數的原型對象(prototype )。該原型對象也有一個本身的原型對象( proto ) ,層層向上直到一個對象的原型對象爲null。根據定義,null沒有原型,並做爲這個原型鏈中的最後一個環節。---來着MDNapp
這裏是官方給出的解釋,咱們用個例子,來具體的去看看這個原型鏈。在舉例以前,咱們先來了解一下,原型和實例的關係。函數
每一個構造函數(constructor)都有一個原型對象(prototype),原型對象都包含一個指向構造函數的指針,而實例都包含一個指向原型對象的內部指針。post
上面的這段還若是你還沒理解,我仍是建議你去看面向對象,搞定對象,這裏已經給你詳細的解釋了。這裏就很少說了。
那咱們想一下,若是讓一個函數的實例對象,指向函數的原型對象,會發生什麼?ui
function Father(){
this.property = true;
}
Father.prototype.getFatherValue = function(){
return this.property;
}
function Son(){
this.sonProperty = false;
}
//繼承 Father
Son.prototype = new Father();//Son.prototype被重寫,致使Son.prototype.constructor也一同被重寫
Son.prototype.getSonVaule = function(){
return this.sonProperty;
}
var instance = new Son();
alert(instance.getFatherValue());//true
複製代碼
instance實例經過原型鏈找到了Father原型中的getFatherValue方法. 爲了找到getFatherValue屬性,都經歷了什麼呢?this
instance --> new Father() --> Father.prototype --> object.prototype --> undefined 這就是你日思夜想不得解的原型鏈啊!是否是很好理解了。
實例與原型鏈接起來的鏈條,叫作原型鏈(我本身定義的)。 spa
上面的例子,就是原型鏈繼承的標準例子。可是原型鏈繼承,是有問題的。高級程序設計上說,他有兩個問題:prototype
是有這兩個問題,但我說也不必定,當你原型鏈不須要飲用類型,建立的子類型不須要傳參!那就不存在問題啊!哈哈哈
別鬧,畢竟這樣的需求不多,甚至根本不可能有,仍是老實兒解決上面的問題吧。設計
原理:在子類型構造函數中調用超類型構造函數。
先來看看原型鏈函數的問題:
function Father (){
this.colors = ['red','green','blue'];
}
function Son (){
}
Son.prototype = new Father();
let instance1 = new Son();
instance1.colors.push('white');
console.log(instance1.colors);
let instance2 = new Son();
console.log(instance2.colors);
複製代碼
執行以後,就會發現,返回的結果是同樣的,也就是說,全部的實例都會共享colors這個屬性。這並非不咱們想要的結果。
在子類型構造函數中調用超類型構造函數。
function Father(name){
this.colors = ['red','green','blue'];
}
function Son(name){
Father.call(this,name);
}
let instance1 = new Son();
instance1.colors.push('white');
console.log(instance1.colors); // ['red','green','blue','white'];
let instance2 = new Son();
console.log(instance2.colors); // ['red','green','blue'];
複製代碼
請記住,函數只是在特定環境中執行的代碼的對象,所以能夠經過call()或apply()方法也能夠在新建立的對象上執行構造函數。
這段話很好理解:誰幹(調)的,誰負責。
結合上面的代碼,instance1調用的colors屬性,那就你instance1對象負責,我instance2沒作任何事,我不負責。
function Father(name){
this.colors = ['red','green','blue'];
this.name = name; //新增code
}
function Son(name){
Father.call(this,name); //將name,傳遞給Father。
}
let instance1 = new Son('hanson'); //建立實例對象時,傳入參數
instance1.colors.push('white');
console.log(instance1.colors); // ['red','green','blue','white'];
console.log(instance1.name); // 'hanson'
let instance2 = new Son();
console.log(instance2.colors); // ['red','green','blue'];
複製代碼
很好理解嘛,經過構造函數Son,咱們給Father傳了參。完美解決傳參問題。
如何解決這兩個問題呢?引出咱們下一個繼承方法---組合繼承。
組合繼承也叫作僞經典繼承,指的是將原型鏈和借用構造函數的技術組合在一塊兒,從而發揮兩者之長的繼承模式
話很少說,上代碼!
function Father(name){
this.colors = ['red','green','blue'];
this.name = name;
}
Father.prototype.sayName = function(){
console.log(this.name)
}
function Son(name,age){
Father.call(this,name); //將name,傳遞給Father。
this.age = age;
}
Son.prototype = new Father();
Son.prototype.sayAge = function(){
console.log(this.age);
}
let instance1 = new Son('hanson',18);//建立實例對象時,傳入參數
Son.prototype.constructor = Son;
instance1.colors.push('white');
console.log(instance1.colors); // ['red','green','blue','white'];
console.log(instance1.sayAge); // 18
let instance2 = new Son('grey',20);
console.log(instance2.colors); // ['red','green','blue'];
console.log(instance2.sayName()); // 'grey'
console.log(instance2.sayAge()); //18
複製代碼
總結:分別擁有本身的屬性,還享有公共的方法,真好。
這兩個繼承方式,都是一個叫克羅克德的人提出的,咱也不知道,咱也不敢問,估計式爲了後面的組合式寄生繼承作鋪墊?
//原型式繼承
var person = {
name:'hanson',
friends:['sha','feng','qiang']
}
var another = Object(person); //複製一份
another.name = 'bo';
another.friends.push('lei');
console.log(another.friends); //['sha','feng','qiang','lei']
console.log(another.name); //'bo'
console.log(person.friends); //['sha','feng','qiang','lei']
複製代碼
相比上面的方法,這個要簡便的多,沒用到構造函數,原型鏈。但問題也十分明顯,污染引用屬性。
//寄生式繼承---也是克羅克德提出來的
function creat(obj) {
var clone = Object(obj);
clone.sayName = function(){
console.log('hanson')
}
return clone;
}
var person = {
age:18,
friends:['qiang','sha','feng']
}
var another = creat(person);
console.log(another.sayName());
console.log(person.sayName());
複製代碼
其實道理沒變,clone了一份對象,可是,一樣,peron對象也被玷污了!不信,你打印一下,person也有了sayName()方法。犧牲太大,反正我不用~
在講解以前呢,咱們先來看看,組合繼承的缺點。
function Father(name){
this.name = name;
this,friends = ['qiang','sha','feng'] ;
}
Father.prototype.constructor = function(){
console.log(this.name);
}
function Son(name,age){
Father.call(this); //第二次調用
}
Son.prototype = new Father(); //第一次調用
Son.prototype.constructor = Son;
Son.prototype.sayName = function(){
console.log(this.name);
}
複製代碼
咱們第一次調用超類型構造函數(Father),無非是想指定子類型的原型,讓他們直接創建聯繫而已。 給他個副本,又如何!
function inheritPrototype(Son,Father){
var prototype = Object(Father.prototype);
prototype.constructor = Son;
Son.prototype = prototype;
}
複製代碼
這個函數,接收兩個參數,一個子類型,一個超類型。在函數內部,
function Father(name){
this.name = name;
this,friends = ['qiang','sha','feng'] ;
}
Father.prototype.constructor = function(){
console.log(this.name);
}
function Son(name,age){
Father.call(this); //第二次調用
}
inheritPrototype(Son,Father);
Son.prototype.sayName = function(){
console.log(this.name);
}
複製代碼
這個例子,只調用了一次超類型構造函數,避免了在子類型上建立沒必要要的屬性和方法。是最理想的繼承方式。 可是,我可能不會用。。。你會用嗎?