零、Object 是数据类型中的一种
众所周知,JS 中七大数据类型
- Undefined
- Null
- Boolean
- Number
- String
- Symbol (ES6)
- Object (引用类型)
前六种为基本类型,第七种,也就是今天的主角 Object 是「引用类型」,何为引用类型?
在 ECMAScript 中,引用类型是一种数据结构,用于将数据和功能组织在一起(常被称为类,不准确,无类和接口的基本结构)。而对象是某个特定引用类型的实例。
与基本类型对比来看,更容易理解:
基本类型在内在中具有固定的大小,而引用类型则不同。例如,对象可以具有任意的长度,无固定大小。
基本类型变量存的是数据的具体值,而引用类型变量保存的是值的引用。(名副其实 —— 引用)
一、Object 是函数?
console.log(typeof Object) // 结果:function
Object.toString() // 结果:function Object() { [native code] }
没错,Object 是系统提供的构造函数。何为构造函数?只是出于创建新对象的目的,以首字母大写(潜规则)的一个普通函数而已。这么用:
// 实例化一个对象
var person = new Object()
console.log(typeof person) // 结果:object
再来看,与实例息息相关相关的两个对象(构造函数也是对象)
// 查看其原型
Object.getPrototypeOf(person) // 结果:{}
// 查看其构造函数
Object.getPrototypeOf(person).constructor // 结果:[Function: Object]
当然,在知道其构造函数或原型时,也可以通过如下方式判断:
// 判断原型与实例对应关系
Object.prototype.isPrototypeOf(person) // 结果:true
// 判断实例与构造函数的关系
person instanceof Object // 结果:true
诶?Object 的原型为空!不,此空非空。让我们解开他神秘的面纱
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()
时,看不到这些?
换句话说,这些属性如何做到隐藏?其实非常简单,拎出一个属性来看
// 获取 personPrototype 的 constructor 属性的描述符
Object.getOwnPropertyDescriptor(personPrototype, 'constructor')
/*
* 结果:
{ value: [Function: Object],
writable: true,
enumerable: false,
configurable: true }
*/
注意,enumerable
属性,此时为 false
,不可枚举。说明 console.log()
打印对象时,只会展示可枚举的属性。让我们来实验一下:
Object.defineProperty(personPrototype, 'constructor', {
enumerable: true
})
console.log(personPrototype) // 结果:{ constructor: [Function: Object] }
终于,一切真相大白。那么,趁机我们来验证下,「引用」类型的内涵
console.log(Object.prototype) // 结果:{ constructor: [Function: Object] }
证明,我们变量 personPrototype
只是一个指向 Object.prototype
的指针,我们刚刚修改的是同一个对象。
总结:Object 只是一个普通的函数,但其原型上定义了各种隐藏的 Object 的属性。我们每次实例化一个对象时,只是继承了 Object 原型中的方法而已。
二、探究Object 与其他引用类型的关系
如果说函数、数组都是对象,Object 与 Function、Array……这些构造函数又有什么联系呢?
直入虎穴,Object 与 Function
// 定义一个函数
function sayName() {
}
console.log(typeof sayName) // 结果:function
console.log(sayName.toString())
/*
function sayName() {
}
*/
竟然,函数也有 toString
方法。不得不怀疑与 Object 千丝万缕的关系,让我们扒一扒,先看其原型与构造函数。
// 查看其原型
console.log(Object.getPrototypeOf(sayName)) // 结果:[Function]
// 查看其构造函数
console.log(Object.getPrototypeOf(sayName).constructor) // 结果:[Function: Function]
现在,我们知道,我们定义一个函数时,其实就是通过 Function
构造函数,实例化一个对象而已,其原型是名为 [Function]
的一个对象。
如今,想起其他定义函数的方法,就很自然了
// 实例化
var sayName = new Function()
// 函数表达式
var sayName = function() {
// 就是一个 Function 的某实例的赋值过程而已
}
那么 [Function]
原型究竟是个什么东西?有什么属性。
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 存在直接继承关系。那么,大胆试一下:
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 的实例!他们到底是如何继承的?
console.log(Object.getOwnPropertyNames(sayName))
/*
* 结果:
* [ 'length', 'name', 'arguments', 'caller', 'prototype' ]
*/
似乎只是原型继承 Object,上边这些方法应该是 Function 构造函数实现的。
三、真相
似乎悟到了什么……
- 如果把函数看做「对象」,那么所有函数的原型都是
[Function]
,构造函数都是[Function: Function]
,包括 Object、Function
console.log(Object.getPrototypeOf(Object)) // 结果:[Function]
console.log(Object.getPrototypeOf(Object) === Object.getPrototypeOf(Function))
// 结果:true
console.log(Object.getPrototypeOf(Object).constructor) // 结果:[Function: Function]
- 如果把函数看做「构造函数」,那么他们的原型各不相同
console.log(Object.prototype) // 结果:{ constructor: [Function: Object] }
console.log(Function.prototype) // 结果:[Function]
console.log(Array.prototype) // 结果:[]
console.log(Object.prototype === Object.getPrototypeOf(Object)) // 结果:false
进一步验证
console.log(Object.getPrototypeOf(Object) === Function.prototype)
// 结果:true
console.log(Object.getPrototypeOf(Function) === Function.prototype)
// 结果:true
终于,水落石出,上图
代码所在地址:boboidream/JavaScriptGo
参考资料
- 《JavaScript 高级程序设计》