Любой способ определить геттеры для ленивых переменных в массивах Javascript?

Я пытаюсь добавить элементы в массив, который оценивается по лени. Это означает, что значение для них не будет рассчитываться или быть известным, пока они не будут доступны. Это как предыдущий вопрос который я задал, но для объектов.

То, что я закончил для объектов, было

Object.prototype.lazy = function(var_name, value_function) {
  this.__defineGetter__(var_name, function() {
    var saved_value = value_function();
    this.__defineGetter__(var_name, function() {
      return saved_value;
    });
    return saved_value;
  });
}

lazy('exampleField', function() {
  // the code that returns the value I want
});

Но я не понял способ сделать это для реальных массивов. В массивах нет таких сеттеров. Вы можете нажать функцию в массив, но вам нужно будет назвать ее функцией, чтобы вернуть объект, который вы действительно хотите. То, что я делаю прямо сейчас, я создал объект, который я рассматриваю как массив.

Object.prototype.lazy_push = function(value_function) {
  if(!this.length)
    this.length = 0;
  this.lazy(this.length++, value_function);
}

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

UPDATE: Следующая функция работает, только если value_function возвращает примитивный тип данных.

Array.prototype.lazy_push = function(value_function) {
  var a = this,
  i = this.length;
  this.push({
    toString: function() {
      return a[i] = value_function();
    }
  });
}

Если вы попытаетесь нажать на объект, имеющий в нем свойства, вы не сможете получить доступ к свойствам до тех пор, пока вы не получите доступ к объекту напрямую. Это не происходит с сеттерами, поэтому я хочу, чтобы какой-то синтаксис настроек для Javascript. На данный момент я буду использовать поддельный массив, которого достаточно для того, что я делаю.

Ответ 1

Хорошо, если я не понял ваш вопрос, я считаю, что нашел простое решение, которое не требует так называемой функции "lazy_push".

Следуя методу из моего предыдущего ответа, вы создаете класс MyArray:

function MyArray(){
     this.object = [];
 }

MyArray.prototype.push = function(what){
     this.object.push(what);
}

Теперь важной частью является функция getter, мы создадим функцию getIdx(), чтобы извлечь значение из массива. Затем функция использует оператор typeof, чтобы определить, является ли возвращаемое значение функцией. Если это так, верните возвращаемое значение из функции, если не вернуть исходное значение.

Код имеет больше смысла:

MyArray.prototype.getIdx = function(which){
     if(typeof this.object[which] == 'function'){
         alert("a function");
         //NOTICE THE '()' AT THE END OF THE NEXT LINE!!!
         return this.object[which]();
     }
     return this.object[which];
 }

Надеюсь, если я не полностью ответу на ваш вопрос, вы можете понять это здесь.

< --------- Мое первоначальное сообщение ------------- >

Не совсем ответ, но несколько важных моментов.

  • В Javascript нет реальных массивов, массив - это просто расширенный объект (как и все в JS).

  • В идеале вы никогда не добавляете функции прототипа в собственные объекты в JS, вы можете случайно перезаписать существующее свойство или создать путающие ошибки в строке. Например, добавление к прототипу объекта будет добавляться к каждому объекту в JS (который есть все), вам нужно абсолютно убедиться, что вы хотите, чтобы каждый тип JS имел это свойство. Это просто опасно, потому что, если вы случайно перезапишете фактические функции Array() или Object(), вы разложите javascript в браузере, период, обновление страницы не будет исправлено.

  • Вместо добавления к прототипу объекта, который вы изменяете, создайте новый объект, который расширяет его. Например, если вы хотите расширить класс Array:

    //create the constructor, 
    //with an object variable that holds the object you wish to extend
    function MyArray(){
         this.object = [];
    }
    
    //create functions which reference the native functions of the object
    MyArray.prototype.push = function(what){
         this.object.push(what);
    }
    
    //Etc... Etc....  Etc.....
    

Не обязательно весело писать все методы доступа для собственных функций Object, но он сохраняет механизм Javascript в безопасности.

Ответ 2

Нет. К сожалению, это большое дело.

Ответ 3

BLEH. Это серьезный облом, который нельзя перегрузить оператора индексатора в JavaScript. Ну что ж. Мы просто должны быть творческими и придумать другое решение. Это хорошо (и весело).: -)

LLer, решение, с которым вы соглашаетесь, - чертовски хорошо. Престижность. Это освежает, чтобы встретить людей, которые действительно понимают JavaScript в этой степени.

После прочтения этого вопроса я был поражен эпической идеей и написал код для удовольствия. Мое решение проблемы очень похоже на ваши и многие другие, которые были сделаны раньше. Но я чувствую, что придумал что-то уникальное и очень аккуратное, поэтому хочу поделиться им.

Итак, у меня есть мой проект на CodePlex, где я использую очень простой метод jQuery-esque для определения свойств (автономных функций getter/setter), очень похожих на тот, который вы используете. Для решения, которое я придумал, я просто просто экстраполирован из этого ранее существующего кода. Здесь мой подход к ленивой загрузке индексов массива. Начиная с самого начала...

Рассмотрим свойство с именем "PageSize". Здесь, как свойство будет использоваться с моей техникой:

var MyClass = function() { }; // MyClass constructor.

var instance = new MyClass();
instance.PageSize(5);

alert(instance.PageSize());

Обратите внимание, что свойство является единственной функцией, где предоставление значения в качестве первого параметра вызывает установщик и отказ от параметра вызывает геттер. Свойство "PageSize" будет определено как часть класса MyClass следующим образом:

MyClass.prototype.PageSize = function(v) { return this.GetSetProperty("PageSize", v, myFunctionThatDoesLazyLoading); };

Функция свойства - это всего лишь оболочка вокруг вызова метода GetSetProperty, который делает фактическое получение и настройку. Здесь показан фрагмент функции GetSetProperty:

Object.prototype.GetSetProperty = function(name, value, loadFunction) {
    if (!this.Properties)
    {
        this.Properties = {};
    }

if (value)
{
    this.Properties[name] = value;
}
else
{       
    if (!this.Properties[name] && loadFunction)
    {
        this.Properties[name] = loadFunction();
    }

    return this.Properties[name]; // This will return "undefined" if property doesn't exist or the loadFunction wasn't provided.
}
};

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

Object.prototype.GetSetProperty = function(name, value, loadFunction, index) {
  if (!this.Properties)
  {
    this.Properties = {};
  }

  if (typeof index === "number" && this.Properties[name] && this.Properties[name].constructor == Array)
  {
    return this.GetSetArrayProperty(name, index, value, loadFunction);
  }
  else 
  {
    value = index;
  }

  if (value)
  {
    this.Properties[name] = value;
  }
  else
  {    
    if (!this.Properties[name] && loadFunction)
    {
      this.Properties[name] = loadFunction();
    }

    return this.Properties[name]; // This will return "undefined" if property doesn't exist or the loadFunction wasn't provided.
  }
};


Object.prototype.GetSetArrayProperty = function(name, index, value, loadFunction) {
  if (value)
  {
    this.Properties[name][index] = value;
  }
  else
  {
    if (!this.Properties[name][index] && loadFunction)
    {
      this.Properties[name][index] = loadFunction();
    }

    return this.Properties[name][index];
  }
};

Объявление прототипа необходимо изменить следующим образом:

MyClass.prototype.PageSize = function(i, v) { return this.GetSetProperty("PageSize", v, myFunctionThatDoesLazyLoading, i); };

Все, кто читает это, могут получить доступ к рабочему набору кода здесь: http://jsbin.com/ajawe/edit

Здесь полный список кода с тестами:

Object.prototype.GetSetProperty = function(name, value, loadFunction, index) {
  if (!this.Properties)
  {
    this.Properties = {};
  }

  if (typeof index === "number" && this.Properties[name] && this.Properties[name].constructor == Array)
  {
    return this.GetSetArrayProperty(name, index, value, loadFunction);
  }
  else 
  {
    value = index;
  }

  if (value)
  {
    this.Properties[name] = value;
  }
  else
  {    
    if (!this.Properties[name] && loadFunction)
    {
      this.Properties[name] = loadFunction();
    }

    return this.Properties[name]; // This will return "undefined" if property doesn't exist or the loadFunction wasn't provided.
  }
};


Object.prototype.GetSetArrayProperty = function(name, index, value, loadFunction) {
  if (value)
  {
    this.Properties[name][index] = value;
  }
  else
  {
    if (!this.Properties[name][index] && loadFunction)
    {
      this.Properties[name][index] = loadFunction();
    }

    return this.Properties[name][index];
  }
};


// Here a nifty function that declares the properties for you.
Function.prototype.CreateProperty = function(propertyName, loadFunction) {
  eval("this.prototype['" + propertyName.toString() + "'] = function(i, v) { return this.GetSetProperty('" + propertyName.toString() + "', v, " + eval(loadFunction) + ", i); };");
};




var myFunctionThatDoesLazyLoading = function() {
  return "Ahoy!";
};


var MyClass = function() { }; // MyClass constructor.
MyClass.prototype.PageSize = function(i, v) { return this.GetSetProperty("PageSize", v, myFunctionThatDoesLazyLoading, i); };

var instance = new MyClass();
alert(instance.PageSize()); // PageSize is lazy loaded.

instance.PageSize(5); // PageSize is re-assigned.
alert(instance.PageSize()); // Returns the new value.

instance.PageSize([1, 2, 3]); // PageSize is re-assigned to have an Array value.
alert(instance.PageSize(2)); // Returns the value at index 2 of the Array value.

instance.PageSize(2, "foo"); // Re-assigns the value at index 2.
alert(instance.PageSize(2)); // Returns the new value at index 2.

MyClass.CreateProperty("NewProp", function() { return ["a", "b", "c"]; }); // Demo of the CreateProperty function.
alert(instance.NewProp());
alert(instance.NewProp(1));

Ответ 4

Мне не очень нравится этот ответ, но можете ли вы сохранить свою переменную как строку выражения, а затем eval(), когда она вам понадобится? Не идеальный, но компактный...

var x = 10, arr = [];
arr.push("x + 10");
alert(eval(arr[0]));
x = 20;
alert(eval(arr[0]));

Я тестировал его, и он работает, даже если это не совсем то, что вы ищете.