写点什么

JavaScript 到底是面向对象还是基于对象?

2019 年 3 月 12 日

JavaScript到底是面向对象还是基于对象?

你好,我是前阿里手淘前端负责人 winter,这篇文章来自于我在极客时间专栏「重学前端」中讲解 JavaScript 的部分。


与其它的语言相比,JavaScript 中的“对象”总是显得不是那么合群。一些新人在学习 JavaScript 面向对象时,往往也会有疑惑:为什么 JavaScript(直到 ES6)有对象的概念,但是却没有像其他的语言那样,有类的概念呢?为什么在 JavaScript 对象里可以自由添加属性,而其他的语言却不能呢?


甚至一些争论中,有人强调,JavaScript 并非“面向对象的语言”,而是“基于对象的语言”,这个说法一度流传甚广,而事实上,我至今遇到的持有这一说法的人中,无一能够回答“如何定义面向对象和基于对象”这个问题。


实际上,基于对象和面向对象两个形容词都出现在了 JavaScript 标准的各个版本当中。我们可以先看看 JavaScript 标准对基于对象的定义,这个定义的具体内容是:“语言和宿主的基础设施由对象来提供,并且 ECMAScript 程序即是一系列互相通讯的对象集合”。这里的意思根本不是表达弱化的面向对象的意思,反而是表达对象对于语言的重要性。


那么,在本篇文章中,我会尝试让你去理解面向对象和 JavaScript 中的面向对象究竟是什么。


什么是对象?


我们先来说说什么是对象,因为翻译的原因,中文语境下我们很难理解“对象”的真正含义。事实上,Object(对象)在英文中,是一切事物的总称,这和面向对象编程的抽象思维有互通之处。中文的“对象”却没有这样的普适性,我们在学习编程的过程中,更多是把它当作一个专业名词来理解。


但不论如何,我们应该认识到,对象并不是计算机领域凭空造出来的概念,它是顺着人类思维模式产生的一种抽象(于是面向对象编程也被认为是:更接近人类思维模式的一种编程范式)。


那么,我们先来看看在人类思维模式下,对象究竟是什么。


对象这一概念在人类的幼儿期形成,这远远早于我们编程逻辑中常用的值、过程等概念。在幼年期,我们总是先认识到某一个苹果能吃(这里的某一个苹果就是一个对象),继而认识到所有的苹果都可以吃(这里的所有苹果,就是一个类),再到后来我们才能意识到三个苹果和三个梨之间的联系,进而产生数字“3”(值)的概念。


在《面向对象分析与设计》这本书中,Grady Booch 替我们做了总结,他认为,从人类的认知角度来说,对象应该是下列事物之一:


  • 一个可以触摸或者可以看见的东西;

  • 人的智力可以理解的东西;

  • 可以指导思考或行动(进行想象或施加动作)的东西。


有了对象的自然定义后,我们就可以描述编程语言中的对象了。在不同的编程语言中,设计者也利用各种不同的语言特性来抽象描述对象,最为成功的流派是使用“类”的方式来描述对象,这诞生了诸如 C++、Java 等流行的编程语言。而 JavaScript 早年却选择了一个更为冷门的方式:原型(关于原型,我在下一篇文章会重点介绍,这里你留个印象就可以了)。这是我在前面说它不合群的原因之一。


然而很不幸,因为一些公司政治原因,JavaScript 推出之时受管理层之命被要求模仿 Java,所以,JavaScript 创始人 Brendan Eich 在“原型运行时”的基础上引入了 new、this 等语言特性,使之“看起来更像 Java”。


在 ES6 出现之前,大量 JavaScript 程序员试图在原型体系的基础上,把 JavaScript 变得更像是基于类的编程,进而产生了很多所谓的“框架”,比如 PrototypeJS、Dojo。事实上,它们成为了某种 JavaScript 的古怪方言,甚至产生了一系列互不相容的社群,显然这样做的收益远远小于损失。


如果我们从运行时角度来谈论对象,就是在讨论 JavaScript 实际运行中的模型,这是由于任何代码执行都必定绕不开运行时的对象模型,不过,幸运的是,从运行时的角度看,可以不必受到这些“基于类的设施”的困扰,这是因为任何语言运行时类的概念都是被弱化的。


首先我们来了解一下 JavaScript 是如何设计对象模型的。


JavaScript 对象的特征


