ECMAScript is an object-oriented programming language for performing computations and manipulating computational objects within a host environment.
— 摘自《ECMAScript® 2018 Language Specification》.

最近在看 ES 规范,越看越觉得这是一门神奇的语言。如标准所言,ES 是一门面向对象的编程语言,但它基于 prototype 的 OO 又很非主流。更神奇的是很多 JSer 拿着这门 OOP 来搞函数式和过程式编程,并且还浪的飞起。本文会围绕 ES 中的对象来展开,一起探索技术,走近科学(瞎扯淡,这并不是一篇正儿八经的技术文章)。

创世篇

null

null是万物之伊始,也是生命的尽头。null 是一种空灵的状态,似是非是,似空非空,正如 null == undefined 但是 null !== undefined,看起来什么都没有,其实是支潜力股。

在 JS 宇宙中,null 是站在原型链顶端的男人,是所有对象原型的尽头,拥有毁灭一切的能力,看谁不爽赋值为 null,这样黑白无常在垃圾回收的时候便可以一波带走。

后来 JS 的造物主 Brendan 觉得 null 的能力过于强大,于是便创造了超级英雄
undefined 与之抗衡。undefined 诞生的初衷,是为了维护 JS 宇宙秩序,解决错误处理和类型转换带来的问题。但是自 undefined 出现之后世界却变得更加混乱,人类不知道何时召唤 null 何时该召唤 undefined。

简单的说,undefined 表示此处应该有个值,但是这个值还没给出来,其实就是占了个坑,这个坑是语言内部实现帮你做的,程序员完全没有必要在代码中显示返回或者指定某个变量为 undefined,undefined 的处理完全交给程序实现就是了。所以这其实是个无需暴露给用户的能力,传说 Google 爸爸在创建 Dart 宇宙的时候就去掉了 undefined,只保留了 null。

Object

古人信奉五行阴阳之说,认为世界由金木水火土五种基本元素构成,基本元素构成各种物质,物质构成世界。在 JS 宇宙中也一样,基本语言类型构成了各种对象,对象构成了整个 JS 世界,要理解这个世界,就得从找对象开始。

在 ES5 时代,对象分三种:

  • native object(原生对象),指语义完全由规范定义并且不掺杂任何宿主环境定义的的对象;

  • host object(宿主对象),由执行环境提供,比如浏览器的window对象和history对象。JS里的对象不是原生对象就是宿主对象。

  • build-in object(内置对象),由ECMA实现提供,程序执行时就存在的对象。所有内置对象都是原生对象。

这三类对象相辅相成,亦相克相生。所谓的道生一,一生二,二生三,三生万物,在 JS 的世界观中,大概就是指的 null 生 Object,Object 生 native & host,native 又分化出buid-in,是为三才,森罗万象。

然而编码不止,变化不息,在 ES6 时代,规范中有关对象的划分又变成了四种:

  • ordinary object:普通对象,需要具备了对象的所有基本内置方法。

  • exotic object:外来对象,如果不完全具备标准对象拥有的基本内置方法,就是外来对象。JS里的对象不是普通对象就是外来对象。

  • standard object:标准对象,语义由本规范定义的对象。

  • built-in object:内置对象,跟ES5中描述一样。

对比来看,ES5 中对象是以宿主环境为条件划分的,ES6 中则是根据对象的基本内置方法。这其实要归结于 ES6 跨越性的变化,必然要动摇到一些基本规范描述来拥抱变化。所谓的无极生太极,太极生两仪,两仪生四象,在 JS 宇宙中大概,也许,null 就是无极,Object 就是太极,一内一外是两仪,四象嘛,当然就是上面那四种对象了啦......

当然我是在扯淡。

Class

在我开始用 JS 搬砖之前,是不那么认真的先用了 Java 和 C++。所以一般看来,在有对象之前必先有类,对象只是类的一个实例而已。就好比找女朋友之前先得构造一个理想女票该有的属性和行为的类,然后再从该类实例化一个对象:

public class GirlFriend {public static final Integer age = 18;public static final String sex = "女";public void eat() {}public void shop() {}public void sleep() {}public static void main(String[] args) {GirlFriend honey = new GirlFriend();}
}

这很符合人类的常识,人们喜欢分类,这样便于组织管理,可以将复杂的问题简单化,清晰化。但是,这并不是世界原本的样子,也不能表现出内心最真实的渴求,只是我们自己一厢情愿的束缚。或许只有当你放下类,放下包袱,放弃规则,放纵去爱,放肆自己,放空未来......才能享受这盛夏光年激荡地青春,才会发现,会发现自己喜欢的并不是萝莉,而是御姐......

