与其他语言相比,JavaScript中的对象总是显得不是那么合群。

我们在学习JavaScript面向对象时,往往也会有疑惑:

为什么JavaScript知道ES6才有对象的概念,但是却没有像其他语言那样,有类的概念呢?

为什么在JavaScript对象里可以自由添加属性,而其他的语言却不能呢?

甚至,在一些争论中,有人强调:JavaScript并非面向对象的语言,而是基于对象的语言。这个说法一度流传甚广。

实际上,基于对象和面向对象两个形容词都出现在了JavaScript标准的各个版本中。

我们可以先看看JavaScript标准对于基于对象的定义,这个定义的具体内容是:

语言和宿主的基础设施由对象来提供,并且JavaScript程序即是一系列相互通讯的对象集合。
这里的意思根本不是表达弱化面向对象的意思,反而是表达对象对于语言的重要性。

那么,在本篇文章中,我们一起来尝试去理解面向对象和JavaScript中的面向对象究竟是什么。

什么是面向对象

我们先来说说什么是对象,因为翻译的原因,中文语境下我们很难理解’对象’的真正含义。事实上,Object在英文中,是一切事物的总称,这和面向对象编程的抽象思维有共通之处。

中文的’对象’却没有这样的普适性,我们在学习编程的过程中,更多是把它当做一个专业名词来理解。

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

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

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

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

1,一个可以触摸或者可以看见的东西;
2,人的智力可以理解的东西;
3,可以指导思维或者行动的东西。

有了对象的自然定义后,我们就可以描述编程语言中的对象了。在不同的编程语言中,设计者也利用各种不同语言特性来抽象描述对象,最为成功的流派是使用’类‘的方式来描述对象,这诞生了诸如C++,Java等流行的编程语言。

而JavaScript早年却选择了一个更为冷门的方式:原型。

这也是文章开头说他不合群的原因之一。

回顾历史,不幸的是,因为公司政治的原因,JavaScript在推出之时受管理层之命被要求模仿Java,所以JavaScript创始人在’原型运行时‘的基础上引入了new,this关键字等语言特性,使之’看起来更像Java‘。

在ES6出现之前,大量的JavaScript程序员试图在原型体系的基础上,把JavaScript变得更像是基于类的编程语言,进而产生了很多所谓的’框架‘,比如你肯定听说过的PrototypeJS,Dojo等。

事实上,它们成为了某种JavaScript方言,甚至产生了一些列互不相容的社群,这样做,显然收益是远大于损失的。

如果我们从运行时的角度来谈论对象,就是在讨论JavaScript实际运行中的模型,这是由于任何代码执行都绕不开运行时的对象模型。

从运行时的角度看,可以不必受到这些诸如’基于类的设施‘的困扰,这是因为任何语言运行时类的概念都是被弱化的。

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

JavaScript对象的特征

在我看来,无论我们使用什么样的编程语言,我们都应该尝试去理解对象的几个本质特性。总结来看,对象有如下几个特点:

对象具有唯一标识性:即使完全相相同的两个对象,也并非同一个对象。
对象有状态:对象具有状态,这意味着同一对象可能处于不同的状态之下。
对象具有行为:即对象的状态,可能因为它的行为产生变迁。

先来看第一个特征,对象具有唯一标识性。一般而言,各种语言的对象唯一标识性都是用内存地址来体现的,对象具有唯一标识性的内存地址,所以具有唯一的标识。

所以,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的属性被设计成比别的语言更加复杂的形式了,它提供了数据属性和访问器属性两类。

JavaScript对象的两类属性

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

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

value:就是属性的值
writable:决定属性能否被赋值
enumerable:决定for…in能否枚举该属性
configurable:决定该属性能否被删除或者改变特征值

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

第二类属性是访问器属性,它也有四个特征。

getter:函数或者undefined,在取属性值的时候被调用
setter:函数或者undefined,在设置属性值的时候被调用
enumerable:决定for…in能否枚举该属性
configurable:决定该属性能否被删除或者改变特征值

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

我们通常用于定义属性的代码会产生数据属性,其中的writable,enumeratable,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来查看这个属性,我们可以发现,这样定义出来的属性都是数据属性。

如果我们想要改变属性的特征,或者定义访问器属性,我们可以使用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函数。

这样,我们就理解了,实际上JavaScript对象的运行时是一个’属性的集合‘,属性以字符串或者Symbol为key,以数据属性特征值或者访问器属性特征值为value。

**对象是一个属性的索引结构。**我们以上面的对象o为例,你可以想象一下’a’是key,{writable:true,value:1,configurable:true,enumerable:true}是value。

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

你甚至可以理解为什么会有’JavaScript不是面向对象‘这样的说法了,这是由于JavaScript的对象设计跟目前主流基于类的面向对象差异非常大。