在我看来,不论我们使用什么样的编程语言,我们都先应该去理解对象的本质特征(参考 Grandy Booch《面向对象分析与设计》)。总结来看,对象有如下几个特点。


  • 对象具有唯一标识性:即使完全相同的两个对象,也并非同一个对象。

  • 对象有状态:对象具有状态,同一对象可能处于不同状态下。

  • 对象具有行为:即对象的状态可能因为它的行为产生变迁。


我们先来看第一个特征,对象具有唯一标识性。一般而言,各种语言的对象唯一标识性都是用内存地址来体现的,所以,JavaScript 程序员都知道,任何不同的 JavaScript 对象其实是互不相等,我们可以看下面的代码,o1 和 o2 初看是两个一模一样的对象,但是打印出来的结果却是 false。


var o1 = { a: 1 };var o2 = { a: 1 };console.log(o1 == o2); // false
复制代码


关于对象的第二个和第三个特征“状态和行为”,不同语言会使用不同的术语来抽象描述它们,比如 C++中称它们为“成员变量”和“成员函数”,Java 中则称它们为“属性”和“方法”。


在 JavaScript 中,将状态和行为统一抽象为“属性”,考虑到 JavaScript 中将函数设计成一种特殊对象(关于这点,我会在后文中详细讲解,此处先不用细究),所以 JavaScript 中的行为和状态都能用属性来抽象。


下面这段代码其实就展示了普通属性和函数作为属性的一个例子,其中 o 是对象,d 是一个属性,而函数 f 也是一个属性,尽管写法不太相同,但是对 JavaScript 来说,d 和 f 就是两个普通属性。


var o = {     d: 1,    f() {        console.log(this.d);    }    };
复制代码


所以,总结一句话来看,在 JavaScript 中,对象的状态和行为其实都被抽象为了属性。如果你用过 Java,一定不要觉得奇怪,尽管设计思路有一定差别,但是二者都很好地表现了对象的基本特征:标识性、状态和行为。


在实现了对象基本特征的基础上, 我认为,JavaScript 中对象独有的特色是:对象具有高度的动态性,这是因为 JavaScript 赋予了使用者在运行时为对象添改状态和行为的能力。


我来举个例子,比如,JavaScript 允许运行时向对象添加属性,这就跟绝大多数基于类的、静态的对象设计完全不同。如果你用过 Java 或者其它别的语言,肯定会产生跟我一样的感受。


下面这段代码就展示了运行时如何向一个对象添加属性,一开始我定义了一个对象 o,定义完成之后,再添加它的属性 b,这样操作,是完全没问题的。这一点你要理解。


var o = { a: 1 };o.b = 2;console.log(o.a, o.b); //1 2
复制代码


为了提高抽象能力,JavaScript 的属性被设计成比别的语言更加复杂的形式,它提供了数据属性和访问器属性(getter/setter)两类。


JavaScript 对象的两类属性


对 JavaScript 来说,属性并非只是简单的名称和值,JavaScript 用一组特征(attribute)来描述属性(property)。


先来说第一类属性,数据属性。它比较接近于其它语言的属性概念。数据属性具有四个特征。


  • value:就是属性的值。

  • writable:决定属性能否被赋值。

  • enumerable:决定 for in 能否枚举该属性。

  • configurable:决定该属性能否被删除或者改变特征值。


在大多数情况下,我们只关心数据属性的值即可。


第二类属性是访问器(getter/setter)属性,它也有四个特征。


  • getter:函数或 undefined,在取属性值时被调用。

  • setter:函数或 undefined,在设置属性值时被调用。

  • enumerable:决定 for in 能否枚举该属性。

  • configurable:决定该属性能否被删除或者改变特征值。


访问器属性使得属性在读和写时执行代码,它允许使用者写入和读出属性时得到完全不同的值,它可以视为一种函数的语法糖。


我们通常用于定义属性的代码会产生数据属性,其中的 writable、enumerable、configurable 都默认为 true。我们可以使用内置函数 Object.getOwnPropertyDescripter 来查看,如以下代码所示:


var o = { a: 1 };o.b = 2;//a和b皆为数据属性Object.getOwnPropertyDescriptor(o,"a") // {value: 1, writable: true, enumerable: true, configurable: true}Object.getOwnPropertyDescriptor(o,"b") // {value: 2, writable: true, enumerable: true, configurable: true}
复制代码


我们在这里使用了两种语法来定义属性,定义完属性后,我们用 JavaScript 的 API 来查看这个属性,我们可以发现,这样定义出来的属性都是数据属性,writeable、enumerable、configurable 都是默认值为 true。


如果我们要想改变属性的特征,或者定义访问器属性,可以使用 Object.defineProperty,示例如下:


