有什么方法可以在Javascript数组中为延迟变量定义getter?

问题描述 投票:13回答:5

我正在尝试将元素添加到延迟评估的数组中。这意味着在访问它们之前,不会计算或知道它们的值。这就像一个previous question I asked但对象。

我最终为对象做的是

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

但我还没有找到一种方法来为真正的阵列做这件事。数组没有这样的setter。您可以将函数推送到数组,但是您必须将其作为函数调用它来返回您真正想要的对象。我现在正在做的是创建了一个我将其视为数组的对象。

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

所以我想知道的是,还有一种方法可以做到这一点,同时仍然在数组而不是假数组吗?

更新:以下函数仅在value_function返回基本数据类型时有效。

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

如果您尝试推送一个包含属性的对象,则在直接访问该对象之前,您将无法访问这些属性。这不会发生在setter中,这就是为什么我想要一些Javascript的设置语法。现在我将使用假阵列,这对我正在做的事情已经足够了。

javascript arrays lazy-evaluation
5个回答
2
投票

好吧,除非我误解了你的问题,我相信我找到了一个简单的解决方案,它不需要所谓的“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];
 }

希望如果我没有完全回答你的问题,你可以从这里弄清楚。

<---------我的原帖------------->

不是一个真正的答案,而是一些重点。

  1. Javascript中没有真正的数组,数组只是一个扩展的对象(就像JS中的所有内容一样)
  2. 理想情况下,您应该永远不会将原型函数添加到JS中的本机对象,您可能会意外覆盖现有属性,或者在线下创建混乱的错误。例如,添加到Object的原型将添加到JS中的每个单独的Object(这就是一切),您需要确保您希望JS中的每个类型都具有该属性。这很危险,因为如果你不小心覆盖了实际的Array()或Object()函数,你将在浏览器中破解javascript,期间刷新页面将无法修复它。
  3. 而不是添加到您正在修改的Object的原型,而是创建一个扩展它的新Object。例如,如果要扩展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
投票

那没有。不幸的是,这是一个大问题。


1
投票

的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());

请注意,属性是单个函数,其中提供值作为第一个参数调用setter并省略参数调用getter。 “PageSize”属性将被定义为MyClass类的一部分,如下所示:

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

属性函数只是一个包含对utitily 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
投票

我不是特别喜欢这个答案,但是你可以将你的“变量”存储为表达式字符串,然后在需要时将它存储为eval()吗?不理想,但它很紧凑......

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

我已经对它进行了测试,但它确实有效,即使它并不是你想要的。


0
投票

在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
© www.soinside.com 2019 - 2024. All rights reserved.