可事实上,这样的对象系统设计虽然特别,但是JavaScript提供了完全运行时的对象系统,这使得它可以模仿多数面向对象的编程范式,所以它也是正统的面向对象语言。

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

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

JavaScript是面向对象还是基于对象相关推荐

  1. muduo网络库学习(一)对io复用的封装Poller,面向对象与基于对象

    高效并发的网络框架大多离不开io多路复用函数,Linux下有三种 select poll epoll 关于三者的区别可以参考 linux网络编程-–几种服务器模型及io多路复用函数 前段时间看Libe ...

  2. 面向对象与基于对象 区别

          很多人没有区分"面向对象"和"基于对象"两个不同的概念.面向对象的三大特点(封装,继承,多态)却一不可.通常"基于对象"是使用对 ...

  3. 面向对象、基于对象和面向过程

    很多人没有区分"面向对象"和"基于对象"两个不同的概念.面向对象的三大特点(封装,继承,多态)缺一不可.通常"基于对象"是使用对象,但是无法 ...

  4. 面向对象和基于对象的区别

    面向对象的三大基本特征: 封装,继承,多态. 基于对象: 也使用了对象,但是无法利用现有的对象的模板产生新的对象类型,继而产生新的对象,基于对象是没有继承的特点. 多态体现的就是继承,没有了继承就无从 ...

  5. javascript 之 面向对象【理解对象】

    第五版本 6.1.1  属性类型      1/数据属性 :包含有数据值的问题.有内部有特性和属性,是为了实现javaScript引擎用的,在javaScript中不能直接访问  [[Configur ...

  6. JavaScript之面向对象学习四原型对象的动态性

    1.由于在原型中查找值的过程是一次搜索,因此我们对原型对象所做的任何修改都能够立即从实例上反映出来---即便是先创建了实例后修改原型也是如此.代码如下: function Person(){ } va ...

  7. Java基于对象基础 基于对象和面向对象的区别(转)

    Java基于对象基础 基于对象和面向对象的区别 JavaScript设计者想把javascript语言设计成基于对象(object-based)的语言,他想把这个与面向对象(object-orient ...

  8. Javascript基于对象基础

    Java基于对象基础 基于对象和面向对象的区别 JavaScript设计者想把JavaScript语言设计成基于对象(object-based)的语言,他想把这个与面向对象(object-orient ...

  9. 面向对象编程风格基于对象编程风格

    本文主要通过实现Thread 类来展现两种编程风格的不同点. 很多人没有区分"面向对象"和"基于对象"两个不同的概念.面向对象的三大特点(封装,继承,多态)缺一 ...

最新文章

  1. c语言编程存航线,C语言编程飞机订票系统如何设计?
  2. 拿来就能用!Dijkstra 算法实现快递路径优化
  3. 前端质量提升利器-马可代码覆盖率平台
  4. 【教师节福利】长大后我就成了你
  5. 一切为了运营!如何从推广短信链接唤起 App?
  6. POJ 3189 Steady Cow Assignment
  7. java获取对象的子_java – 如何根据子对象字段获取父对象
  8. STM32H743+CubeMX-定时器TIM发送非对称PWM(使用一个通道)
  9. 《Python编程从入门到实践》记录之Python函数返回值
  10. php json_encode options,json_encode($json,$option) 对变量进行 JSON 编码说明
  11. Prometheus+Grafana可视化监控SpringBoot项目
  12. 如何保证进程间同步工作_如何在工作自动化进程中占据优势?开关电源芯片U6605D有答案...
  13. 2008中国最佳寓言
  14. 用Dynamips和虚拟机搭建虚拟网络1
  15. keil安装stm32系列
  16. andorid 查看 Activity任务栈
  17. 输出10000以内的质数C语言
  18. 微信小程序踩坑–卸载所有页面(含tabBar)跳转到指定页面
  19. FreeBSD常用命令110条
  20. OC语言——基本语法和思想

热门文章

  1. Office快捷键你了解多少呢?
  2. 抖音近期比较火的云蹦迪蘑菇头版教程
  3. p201 谱集是闭集 有界集
  4. GIS系列(十三)什么是b3dm?详解b3dm
  5. 数据挖掘实验——python实现朴素贝叶斯分类
  6. 使用Python第三方库xlwings将单个excel工作表快速拆分为多个工作表(附打包好的exe文件)
  7. 程序员的私藏好书中,一定有这 7 本
  8. 分别使用dom和dom4j解析XML文档
  9. 笔记木计算机自动关机怎么办,电脑总是自动关机怎么办
  10. 华硕天选2,Ubantu18.04,RTX3060显卡驱动安装