var o = { a: 1 };Object.defineProperty(o, "b", {value: 2, writable: false, enumerable: false, configurable: true});//a和b都是数据属性,但特征值变化了Object.getOwnPropertyDescriptor(o,"a"); // {value: 1, writable: true, enumerable: true, configurable: true}Object.getOwnPropertyDescriptor(o,"b"); // {value: 2, writable: false, enumerable: false, configurable: true}o.b = 3;console.log(o.b); // 2
复制代码


这里我们使用了 Object.defineProperty 来定义属性,这样定义属性可以改变属性的 writable 和 enumerable,我们同样用 Object.getOwnPropertyDescriptor 来查看,发现确实改变了 writable 和 enumerable 特征。因为 writable 特征为 false,所以我们重新对 b 赋值,b 的值不会发生变化。


在创建对象时,也可以使用 get 和 set 关键字来创建访问器属性,代码如下所示:


var o = { get a() { return 1 } };
console.log(o.a); // 1
复制代码


访问器属性跟数据属性不同,每次访问属性都会执行 getter 或者 setter 函数。这里我们的 getter 函数返回了 1,所以 o.a 每次都得到 1。


这样,我们就理解了,实际上 JavaScript 对象的运行时是一个“属性的集合”,属性以字符串或者 Symbol 为 key,以数据属性特征值或者访问器属性特征值为 value。对象是一个属性的索引结构(索引结构是一类常见的数据结构,我们可以把它理解为一个能够以比较快的速度用 key 来查找 value 的字典)。我们以上面的对象 o 为例,你可以想象一下“a”是 key。


这里{writable:true,value:1,configurable:true,enumerable:true}是 value。我们在前面的类型课程中,已经介绍了 Symbol 类型,能够以 Symbol 为属性名,这是 JavaScript 对象的一个特色。


讲到了这里,如果你理解了对象的特征,也就不难理解我开篇提出来的问题。


你甚至可以理解为什么会有“JavaScript 不是面向对象”这样的说法:JavaScript 的对象设计跟目前主流基于类的面向对象差异非常大。而事实上,这样的对象系统设计虽然特别,但是 JavaScript 提供了完全运行时的对象系统,这使得它可以模仿多数面向对象编程范式(下一节课我们会给你介绍 JavaScript 中两种面向对象编程的范式:基于类和基于原型),所以它也是正统的面向对象语言。


JavaScript 语言标准也已经明确说明,JavaScript 是一门面向对象的语言,我想标准中能这样说正因为 JavaScript 的高度动态性的对象系统。


所以,我们应该在理解其设计思想的基础上充分挖掘它的能力,而不是机械地模仿其它语言。


结语


要想理解 JavaScript 对象,必须清空我们脑子里“基于类的面向对象”相关的知识,回到人类对对象的朴素认知和面向对象的语言无关基础理论,我们就能够理解 JavaScript 面向对象设计的思路。


在这篇文章中,我从对象的基本理论出发,和你理清了关于对象的一些基本概念,分析了 JavaScript 对象的设计思路。接下来又从运行时的角度,介绍了 JavaScript 对象的具体设计:具有高度动态性的属性集合。


很多人在思考 JavaScript 对象时,会带着已有的“对象”观来看问题,最后的结果当然就是“剪不断理还乱”了。


在「重学前端」专栏中,我会继续带大家探索 JavaScript 对象的一些机制,看 JavaScript 如何基于这样的动态对象模型设计自己的原型系统,以及大家熟悉的函数、类等基础设施,期待你的到来。


拓展阅读:


JavaScript对象:我们真的需要模拟类吗?


HTML链接:除了a标签,还有哪些标签叫链接?


2019 年 3 月 12 日 14:469899

评论 3 条评论

发布
用户头像
JS其实是一个混合语言,具备不完善的面向对象特征,又不完善的面向函数特征,所以无法给它完全归类
2019 年 03 月 15 日 16:31
回复
用户头像
企业对存储的诉求有一定的延续性,但其访问的介质不外乎是主机、PC、移动端以及应用,针对不同的访问介质来看,面向对象存储的解决方案也有所不同。然而如果应用软件不支持HTTP下REST API的方式,需要以传统文件服务器协议的方式访问,则需要在面向存储对象前面加一个网关进行协议的转换。
2019 年 03 月 14 日 09:43
回复
用户头像
“为什么在 JavaScript 对象里可以自由添加属性,而其他的语言却不能呢”,“其他语言”是指的系统级编程语言C、java,还是同为脚本语言的Python,php。众所周知,常见的脚本编程语言包括javascript都支持动态增加属性,而且经常使用,这也是脚本语言灵活所在。
2019 年 03 月 13 日 11:13
回复
没有更多了
发现更多内容

