本人之前對Class一直不夠重視。平時對原型的使用,也僅限於在建構函式的prototype上掛屬性。原型尚且用不著,更何況你Class只是原型的一顆語法糖?
直到公司開始了一個webgis專案,使用openlayers。看了下openlayers的程式碼,整個api都是用Class構建的。我才意識到,對Class的使用已經很普遍了,很多庫都在基於Class構建的,所以必須把它研究明白了。
我是這麼想的,先把原型搞明白,再把Class搞明白,最後實踐一下,把Class的各項語法,用原型還原出來。這樣,一定能很好的掌握JS的物件導向思想。
一、回顧一下物件的原型
對於一門程式語言來說,把同一類事物抽象出一個資料結構,並以此為模板建立範例,是一個基本的需求,這也就是物件導向的思想。
JS從一開始就被設計成一門物件導向的語言,它是通過建構函式來作為「模板」,來生成物件的。比如這樣:
function Student(name, age) {
this.name = name;
this.age = age;
this.say = function(intro) {
console.log(intro);
}
}
let xiaohong = new Student('小紅', 14);
let xiaoming = new Student('小明', 15);
xiaohong.say('我是小紅,我喜歡看電影'); //我是小紅,我喜歡看電影
xiaoming.say('我是小明,我喜歡小紅'); //我是小明,我喜歡小紅
JS中的建構函式和普通函數有什麼不同呢?
其實,任何一個普通函數通過new運運算元呼叫,都可以稱作建構函式。建構函式的特別之處,就是裡面多了一個this。這個this就是建構函式所返回的物件。普通函數裡面沒有this,通過new呼叫得到的是一個空物件,沒有任何意義。
現在,可以通過建構函式輕鬆生成同一類事物——學生了。他們都有姓名和年齡,卻又各不相同。
然而,還有一些東西,是他們都一樣的,是他們共同分享的。比如他們的班級都是三年二班,班主任都是周杰倫。怎麼表示這種關係呢?
這就是prototype,也就是原型。
在JS中,所有函數都有一個prototype屬性。這是一個物件,預設只有一個屬性:constructor,指向建構函式自身。也就是說,建構函式和原型,通過prototype和construcotr,相互參照。
通過建構函式生成的所有物件,共同分享這個prototype物件。
function Student(name, age) {
this.name = name;
this.age = age;
}
Student.prototype.className = '三年二班';
Student.prototype.teacher = '周杰倫';
let xiaohong = new Student('小紅', 14);
let xiaoming = new Student('小明', 15);
console.log(xiaohong.className, xiaohong.teacher); //三年二班 周杰倫
console.log(xiaoming.className, xiaoming.teacher); //三年二班 周杰倫
現在,我們有了建構函式、原型、物件。它們是什麼關係呢?
建構函式就是原型和物件之間的紐帶,負責為原型這個「媽媽」生「孩子」,也就是物件。原型上的東西,是所有孩子都一樣的,比如國家、膚色。建構函式上的東西,是孩子們可以個性化的,比如相貌、身高。
也許你還聽說過constructor和__proto__,它們又是做什麼的?很簡單,它們的存在,只是為了:讓建構函式、原型、物件三者之間可以相互參照。
function Student(name, age) {
this.name = name;
this.age = age;
}
let xiaohong = new Student('小紅', 14);
console.log(Student.prototype); //{constructor: Student}
console.log(Student.prototype.constructor === Student); //true
console.log(xiaohong.__proto__ === Student.prototype); //true
console.log(xiaohong.constructor === Student); //true
通過===我們可以得知,它們之間確實是相互參照關係,而不是隻是值想等的關係。
二、用原型實現Class的各項語法
接下來,我們用原型的寫法,把Class的各項語法還原出來。
(1)建構函式(範例屬性和方法)
//Class語法
class Student {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
//原型語法
function Student(name, age) {
this.name = name;
this.age = age;
}
(2)原型的屬性和方法
//Class語法
class Student {
teacher = '周杰倫';
say() {
console.log('認真聽講!');
}
}
//原型語法
function Student() {}
Student.prototype.teacher = '周杰倫';
Student.prototype.say = function() {
console.log('認真聽講!');
};
let xiaohong = new Student();
console.log(xiaohong.teacher); //周杰倫
xiaohong.say(); //認真聽講!
(3)靜態屬性和方法
//Class語法
class Student {
static teacher = '周杰倫';
static say() {
console.log('認真聽講!');
}
}
//原型語法
function Student() {}
Student.teacher = '周杰倫';
Student.say = function() {
console.log('認真聽講!');
};
console.log(Student.teacher); //周杰倫
Student.say(); //認真聽講!
(4)繼承
// Class語法
class Person {
constructor(name) {
this.name = name;
}
say() {
console.log('我會說話');
}
static think() {
console.log('人類會思考');
}
}
class Student extends Person {
constructor(name, school) {
super(name);
this.school = school;
}
study() {
console.log('我要上學');
}
}
let xiaohong = new Student('小紅', '十一學校');
// 原型語法
function Person(name) {
this.name = name;
}
Person.prototype.say = function() {
console.log('我會說話');
}
Person.think = function() {
console.log('人類會思考');
}
function Student(school) {
this.school = school;
}
Student.prototype = new Person('小紅');
Student.prototype.constructor = Student;
Student.prototype.study = function() {
console.log('我要上學');
};
Object.setPrototypeOf(Student, Person);
let xiaohong = new Student('十一學校');
console.log(xiaohong.name); //小紅
console.log(xiaohong.school); //十一學校
xiaohong.say(); //我會說話
xiaohong.study(); //我要上學
Student.think(); //人類會思考
由此可見,特別在繼承的語法上,Class要比原型簡單的多。
總的來說,作為原型的語法糖,Class不僅語意更明確,更好理解,寫法上也更簡單。所以,以後就愉快的使用Class吧!
本人水平非常有限,寫作主要是為了把自己學過的東西捋清楚。如有錯誤,還請指正,感激不盡。