Как я могу расширить Array без объявления класса? - PullRequest
1 голос
/ 23 июня 2019

Я действительно изо всех сил пытался создать собственный объект, который мог бы наследовать все свойства массива, но при этом вести себя так же, как обычный экземпляр, то есть instanceof и constructor будут вести себя так, как вы хотитеих к.Я читал, что объявления классов были просто синтаксическим сахаром, поэтому я никогда не обращался к ним за решением (я очень мало знал о них).

До того, как у меня был большой прорыв, я создал эту мерзость:

function arrayLike() {
    let al = [];

    //make obj.constructor work
    Object.defineProperty(al, 'constructor', {value: arrayLike}); 

    //add methods

    //make (obj instanceof arrayLike) == true
    return new Proxy(al, {
        getPrototypeOf() {
            return arrayLike.prototype;
        },
    })
}

//make (obj instanceof Array) == true
Reflect.setPrototypeOf(arrayLike.prototype, Array.prototype);

Так получилось, что я увидел пример класса, очень близкий к тому, что я хотел сделать, а затем обнаружил, что он идеально подходит для работы:

class arrayLike extends Array {
    //add methods
}

Глядя на негов Chrome DevToos я вижу, что то, что я создал, совсем не имеет такой структуры, как эта.

Если объявления классов действительно являются синтаксическим сахаром, то как, черт возьми, вы создаете этот объект без него?

Ответы [ 2 ]

2 голосов
/ 23 июня 2019

Javascript - это язык, имеющий форму наследования, которая называется прототип наследования .

Идея заключается в том, что для данного объекта он имеет скрытое свойство, называемое prototype , которое является ссылкой на другой объект, который называется объектом-прототипом.

Это отношение важно, когда вы запрашиваете у движка javascript значение свойства объекта, давайте назовем его foo просто чтобы исправить идею. Движок javascript сначала проверит ваш объект, чтобы увидеть, имеет ли он свойство с именем foo : если свойство определено для вашего объекта, его значение возвращается и поиск завершается. Если, в противном случае, у вашего объекта нет свойства с именем foo , то выполняется поиск объекта-прототипа, и тот же процесс повторяется снова.

Эта процедура повторяется до тех пор, пока не будет исследована вся так называемая цепочка прототипов . Корень цепочки прототипов - это встроенный объект javascript, на который можно ссылаться с помощью выражения Object.prototype , и это объект, из которого происходят все остальные объекты javascript. Обратите внимание, что если свойство foo отсутствует в всех объектах, составляющих всю цепочку прототипов, то возвращается значение undefined .

Это реальная форма наследования, встроенная в javascript, и это бизнес, который действительно стоит за клавиатурой ES6 class , это удобство, которое скрывает этот беспорядок и создает впечатление, что javascript имеет форму наследование классов (наследование классов более широко известно, и большинству программистов легче придумать, чем наследование прототипов).

Минимум, который вы можете сделать, чтобы взять объект и решить, что он должен вести себя как массив, следующий:

const myArray = [];
const myObject = { foo: "bar" }

Object.setPrototypeOf(myObject, myArray);

myObject.push("hello");
myObject.push("world");
console.log(myObject.length); // prints 2

Эта книга - лучший справочник, который я знаю по языку JavaScript. Это тоже хорошо , но в наши дни оно немного устарело, и следовать за ним не так просто, как в предыдущем.

Пример, несколько более сложный, чем предыдущий, можно реализовать с помощью функции в качестве конструктора. На самом деле это старый способ ES5 реализовать классовое наследование, то, что вы делали во время ES5 для имитации классов:

function SpecialArray(name) {
  this.name = name;
}

SpecialArray.prototype = []; 

// fix the constructor property mess (see the book linked above)
Object.defineProperty(SpecialArray.prototype, "constructor", {
  value: SpecialArray,
  enumerable: false,
  writable: true
});

SpecialArray.prototype.getSalutation = function() {
  return "Hello my name is " + this.name;
};

const mySpecialArray = new SpecialArray("enrico");

// you can call the methods and properties defined on Array.prototype
mySpecialArray.push("hello");
mySpecialArray.push("world");
console.log(mySpecialArray.length); // prints 2

// you can use the methods and properties defined on SpecialArray.prototype
console.log(mySpecialArray.name); // prints enrico
console.log(mySpecialArray.getSalutation()); // prints Hello my name is enrico

// important sanity checks to be sure that everything works as expected
console.log(mySpecialArray instanceof Array); // prints true
console.log(mySpecialArray instanceof SpecialArray); // prints true
console.log(mySpecialArray.constructor === SpecialArray); // prints true

// you can iterate over the special array content
for (item of mySpecialArray){
  console.log(item);
}

// you can read special array entries
console.log(mySpecialArray[1]); // prints world
0 голосов
/ 23 июня 2019

Редактировать : я изучаю транспонированный код babel и нахожу, что для правильного расширения встроенного класса, такого как Array, необходимо одно дополнительное касание, нам нужно сначала обернуть конструктор Array в обычную функцию Wrapper,в противном случае цепочка прототипов будет нарушена при построении.

function _wrapNativeSuper(Class) {
  _wrapNativeSuper = function _wrapNativeSuper(Class) {
    function Wrapper() {
      var instance = Class.apply(this, arguments)
      instance.__proto__ = this.__proto__.constructor.prototype;
      return instance;
    }
    Wrapper.prototype = Object.create(Class.prototype, {
      constructor: {
        value: Wrapper,
        enumerable: false,
        writable: true,
        configurable: true
      }
    });
    Wrapper.__proto__ = Class;
    return Wrapper;
  };
  return _wrapNativeSuper(Class);
}

Синтаксис объявления класса делает 3 вещи.

  1. правильно настроить конструктор
  2. setupцепочка прототипов должным образом
  3. наследует статические свойства

Таким образом, чтобы воспроизвести то, что class Foo extends Array {} делает в старой школе, вам нужно выполнить эти 3 вещи соответственно.

// 0. wrap the native Array constructor
// this step is only required when extending built-in objects like Array
var _Array = _wrapNativeSuper(Array)

// 1. setup the constructor
function Foo() { return _Array.apply(this, arguments) }

// 2. setup prototype chain
function __dummy__() { this.constructor = Foo }
__dummy__.prototype = _Array.prototype
Foo.prototype = new __dummy__()

// 3. inherit static properties
Foo.__proto__ = _Array

Пример выполнения ниже:

function _wrapNativeSuper(Class) {
  _wrapNativeSuper = function _wrapNativeSuper(Class) {
    function Wrapper() {
      var instance = Class.apply(this, arguments)
      instance.__proto__ = this.__proto__.constructor.prototype;
      return instance;
    }
    Wrapper.prototype = Object.create(Class.prototype, {
      constructor: {
        value: Wrapper,
        enumerable: false,
        writable: true,
        configurable: true
      }
    });
    Wrapper.__proto__ = Class;
    return Wrapper;
  };
  return _wrapNativeSuper(Class);
}

// 0. wrap the native Array constructor
// this step is only required when extending built-in objects like Array
var _Array = _wrapNativeSuper(Array)

// 1. setup the constructor
function Foo() { return _Array.apply(this, arguments) }

// 2. setup prototype chain
function __dummy__() { this.constructor = Foo }
__dummy__.prototype = _Array.prototype
Foo.prototype = new __dummy__()

// 3. inherit static properties
Foo.__proto__ = _Array


// test
var foo = new Foo;
console.log('instanceof?', foo instanceof Foo);

Foo.prototype.hi = function() { return 'hello' }
console.log('method?', foo.hi());
...