【问题标题】:Array subclassing with setPrototypeOf使用 setPrototypeOf 进行数组子类化
【发布时间】:2015-01-16 13:55:54
【问题描述】:

所以我阅读了一些关于subclassing Array in JavaScript 的博客文章、SO 线程和其他讲座。对该主题的一般看法是,没有办法创建具有某种缺点的子类。

在尝试一些事情时,我为自己想出了这个解决方案:

// This is the constructor of the new class.
function CustomArray() {

    // The "use strict" statement changes the way the "magic" variable
    // "arguments" works and makes code generally safer.
    "use strict";

    // Create an actual array. This way the object will treat numeric
    // properties in the special way it is supposed to.
    var arr = [],
        i;

    // Overwrite the object's prototype, so that CustomArray.prototype is
    // in the prototype chain and the object inherits its methods. This does
    // not break the special behaviour of arrays.
    Object.setPrototypeOf(arr, CustomArray.prototype);

    // Take all arguments and push them to the array.
    for (i = 0; i < arguments.length; i++) {
        arr.push(arguments[i]);
    }

    // Return the array with the modified prototype chain. This overwrites
    // the return value of the constructor so that CustomArray() really
    // returns the modified array and not "this".
    return arr;
}

// Make CustomArray inherit from Array.
CustomArray.prototype = Object.create(Array.prototype);

// Define a method for the CustomArray class.
CustomArray.prototype.last = function () {
    return this[this.length - 1];
};

var myArray = new CustomArray("A", "B", 3);
// [ "A", "B", 3 ]

myArray.length;
// 3

myArray.push("C");
// [ "A", "B", 3, "C" ]

myArray.length;
// 4

myArray.last();
// "C"

我的问题是:这段代码有什么问题吗?在这么多人在我之前搜索之后,我很难相信我想出了“唯一的解决方案”。

【问题讨论】:

  • 这不是您问题的答案,但请注意您链接到的文章标题为“ECMAScript 5 如何仍然不允许子类化数组”和您的解决方案使用 ECMAScript 6 函数。
  • @apsillers 你是对的。我什至没有想到这一点。但这篇文章似乎仍然适用。描述的问题在 ECMAScript 6 中仍然相同。
  • @apsillers 这有点尴尬。当我为这个问题构建简化示例时,这条线不知何故丢失了。我编辑了问题以包含代码行。

标签: javascript arrays prototype


【解决方案1】:

文章讨论了如何创建数组“子类”。也就是说,我们要创建一个对象,其原型链中有Array.prototype,但其直接原型父对象不是Array.prototype(即,原型父对象可以提供数组原型之外的其他方法)。

那篇文章说,创建数组“子类”的一个基本困难是数组的行为来自两者

  1. 他们的原型,
  2. 只是数组实例。

如果数组从Array.prototype 继承所有行为,我们的工作会非常快。我们只需创建一个原型链包含Array.prototype 的对象。该对象将成为我们数组子类实例的理想原型。

但是,数组具有特殊的自动行为,数组实例独有,并且不是从原型继承的。 (特别是,我的意思是length 属性周围的行为会在数组更改时自动更改,反之亦然。)这些是由Array 构造函数创建时提供给每个数组实例的行为,没有办法在 ECMAScript 5 中忠实地模仿它们。因此,数组子类的实例必须最初由 Array 构造函数创建。如果我们想要适当的 length 行为,这是不可协商的。

此要求与我们的其他要求相冲突,即实例必须具有不是Array.prototype 的原型。 (我们不想向Array.prototype 添加方法;我们想向使用Array.prototype 作为其自己的原型的对象添加方法。)在ECMAScript 5 中,使用Array 构造函数创建的任何对象都必须具有原型Array.prototype 的父级。 ECMAScript 5 规范没有提供在对象创建后更改其原型的机制。

相比之下,ECMAScript 6 确实提供了这种机制。您的方法与文章“Wrappers. Prototype chain injection.”部分下描述的基于__proto__ 的方法非常相似,只是您使用ECMAScript 6 的Object.setPrototypeOf 而不是__proto__

您的解决方案正确满足以下所有要求:

  1. 每个实例实际上都是一个数组(即,由Array 构造函数构造)。这可确保 [[Class]] 内部属性正确,并且 length 行为正确。
  2. 每个实例都有一个直接原型,它不是Array.prototype,但在其原型链中仍包含Array.prototype

这些要求以前在 ES5 中是不可能满足的,但是 ES6 让它变得相当容易。在 ES5 中,您可能有一个无法满足要求 #2 的数组实例,或者一个无法满足要求 #1 的普通对象。

【讨论】:

  • 不错,深入的答案,虽然它并没有真正回答我原来的问题。我熟悉您在尝试继承 Array 时遇到的问题,否则我可能无法提出解决方案。我询问了使用我的代码的原因,或者换句话说:“这是一个好的解决方案吗?”。虽然我开始觉得 SO 是这类问题的错误平台......
  • @Butt4cak3 我在最后添加了一点,证明您已满足这两个要求。您的代码似乎没有任何问题。当然,我不能证明没有问题;也许其他人会识别出一个。
  • 谢谢。我仍然会将您的答案标记为正确的,因为它提供的信息非常丰富。
  • 此解决方案创建的子类需要与其自己的子类相同的机制。一个小问题;)
【解决方案2】:

实际上,通过使用神秘的Array.of() 方法,甚至无需接触Object.setPrototypeOf()__proto__ 就可以实现数组子类化。 Array.of() 能够切换用于构造数组的构造函数。由于通常它绑定到Array 对象,因此它会生成普通数组,但是一旦它绑定到另一个可用作构造函数(又名函数)的对象,它就会将该对象用作构造函数。让我们使用Array.of() 进行一些数组子类化

function SubArr(){}
SubArr.prototype = Object.create(Array.prototype);
SubArr.prototype.last = function(){return this[this.length-1]};

var what = Array.of.call(SubArr, 1, 2, 3, 4, "this is last");
console.log(JSON.stringify(what,null,2));
console.log(what.last());
console.log(what.map(e => e));
console.log(what instanceof Array);
console.log(Array.isArray(what));
what.unshift("this is first");
console.log(JSON.stringify(what,null,2));

如您所见,使用Array.of() 完成数组子类化是一项非常简单的任务,您可以找到它的规范here。有趣的部分是;

注意 2 of 函数是一种有意的通用工厂方法;它 不要求它的 this 值是 Array 构造函数。 因此它可以转移到其他构造函数或被其他构造函数继承 可以使用单个数字参数调用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-28
    • 1970-01-01
    • 2017-12-16
    • 2015-11-04
    • 2019-04-26
    • 1970-01-01
    相关资源
    最近更新 更多