Object.create从原始值继承JavaScript对象

时间:2018-07-20 22:12:55

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

JavaScript inheritance with Object.create()?

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

建议Object.create()在ES5中设计为提供一种简单的方式来继承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

我想念什么?有什么解决方法吗?还是由于某种原因不可能?

谢谢。

编辑: 我确实不想在这里扩展特定主题或规范问题,但是不幸的是,我无法避免人们问我“为什么需要这个?”我认为这完全超出了主题。

1 个答案:

答案 0 :(得分:3)

在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中的所有这些函数都在检查对象的内部属性,他们希望在其中看到原语。数字对象的此内部插槽不被继承,因此当您进行y = Object.create(x) 时,包含3的x的插槽未被继承,因此在{{1 }},该插槽不包含原始数字! y中的所有方法都希望内部插槽(称为Number.prototype ...请参阅官方ECMAScript Specification Section on Number Objects具有原始值。

现在向下滚动至20.1.3节,您可以看到number操作如何尝试通过抽象操作[[NumberData]]提取thisNumberValue插槽中的值,并抛出一个[[NumberData]](如果未签出)。这就是您所看到的!

那对您意味着什么?

如果要使用原型继承来创建其原型是现有数字对象的新数字对象,并以使新对象的数值与原始对象相同的方式执行此操作,则直接在JavaScript中TypeError对象不是这样工作的!但是,您可以创建自己的数字类型,将原始值存储在可继承的属性中。

您可以尝试的另一件事:为每个基元创建自己的函数。例如:

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属性。您需要实现自己的派生函数并自行设置原型。这是一个hack,但我希望它能有所帮助!

添加

关于为什么 [[NumberData]]插槽未继承,这是ES9 Spec的引文:

  

内部插槽对应于与对象关联并由各种ECMAScript规范算法使用的内部状态。内部插槽不是对象属性,也不会继承。根据特定的内部插槽规范,这种状态可以由任何ECMAScript语言类型的值或特定的ECMAScript规范类型的值组成。除非另有明确说明,否则内部插槽将作为创建对象过程的一部分进行分配,并且可能不会动态添加到对象中。除非另有说明,否则内部插槽的初始值为未定义的值。本规范中的各种算法都会创建具有内部插槽的对象。但是,ECMAScript语言没有提供将内部插槽与对象关联的直接方法。

虽然这种语言明确表明无法在对象上创建或设置插槽,但似乎我们甚至无法检查一个插槽。因此,从对象中获取数值将需要使用[[NumberData]]中程序员可访问的valueOf属性。除非有人做类似Object.prototype这样的疯狂事情。 :)