一、本文想給你聊的東西包含一下幾個方面:(僅限于es6之前的語法哈,因為es6里面class這關鍵字用上了,,)
1.原型是啥?原型鏈是啥?
2.繼承的通用概念,
3.Javascript實作繼承的方式有哪些?
二、原型是啥?原型鏈是啥?
1.原型是函式本身的prototype屬性,
首先js和java不一樣,js頂多算是一個基于物件的語言,而不是標準的面向物件的語言,
所以我們談繼承,只能是基于new關鍵字作用域建構式的場景,
上代碼:
function Person(name,age) { this.name = name; this.age = age; } console.log(Person.prototype);
代碼 1

圖1
定義一個建構式,默認起原型就是一個Object物件,相當于一個new Object(),
而且還有一個new出來的物件有一個隱式原型屬性__proto__,也指向了建構式的原型,
也就是說: Person.prototype === new Person().__proto__,
用圖來表示就是:

圖2
在上圖中,我把Object.prototype 叫rootObject,那么rootObject中就有了所有物件都共享的方法,如下圖:

圖3
如果person.toString()方法呼叫,那么起自身沒有toString方法,就是走__proto__指向的原型物件中找,而object中也沒有
所有就找到了根物件,所以建構式原型物件存在的意義是使得該建構式產生的物件可以共享屬性和方法,
所以原型物件中的屬性和方法就類似于java類中定義的static屬性和方法,所有物件都可以共享,
那么如上圖2所示,person -> object -> rootObject之間就形成了原型鏈,
二、繼承的通用概念
如果一個類B繼承了類A,在java中這些寫:class B extends A{}
那么類B就擁有了A中的所有屬性和方法,
繼承是面向物件編程的一大特性,目的很簡單,就是復用,
三、javascript中實作繼承的方式有哪些?
1.原型鏈
假如有個建構式Student想要繼承Person函式,想擁有Person中的屬性和方法,可以使用原型鏈來實作,
上代碼
// 定義Person
function Person(name,age) {
// 保證屬性有初始值
this.name = name ? name : "";
this.age = (age || age === 0) ? age : 0;
this.setName = function (name) {
this.name = name;
}
this.setAge = function (age) {
this.age = age;
}
this.getPersonInfo = function () {
return "[Person]: " + this.name + "_" + this.age;
}
}
// 定義一個所有Person物件都能共享的屬性和方法
Person.prototype.typeDesc = "人類";
Person.prototype.hello = function () {
console.log("hello");
}
function Student(score) {
this.score = score;
this.setScore = function (score) {
this.score = score;
}
this.getStudentInfo = function () {
return "[Student:]: " + this.score;
}
}
// 修改Student的原型
Student.prototype = new Person();
let student1 = new Student(90);
let student2 = new Student(80);
let student3 = new Student(70);
console.log(student1.typeDesc); // 能訪問
student1.setName("aa");
student1.setAge(99);
console.log(student1.getPersonInfo()); // 能訪問
console.log(student1.getStudentInfo()); // 能訪問
代碼2
給你一張圖吧 更清楚

