面向对象不必基于类。

现在

ES6 引入基于类的对象声明方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 定义类
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}

toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}

// 初始化
let p = new Point(0,0);
console.log(p.toString());

// 继承
class ThreeDPoint extends Point{
constructor(x, y, z){
super(x, y);
this.z = z;
}

toString(){
return '(' + this.x + ', ' + this.y + ', ' + this.z + ')';
}
}

但这只是为了照顾 Java 和 C++ 程序员而引入的语法糖。底层面向对象的实现没有改变。

过去

ES5 之前的对象声明是这样的

1
2
3
4
5
6
7
8
9
10
11
12
// 定义类
function Point(x, y) {
this.x = x;
this.y = y;
}

Point.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
};

// 初始化
var p = new Point(1, 2);

继承的方式很灵活,根据不同的目的有不同的写法。

参考 JavaScript 继承

区别

面向对象强调的是对象,而不是类。所以没有叫面向类编程。

面向对象的实现方式除了基于类的方式,还有基于原型的方式。

基于原型的方式能够实现基于类的方式,如 ES6 引入的语法糖。

而基于类的实现方式大部分不能实现基于原型的方式。因为大部分的语言类型层级是静态的。

也就是说在编译的时候类型继承树是已经确定的,运行的时候不能更改继承关系。

而基于原型的方式在运行的时候可以更改继承关系。更改 prototype 即可。

基于类的面向对象编程经常有基本类型难以更改的问题。这是因为类型继承树是定死的,后期改起来影响很大。

比如你有一个列表的基本组件 ListBase 。所有列表继承它。

如果你想来一个新的列表跟 ListBase 表现不一致,那么就要重载其中不一致的部分。

如果被重载的函数太长,或者里面引用了私有变量,就很难实现。

这时候要么改 ListBase 要么把 ListBase 的代码复制一遍,重新做一个 NewListBase

改动 ListBase,因为用到的地方很多,无法保证不会出问题。

复制一遍,如果 ListBase 改动很难同步。也是产生代码腐臭的经典方式。

如果是用原型的方式,直接继承 prototype 。然后改想要改的部分就可以了。

相当于复制了代码,但是这段复制的代码是在运行的时候复制出来的。所以 ListBase 的改动都会同步。

基于原型的继承最大的缺点是接受了基于类继承的先入为主后很难理解。

基于原型的继承在代码中没有类的概念,所有类的概念都是人为为了模拟基于类的面向对象而产生的。

基于原型的继承只有对象的复制和修改。