背景
刚一直在研究 JavaScript 的 Decorator 模式。然后好多教程不约而同的提到了 Object.defineProperty()
,有的还提到了 prototype
。接着再查询,就几乎都指向了 prototype
。而我现在全栈都在使用 JavaScript,无论是想提高 JavaScript 的水平,还是能看懂其他关于 JavaScript 的资料,看来这个 prototype
的原理是不能了解的了。
题外话
刚才就这个 prototype
的问题在 Google 上查找了很多资料,阮一峰的、MDN 的,还有其他不知名的中文博客。MDN 的资料一板一眼,像是标准的教科书;其他中文博客则毫无章法的乱说一通,草草用生硬晦涩的文字说了一下 prototype 的定义,然后就大篇幅的讨论怎么用,怎么好;而阮一峰的文章则从 JavaScript 的由来、开发 JavaScript 时遇到的问题以及如何解决的阐述了 prototype 的定义、由来和目标。可以说全文绝大篇幅都在分析 JavaScript 的历史,只用了一点点的笔墨写了几个简单的函数,就把 prototype 的来龙去脉全部讲清楚了。
阮一峰的思路正应了最近刚刚领悟到的看事物,要看这个事物从哪儿来,到哪儿去的精髓。“从哪儿来”就是为什么要搞出来这个 prototype,当时是什么环境,遇到了什么问题;“到哪儿去”就是这个 prototype 通过什么方式解决的这个问题,是否还留下了什么问题。想了解事物的本质,不明确事物身处的环境而架空了光谈事物的功能,只能引人走向歧途。好比我是一个 100 年前来的古人,看到了现代的洗衣机,我就会问这个是什么啊,如果有人给我解释说这个洗衣机能洗衣服,还能甩干衣服。那我接着就会想,那应该也能洗澡吧,洗个土豆啥的也行。但如果有人分析说是因为某发明公司发现人类手工洗衣服比较累,为了解决人类手洗衣服费时费力的问题所发明的这个设备,我便彻底了解了洗衣机的本质。
Prototype 出现的背景
JavaScript 的出现
上个世纪 90 年代初,网景公司刚推出浏览器没有多久。在用户使用的过程中,发现一些问题,例如浏览器上有一些内容需要用户填写,但是存在不少用户没有填写就提交的情况。这样导致用户一些明显的错误不能被及时提醒,而服务器侧又需要处理一些本来不需要处理的事务。在此情况下,网景公司需要一种网页脚本语言,可以允许浏览器和网页互动。
工程师 Brendan Eich 负责这种语言的开发。他认为由于只需要解决一些小的问题,该语言只需要提供一些简单的功能就可以。
当时,C++ 和 Java 所代表的面向对象编程思想风头正劲,Brendan Eich 也受到了很大影响。于是 JavaScript 中的数据类型都是对象。
折衷方案
JavaScript 的设计初衷是提供一个能够实现一些简单功能的语言。如果把 Java 的所有特性都加入进来的话,对于仅仅是辅助功能的脚本语言来说可能太重了。于是 Brendan Eich 没有选择引入正式类
的概念,而用普通函数实现了类和其构造函数的功能。
折衷方案的问题
由于没有引入类,对象之间无法共享属性和方法,对应到 Java 来说应该是静态属性和静态方法。导致两个问题:一是无法实现数据共享;二是资源浪费。
引入 prototype 属性
为了解决以上问题,Brendan Eich 决定为构造函数设置一个 prototype 属性。该属性包含一个对象,所有实例对象需要共享的属性和方法,都放在这个对象里面。不需要共享的属性和方法,则放入各自的构造函数中。实例对象一旦创建,将自动引用 prototype 对象的属性和方法。也就是说,实例对象的属性和方法,分成两种,一种是本地的,另一种是引用的。
由于所有的实例对象共享同一个 prototype 对象,那么从外界看起来,prototype 对象就好像是实例对象的原型,而实例对象则好像"继承"了 prototype 对象一样。