一、本文想給你聊的東西包含一下幾個(gè)方面:(僅限于es6之前的語法哈,因?yàn)閑s6里面class這關(guān)鍵字用上了。。)
1.原型是啥?原型鏈?zhǔn)巧叮?/p>
2.繼承的通用概念。
3.Javascript實(shí)現(xiàn)繼承的方式有哪些?
?文章來源地址http://www.zghlxwxcb.cn/news/detail-439522.html
二、原型是啥?原型鏈?zhǔn)巧叮?/p>
1.原型是函數(shù)本身的prototype屬性。
首先js和java不一樣,js頂多算是一個(gè)基于對象的語言,而不是標(biāo)準(zhǔn)的面向?qū)ο蟮恼Z言。
所以我們談繼承,只能是基于new關(guān)鍵字作用域構(gòu)造函數(shù)的場景。
上代碼:
function Person(name,age) { this.name = name; this.age = age; } console.log(Person.prototype);
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?代碼 1
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 圖1
?定義一個(gè)構(gòu)造函數(shù),默認(rèn)起原型就是一個(gè)Object對象,相當(dāng)于一個(gè)new Object()。
而且還有一個(gè)new出來的對象有一個(gè)隱式原型屬性__proto__,也指向了構(gòu)造函數(shù)的原型。
也就是說: Person.prototype? ?=== new Person().__proto__。
用圖來表示就是:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 圖2
?
在上圖中,我把Object.prototype 叫rootObject,那么rootObject中就有了所有對象都共享的方法,如下圖:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?圖3
?如果person.toString()方法調(diào)用,那么起自身沒有toString方法,就是走_(dá)_proto__指向的原型對象中找,而object中也沒有
所有就找到了根對象。所以構(gòu)造函數(shù)原型對象存在的意義是使得該構(gòu)造函數(shù)產(chǎn)生的對象可以共享屬性和方法。
所以原型對象中的屬性和方法就類似于java類中定義的static屬性和方法,所有對象都可以共享。
那么如上圖2所示,person? ->? object? -> rootObject之間就形成了原型鏈。
?
二、繼承的通用概念
如果一個(gè)類B繼承了類A,在java中這些寫:class B extends A{}??
那么類B就擁有了A中的所有屬性和方法。
繼承是面向?qū)ο缶幊痰囊淮筇匦?,目的很簡單,就是?fù)用。
?
三、javascript中實(shí)現(xiàn)繼承的方式有哪些?
1.原型鏈
假如有個(gè)構(gòu)造函數(shù)Student想要繼承Person函數(shù),想擁有Person中的屬性和方法,可以使用原型鏈來實(shí)現(xiàn)。
上代碼
// 定義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;
}
}
// 定義一個(gè)所有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是對象的私有屬性,不屬于“類級別”,但是他們卻出現(xiàn)在了Student的原型對象中,而且此時(shí)如果你
console.log(student2.name),發(fā)現(xiàn)其訪問到了原型person對象的name屬性了,是個(gè)初始的空字符串,這里為什么要在Person函數(shù)中使用初始值,
這個(gè)在工作中是很常見的,對象創(chuàng)建出來一般屬性都是需要初始值的。
所以原型鏈實(shí)現(xiàn)繼承,缺點(diǎn)是:原型對象中多出了一些沒必要的屬性。
而且由于student2和student3等其他Student的對象仍然能訪問到原型對象person中的屬性,這會讓人產(chǎn)生錯(cuò)覺,以為他們也擁有name,age的私有屬性。
于是,你接著看下面的方式。
?
2.復(fù)用構(gòu)造方法
這東西嚴(yán)格來講,我感覺不太像繼承,但是好像用起來還挺好用,起碼省事了。。。。
繼續(xù)哈,上代碼?。ǜ淖円幌麓a2)
// 定義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; } } // 定義一個(gè)所有Person對象都能共享的屬性和方法 Person.prototype.typeDesc = "人類"; Person.prototype.hello = function () { console.log("hello"); } function Student(name, age, score) { // 使用call調(diào)用函數(shù),可以改變this指向,服用了父類的構(gòu)造方法 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
此時(shí)雖然,雖然復(fù)用了Person構(gòu)造函數(shù),但是原型Person的原型student1無法訪問到。
缺點(diǎn)很明顯:雖然復(fù)用了Person的構(gòu)造函數(shù),但是卻沒有繼承Person的原型。
好了,我們演變一下。。
?
3.共享原型
基于上述代碼3,在Student函數(shù)后面加入如下代碼:
Student.prototype = Person.prototype;
? 代碼 4
其實(shí)就是兩個(gè)構(gòu)造函數(shù)都指向同一原型。。
此時(shí)發(fā)現(xiàn),student1能訪問Person原型上的內(nèi)容了。
還是要問一下,這樣就行了嗎?
問題:一旦Student向原型里面加了變量或者函數(shù),或者修改原型中的變量內(nèi)容時(shí),哪怕是Person構(gòu)造出來的對象,
同樣也感知到了。。。。? 這樣互相影響的話,兩個(gè)構(gòu)造函數(shù)的原型中的變量和函數(shù)摻雜在一起,確實(shí)不合適?
那怎么辦呢?
來吧,看看下面的變種。
?
4.圣杯模式
說實(shí)話我也不知道為啥取名叫圣杯模式,感覺也不是官方的命名,有些人還叫其他名字。
把代碼4替換成如下代碼:
// 定義空函數(shù) function F() {} // 空函數(shù)和Person共享原型 F.prototype = Person.prototype; // 改變Student的原型 Student.prototype = new F(); // 添加原型上的構(gòu)造函數(shù) Student.prototype.constructor = Student;
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?代碼 5
這樣做Student的原型和Person的原型就不是一個(gè)對象了,而且不像原型鏈那樣,由于new Person()作為Student.prototype導(dǎo)致該原型對象中包含了Person對象的私有屬性。
來吧,給你個(gè)最終版本的代碼,希望能幫助到你,能力有限,相互借鑒哈。。
?
5.圣杯模式+復(fù)用構(gòu)造函數(shù)(算是比較完美了)
?
// 定義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; } } // 定義一個(gè)所有Person對象都能共享的屬性和方法 Person.prototype.typeDesc = "人類"; Person.prototype.hello = function () { console.log("hello"); } function Student(name, age, score) { // 使用call調(diào)用函數(shù),可以改變this指向,服用了父類的構(gòu)造方法 Person.call(this, name,age); this.score = score; this.setScore = function (score) { this.score = score; } this.getStudentInfo = function () { return "[Student:]: " + this.score; } } // 定義空函數(shù) function F() {} // 空函數(shù)和Person共享原型 F.prototype = Person.prototype; // 改變Student的原型 Student.prototype = new F(); // 添加原型上的構(gòu)造函數(shù) 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
總結(jié):可能我們在平常工作中很少這樣寫代碼,或者用到這種繼承模式,但是框架中很有可能會用到這些思想。
圣杯模式是共享原型模式的一個(gè)變種,使用空函數(shù)F來作為中間橋梁,巧妙得解決了共享原型模式的問題,同時(shí)
也解決了原型鏈模式的產(chǎn)生多余屬性的問題。文章來源:http://www.zghlxwxcb.cn/news/detail-439522.html
?
到了這里,關(guān)于一文讓你搞懂javascript如何實(shí)現(xiàn)繼承的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!