用原型實現Class的各項語法

路澤宇發表於2023-11-09

本人之前對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吧!

 

本人水平非常有限,寫作主要是為了把自己學過的東西捋清楚。如有錯誤,還請指正,感激不盡。

相關文章