js原型、原型鏈及繼承實現(xiàn)方式

js繼承主要包括原型鏈繼承、借用構(gòu)造函數(shù)繼承、組合繼承(原型鏈+構(gòu)造函數(shù))、組合繼承優(yōu)化、寄生組合繼承5中方式,后3中都是由前兩個組合優(yōu)化而來,所以要先了解原型及原型鏈相關(guān)內(nèi)容。
1、原型對象及原型鏈
在js中,一切皆對象,所以原型也是一個對象,稱為原型對象。
在js中,每個函數(shù)類型的數(shù)據(jù),都有一個prototype的屬性,該屬性所指向的對象就是原型對象。對于原型對象而言,其constructor屬性則指回構(gòu)造函數(shù)(構(gòu)造函數(shù)與普通函數(shù)本質(zhì)上沒有差別,只是可以用new 關(guān)鍵字創(chuàng)建對象)。原型對象最主要的作用是存放對象的共有屬性和共有方法。
構(gòu)造函數(shù)類似java中的類,每次創(chuàng)建實例對象時,都會重復(fù)創(chuàng)建一次構(gòu)造函數(shù)里面的屬性和方法,這就有些浪費了。如果把共有屬性和方法定義到構(gòu)造函數(shù)的原型對象里,各個實例對象直接原型對象的方法即可(通過原型鏈實現(xiàn)),避免浪費空間。
示例:

function Person(name, age) {
  this.name = name;
  this.age = age;
  this.say = function () {
    console.log("構(gòu)造函數(shù)方法--- ", this.name, this.age);
  }
}
Person.prototype.sex = "女";
Person.prototype.color = ["yellow", "red", "blue"];
Person.prototype.hello = function () {
  console.log("原型對象方法---- hello ", this.sex);
}
let p1 = new Person("dlj1", 20);
let p2 = new Person("dlj2", 30);
console.log("p1 is ", p1);
console.log("p2 is ", p2);
p2.hello();
console.log("p1.color ", p1.color.join("/"));
console.log("person 原型對象 ", p1.__proto__, p1.constructor.prototype == p1.__proto__, p1.__proto__ == Person.prototype, Person.prototype.constructor == p1.constructor);
console.log("person 原型對象上級原型對象包含join方法 ", p1.color.__proto__.hasOwnProperty("join"));

image.png

從上面的示例可以看出,實例對象中沒有sex屬性和hello方法,但是并不影響p2調(diào)用hello方法,就是因為當(dāng)實例對象內(nèi)沒有調(diào)用的方法或?qū)傩詴r,就會向?qū)?yīng)構(gòu)造函數(shù)的原型對象中查找,如果原型對象中也沒有,則會繼續(xù)向原型對象的原型對象上查找,直到最頂級Object.prototype(所有原型對象都是object的實例),如果最頂級沒有找到則返回undefined,這個順著proto逐步向上查找,形成了像鏈條一樣的結(jié)構(gòu)就叫原型鏈(也叫隱式原型鏈)。例如p1的原型對象中沒有join方法,p1.color可以調(diào)用join就是因為上級原型對象(array)中包含join方法。

2、繼承方式介紹
1)原型鏈繼承
將父類實例作為子類原型,由于方法定義在父類原型上,復(fù)用了父類構(gòu)造函數(shù)的方法,所以實現(xiàn)了方法復(fù)用。
示例:

function Person(name) {
  this.name = name || "父類";
  this.say = function () {
    console.log("父類構(gòu)造函數(shù)方法--- ", this.name);
  }
}
Person.prototype.sex = "女";
Person.prototype.color = ["yellow", "red", "blue"];
Person.prototype.hello = function () {
  console.log("父類原型對象方法---- hello ", this.sex);
}
function Child(age) {
  this.age = age;
}
Child.prototype = new Person("dlj");
Child.prototype.constrctor = Child;
Child.prototype.address = "北京";
Child.prototype.cHello = function () {
  console.log("子類原型對象方法---- chello ", this.sex);
}
let chd1 = new Child("22");
console.log(chd1.say());
console.log(chd1.hello());
console.log(chd1.cHello());
console.log("age name sex --- ", chd1.age, chd1.name, chd1.sex);
console.log("chd1  ", chd1);
console.log("chd1.__proto__   ", chd1.__proto__);
console.log("chd1.__proto__.__proto__   ", chd1.__proto__.__proto__);
console.log("chd1.__proto__  has color is ", chd1.__proto__.hasOwnProperty("color"));
chd1.color.push("aa");
console.log(new Person().color);
image.png