这便是 JS 的无类哲学:世界本无类,对象亦无根,本是弱类型,何处惹尘埃?

然尘世熙攘,你我结庐于人境,谁能不闻车马喧?能够做到超脱的毕竟是少数,这不,ES6 中还是引入了类的概念。

不过在我看来,ES6 的 class 并未动摇 JS 无类对象的哲学根基,更像是普渡众生的炫迈语法糖。毕竟,typeof class GirlFriend {} 返回的并不是一个类,而是一个 function。这意味着JS虽然有 class,本质上依然是构造函数,并不能像 Java 那样表演多继承、嵌套类等“高难度”动作。

这样也好,让 JSer 们继续做一个不拘一格的自由主义者。

混沌篇

function VS object

有很长一段时间,我无法清晰的理解 function 和 object 之间的暧昧关系,就像这样:

Function instanceof Object // 返回 true
Object instanceof Function // 依然返回 true

那么问题来了,到底是先有 Object,还是先有 Function ?

按照创世篇的理念来讲,必然是先有 Object 的概念,然后才孕育出 Function,所以 Object 是蛋,Function 是鸡(有关先有蛋还是先有鸡的哲学问题,我是更倾向于先有蛋的。这个蛋是天地未开,阴阳一体,混沌之道。所谓道生蛋,蛋生鸡,鸡中有蛋,蛋中有鸡,鸡又生蛋,蛋又生鸡,蛋蛋鸡鸡,无穷尽也,说的就是这个道理)。

当然我是有理论依据的,按照 ES2018 中关于 Object 的描述:

In ECMAScript, an object is a collection of zero or more properties each with attributes that determine how each property can be used—for example, when the Writable attribute for a property is set to false, any attempt by executed ECMAScript code to assign a different value to the property fails. Properties are containers that hold other objects, primitive values, or functions. A primitive value is a member of one of the following built-in types: Undefined, Null, Boolean, Number, String, and Symbol; an object is a member of the built-in type Object; and a function is a callable object. A function that is associated with an object via a property is called a method.

这里明确指出了函数是可调用的对象。对象本身的定义就是属性的集合,函数就是拥有特定属性的集合。所以从表面上看,确实是先有对象的概念才衍生出函数的概念。然细思极恐,如果你仔细研读标准,就会发现在对象诞生之时,函数其实已经出现了!所以标准中才叫function object:

An ECMAScript function object is an ordinary object and has the same internal slots and the same internal methods as other ordinary objects.

这是一种既满足先有蛋后有鸡又满足同时有蛋和鸡的量子叠加态。所以这个时候讨论谁先谁后已经没有意义了,本自同根生,相煎何太急?

况且他们真的是同根生,这是有科学依据的:

Object.getPrototypeOf(Function) === Object.getPrototypeOf(Object)

规范中使用[[prototype]]表示原型,并提供了getPrototypeOf方法来获取它,浏览器有一个非标准的实现,可通过__proto__内部属性来访问,本文图方便就使用__proto__来访问。

回到最初的问题,正是因为 Object 和 Function 的[[prototype]]相同,所以 instanceof 才会返回 true。不过这里的 Object 并不是我们所说的 Object 数据类型,而是对象构造函数。

typeof Function // 返回 "function"
typeof Object   // 依然返回 "function"

构造函数都有一个prototype属性,所有通过构造函数实例化的对象的[[prototype]]都会指向该构造函数的 prototype 属性的引用,即:

实例对象.__proto__ === 构造函数.prototype              // ①

所有函数都是基于 Function 构造出来的,由式①可知:

// Function 和 Object 作为构造函数,自然不例外
Function.__proto__ === Function.prototype           // ②
Object.__proto__ === Function.prototype             // ③
在座的各位函数.__proto__ === Function.prototype    // ④

前面也有说到,所有对象的原型都会指向一个最基本的太极对象,太极原型终于无极。Function 构造函数作为一个特殊的对象,自然也有:

Object.prototype.__proto__ === null
Object.__proto__.__proto__ === Object.prototype
Function.__proto__.__proto__ === Object.prototype   // ⑤
Function.prototype.__proto__ === Object.prototype   // 可由②⑤推出
在座的各位函数.__proto__.__proto__ === Object.prototype // 可由④⑤推出

明白了上述道理,也就明白了 JS 原型的真谛,可谓玄之又玄,众妙之门。

prototype chain

ES 原型链的设计,其实是非常符合自然法则的。