圖 4
老鐵,你思考下?雖然看似student1物件能訪問了能訪問了Person中定義的方法和屬性,但是有沒有問題呢?
本來name,age是物件的私有屬性,不屬于“類級別”,但是他們卻出現在了Student的原型物件中,而且此時如果你
console.log(student2.name),發現其訪問到了原型person物件的name屬性了,是個初始的空字串,這里為什么要在Person函式中使用初始值,
這個在作業中是很常見的,物件創建出來一般屬性都是需要初始值的,
所以原型鏈實作繼承,缺點是:原型物件中多出了一些沒必要的屬性,
而且由于student2和student3等其他Student的物件仍然能訪問到原型物件person中的屬性,這會讓人產生錯覺,以為他們也擁有name,age的私有屬性,
于是,你接著看下面的方式,
2.復用構造方法
這東西嚴格來講,我感覺不太像繼承,但是好像用起來還挺好用,起碼省事了,,,,
繼續哈,上代碼啊(改變一下代碼2)
// 定義Person function Person(name,age) { // 保證屬性有初始值 this.name = name ? name : ""; this.age = (age || age === 0) ? age : 0; this.setName = function (name) { this.name = name; } this.setAge = function (age) { this.age = age; } this.getPersonInfo = function () { return "[Person]: " + this.name + "_" + this.age; } } // 定義一個所有Person物件都能共享的屬性和方法 Person.prototype.typeDesc = "人類"; Person.prototype.hello = function () { console.log("hello"); } function Student(name, age, score) { // 使用call呼叫函式,可以改變this指向,服用了父類的構造方法 Person.call(this, name,age); this.score = score; this.setScore = function (score) { this.score = score; } this.getStudentInfo = function () { return "[Student:]: " + this.score; } } let student1 = new Student("aa", 99, 99); console.log(student1.typeDesc); // undefined console.log(student1.hello); // undefined console.log(student1.getStudentInfo()); // 能訪問 console.log(student1.getPersonInfo()); // 能訪問
代碼 3
此時雖然,雖然復用了Person建構式,但是原型Person的原型student1無法訪問到,
缺點很明顯:雖然復用了Person的建構式,但是卻沒有繼承Person的原型,
好了,我們演變一下,,
3.共享原型
基于上述代碼3,在Student函式后面加入如下代碼:
Student.prototype = Person.prototype;
代碼 4
其實就是兩個建構式都指向同一原型,,
此時發現,student1能訪問Person原型上的內容了,
還是要問一下,這樣就行了嗎?
問題:一旦Student向原型里面加了變數或者函式,或者修改原型中的變數內容時,哪怕是Person構造出來的物件,
同樣也感知到了,,,, 這樣互相影響的話,兩個建構式的原型中的變數和函式摻雜在一起,確實不合適?
那怎么辦呢?
來吧,看看下面的變種,
4.圣杯模式
說實話我也不知道為啥取名叫圣杯模式,感覺也不是官方的命名,有些人還叫其他名字,
把代碼4替換成如下代碼:
// 定義空函式 function F() {} // 空函式和Person共享原型 F.prototype = Person.prototype; // 改變Student的原型 Student.prototype = new F(); // 添加原型上的建構式 Student.prototype.constructor = Student;
代碼 5
這樣做Student的原型和Person的原型就不是一個物件了,而且不像原型鏈那樣,由于new Person()作為Student.prototype導致該原型物件中包含了Person物件的私有屬性,
來吧,給你個最終版本的代碼,希望能幫助到你,能力有限,相互借鑒哈,,
5.圣杯模式+復用建構式(算是比較完美了)
// 定義Person function Person(name,age) { // 保證屬性有初始值 this.name = name ? name : ""; this.age = (age || age === 0) ? age : 0; this.setName = function (name) { this.name = name; } this.setAge = function (age) { this.age = age; } this.getPersonInfo = function () { return "[Person]: " + this.name + "_" + this.age; } } // 定義一個所有Person物件都能共享的屬性和方法 Person.prototype.typeDesc = "人類"; Person.prototype.hello = function () { console.log("hello"); } function Student(name, age, score) { // 使用call呼叫函式,可以改變this指向,服用了父類的構造方法 Person.call(this, name,age); this.score = score; this.setScore = function (score) { this.score = score; } this.getStudentInfo = function () { return "[Student:]: " + this.score; } } // 定義空函式 function F() {} // 空函式和Person共享原型 F.prototype = Person.prototype; // 改變Student的原型 Student.prototype = new F(); // 添加原型上的建構式 Student.prototype.constructor = Student; let student1 = new Student("aa", 99, 99); console.log(student1.typeDesc); // 人類 student1.hello(); // hello console.log(student1.getStudentInfo()); // 能訪問 console.log(student1.getPersonInfo()); // 能訪問 let student2 = new Student("bb", 33, 88); student2.setScore(89); // student2和student1都各自有自己的私有屬性,并不會受影響, console.log(student1.getStudentInfo()); console.log(student2.getStudentInfo()); Student.prototype.temp = "新加屬性"; console.log(Person.prototype.temp); // undefined
代碼 6
總結:可能我們在平常作業中很少這樣寫代碼,或者用到這種繼承模式,但是框架中很有可能會用到這些思想,
圣杯模式是共享原型模式的一個變種,使用空函式F來作為中間橋梁,巧妙得解決了共享原型模式的問題,同時
也解決了原型鏈模式的產生多余屬性的問題,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/552262.html
標籤:其他
上一篇:web游覽器的標簽頁仿 ios mac 蘋果的墓碑機制 (js代碼)
下一篇:返回列表