從上面的示例可以看出:
①子類原型對象中包含了父類構(gòu)造函數(shù)中的屬性、方法和子類原型對象中的屬性、方法,但是不包含父類原型對象中方法和屬性color。
②子類實例能調(diào)用父類原型對象中的hello方法 color屬性則是由于父類原型對象是子類原型對象的原型對象。
③父類原型對象中引用類型數(shù)據(jù)會在各個實例中共享,一個子類實例修改父類原型對象中引用屬性的值(color),其他實例對象調(diào)用該引用屬性時,值也是修改后的。
④只能繼承一個父類,不能多繼承。
注意:子類原型對象上定義屬性和方法必須在Child.prototype = new Person("dlj")之后,否則子類原型對象上獨有的屬性和方法會丟失。
2)借用構(gòu)造函數(shù)
借用父類的構(gòu)造函數(shù)來增強(qiáng)子類實例,相當(dāng)于復(fù)制父類的實例屬性給子類,所以子類實例之間相互獨立,不共享父類的引用屬性,并可實現(xiàn)多繼承(call或apply).
示例:

function Person1(name) {
 this.name = name || "父類";
 this.job = ["job1", "job2"];
 this.say = function () {
   console.log("父類構(gòu)造函數(shù)方法--- ", this.name);
 }
}
Person1.prototype.sex = "女";
Person1.prototype.color = ["yellow", "red", "blue"];
Person1.prototype.hello = function () {
 console.log("父類原型對象方法---- hello ", this.sex);
}
function Person2(name) {
 this.name2 = name || "父類2";
 this.job2 = ["job1", "job2"];
 this.say2 = function () {
   console.log("父類構(gòu)造函數(shù)方法--- ", this.name2);
 }
}
Person2.prototype.sex2 = "女";
Person2.prototype.color2 = ["yellow", "red", "blue"];
Person2.prototype.hello2 = function () {
 console.log("父類原型對象方法---- hello ", this.sex2);
}
function Child(name, age) {
 Person1.call(this, name);
 Person2.call(this, name);
 this.age = age;
 this.hello3 = function () {
   console.log("子類構(gòu)造函數(shù)方法--- ", this.age);
 }
}
Child.prototype.address = "北京";
Child.prototype.cHello = function () {
 console.log("子類原型對象方法---- chello ");
}
let chd1 = new Child("dlj", 11);
let chd2 = new Child("dlj1", 111);
console.log("chi1 is ", chd1);
console.log("chd1.__proto__ ", chd1.__proto__);
console.log("chd1.__proto__.__proto__  ", chd1.__proto__.__proto__);
chd1.job2.push("job3");
console.log("chd1.job2 ", chd1.job2, chd2.job2);
console.log("chd1.sex ", chd1.sex);//undefined 子類實例未繼承父類原型中屬性
chd1.say2();
//console.log("chd1.hello ", chd1.hello());//報錯 chd1.hello is not a function 子類實例未繼承父類原型中和方法
image.png

從示例中可以看出:
①因為通過父類構(gòu)造函數(shù)繼承,不涉及父類原型對象,所以父類原型對象中的屬性和方法不可用(無法方法復(fù)用),子類只能調(diào)用自身和繼承父類構(gòu)造函數(shù)中的方法和屬性。
②子類可以繼承多個父類,實現(xiàn)多繼承。
③父類中引用屬性(job2)在子類實例中相互獨立。
3)組合繼承
將借用構(gòu)造函數(shù)和原型鏈繼承組合使用,既可以實現(xiàn)父類原型對象方法的復(fù)用,有可以使父類中引用屬性相互獨立并實現(xiàn)多繼承。

function Person1(name) {
  this.name = name || "父類";
  this.job = ["job1", "job2"];
  this.say = function () {
    console.log("父類構(gòu)造函數(shù)方法--- ", this.name);
  }
}
Person1.prototype.sex = "女";
Person1.prototype.color = ["yellow", "red", "blue"];
Person1.prototype.hello = function () {
  console.log("父類原型對象方法---- hello ", this.sex);
}
function Person2(name) {
  this.name2 = name || "父類2";
  this.job2 = ["job1", "job2"];
  this.say2 = function () {
    console.log("父類構(gòu)造函數(shù)方法--- ", this.name2);
  }
}
Person2.prototype.sex2 = "女";
Person2.prototype.color2 = ["yellow", "red", "blue"];
Person2.prototype.hello2 = function () {
  console.log("父類原型對象方法---- hello ", this.sex2);
}
function Child(name, age) {
   Person1.call(this, name);
  Person2.call(this, name);
  this.age = age;
  this.hello3 = function () {
    console.log("子類構(gòu)造函數(shù)方法--- ", this.age);
  }
}
Child.prototype = new Person1("dlj");
Child.prototype.constrctor = Child;

Child.prototype.address = "北京";
Child.prototype.cHello = function () {
  console.log("子類原型對象方法---- chello ");
}
let chd1 = new Child("dlj", 11);
let chd2 = new Child("dlj1", 111);
console.log("chi1 is ", chd1);
console.log("chd1.__proto__ ", chd1.__proto__);
console.log("chd1.__proto__.__proto__  ", chd1.__proto__.__proto__);
chd1.job2.push("job3");
console.log("chd1.job2 ", chd1.job2, chd2.job2);
console.log("chd1.sex ", chd1.sex);
chd1.say2();
chd1.hello();
image.png

從示例可以看出子類實例中屬性、方法與子類原型對象的屬性和方法有重復(fù),這也算是組合繼承唯一的缺點了。
4)組合繼承優(yōu)化
3)的實現(xiàn)方式之所以造成屬性或方法重復(fù),是因為調(diào)用了兩次創(chuàng)建父類實例的方法,這是將父類原型對象直接賦值給子類原型可以避免一次創(chuàng)建父類實例方法的調(diào)用,從而解決重復(fù)的問題。將上個示例中Child.prototype = new Person1("dlj") 改為Child.prototype = Person1.prototype(或者改為Child.prototype = Object.create(Person1.prototype));其他不動,與3)相比,重復(fù)屬性和方法被去掉了。
示例:


image.png

改為Child.prototype = Object.create(Person1.prototype))時

接3)中示例
console.log("Child.prototype.__proto__ ", Child.prototype.__proto__);
console.log("Person1.prototype ", Person1.prototype);
console.log(Child.prototype.__proto__ == Person1.prototype);

//Child.prototype = new Person1("dlj")時 
Child.prototype.__proto__  {}
Person1.prototype  Person1 {
  sex: '女',
  color: [ 'yellow', 'red', 'blue' ],
  hello: [Function],
  constrctor: [Function: Child],
  address: '北京',
  cHello: [Function]
}
false

//Child.prototype = Object.create(Person1.prototype)時
Child.prototype.__proto__  Person1 {
  sex: '女',
  color: [ 'yellow', 'red', 'blue' ],
  hello: [Function]
}
Person1.prototype  Person1 {
  sex: '女',
  color: [ 'yellow', 'red', 'blue' ],
  hello: [Function]
}
true

Object.create(object,propertiesObject)方法創(chuàng)建一個新對象,使用第一個參數(shù)來提供創(chuàng)建對象的proto(以第一個參數(shù)作為新對象的構(gòu)造函數(shù)的原型),第二個可選參數(shù),是添加到新創(chuàng)建對象的屬性。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容