function Person(name) {
this.name = name
}
Person.prototype.say = function() {
console.log(`你好,我是 ${this.name}`)
}
// 嘗試定義一個(gè)子類,來繼承 Person
function Student(name, id){
Person.call(this, name) // 調(diào)用父類構(gòu)造函數(shù)
this.id = id
}
Student.prototype.fun = function(){
console.log(`你好,我是 ${this.name},我的學(xué)號是${this.id}`);
}
let s = new Student('瑪麗','1534543')
s.fun()//你好,我是 瑪麗,我的學(xué)號是1534543
s.toString()
// s.say() s的proto中找,proto指向器構(gòu)造函數(shù)的prototype,原型是一個(gè)對象也有proto,proto又可以指向構(gòu)造函數(shù)的prototype
console.log(Student.prototype.__proto__===Object.prototype);//true
s.fun()//你好,我是 瑪麗,我的學(xué)號是1534543
說明Person的屬性成功被繼承了
但是這個(gè)時(shí)候的say()方法未定義,s.say() s的proto中找,proto指向器構(gòu)造函數(shù)的prototype,這
原型是一個(gè)對象也有proto,proto又可以指向構(gòu)造函數(shù)的prototype,這個(gè)時(shí)候應(yīng)該是指向Object,身上并沒say方法
加上這句話
Student.prototype=Person.prototype;
找不到say時(shí),就 proto->Student.prototype->Object.prototype現(xiàn)在把指向改成了
但是!如果這個(gè)時(shí)候 Student 想要給自己的 prototype 加一個(gè)新方法,怎么辦?我們知道因?yàn)橹皇菑?fù)制了地址,如果修改了 Student.prototype,Person.prototype 也將被修改,這顯然是我們不愿意看到的。
Person.prototype,找到了了Person上的say方法,但是Student.prototype.fun上的fun找不到了
再進(jìn)行更改:
Student.prototype.__proto__ = Person.prototype
改成這樣,就會先去prototype尋找,沒找到的話再順著proto往上找到Person
是不是看起來有點(diǎn)眼熟?
Array.prototype.__proto__ === Object.prototype // true
這就是原型鏈!這就是我們所說的繼承!是不是有點(diǎn)感覺了?
當(dāng)然,剛才也說了,我們最好用下面這種寫法:
Student.prototype = Object.create(Person.prototype)
復(fù)制代碼回顧一下到現(xiàn)在我們做了什么?
首先我們通過 call 父級構(gòu)造函數(shù),來實(shí)現(xiàn)屬性的繼承,有了 姓名
然后我們通過建立原型鏈,來實(shí)現(xiàn)方法的繼承,我們的 小明 可以 自我介紹 了
看似已經(jīng)結(jié)束,但是實(shí)際上還有一個(gè)隱藏的 Bug,我們接下來來解決這個(gè) Bug。
解決 constructor 的問題
細(xì)心的你會發(fā)現(xiàn)(我們在最開始也說過了),我們的對象實(shí)例和構(gòu)造函數(shù)中是有一個(gè) constructor 屬性的,比如:
const arr = [1, 2]
arr.__proto__.constructor === Array // true
Array.prototype.constructor === Array // true
但是,Student.prototype 中的 constructor 被剛才的那一番操作給搞沒了,我們需要把它弄回來:
Student.prototype.constructor = Student
這樣就完成了一波類的繼承。
總結(jié)
function Person(name) {
this.name = name
}
Person.prototype.say = function() {
console.log(`你好,我是 ${this.name}`)
}
// 嘗試定義一個(gè)子類,來繼承 Person
function Student(name, id){
Person.call(this, name) // 調(diào)用父類構(gòu)造函數(shù)
this.id = id
}
Student.prototype.fun = function(){
console.log(`你好,我是 ${this.name},我的學(xué)號是${this.id}`);
}
// Student.prototype=Person.prototype;
Student.prototype.__proto__ = Person.prototype;
Student.prototype.constructor = Student
let s = new Student('瑪麗','1534543')
let p = new Person('張三')
s.say()
s.fun()
用 class 繼承
既然他本身就是語法糖,我個(gè)人認(rèn)為沒必要搞那么細(xì),其實(shí)本質(zhì)跟上面的使用原型鏈的繼承是一樣的,搞清楚是怎么寫的就好啦:
// 定義父類
class Person {
constructor(name) { // 定義屬性
this.name = name
}
say() { // 定義方法
console.log(`你好,我是 ${this.name}`)
}
}
// 定義子類
class Student extends Person {
constructor(name, id) {
super(name) // 這里的 姓名 兩個(gè)字要與父類中的一樣,繼承屬性和方法
this.id = id // 定義新屬性
}
fun() { // 定義新方法
console.log(`我的學(xué)號是 ${this.id}`)
}
}
let s = new Student('小紅', 345678)
s.say() // 你好,我是 小紅
s.fun() // 我的學(xué)號是 345678
class類內(nèi)部定義的所有方法都是不可枚舉的。這點(diǎn)和ES5行為不一致。
類和模塊的內(nèi)部默認(rèn)使用嚴(yán)格模式,所以不需要使用use strict指定運(yùn)行模式。
類必須使用 new 來調(diào)用,否則會報(bào)錯。這是他跟普通構(gòu)造函數(shù)的一個(gè)主要區(qū)別,后者不用 new 也可以執(zhí)行。
類內(nèi)部不存在變量提升,這一點(diǎn)與ES5完全不同。
class繼承可以實(shí)現(xiàn)原生構(gòu)造函數(shù)的繼承,而ES5不可以。