对 JS 原型的一些思考

发布于 2022-08-03 16:07:24 字数 2583 浏览 162 评论 1

我们常常听说 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

踏月而来 2022-05-01 13:20:33

让我好好消化消化

~没有更多了~

关于作者

苦妄

暂无简介

0 文章
0 评论
22 人气
更多

推荐作者

遂心如意

文章 0 评论 0

5513090242

文章 0 评论 0

巷雨优美回忆

文章 0 评论 0

junpengz2000

文章 0 评论 0

13郎

文章 0 评论 0

qq_xU4RDg

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文