我们每个人看似独立的个体,其实都可以追溯到共同的祖先。就好比 JS 中的对象看似独立,其实都有着同一个原型。原型链就跟血液一样,可以遗传父辈属性实现继承,但是比起血缘关系,又更像是血继限界,除了遗传之外还能进化出新的能力。这一点,比起基于类的继承更加灵活,也更符合进化论的思想。

但是有一点,ES 是单原型单继承的,这不符合自然规律。现实中孩子一般继承了父母双方的基因。试想一下,如果对象的原型是一个数组,可继承每一个原型对象的属性,那么 JS 世界会发生哪些变化?

最直接的就是可以支持多继承了,但本质上不会有变化,最终都会上溯到 Object.prototype。不过查户口会变得异常困难。如果要判断一个对象是否具有某个属性,要遍历的就不是原型链了,而是原型网,这是一个十分耗时的操作,所以单继承虽然丧失了生物的多样性,却保持了血统的纯正性,让这门语言可以一直保持简单,优雅。

嗯,我成功的说服了自己,单原型单继承并不是 JS 的缺陷,而是体现 JS 简单耐用的神来之笔,在前端开发场景下,更能突显出它的优势。因为,老实说,前端的业务场景本就没有后端复杂,没必要引入一套复杂的体系。

然而原型链设计的最让我诧异的是,实例对象竟然可直接访问并修改原型,从而影响所有其他实例对象,不愧是一把原型链,连接彼此心,牵动你和我:

function GirlFriend() {} // 或者 class GirlFriend {}// 假设张无忌同时谈了两个女朋友
let zhaoMin = new GirlFriend()
let zhouZhiRuo = new GirlFriend()// 某天周芷若黑化跟张无忌分手了
zhouZhiRuo.breakUp = true// 周芷若一气之下将其原型也修改了
zhouZhiRuo.__proto__.breakUp = true// 然后赵敏也躺枪了,张无忌成单身狗了
console.log(zhaoMin.breakUp) // true

这是不是一件非常可怕的事情!这肯定是一件非常可怕的事情!赵敏是无辜的啊!韦小宝该怎么办?

后来,我在规范中看到这样一段描述:

Every ordinary object has a Boolean-valued [[Extensible]] internal slot that controls whether or not properties may be added to the object. If the value of the [[Extensible]] internal slot is false then additional properties may not be added to the object. In addition, if [[Extensible]] is false the value of the [[Prototype]] internal slot of the object may not be modified. Once the value of an object's [[Extensible]] internal slot has been set to false it may not be subsequently changed to true.

看样子,只要将原型对象的内部属性[[Extensible]]设置为 false 即可防止被子对象篡改。然而由于是内部属性,并不属于 ES 语言的一部分,浏览器也没有像暴露原型一样将其暴露出来,所以此路不通。另外,即使用 ES6 新增的 class,也无法避免被子对象修改的命运,估计在后面的
ES 版本中会加上 class 限定符吧。

难道就没有别的办法了吗?解铃还须系铃人,既然问题出在原型上,那么还是得从原型下手。赵敏心想,如果我也直接修改原型上breakUp属性为 false,那么周芷若也会回到无忌哥身边,干脆一不做二不休:

function Wife() {}
zhaoMin.__proto__ = Wife.prototype

从此,张无忌和赵敏过上了幸福快乐的生活。这个故事有些夸张,但你我身边,或许就有周芷若和赵敏这样的人才。

可以说,ES 的原型链设计的相当自由,它只是提供了一个 playground,至于怎么去写,怎么去玩,规则都可以由你自己定义。ES 设计之初的理念就是越简单越好,所谓大道至简,悟在天成,JS 的灵活,得益于它的简单,JS 的复杂,亦归咎于它的简单。

飞升篇

慢慢地,我开始觉着 ES 的设计理念由内到外散发着一股自由的气息,在 JS 的世界中,你可以很面向对象,也可以很面向过程,还可以很函数式;时而腾云驾雾游九州,时而不慎跌落终结谷;有精华亦有糟粕,正如有光明必有阴影。JS 开发路上,可能会经历人生的大彻大悟,大起大落,但这不正是我们生活的真实写照吗?我们要时刻保持一种包容和谦卑的态度,去书写更加优雅和睿智的人生,打造属于前端开发者的未来。

好了我编不下去了了,先这样吧。本文是《ECMAScript 2018 标准导读》中的第一篇番外,感兴趣的话可以关注下,带着哲思搞技术,你会发现编程竟如此有趣。

