细说ECMAScript对象(一) — 对象分类

293 阅读6分钟

不知道大家有没有这种感觉,日常开发中,总觉的对javascript的知识掌握的很多,但是想系统的梳理一下JS的知识体系,又觉得繁杂的无从下手,每次下定决心开始梳理,中途就不了了之了,这是第N次开始梳理JS,区别前几次暗自整理笔记,这次采用以公开的技术文章的方式梳理,希望能得到一些正反馈,让我能坚持的久一些。。。

本次梳理过的设想的是从JS对象开始整理,这种原理性内容一般都是比较乏味的,我写起来乏味,大伙读起来也发乏味,因此决定每篇文章的内容不会很长,但会很精炼。废话不多说,废话又说了很多,接下来是正文。


前置提醒: JS ( Javascript )和 ES ( ECMAScript )之分

JS是由网景公司开发的主要用于浏览器的脚本语言,当时取名Javascript(据说是为了蹭当时已经很火的?java的热度),为了标准化,交由ECMA国际标准化组件做标准化管理,从而诞生了ECMA-262标准化规范,称为ECMAScript。可以理解为ES是JS标准化后的版本,从研究的方向上考虑,ES和JS有很多不同,如果想了解具体区别,请自行查阅相关资料,为了行文方便,本篇文章对这两个概念做了混淆处理,统一用JS称呼。

这是本系列的第一篇文章,众所周知,在JS中,对象是一等公民,万物皆对象。那么JS是如何定义和管理对象的行为的呢? 接下来浅谈一下对这个问题的理解。

内部方法、内部槽以及抽象操作的概念

内部方法内部槽整体可以理解为是 js 对象内部行为的抽象,分开讲:

  • 内部方法:可以理解为是对js对象内部行为的抽象,当开发者在js语法层面对一个对象进行操作的时候,js引擎内部实际会调用对应的内部方法完成对应操作。一个js对象的实际语义是由内部方法定义的。

  • 内部槽:可以理解是js对象内部对当前对象状态的抽象,比如,在js中,每个普通对象都有一个内部槽[[Extensible]],其值是一个boolean类型,用来指示当前对象是否可扩展(可扩展指的是,能否向该对象添加属性)。

在ECMAScript规范文档中,内部方法和内部槽表示为:[[xxx]]

通常来说内部方法定义了一个js对象在语义上的 原子操作,所谓语义上的原子操作(后面简称原子操作) ,是指对象在语义上的单个行为描述,比如有如下对象:

let obj = {
    a:123
}

当我们使用obj.a来读取obj对象的a属性的值时,js引擎内部会通过[[Get]]这个内部方法来读取属性值,因为[[Get]]内部方法只是对读取对象属性值这一单独行为的定义,因此这是一次原子操作。而内部槽定义了一个js对象内部的单个状态。

原子操作的概念,引出了抽象操作的概念:

  • 抽象操作:由一系列原子操作和内部槽构成的操作,即,一次抽象操作会调用多个内部方法和内部槽。

理清了上面的概念,可以说,在JS中,一个对象的行为和能力是由内部方法和内部槽定义的。对于js开发者而言,无论是内部方法还是内部槽,亦或者抽象操作,均无法直接调用。

PS:之所以强调原子操作是指语义上的原子操作,是因为在ECMAScript文档中,一些内部方法的执行机制,也会调用其它抽象操作,比如,对于[[GetPrototypeOf]]内部方法,其执行时,实际会调用OrdinaryGetPrototypeOf这一抽象操作,而OrdinaryGetPrototypeOf抽象操作内部实际直接返回的是[[Prototype]]内部槽指定的状态值。为了更严谨一些,在原子操作前面加上语义上的限定语,表示内部方法的行为是指语义描述上的单次行为,这一单次行为有可能由多个实际执行步骤实现的。

普通对象(Ordinary Object)和异质对象(Exotic Object)

在JS中,所有的对象其实就分为两类:

  • 普通对象:普通对象包含特定的内部方法和内部槽
  • 异质对象:除了普通对象以外的对象均为异质对象

一个普通对象必须包含以下内部方法:

  如果普通对象同时是函数对象,除了上面列出来的内部方法,还必须包含下面的内部方法:

对于异质对象,和普通对象的区别只是内部方法的定义有所区别。而且在js中,基本99%都是普通对象,只有有限的几种异质对象,具体如下:

  • bind函数

    •   [[Call]][[Constructor]]内部方法的定义不同于普通对象。
  • Array类型对象

    •   [[DefineOwnProperty]]内部方法的定义不同于普通对象。
  • String类型对象

    •   [[GetOwnProperty]][[DefineOwnProperty]][[OwnPropertyKeys]]内部方法的定义不同于普通对象。
  • Arguments对象:

    •   [[GetOwnProperty]][[DefineOwnProperty]][[Get]][[Set]][[Delete]]内部方法的定义不同于普通对象
  • 类型数组对象:

    •   [[GetOwnProperty]][[HasProperty]][[DefineOwnProperty]][[Get]][[Set]][[Delete]][[OwnPropertyKeys]]内部方法的定义不同于普通对象。
  • 模块命名空间对象:

    •   [[GetPrototype]][[SetPrototype]][[IsExtensible]][[PreventExtensions]][[GetOwnProperty]][[DefineOwnProperty]][[HasProperty]][[Get]][[Set]][[Delete]][[OwnPropertyKeys]]内部方法的定义不同于普通对象
  • 原型不可变的外部对象:

    •   原型不可变的外部对象,比如浏览器环境下的window、Location等对象,这些对象是宿主环境提供的对象,其原型对象不可修改,也是异质对象。
    •   [[SetPrototypeOf]]内部方法的定义不同于普通对象。
  • Proxy代理对象:

    •   Proxy代理对象是一个特殊的异质对象,特殊在,它可以自定义自身的内部方法的实现。

总结

在JS世界中,对象是由内部方法 内部槽抽象操作来定义能力和行为的,从宏观上讲,所有对象分为两类,分别是普通对象异质对象,根据内部方法定义的不同来区别这两种类型。