JavaScript 用函数对象来模拟类

· 741 words · 4 min

原型

OOP 编程范式中最流行的是用“类”(class)来描述对象,还有一种是基于原型来描述对象的方式,JS 就是其中的代表。 基于类提倡关注父类和子类关系的开发模型,基于原型看似更提倡关注对象实例的行为。 两种模型都可以满足基本的复用和抽象需求,只是适用场景不同。JS 之前,原型更多与高动态性语言配合,并且多数基于原型的语言提倡运行时修改原型。因此原型更加契合 JS 动态语言的特征。

原型的方式实现“复制操作”有两种思路:一种是并不是真正的复制,而是使新对象持有一个原型的引用。 另一种是真正的复制,两个对象毫无关联。很显然,JS 选择了前一种。

原型系统的特征为:

ES6 之后,JS 提供了内置函数用来操作原型:

类与原型

ES5 之前,“类”是对象的私有属性 [[class]],唯一可以访问 [[class]] 的方式是使用 Object.prototype.toString。 ES5 之后,[[class]] 私有属性被 Symbol.toStringTag 代替,Object.prototype.toString 的意义从命名上不再跟 class 相关。 我们甚至可以自定义 Object.prototype.toString 的行为:

var o = { [Symbol.toStringTag]: "MyObject" };
console.log(Object.prototype.toString.call(o)); // '[object MyObject]'

new 也是 JS 面向对象中的一部分,JS 在 ES6 之前用函数对象当做构造器来模拟类。 new 运算接受一个构造器和一组调用参数,new 的时候发生了这几件事:

有两种方式给构造器创建出来的对象上添加属性:一种是直接在构造器中添加,另一种是在构造器的 prototype 上添加。

function c1(){
  this.p1 = 1;
  this.p2 = function(){
    console.log(this.p1);
  }
} 
var o1 = new c1;
o1.p2();

function c2(){}
c2.prototype.p1 = 1;
c2.prototype.p2 = function(){
  console.log(this.p1);
}

var o2 = new c2;
o2.p2();

第一种方法是直接在构造器中修改 this,给 this 添加属性。 第二种方法是修改构造器的 prototype 属性指向的对象,它是从这个构造器构造出来的所有对象的原型。

ES6 中新增的 class 使得 newfunction 的搭配的怪异行为退休了,让 function 回归了原本函数的含义。

From 极客时间