· 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
回归了原本函数的含义。