对 JS 原型的一些思考
我们常常听说 JavaScript 是一门基于原型的面向对象语言,然后也有一堆说 原型 的文章,不过大都是列举一下用法,说说原型链,就完了。至于究竟 原型 是什么,语义是什么,甚少有文章会提到。原型这个词其实是非常中肯的。
简述
写程序的都知道 原型 这个东西,就是万恶的产品经理经常搞的那个嘛,假设产品经理是用的 Sketch 做的原型,那么设计会从产品经理那儿拷贝一份产品经理做好的原型,然后在上面修改(当然不一定这么干,不过可以这么干就是了),最后的苦逼的小前端就会基于设计给出的设计稿生成页面。
我们可以这么说,设计做的事情是把原型改成设计稿, 而小前端做的则是把设计稿作为参考生成 html/css/js。而 JavaScript 中的原型,也差不是这个理,也就是用来 复制一个副本 & 在副本的基础上修改
。所以说原型这个词是比较中肯的。
起源
从网上都能找到 JavaScript 里基于原型的面向对象(Prototype-based oo)的起源,Self 语言(真正的起源其实应该是 actor,不过这货资料极其稀有)。这个小众的 Self 语言,网上没什么资料。
在只言片语中,可以找出一些 Self 语言中的重要信息:
- Self 中一切都是对象,对象有数个 slot(slot 是一个方法或数据,跟 js 的属性有点类似)
- Self 中新建对象的方法为复制现有对象,再酌情增删改 slot
oldObj copy newProp: 'Hello, World!'.
- Self 中被复制的对象可以叫做 prototype,因为原对象并不完善,要复制一份出来修修补补
- Self 中可以明确地选取 slot 指明 parent 属性,以供 delegation
另外有一篇 paper 讨论了 delegation(委托)和 inheritance(继承)分别实现 prototypal 和 class 版本的面向对象编程的 行为共享。[1]
语义
原型,顾名思义,本身就拥有一定的功能,只需要改改就能用。JavaScript 中每个可构造的函数(非箭头函数)都有个prototype字段,这货就是所谓的“原型”。
按照基于原型的OO语言的惯例,新建对象相当于对原有对象的扩展或者复制。对应到JS,就是prototype,而JavaScript有一个new
关键字,在语言的spec中的语义是将构造函数F的prototype设定为新对象的proto, 并执行F。我们可以换一种等价的说法,new操作符创建了一个新对象,并将F.prototype以某种形式复制(实际上是给了个引用,但是可以理解为写时复制),而F本身则是对新对象的一些修饰。这样的,原型就真的实至名归了。
既然new是对原型的拷贝,那么很自然的就有:obj instanceof F的判别方式是obj是否具有F.prototype的所有属性;Function.prototype 是function(){}。这样是很符合直觉的。
delegation
网上流行一个所谓的“原型继承”的说法。其实经过考证,这个词应该是 Douglas Crockford 创造的(不能说他坏话),原型继承 想要表达的意思就是 implicit delegation。
implicit delegation就是,向对象A请求属性prop时,A会首先在自身查找,如果不存在,则查找其 proto,此过程会持续到 proto 为 null。也就是我们常说的原型链。我们可以发现,其实这个是复制属性的语义的超集(如果运行时 proto 都不变就跟复制一样)。
虽然我们常说继承是为了多态,但是从效果上看继承是共享代码。也就是说,继承只是一种共享代码的方式,delegation也是共享代码的方法,而且都知道delegation可以实现继承的效果,反之则不成立。
prototype 对比 class
- 类比编程语言,编译语言的编译阶段就好像是 class 到 instance 的过程,而执行阶段则像是原型被复制并修饰(由程序加载器把程序加载到内存的指定位置)
- docker 的 image 可以说是运行时容器的 prototype
- 人类认知事物的时候往往是通过某个具体的单位,比如某一头大象开始的,而不是通过 class(也是抽象的属性们)
一些事实
JS 中判断对象的类型,都是根据一些 slot(或者说属性),这些属性的访问可能不完全符合原型链的规则。比如说继承基本类型如 Array,原生 extends 写法和由 babel 转译出来的 extends 并不是完全等价的(虽然按理说语义上等价)。
总的来说,原型式面向对象就是通过复制(都明白,不一定真的把每个属性都弄一份副本,写时复制也等价)旧的对象来生成新对象,并在此之上进行修改的编程方式。“复制”则就有了复用代码的语义,只是JS的“复制”恰好使用的是软复制(就像 symlink 一样)。
参考:[1] .Lieberman H. Using prototypical objects to implement shared behavior in object-oriented systems[C] // ACM Sigplan Notices. ACM, 1986, 21(11): 214-223.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
让我好好消化消化