不知道大家有没有这种感觉,日常开发中,总觉的对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世界中,对象是由内部方法 、 内部槽和抽象操作来定义能力和行为的,从宏观上讲,所有对象分为两类,分别是普通对象和异质对象,根据内部方法定义的不同来区别这两种类型。