Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
201 views
in Technique[技术] by (71.8m points)

javascript - Array subclassing with setPrototypeOf

So I read some blog posts, SO threads and other lectures about subclassing Array in JavaScript. The general view on the topic is that there is no way to create a subclass with some kind of downside.

While trying out a few things I came up with this solution for myself:

// 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"

My question is: Is there anything wrong with this code? I find it hard to believe that I came up with "the one solution" after so many people searched before me.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The article discusses how to create an array "subclass". That is, we want to create an object that has Array.prototype in its prototype chain, but with an immediate prototype parent that is not Array.prototype (i.e., so that the prototype parent can provide additional methods beyond the array prototype).

That article says that a fundamental difficulty in creating an array "subclass" is that arrays get their behavior from both

  1. their prototype, and
  2. simply being array instances.

If arrays inherited all their behavior from Array.prototype, our work would be very quick. We would simply create an object whose prototype chain includes Array.prototype. That object would make an ideal prototype for our array-subclass instances.

However, arrays have special automatic behaviors that are unique to array instances, and are not inherited from the prototype. (In particular, I mean the behaviors around the length property automatically changing when the array changes, and vice versa.) These are behaviors supplied to each array instance when it is created by the Array constructor, and there is no way to mimic them faithfully in ECMAScript 5. Therefore, the instance of your array subclass must be originally created by the Array constructor. This is non-negotiable, if we want the appropriate length behaviors.

This requirement conflicts with our other requirement that the instance must have a prototype that is not Array.prototype. (We don't want to add methods to Array.prototype; we want to add methods to an object that uses Array.prototype as its own prototype .) In ECMAScript 5, any object created using the Array constructor must have the prototype parent of Array.prototype. The ECMAScript 5 spec provides no mechanism to change an object's prototype after it is created.

By contrast, ECMAScript 6 does provide such a mechanism. Your approach is quite similar to the __proto__-based approach described in the article, under the section "Wrappers. Prototype chain injection.," except you use ECMAScript 6's Object.setPrototypeOf instead of __proto__.

Your solution correctly satisfies all of the following requirements:

  1. Each instance actually is an array (i.e., has been constructed by the Array constructor). This ensures that the [[Class]] internal property is correct, and length behaves correctly.
  2. Each instance has an immediate prototype that is not Array.prototype, but still includes Array.prototype in its prototype chain.

These requirements were previously impossible to satisfy in ES5, but ES6 makes it fairly easy. In ES5, you could have an array instance that fails to satisfy requirement #2, or a plain object that fails to satisfy requirement #1.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...