4年Java开发经验裸辞之后闭关修炼2个多月,成功拿下美团、京东、字节跳动(Java架构师)offer。

Java成神之路

Java 程序员 架构 面试 编程语言

解析字节算法面试真题,深入探究ArrayList应用原理

小Q

Java 学习 编程 架构 面试

什么是全场景AI计算框架MindSpore?

华为云开发者社区

人工智能 AI mindspore

一文为你详解Unique SQL原理和应用

华为云开发者社区

数据库 sql unique

腾讯高工强烈推荐的“Netty速成手册”原理+应用+调优,带你将知识点一网打尽

比伯

Java 编程 程序员 架构 Netty

dubbogo 3.0:牵手 gRPC 走向云原生时代

阿里巴巴云原生

go gRPC 云原生 中间件 dubbo-go

产品策略闭环是个什么环?

万事ONES

项目管理 团队协作 需求管理 需求分析 产品策略

从 JMM 透析 volatile 与 synchronized 原理

码哥字节

volatile JVM JMM Java 25 周年 synchronized

EZYTRX波场智能合约APP系统软件开发

开發I852946OIIO

系统开发

智慧城市建设,社区智能化系统搭建解决方案

t13823115967

智慧城市 平安小区

熟练掌握Spring Cloud已然成为Java工程师的面试门槛,简历上没写熟悉掌握微服务连面试机会都难得!

Java成神之路

Java 程序员 架构 面试 编程语言

五年开发经验裸辞之后投递简历,收到阿里面试邀请四面成功斩获offer,特分享本次阿里面经希望对大家有所帮助。

Java成神之路

Java 程序员 架构 面试 编程语言

智慧警务可视化平台开发,重点人员管控系统搭建

t13823115967

智慧公安 智慧警务系统开发

将原则纳入到架构的生命中

soolaugust

架构 思考 设计

云图说 | 云上资源管控有神器!关于IAM,你想知道的都在这里!

华为云开发者社区

服务 权限管理 iam

一口气说出四种幂等性解决方案,面试官露出了姨母笑~

不才陈某

Java 分布式 接口

倾斜摄影实景三维在智慧工厂 Web 3D GIS 数字孪生应用

一只数据鲸鱼

GIS 数字化 数据可视化 3D渲染 数字工厂

面试被问高并发一脸懵?那是你没看过我整理得高并发回答模板

小Q

Java 学习 面试 高并发 性能调优

好久不见!这份Spring全家桶、Docker、Redis架构大礼包免费赠送

Java架构之路

Java 程序员 架构 面试 编程语言

VACUUM无法从表中删除死元组的三个原因

PostgreSQLChina

数据库 postgresql

硬肝到秃头!Alibaba强推并发编程笔记我跪了,真的学到好多东西!

Java架构追梦

Java 学习 架构 面试 并发编程

从源码的角度搞懂 Java 动态代理!

Java架构师迁哥

我和阿里P7差的不是薪资?而是Redis+微服务+Nginx+MySQL+Tomcat

Java架构之路

Java 程序员 架构 面试 编程语言

耗时一个月整理的97道大厂Java核心面试题出炉,精心整理,无偿分享

Java架构之路

Java 程序员 架构 面试 编程语言

80%Java开发者面试都问的SpringBoot你竟不会?看完这些笔记足以

Java架构之路

Java 程序员 架构 面试 编程语言

Java进阶文档:彻底搞懂JVM+Linux+MySQL+Netty+Tomcat+并发编程

Java架构之路

Java 程序员 架构 面试 编程语言

一文带你彻底了解大数据处理引擎Flink内存管理

华为云开发者社区

大数据 数据 处理

朋友突然从某度外包人员摇身一变成为大厂架构师,在我的死缠烂打下他说出了自己的秘密武器。

Java成神之路

Java 程序员 架构 面试 编程语言

《码出高效:Java 开发手册》“码” 出高效的同时编写出高质量的代“码”。PDF文档资料免费开放下载!

Java成神之路

Java 程序员 架构 面试 编程语言

为了SpringBoot提交Tomcat执行,我总结了这么多

小Q

tomcat 学习 面试 微服务 springboot

IDEA 文档插件 DocView 版本更新:修改 UI 并支持 IDEA 2020.3 !

程序员小航

idea插件 IntelliJ IDEA 文档生成

JavaScript到底是面向对象还是基于对象?-InfoQ