【问题标题】:JavaScript Object inheritance by Object.create from primitive valuesObject.create 从原始值继承 JavaScript 对象
【发布时间】:2018-07-20 22:12:55
【问题描述】:

JavaScript inheritance with Object.create()?

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain

建议在 ES5 中设计 Object.create() 以提供一种简单的方式来继承 JavaScript 对象。

这按如下预期工作:

const A = function() {};
A.prototype.x = 10;
A.prototype.say = function() {
  console.log(this.x);
};

const a = new A();
a.say(); //10

const b = Object.create(a);
b.say(); //10

但是,现在我想继承一个原始对象,例如 Object(3).

出于某种原因,人们(在大多数情况下,觉得很难用某个问题解决问题)倾向于问“你为什么需要这个?”这与规范无关,因此,最好指定。

我尝试创建一种按需为任何 JavaScript 对象提供类型的方法。

幸运的是,3 这样的每个原始值都可以通过Object(3) 提升到一个对象。

现在,我尝试了Object.create 在这种情况下的工作方式,但它不起作用。

const a = Object(3);

console.log(
  a
);//[Number: 3]
console.log(
  a.toString()
);// 3

const b = Object.create(a);

console.log(
  b
);//{}
console.log(
  Object.getPrototypeOf(b)
);
console.log(
  b.toString()
);
//TypeError: Number.prototype.toString requires that
// 'this' be a Number

我错过了什么?有什么解决方法吗?还是因为某种原因不可能?

谢谢。

编辑: 我真的不想在这里扩展特定主题或规范问题,但不幸的是,我无法避免人们问我“你为什么需要这个?”我相信这完全不在主题范围内。

【问题讨论】:

标签: javascript object inheritance prototypal-inheritance prototype-chain


【解决方案1】:

那些在 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的原型是xy的原型是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)。 :)

【讨论】:

  • 这很有趣!感谢您花时间发布此内容。如果一个对象拥有这个 [[NumberData]] 属性,有什么方法可以判断(没有从 valueOf 捕获错误)?
  • 哇!谢谢!这真是一篇了不起的文章,涵盖了有关该主题的各种观点。现在我明白为什么它不起作用了,您提供了一个很好的解决方法,正是我需要的!
  • 问得好,@MarkMeyer,我想也许有一个众所周知的符号,但我找不到......我会尽快研究它。
  • 是的,我认为通过定义 Symbol[toPrimitive] 可能有一个解决方法,但没有骰子。
  • 这是个好主意,但[[NumberData]] 是一个内部插槽,无法通过数字构造接受访问。没有众所周知的符号链接到它。我在答案中添加了一点。我很确定答案是否定的。
猜你喜欢
  • 1970-01-01
  • 2013-02-09
  • 1970-01-01
  • 2012-10-13
  • 1970-01-01
  • 2011-09-14
  • 1970-01-01
  • 1970-01-01
  • 2012-01-31
相关资源
最近更新 更多