JavaScript对象模型与原型体系

当我们谈论起JavaScript与其他类似Java这种面向对象编程语言的不同的时候, 听到的最多的就是“JavaScript不是一种真正的面向对象的编程语言, 因为它没有类的概念(当然es6以后也有了), 而是基于原型的编程语言”。

最初的时候我也和大家一样接受了这种认知, 甚至连《JavaScript高级程序设计》这本书中也提到:

“面向对象的语言有一个标志,那就是都有类的概念”

直到最近在做知识梳理的时候, 对JavaScript这门语言中关于对象模型的设计思路有了更深的认识, 发现这方面以前理解的还是太局限。

编程语言中的“对象”

首先我们需要理清编程语言中Object(对象)到底是什么, 以及为什么要面向对象编程(OOP)

Object 这个概念其实并不仅仅是计算机科学领域中的概念, 而是人们站在自身角度理解和描述整个世界的时候, 对万事万物的一个抽象的统称。

当软件工程师试图用手中的代码来对这个世界建模的时候, 自然而然地把Object模型的思维模式引入到编程语言中了。

「面向对象分析与设计」一书中总结了程序设计领域对象模型所具有的三个特征:

  • 对象具有唯一性, 每个对象都是唯一的个体,即便是两个对象的内容完全相同
  • 每个对象都有状态,同一个对象,状态可以改变
  • 每个对象都有行为,行为可以改变自身的状态

编程语言的开发者们根据各自语言的特性, 对自身的对象模型进行了不同的设计与实现。

JavaScript在设计之初采用了对象原型的方式来描述对象模型, 而诸如Java这类编程语言则选择以来描述并设计自己的对象模型。

后者的流行和成功给大家造成了先入为主的错觉,因此便有了所谓“面向对象”和“基于原型”的区分, 而事实上只是对于对象模型设计思路上的不同而已, 如果一定要有所区分的话, 只能说基于“类”的面向对象基于“原型”的面向对象编程。

无论是还是原型,都定义了如何完成一个新对象的创建,只是侧重点不同而已。

基于的编程语言,通过类来描述实例对象应该具有哪些状态和行为,类与类之间又可能形成了继承、组合等关系。像是一个模板,有了类的定义,才能根据模板完成相应对象的创建工作。

基于原型的编程语言,创建一个新对象不是根据“模板”,而是通过“复制”另外一个已经存在的对象(原型)来创建一个新对象。

JavaScript在通过创建新对象时并不是简单地复制原型对象的内容给新对象,而是让每个实例对象都保留一个私有字段[[prototype]]来指向它的原型对象; 当读取该对象属性时候,如果对象本身没有该属性,则会根据[[prototype]]去原型对象中查找。

JavaScript中的对象模型

虽然JavaScript在对象模型的设计上选择了原型的方式, 然而由于一些历史原因, JavaScript不得不引入 new等关键字, 试图在语法特性上看起来更像Java这种基于类的面向对象编程语言。

这导致在ES6引入class关键字定义“类”之前, 人们不得不采用各种繁琐复杂的方式来创建对象,实现继承等特性。

经过前面对于对象模型的讨论, 那么JavaScript的对象模型是如何设计的呢?我对这些知识点进行了如下梳理: