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

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

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

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);
}

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

ОБНОВЛЕНИЕ: Следующая функция работает, только если значение_функции возвращает тип данных примитива.

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

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

Ответы [ 5 ]

2 голосов
/ 12 июня 2010

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

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

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

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

Теперь важной частью является функция получения, мы создадим функцию 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];
 }

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

<--------- Мой оригинальный пост ------------->

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

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

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

  3. Вместо добавления к прототипу изменяемого объекта, создайте новый объект, расширяющий его. Например, если вы хотите расширить класс 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.

1 голос
/ 24 июня 2010

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

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

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

1 голос
/ 11 июня 2010

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

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

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

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

Давайте рассмотрим свойство с именем «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.
}
};

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

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's 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));
1 голос
/ 10 июня 2010

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

0 голосов
/ 16 февраля 2019

В 2019 году вы могли бы использовать Proxy .

Вот пример, в котором предполагается, что любое значение, являющееся функцией, должно оцениваться при доступе.

function LazyArray() {
  return new Proxy([], {
    get: (obj, prop) => {
      if (typeof obj[prop] === 'function') {
        // replace the function with the result
        obj[prop] = obj[prop]()
      }
      return obj[prop]
    },
  })
}

const a = LazyArray()

a[0] = () => {
  console.log('calculating...')
  return 42
}

console.log(a[0]) // lazy evaluated
console.log(a[0])

console.log(a.length) // accessing other properties
...