renaesop/blog

对JS原型的一些思考

Opened this issue · 4 comments

我们常常听说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.

让我好好消化消化 😸

@xiaoyu2er 不一定很正确=。=

一头雾水

大概理解了一些,但还是很模糊,看完再去看别的文章