那些在 JavaScript 中被称为“盒装原语”的对象肯定看起来很奇怪。让我们看看您发现了什么,以及如何使用通过原型的派生来做您想做的事情。
首先,记住y = Object.create(x) 做了什么:它创建了一个原型为 x 的新对象 y。
x = {name: "Rex"};
x.constructor; // [Function: Object]
typeof x; // "object"
y = Object.create(x);
Object.getPrototypeOf(y) === x; // true
x.isPrototypeOf(y); // true
y.name; // "Rex"
很好:x 指的是一个对象,y 是一个原型为x 的新对象。 x 引用的对象有一个可枚举的、可配置的、可写的值属性,称为name,由 y 继承。在 x 中,name 是一个自己的属性;在y 中,它是一个继承 属性。但在这两种情况下,属性都非常“正常”。
现在让我们使用Number 对象:
x = Object(3); // [Number: 3]
x.constructor; // [Function: Number]
typeof x; // "object"
x.valueOf(); // 3
x + 0 // 3
y = Object.create(x);
Object.getPrototypeOf(y) === x; // true
x.isPrototypeOf(y); // true
// So far so good, but now
y.valueOf()
// TypeError: Number.prototype.valueOf requires that
// 'this' be a Number
y + 0
// TypeError: Number.prototype.valueOf requires that
// 'this' be a Number
哇,刚刚发生了什么?这是说y 不是数字吗?让我们检查一下:
y.constructor // [Function: Number]
嗯,它看起来确实像一个数字。由于y的原型是x,y的原型是Number.prototype,所以y当然可以访问Number.prototype中的所有功能。但似乎无论我们调用哪个,例如:
y.toFixed(2)
y.toLocaleString()
等等,我们得到那个错误!这里发生的情况是Number.prototype 中的所有这些函数都在检查对象的 internal 属性,他们希望在其中看到一个原语。数字对象的这个内部槽没有被继承,所以当你这样做时y = Object.create(x)x 中包含 3 的槽没有被继承,所以在y 中,插槽不包含原始数字! Number.prototype 中的所有方法都期望内部插槽(称为 [[NumberData]] ... 请参阅官方 ECMAScript Specification Section on Number Objects 以具有原始值。
现在向下滚动一点到第 20.1.3 节,您可以看到所有数字操作如何尝试通过抽象操作 thisNumberValue 提取 [[NumberData]] 插槽中的值,如果它不检查。这就是你所看到的!
那么这对你意味着什么?
如果你想使用原型继承来创建一个新的数字对象,其原型是一个现有的数字对象,并以新对象的数值与原始对象相同的方式进行,你不能这样做这直接在 JavaScript 中。这不是Number 对象的工作方式!但是,您可以创建自己的数字类型,其中原始值存储在可继承的属性中。
您可以尝试另一件事:为每个原语创建自己的函数。例如:
function createNewNumber(original) {
// n is expected to be a Number object
const derived = new Number(original.valueOf());
Object.setPrototypeOf(derived, original);
return derived;
}
现在
x = Object(5) // [Number: 5]
y = createNewNumber(x) // [Number: 5]
x.isPrototypeOf(y) // true
这可能是你想要的,但记住你不能直接使用Object.create!正如我们亲眼所见,对数字使用 Object.create 根本不会继承 [[NumberData]] 属性。需要自己实现派生函数,自己设置原型。这是一个 hack,但我希望它有所帮助!
附录
至于为什么 [[NumberData]] 插槽没有被继承,这里引用 ES9 规范:
内部槽对应于与对象相关联并被各种 ECMAScript 规范算法使用的内部状态。内部插槽不是对象属性,它们不会被继承。根据特定的内部槽规范,这种状态可能由任何 ECMAScript 语言类型的值或特定的 ECMAScript 规范类型值组成。除非另有明确规定,否则内部插槽是作为创建对象过程的一部分分配的,并且可能不会动态添加到对象中。除非另有说明,否则内部槽的初始值是未定义的值。本规范中的各种算法创建具有内部槽的对象。但是,ECMAScript 语言没有提供将内部槽与对象关联的直接方法。
虽然这种语言清楚地表明无法在对象上创建或设置插槽,但似乎我们甚至无法检查。因此,从对象中获取数值需要使用来自Object.prototype 的程序员可访问的valueOf 属性。除非有人做一些疯狂的事情,比如Object.create(null)。 :)