静静的看

零、Object 是数据类型中的一种

众所周知,JS 中七大数据类型

  1. Undefined
  2. Null
  3. Boolean
  4. Number
  5. String
  6. Symbol (ES6)
  7. Object (引用类型)

前六种为基本类型,第七种,也就是今天的主角 Object 是「引用类型」,何为引用类型?

在 ECMAScript 中,引用类型是一种数据结构,用于将数据和功能组织在一起(常被称为类,不准确,无类和接口的基本结构)。而对象是某个特定引用类型的实例。

与基本类型对比来看,更容易理解:

  • 基本类型在内在中具有固定的大小,而引用类型则不同。例如,对象可以具有任意的长度,无固定大小。

  • 基本类型变量存的是数据的具体值,而引用类型变量保存的是值的引用。(名副其实 —— 引用)

一、Object 是函数?

1
2
3
console.log(typeof Object) // 结果:function
Object.toString() // 结果:function Object() { [native code] }

没错,Object 是系统提供的构造函数。何为构造函数?只是出于创建新对象的目的,以首字母大写(潜规则)的一个普通函数而已。这么用:

1
2
3
4
// 实例化一个对象
var person = new Object()
console.log(typeof person) // 结果:object

再来看,与实例息息相关相关的两个对象(构造函数也是对象)

1
2
3
4
5
// 查看其原型
Object.getPrototypeOf(person) // 结果:{}
// 查看其构造函数
Object.getPrototypeOf(person).constructor // 结果:[Function: Object]

当然,在知道其构造函数或原型时,也可以通过如下方式判断:

1
2
3
4
5
// 判断原型与实例对应关系
Object.prototype.isPrototypeOf(person) // 结果:true
// 判断实例与构造函数的关系
person instanceof Object // 结果:true

诶?Object 的原型为空!不,此空非空。让我们解开他神秘的面纱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let personPrototype = Object.getPrototypeOf(person)
// 获取所有实例属性,不论是否可枚举即 enumerable = true || false
Object.getOwnPropertyNames(personPrototype)
/*
* 结果:
[ 'constructor',
'toString',
'toLocaleString',
'valueOf',
'hasOwnProperty',
'isPrototypeOf',
'propertyIsEnumerable',
'__defineGetter__',
'__lookupGetter__',
'__defineSetter__',
'__lookupSetter__',
'__proto__' ]
*/

具体每个属性都有何用处,暂时就不展开了。

那么有个问题,为什么我们使用 console.log() 时,看不到这些?

换句话说,这些属性如何做到隐藏?其实非常简单,拎出一个属性来看

1
2
3
4
5
6
7
8
9
10
// 获取 personPrototype 的 constructor 属性的描述符
Object.getOwnPropertyDescriptor(personPrototype, 'constructor')
/*
* 结果:
{ value: [Function: Object],
writable: true,
enumerable: false,
configurable: true }
*/

注意,enumerable 属性,此时为 false ,不可枚举。说明 console.log() 打印对象时,只会展示可枚举的属性。让我们来实验一下:

1
2
3
4
5
Object.defineProperty(personPrototype, 'constructor', {
enumerable: true
})
console.log(personPrototype) // 结果:{ constructor: [Function: Object] }

终于,一切真相大白。那么,趁机我们来验证下,「引用」类型的内涵

1
console.log(Object.prototype) // 结果:console.log(Object.prototype)

证明,我们变量 personPrototype 只是一个指向 Object.prototype 的指针,我们刚刚修改的是同一个对象

总结:Object 只是一个普通的函数,但其原型上定义了各种隐藏的 Object 的属性。我们每次实例化一个对象时,只是继承了 Object 原型中的方法而已。

二、探究Object 与其他引用类型的关系

如果说函数、数组都是对象,Object 与 Function、Array……这些构造函数又有什么联系呢?

直入虎穴,Object 与 Function

1
2
3
4
5
6
7
8
9
10
11
12
13
// 定义一个函数
function sayName() {
}
console.log(typeof sayName) // 结果:function
console.log(sayName.toString())
/*
function sayName() {
}
*/

竟然,函数也有 toString 方法。不得不怀疑与 Object 千丝万缕的关系,让我们扒一扒,先看其原型与构造函数。

1
2
3
4
5
// 查看其原型
console.log(Object.getPrototypeOf(sayName)) // 结果:[Function]
// 查看其构造函数
console.log(Object.getPrototypeOf(sayName).constructor) // 结果:[Function: Function]

现在,我们知道,我们定义一个函数时,其实就是通过 Function 构造函数,实例化一个对象而已,其原型是名为 [Function] 的一个对象。

如今,想起其他定义函数的方法,就很自然了

1
2
3
4
5
6
7
// 实例化
var sayName = new Function()
// 函数表达式
var sayName = function() {
// 就是一个 Function 的某实例的赋值过程而已
}

那么 [Function] 原型究竟是个什么东西?有什么属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let functionPrototype = Object.getPrototypeOf(sayName)
// 查看函数原型的属性
console.log(Object.getOwnPropertyNames(functionPrototype))
/*
* 结果:
[ 'length',
'name',
'arguments',
'caller',
'apply',
'bind',
'call',
'toString',
'constructor' ]
*/
// 再深入挖掘下,函数原型的原型,见证奇迹的时刻
console.log(Object.getPrototypeOf(functionPrototype))
/*
* 结果:(得益于之前把 constructor 属性设置为可枚举)
* { constructor: [Function: Object] }
*/

所以说,这个 [Function] 是直接由 Object 函数构造的,这不废话吗!不,请注意「直接」,说明 Function 与 Object 存在直接继承关系。那么,大胆试一下:

1
2
3
4
console.log(sayName instanceof Function) // 结果:true
console.log(sayName instanceof Object) // 结果:true
console.log(Function instanceof Object) // 结果:true
console.log(Object instanceof Object) // 结果:true

炸裂了,不但 Function 是Object的实例,连 Object 也是 Object 的实例!他们到底是如何继承的?

1
2
3
4
5
6
console.log(Object.getOwnPropertyNames(sayName))
/*
* 结果:
* [ 'length', 'name', 'arguments', 'caller', 'prototype' ]
*/

似乎只是原型继承 Object,上边这些方法应该是 Function 构造函数实现的。

三、真相

似乎悟到了什么……

  • 如果把函数看做「对象」,那么所有函数的原型都是 [Function],构造函数都是 [Function: Function],包括 Object、Function
1
2
3
4
5
6
console.log(Object.getPrototypeOf(Object)) // 结果:[Function]
console.log(Object.getPrototypeOf(Object) === Object.getPrototypeOf(Function))
// 结果:true
console.log(Object.getPrototypeOf(Object).constructor) // 结果:[Function: Function]
  • 如果把函数看做「构造函数」,那么他们的原型各不相同
1
2
3
4
5
6
7
console.log(Object.prototype) // 结果:{ constructor: [Function: Object] }
console.log(Function.prototype) // 结果:[Function]
console.log(Array.prototype) // 结果:[]
console.log(Object.prototype === Object.getPrototypeOf(Object)) // 结果:false

进一步验证

1
2
3
4
5
console.log(Object.getPrototypeOf(Object) === Function.prototype)
// 结果:true
console.log(Object.getPrototypeOf(Function) === Function.prototype)
// 结果:true

终于,水落石出,上图

引用类型间原型关系

代码所在地址:boboidream/JavaScriptGo

参考资料

  • 《JavaScript 高级程序设计》