跳到主要内容

面向对象编程及其在 JS/TS 中的实现

· 阅读需 6 分钟

OOP 在不同编程语言中都通用的概念,并用 js 举例说明,同时包含 ts 相对于 js 独有的特性。

历史

谈到 OOP(面向对象编程),我的印象总是停留在我刚学一些静态语言时(如 java),相对于 python、JavaScript,OOP 的概念总是被很早的引入。视频里的讲师总是举一些诸如 Animal 类和 Cat\Dog 对象的例子。Ok 能听懂,so what the fuck is next? 这东西到底有啥用?实际编写网站项目时,也用不着什么 Animal 类啊。

在一番探索后,我目前的想法是:OOP 本身是为了降低代码的耦合度而提出的,那么一切代码耦合度较高的地方都可以尝试用 OOP。另外,现代的框架很多时候是这样工作的,框架规范了写法是在类内书写属性和方法,然后创建对象的工作交给了框架在后台进行,故看不到。这样的框架在前后端都有,比如 react、eggjs。也就是说,实际上 OOP 无处不在,学习 OOP 有助于理解框架并更好的使用。

说到 Javascript 的面向对象,ES6 之前是借助一套复杂难懂的原型系统实现,ES6 之后向主流的面向对象语言写法靠近,采用了 class 关键字来创建类。以前的那套只做了解,重点掌握的应该是现代的写法。

规范

OOP 作为一种编程范式,有它自己的一套规定和要素。

属性和方法

class Animal {
constructor(type = "cat", legs = 4) {
this.type = type;
this.legs = legs;
}
}

let dog = new Animal((type = "dog"), (legs = 4));
dog.legs = 2; // 对象属性可以修改

constructor 构造函数执行类的初始化操作,如果不需要初始化,可以不定义构造函数,这种情况相当于构造函数内部为空。

静态方法

无需实例化对象就可以调用类中的方法。

class Animal {
constructor(type = "cat", legs = 4) {
this.type = type;
this.legs = legs;
}

static return10() {
return "10";
}
}

Animal.return10();

getter/setter

明明是方法,但是调用时形式像是属性。

class Animal {
constructor(type = "cat", legs = 4) {
this.type = type;
this.legs = legs;
}

get metaData() {
return `${this.type}, ${this.legs}`;
}

set setType(value) {
this.type = value;
}
}

let dog = new Animal((type = "dog"), (legs = "2"));

console.log(dog.metaData); // getter调用
dog.setType = "alaska dog"; // setter调用

继承和多态

新类拓展父类,而不需要将父类复制一遍,这就是继承。问题是,如何继承?如何拓展?借助 super 关键字。

class Animal {
constructor(type = "cat", legs = 4) {
this.type = type;
this.legs = legs;
}

makeNoise(noise = "loud noise") {
console.log(noise);
}
}

class Cat extends Animal {
constructor(type, legs, tail) {
super(type, legs); // 继承
this.tail = tail;
}

makeNoise(noise, db) {
// 重写
super.makeNoise(noise); // 继承
console.log(`DB is ${db}`); // 拓展的新功能
}
}

let cat = new Cat("cat", 4, true);

cat.makeNoise("meow", 40);

这里的 super 就起到继承的作用,构造函数的继承就是 super(),而方法的继承是 super.methodName()。super 只能在类的构造函数和方法中使用。必须在构造函数中首先调用 super:在子类的构造函数中,必须在使用 this 之前先调用 super。否则会抛出错误。

在上面的例子中,还实现了多态。多态在 javascript 中就是重写方法,也就是一个方法在不同类中有不同行为。如上面的makeNoise,在父类和子类的逻辑不同。


以下内容只存在于 typescript 中。

权限控制符:public、private、protect

对一个变量、方法的使用范围做限制的修饰符。public 可以在类和对象使用;private 只能在类内使用;private 可以在类及其子类中使用。

interface 接口

接口的含义是,使用这个接口(关键字是 implements)的类,必须将接口中的属性和方法全部实现一遍。

abstract class 抽象类

抽象类是类的模板,可以被类继承,但是不能直接实例化出对象。

拧巴的地方

JavaScript 中并没有类这个数据类型,类的实质是函数:

class Person {}
console.log(Person); // class Person {}
console.log(typeof Person); // function

ES6 中的 class 很大程度上是基于既有原型机制的语法糖。