如何优雅的理解ECMAScript中的对象相关推荐

  1. JavaScript中的对象,如何创建对象,创建对象的7种模式

    ECMA-262把对象定义为:"无需属性的集合,其属性可以包含基本值.对象或者函数."严格来讲,这就相当于说明对象是一组没有特定顺序的值.对象的每个属性或方法都有一个名字,而每个名 ...

  2. 理解js中的面向对象

    目录 前言: 一点疑问: 1.封装 2.继承 原型链的查找机制 不容易理解的点: ----重点在最后---- 前言: js是一门面向对象的语言,但是又没有类的概念,虽然后来加入了class,但也就是个 ...

  3. python一切皆对象的理解_Python中万物皆对象?的理解

    在很多地方都看到有过这样一句话,但是对象这个词的理解依然停留在谈朋友那个对象上-- python中一切皆为对象,一个对象的特征也称为属性(attribute).它所具有的行为也称为方法(method) ...

  4. python iterable对象_如何理解Python中的iterable对象

    转载请注明出处:https://www.jianshu.com/u/5e6f798c903a [^*] 表示注脚,在文末可以查看对应连接,但简书不支持该语法. 首先,容器和 iterable 间没有必 ...

  5. 十分钟理解javascript中的this对象

    最近在参加的几场面试中都涉及到了对于js中this对象的理解,那么怎样去理解this呢?这里针对不同的场景通过代码来帮助我们理解好this. this到底指向什么? this指向什么呢?一言以蔽之: ...

  6. 教妹学Java(二十三):怎么理解 Java 中对象和类的概念?

    你好呀,我是沉默王二,CSDN 排名前十的博客专家.这是<教妹学 Java>专栏的第二十三篇,我们来理解一下 Java 中的对象和类,以及它们俩的概念--什么是对象?什么又是类? 本专栏中 ...

  7. js中如何优雅修改一个多层嵌套list对象的值

    如何优雅更改一个N层嵌套对象属性的值 看下边的代码,要加工一个嵌套list的属性值,然后再赋值回去,大概只能这样写: dp.data.treelist = dp.data.treelist.map(. ...

  8. java中对象的生存期_深入理解Java虚拟机-判断对象是否存活算法与对象引用

    我们知道Java中的对象一般存放在堆中,但是总不能让这些对象一直占着内存空间,这些对象最终都会被回收并释放内存,那么我们如何判断对象已经成为垃圾呢?这篇文章会提出两种算法解决这个问题.另外,本文还要谈 ...

  9. python是动态_Python中的对象和动态性 [菜鸟的理解,高手莫入]

    当我们谈到python时常常会说python中一切都是对象,字符串是对象,整数型是对象,标准库中的对象当然也是对象,class本身也是对象,类型(type)也是对象. 但是初学的时候会错误地认为,既然 ...

最新文章

  1. [ USACO 2017 FEB ] Why Did the Cow Cross the Road III (Gold)
  2. oracle database link使用说明
  3. MyBatis——@Result注解column参数传递——父查询函数的参数传递到子查询
  4. C# ref与out区别
  5. Oracle 9i10g编程艺术 深入数据库体系结构
  6. 哲理故事300篇(中)
  7. python——reportlab
  8. 死磕Mosek!新mosek学习笔记1:VS项目配置。
  9. 史上最全的Go语言模块(Module)管理详解(基于Go1.19)
  10. iPhone, iPad, 的Safari书签和阅读列表不同步问题
  11. 2018年最新从PayPal提现美金的方法(实战教程)!
  12. 2020年开发踩坑记录
  13. IP地址(分类)、子网掩码、网络号、主机号、子网号
  14. ue4 android log,UE4+Log日志
  15. Linux虚拟机增加sda容量
  16. # 智慧社区管理系统-核心业务管理-01车位收费
  17. 8个经典数据分析模型
  18. 科技青年 | 训练机器说话20年,他勇闯阿里巴巴宝库
  19. (找规律)3 2 5 5 4 6 7 ? 金字塔
  20. githubusercontent.com被墙

热门文章

  1. 3dsmax怎么添加uv坐标_YND科研绘图3Dsmax基础操作
  2. 伺服驱动器生产文件_直流伺服系统的组成和控制原理详解
  3. php修罗XiunoBBS轻论坛程序源码开源版
  4. 很好看的加载跳转网站源码
  5. Vue系列vue-router的配置使用(一)
  6. java阿里云短信服务开通验证码功能实现(1)
  7. LPVOID是一个没有类型的指针
  8. Linux: chmod 和 chown用法小结
  9. 使用CSS和JQuery,模拟超链接的用户单击事件
  10. 视差滚动的爱情故事之优化篇