使用setPrototypeOf进行数组子类化

时间:2015-01-16 13:55:55

标签: javascript arrays prototype

所以我读了一些关于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"

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

2 个答案:

答案 0 :(得分:2)

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

那篇文章说创建数组“子类”的一个根本难点是数组从得到它们的行为

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

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

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

    相比之下,ECMAScript 6确实提供了这样一种机制。除了使用ECMAScript 6的Array.prototype而不是__proto__之外,您的方法与本文“Wrappers. Prototype chain injection.”部分中描述的基于Object.setPrototypeOf的方法非常相似。 / p>

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

    1. 每个实例实际上都是一个数组(即,由__proto__构造函数构造)。这可确保Array内部属性正确,[[Class]]行为正确。
    2. 每个实例都有一个不是length的直接原型,但在原型链中仍然包含Array.prototype
    3. 以前在ES5中无法满足这些要求,但ES6使其变得相当容易。在ES5中,您可能有一个无法满足要求#2的数组实例,或者无法满足要求#1的普通对象。

答案 1 :(得分:1)

实际上,通过利用神秘的Object.setPrototypeOf()方法,甚至无需触摸__proto__Array.of()即可进行数组子类化。 Array.of()能够切换用于构造数组的构造函数。由于它通常绑定到Array对象,因此它会生成普通数组,但是一旦它绑定到另一个可以用作构造函数(a.k.a函数)的对象,它就会将该对象用作构造函数。让我们用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:功能是一种有意的通用工厂方法;它   不要求其值为Array构造函数。   因此,它可以转移到其他构造函数或由其他构造函数继承   可以使用单个数字参数调用。