JavaScript 里的一切都可以被当作对象。原始类型也是可以的。

我们来新建一个数值,新建数值有两种方法:

  • 一个是 var n1 = 1

  • 一个是 var n2 = new Number(1)

两种方案都是表示数值 1,只是 n1 是原始类型的数值,而 n2 是数值包装对象。

var n1 = 1
var n2 = new Number(1)

typeof n1 // "number"
typeof n2 // "object"

所以它俩的内存图是这样的:

number

n1 是原始类型所以值是存在栈里面的,而 n2 是对象复杂类型,它是在栈里存了一个内存地址(假设地址为 ADDR 35),并把值 1 存在了堆中地址为 35 的位置。

地址 35 中存的 Number 实例对象,它除了存值为 1 的原始值外,还有一个 __proto__ 的对象。

__proto__ 对象

__proto__ 对象下包含了所有 Number 实例对象的共有属性。所有 Number 实例对象都会有一个 __proto__ 指向同一块内存。

var nn = new Number(0)
var nn2 = new Number(2)
nn.__proto__ === nn2.__proto__ // true

number-instance可以看出 nn 的 __proto__ 对象下有 constructor 指向到 Number 函数, 然后还有 Number 实例的几个方法:toExponential、toFixed、toLocaleString、toPrecision、toString、valueOf,然后还有对象的 __proto__。而对象的 __proto__ 除了 constructor 函数,还包含实例方法 hasOwnProperty、isPropertypeOf、propertyIsEnumerable、toLocaleString、toString、valueOf。

nn 有自己的 toString、toLocaleString、valueOf,如果我们执行 nn.toString(),执行的就是 Number.prototype.toString 方法,假设 Number 的原型对象没有 toString 方法,就会去原型链的上一级 Object.prototype 里面找 toString,就会执行这个方法,假设 Object.prototype 也没有,JavaScript 引擎会继续往上找,不过发现已经到了原型链的顶端 null 了,于是就返回 undefined。

nn 的共有属性包含对象的共有属性。即 nn.__proto__.__proto__实例对象.__proto__ 又对应的对象.prototype

这里形成的原型链就是:

nn --.__proto__--> Number.prototype --.__proto__--> Object.prototype --.__proto__--> null

可以得出这样一颗原型链树

prototype-tree

实例对象与「对象」的关系

Number.prototype 里 Number 即构造函数(这里的「对象」),构造函数.prototype === 实例对象.__proto__,Number 的实例对象是 nn,那 Number 又是谁的实例对象了?我们声明一个 Number 包装对象的数值是 var nn = new Number(0),Number 是一个函数,可以得知 Number 的构造函数就是 Function。所以我们又可以画一颗树:

prototype-tree

最后总结为:

  1. 构造函数.prototype.__proto__ === Object.prototype (除 Object 以外的构造函数,因为 Object.prototype.__proto__ 是原型链顶点 null)
  2. 构造函数.__proto__ === Function.prototype

套用一下 比如 构造函数是 Function

Function.prototype.__proto__ === Object.prototype
Function.__proto__ === Function.prototype
// => Function.__proto__.__proto__ === Object.prototype

又比如构造函数是 Object

Object.prototype.__proto__ === null
Object.__proto__ === Function.prototype