昨天讲完 Javascript OOP 两个重要支柱,今天接着这个主题,来讲讲 class 吧!
Class 可以想像成印章,每压一下就盖出一个印,每 new
一下就产生一个物件。
class
是 ECMAScript 6 引入的语法,但由於 Javascript 仍是基於原型(prototype-based)的语言,所以这个所谓的 class
,其实也只是语法糖,Javascript 依然没有真正的 "class",所以透过原型链(prototype chain) 来营造出继承的效果。
class
可以当作特别形式的函式,所以宣告也有分 class expressions 跟 class declarations,我个人比较喜欢後者,可以少写一点XD
class expressions
const Person = class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
};
class declarations
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
上述都只是宣告,现在用 new
来真正将这些 class「实体化」:
const person = new Person('Joey', 20);
基本上 class
内的语法,跟我们昨天用 function 在写的时候其实非常像,只是有几点需要注意:
constructor
,用来建立和初始化一个类别的物件,里面基本上就是在做初始化,且一个 class
里面只能有一个。
而一般的 method 则是像下面这样,用一般的非箭头函式,但 function
关键字也可以省略:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayHello() {
return `Hello, ${this.name}~`;
}
}
const person = new Person('Joey', 20);
console.log(person.sayHello());
执行结果
Hello, Joey~
class
有几个分解动作,是 Javascript 背後帮我们完成的:
class
里的 constructor
() 抓出来,指定给 Person
class
里的其他 method 指定给 Person.prototype
你会发现其实用 function 都做得到,只是用 class
会比较有在写 OOP 的感觉,所以才说 class
是「特别形式」的 function。
比如说我们有个 class
叫做 Animal
:
class Animal {
constructor(name, age) {
this.name = name;
this.age = age;
}
speak() {
console.log(this.name + ' 发出声音!');
}
}
Animal
是个比较大的范围,动物(基本上)都会发出声音,如果今天我们需要建立一个猫的类别,猫也是动物的一种,所以动物会有的属性,猫都会有:
class Animal {
constructor(name, age) {
this.name = name;
this.age = age;
}
speak() {
console.log(this.name + ' 发出声音!');
}
}
class Cat {
constructor(name, age) {
this.name = name;
this.age = age;
}
speak() {
console.log(this.name + ' 发出声音!');
}
}
可以看到其实很大量的 code 在重复,有这种大类别包含小类别的状况,就可以使用 extends
去继承大类别:
class Animal {
constructor(name, age) {
this.name = name;
this.age = age;
}
speak() {
console.log(this.name + ' 发出声音!');
}
}
class Cat extends Animal {}
const cat = new Cat('Lulu', 5);
cat.speak();
console.log(cat.age);
执行结果
Lulu 发出声音!
5
但基本上不会只写一行
class Cat extends Animal {}
因为如果子类别长得跟父类别一模一样,那好像也没什麽必要分出来,假如我们希望让猫的声音更有区别性,可以去覆写父类别的 method:
class Animal {
constructor(name, age) {
this.name = name;
this.age = age;
}
speak() {
console.log(this.name + ' 发出声音!');
}
}
class Cat extends Animal {
speak() {
console.log(this.name + ' 喵~~~~');
}
}
const cat = new Cat('Lulu', 5);
cat.speak();
console.log(cat.age);
执行结果
Lulu 喵~~~~!
5
但你是否有注意到一点,Cat
类别里面怎麽会没有 constructor
method?这样也能跑吗?
其实是可以的,因为如果用 extends
语法去继承父类别,而又没有给 constructor
method 的时候,Javascript 会在背後偷偷帮你补上:
class Cat extends Animal {
// 这一段是自动补上的 start
constructor(...args) {
super(...args);
}
// 这一段是自动补上的 end
speak() {
console.log(this.name + ' 喵~~~~');
}
}
这个 super
是只能够用在子类别的语法,意思是呼叫父类别的建构子。在 constructor
里面务必要先执行 super
,this
才会有东西,再开始使用 this.name
、this.age
之类的属性,不然像这样死掉哦:
Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
发现 class
讲完还有一点时间,我们再来看看,同样是 method,写在类别上,跟写在实体化的物件上,有什麽样的差别呢?
假如我们一样是电商系统,要判断 user
的钱包有没有办法负担商品价格,所以我们有个 checkPriceAffordable
的 method。
方案A,如果写在类别上是这样:
class User {
constructor(name, money) {
this.name = name;
this.money = money;
}
checkPriceAffordable(price) {
return this.money >= price;
}
}
const user = new User('Joey', 2000);
if (user.checkPriceAffordable(1000)) {
console.log('可以买! 买起来!!!!');
}
执行结果
可以买! 买起来!!!!
方案 B,写在实体化出来的物件里面:
class User {
constructor(name, money) {
this.name = name;
this.money = money;
this.checkPriceAffordable = function(price) {
return this.money >= price;
}
}
}
const user = new User('Joey', 2000);
if (user.checkPriceAffordable(1000)) {
console.log('可以买! 买起来!!!!');
}
执行结果
可以买! 买起来!!!!
差别其实就在於
checkPriceAffordable
new
一次就复制一个
当我们呼叫 user.checkPriceAffordable()
的时候,Javascript 是怎麽找到 checkPriceAffordable
这个 method 呢?
查找的顺序就是由内而外,沿着原型链往上找,先看 user
物件本人有没有这个 method,如果没有就顺着 __proto__
往上看 User
这个类别有没有。
所以理论上好像会是存在实体化物件上,呼叫的效率最高。
但事实上是,Javascript 当然也知道这一点,因此有针对原型链查找的效能最佳化,其实花费的时间不会差太多,但可以肯定的却是,存在实体化物件中,每复制一份就多花一份记忆体来存。
因此原则上还是把共用的 method 直接写在类别或原型上即可。
Javascript 虽然常被说很松散,过度自由,但也是因为如此,要写 FP 要写 OOP,都可以自由选择,class
虽然也是透过 prototype 做出来的语法糖,少了像是 private
、public
这种方便的语法,但有总比没有好,让这个语言充满了非常多可能性!
在喧嚣嘈杂的社会里
或许你我都是
寂静的克隆体
Class MDN
深入浅出 JavaScript ES6 Class
<<: 30-12 之 Domain Layer - Domain Model ( 未完成版 )
火焰文字 教学原文参考:火焰文字 这篇文章会介绍在 GIMP 里使用涂抹工具、渐层映对、文字...等...
何谓Git分支? 说明 : 在实务开发上,通常会有主要运行版本、测试版本,以及跟员工A、B自己各别修...
今天来介绍很难预约的狗一下居酒食堂 这是去年12月底去吃的 并提前一个月预定 当天的方案是88道菜吃...
前几礼拜终於收到挂号寄来的大学学费总收据, 开始估计自己的价值和手上的筹码。 演算法竞赛选手的深厚 ...
参赛动机 厘清JavaScript中自己一知半解的概念 透过写文章加强记忆,培养自己写文章的能力 ...