正文
JavaScript 是ECMAScript标准的一种实现,是一门支持面向对象、函数式编程的混合性的动态弱类型、解释型语言,主流的运行时有浏览器和node.js,语法与语言设计参考了c、java、self、Scheme。
在几乎所有的语言中,都有“类型系统”,严格意义上讲“类型系统”包含了类型、类型系统的规则两部分。类型系统是一个编程语言的地基。
类型是对数据的分类,从更细的粒度定义数据的结构、可执行的操作、表示的意义,编程语言提供的类型更底层,抽象层次很低,一般分为基本类型和引用类型(复合类型),开发者可以通过这些基础类型构建复杂的复合类型,或者也可以说数据结构。
一个编程语言典型的“类型系统”一般会有如下基本内容:
- 基础类型(原始类型、值类型)
- 复合类型(引用类型)
- 任意类型
- 面向对象,即所有具备面向对象特征的类型
- 接口
从《JavaScript语言精髓与编程》中提到JavaScript有6种类型系统的分类
- 8种基本数据类型
- 值类型与引用类型(基本数据类型和复杂数据类型)
- ECMAScript语言类型
- ECMAScript规范类型
- 对象类型
- 原子对象类型系统
目前网上和主流官方文档的JavaScript的类型分类是2方法:Object和其他7种值类型。
JavaScript中只有一种引用类型(复杂数据类型、复合类型),就是Object,但实际上JavaScript中的很多主要语言特性都是基于其衍生的。如下:
- object类型
- Array类型
- Map类型
- Set类型
- Date类型
- RegExp类型
- function类型
- Error类型
- 基本包装类型(String、Boolean、Number)
- 单体内置对象(Global、Math)
- 宿主对象(如Window、Navigator)
- 引擎扩展对象(如debug)
可以看到除了原始类型外,其他都是对象类型,js是通过原型继承的方式来扩展和构造不同类型的。
原型继承
instanceof 运算符用来检测对象是不是类的一个实例
typeof 返回操作数的类型如下表
| 类型 | 结果 |
|---|---|
| Undefined | "undefined" |
| Null | "object"(原因) |
| Boolean | "boolean" |
| Number | "number" |
| BigInt | "bigint" |
| String | "string" |
| Symbol | "symbol" |
| Function(在 ECMA-262 中实现 [[Call]];classes也是函数) | "function" |
| 其他任何对象 | "object" |
可以看到除了值类型外,Function也是一种特殊的顶层类型,而其他均为object类型,null由于历史错误原因是object类型。
另外一个就是所有Object衍生的类型(包括函数)的原型对象的“原型”都指向了基类“Object”的原型对象,即Object.prototype,这些衍生类型有自己的原型对象,定义了特定的属性和方法,并重写了基类的某些方法,如toString方法。
而这个基类的原型对象的原型指向了null
Object.prototype.__proto // null
let a = {b:1}
// undefined
a.toString === Object.prototype.toString
// true
let b = [1,2]
b.toString === Object.prototype.toString
// false
b.toString === Array.prototype.toString
// true
let c = function (){}
c.toString === Function.prototype.toString
// true
let s = '123'
s.toString === String.prototype.toString
// true
const f = function(){}
f.toString === Function.prototype.toString
// true
const date = new Date();
date.toString === Date.prototype.toString
// true
const set = new Set([1,2])
set.toString === Set.prototype.toString
// true
JavaScript中什么会有ƒ () { [native code] }
它是一个预定义的函数,这个函数就是空函数或者空操作,通常表示为 ƒ () { [native code] },这个预定义的函数并不是一个普通的 JavaScript 函数,而是一个由 JavaScript 引擎实现的本地(native)代码函数。
因此,它的实现方式与普通的 JavaScript 代码不同。这个函数并不包含任何实际的代码,它只是一个占位符,用于占据函数对象的位置。这个预定义函数也被称为“空函数”或“占位符函数”。
这个预定义函数的作用是为所有函数对象提供默认的属性和方法。由于所有函数对象都继承自 Function.prototype,因此它们也会继承这个预定义函数的属性和方法,比如 call() 和 apply() 方法等
Function.prototype
//ƒ () { [native code] }
Function.prototype === Function.__proto__
// true 特别的Function构造函数的原型(继承类的原型对象)等于原型对象
Function.prototype.__proto__ === Object.prototype
//true
如何区分Object的子实例(直接实例和构造函数产生的实例(纯粹的对象))和其他衍生类型(Object、Array)的实例呢?
typeof a
// 'object'
typeof b
// 'object'
typeof c
// 'function'
typeof s
// 'string'
typeof date
// 'object'
typeof set
// 'object'
可见,typeof除了function类型外,其他都会得到“object”类型。
分析下两个的区别,构造函数产生的实例,其原型(proto)指向了构造函数的“原型对象”,而构造函数“原型对象”的原型指向了Object基类的“原型对象”。通过这个原型链,最后的实例可以访问Object.prototype,即Object基类的原型对象上的属性和方法。
在对象的衍生的这些构造类型中,Array、Date、Function、RegExp四个构造函数重写了Object的toString方法、其他均没有。构造函数作为一种特殊的产出对象的函数,由其new出来实例(对象),如果不主动设置原型中的属性和方法,默认是会访问到Object.prototype中的所有属性和方法的。
由此,可以用以下来判断是不是一个比较纯粹的对象(非Array、Date、Function、RegExp类型)(object、map、set、error)
foo.toString === Object.prototype.toString && foo.toString.toString().includes('[native code]')