· 741 words · 4 min
OOP 编程范式中最流行的是用“类”(class)来描述对象,还有一种是基于原型来描述对象的方式,JS 就是其中的代表。 基于类提倡关注父类和子类关系的开发模型,基于原型看似更提倡关注对象实例的行为。 两种模型都可以满足基本的复用和抽象需求,只是适用场景不同。JS 之前,原型更多与高动态性语言配合,并且多数基于原型的语言提倡运行时修改原型。因此原型更加契合 JS 动态语言的特征。
原型的方式实现“复制操作”有两种思路:一种是并不是真正的复制,而是使新对象持有一个原型的引用。 另一种是真正的复制,两个对象毫无关联。很显然,JS 选择了前一种。
原型系统的特征为:
[[prototype]],那么该属性指向对象的原型ES6 之后,JS 提供了内置函数用来操作原型:
Object.create 根据指定原型创建对象,可以为 null。Object.getPrototypeOf 获得一个对象的原型。Object.setPrototypeOf 设置一个对象的原型。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 属性(注意与私有字段 [[prototype]] 的区分)为原型,创建新对象this 和调用参数传给构造器,执行有两种方式给构造器创建出来的对象上添加属性:一种是直接在构造器中添加,另一种是在构造器的 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 使得 new 跟 function 的搭配的怪异行为退休了,让 function 回归了原本函数的含义。