2015-01-16 8 views
0

Итак, я прочитал несколько сообщений в блогах, SO-темы и другие лекции о . Общий взгляд на эту тему заключается в том, что нет никакого способа создать подкласс с некоторым недостатком.Array subclassing with setPrototypeOf

Хотя попробовать несколько вещей, которые я придумал это решение для себя:

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

Мой вопрос: есть ли что-нибудь не так с этим кодом? Мне трудно поверить, что я придумал «одно решение» после того, как многие люди искали меня передо мной.

+1

Это не ответ на ваш вопрос, но обратите внимание, что статья, на которую вы ссылаетесь, озаглавлена ​​«Как ECMAScript 5 * до сих пор не позволяет подклассифицировать массив», и ваше решение использует ECMAScript ** 6 ** функция. – apsillers

+0

@ capsillers Вы правы. Я даже об этом не думал. Но статья все еще, кажется, применяется. Описанные проблемы все еще сохраняются в ECMAScript 6. – Butt4cak3

+0

@apsillers Это неловко. Эта строка как-то заблудилась, когда я построил упрощенный пример для этого вопроса. Я редактировал вопрос, чтобы включить строку кода. – Butt4cak3

ответ

2

В статье обсуждается, как создать массив «подкласс». То есть мы хотим создать объект с 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 обеспечивает такой механизм. Ваш подход очень похож на подход, основанный на __proto__, описанный в статье, в разделе «Wrappers. Prototype chain injection.», за исключением того, что вы используете ECMAScript 6 Object.setPrototypeOf вместо __proto__.

Ваше решение правильно удовлетворяет всем следующим требованиям:

  1. Каждый экземпляр фактически является массивом (т.е. был построен с помощью Array конструктора). Это гарантирует правильность внутреннего свойства [[Class]], и length ведет себя правильно.
  2. Каждый экземпляр имеет прототип, который не является Array.prototype, но по-прежнему включает Array.prototype в цепочке прототипов.

Эти требования ранее не были удовлетворены в ES5, но ES6 делает его довольно простым. В ES5 у вас может быть экземпляр массива, который не удовлетворяет требованию № 2 или простому объекту, который не удовлетворяет требованию №1.

+1

Приятный, углубленный ответ, хотя на самом деле он не отвечает на мой первоначальный вопрос. Я знаком с проблемами, с которыми вы столкнулись при попытке подкласса Array, иначе я * вероятно * не смог бы найти решение. Я попросил причины ** не ** использовать мой код или, другими словами: «Это хорошее решение?». Хотя я начинаю чувствовать, что SO - неправильная платформа для такого типа вопросов ... – Butt4cak3

+0

@ Butt4cak3 Я добавил немного в конце, демонстрируя, что вы выполнили два требования. В коде нет ничего плохого. Конечно, я не могу * доказать * отсутствие проблем; возможно, кто-то другой его идентифицирует. – apsillers

+0

Спасибо. Я по-прежнему буду отмечать ваш ответ как правильный, поскольку он очень информативен. – Butt4cak3

0

На самом деле подклассы массива возможны, даже не касаясь 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. Поэтому он может быть передан или унаследован другими конструкторами , которые могут быть вызваны с помощью одного числового аргумента.