我第一次接觸面向?qū)ο髸r(shí)屯伞,使用的是 Python螟炫,運(yùn)用 def __init__(self, *args):
這種 magic method 來(lái)完成自身參數(shù)的初始化璧疗。 JS 當(dāng)中也有類似的構(gòu)造器 constructor 肴甸。
我先用 ES6 中新引入的 class 語(yǔ)法來(lái)定義一個(gè)類相嵌,因?yàn)檫@樣容易理解屎暇。
class Student {
constructor(name) {
this.name = name;
}
hello() {
return 'Hello, ' + this.name + '!';
}
}
再定義一個(gè)類來(lái)繼承
class PrimaryStudent extends Student {
constructor(name, grade) {
super(name); // 調(diào)用父類的構(gòu)造函數(shù)
this.grade = grade;
}
myGrade() {
return 'I am at grade ' + this.grade;
}
}
看似與 Python 極為相似承桥,難怪我直接用 React 寫應(yīng)用的時(shí)候并沒(méi)有發(fā)現(xiàn)不對(duì)。這種新的語(yǔ)法簡(jiǎn)化了 JS 原本復(fù)雜的原型 (prototype) 繼承根悼,也讓直接了解新語(yǔ)法的我忽略了它的本質(zhì)及特性凶异。
(為了學(xué)習(xí))回歸原本的面貌
function Student(name) {
this.name = name;
}
Student.prototype.hello = function() {
return 'Hello, ' + this.name + '!';
}
var kitty = new Student('kitty');
var daisy = new Student('daisy');
可以觀察到我們并不在 Student 中直接寫 hello 函數(shù)蜀撑,而是寫在了它的 prototype 中。只要我們給 prototype 定義了函數(shù)剩彬,那么 Student 生成的對(duì)象就都可以調(diào)用這個(gè)函數(shù)酷麦,省去每個(gè)對(duì)象再獨(dú)自生成函數(shù)的操作。
我們把 Student.prototype 想象成一根樹(shù)枝喉恋,kitty 和 daisy 就是生出的綠葉了沃饶,它們共用樹(shù)枝上的器官,不必自己再生出相同的器官來(lái)消耗資源轻黑。此時(shí) kitty 和 daisy 的原型指向 Student.prototype糊肤。
Javascript規(guī)定,每一個(gè)構(gòu)造函數(shù)都有一個(gè)
prototype
屬性氓鄙,指向另一個(gè)對(duì)象馆揉。這個(gè)對(duì)象的所有屬性和方法,都會(huì)被構(gòu)造函數(shù)的實(shí)例繼承抖拦。
進(jìn)一步升酣,為了避免生成對(duì)象的時(shí)候漏寫 new,可以封裝一下蟋座。
function Student(props) {
this.name = props.name || '匿名'; // 默認(rèn)值為'匿名'
this.grade = props.grade || 1; // 默認(rèn)值為1
}
Student.prototype.hello = function () {
alert('Hello, ' + this.name + '!');
};
function createStudent(props) {
return new Student(props || {}); // 兼顧沒(méi)有參數(shù)的情況
}
然后直接調(diào)用 createStudent() 就可以了拗踢。
Student.prototype 的繼承應(yīng)該如何寫呢脚牍?
function PrimaryStudent(props) {
// 調(diào)用Student constructor構(gòu)造函數(shù)向臀,綁定this變量
Student.call(this, props);
this.grade = props.grade || 1;
}
別以為這就結(jié)束了。
Student.call(this, props);
這句代碼诸狭,確實(shí)和 Student 搭上邊了券膀,但此時(shí) PrimaryStudent.prototype 是誰(shuí)?
我們的目標(biāo)是構(gòu)造一個(gè) PrimaryStudent.prototype 驯遇,令它指向上層的 Student.prototype芹彬。
有誰(shuí)是這種關(guān)系?kitty 和 daisy 的原型好像就是叉庐。
我就不想了舒帮,前輩的方法是這樣的。
function F() {
}
F.prototype = Student.prototype;
PrimaryStudent.prototype = new F();
// 把PrimaryStudent原型的構(gòu)造函數(shù)修復(fù)為PrimaryStudent(因?yàn)樯弦痪湫薷牧薱onstructor)
PrimaryStudent.prototype.constructor = PrimaryStudent;
- 構(gòu)造一個(gè)函數(shù) F 陡叠,令 F.prototype = Student.prototype
- 生成一個(gè)中間對(duì)象玩郊,那么這個(gè)對(duì)象的 prototype 就可以指向 Student.prototype
這不就是我們想要的嗎?我們把該對(duì)象賦給 PrimaryStudent.prototype 枉阵,那么就可以實(shí)現(xiàn) PrimaryStudent.prototype —> Student.prototype 的指向連接了译红。
// 繼續(xù)在PrimaryStudent原型(就是new F()對(duì)象)上定義方法:
PrimaryStudent.prototype.getGrade = function () {
return this.grade;
};
// 創(chuàng)建xiaoming:
var xiaoming = new PrimaryStudent({
name: '小明',
grade: 2
});
xiaoming.name; // '小明'
xiaoming.grade; // 2
// 驗(yàn)證原型:
xiaoming.__proto__ === PrimaryStudent.prototype; // true
xiaoming.__proto__.__proto__ === Student.prototype; // true
// 驗(yàn)證繼承關(guān)系:
xiaoming instanceof PrimaryStudent; // true
xiaoming instanceof Student; // true
封裝它
function extend(Child, Parent) {
var F = function(){};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
Child.uber = Parent.prototype;
}
Child.uber = Parent.prototype;
意思是為子對(duì)象設(shè)一個(gè)uber屬性,這個(gè)屬性直接指向父對(duì)象的prototype屬性兴溜。(uber是一個(gè)德語(yǔ)詞侦厚,意思是"向上"耻陕、"上一層"。)這等于在子對(duì)象上打開(kāi)一條通道刨沦,可以直接調(diào)用父對(duì)象的方法诗宣。這一行放在這里,只是為了實(shí)現(xiàn)繼承的完備性已卷,純屬備用性質(zhì)梧田。
參考資料
廖雪峰的三篇文章(個(gè)人感覺(jué)有些晦澀,最好再去看看別的資料)
推薦阮一峰的三篇文章侧蘸,由簡(jiǎn)入深裁眯,非常不錯(cuò)。
http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_encapsulation.html
http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_inheritance.html
http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_inheritance_continued.html