按需创建对象

时间:2015-02-01 11:06:46

标签: javascript

我有一个相当不寻常的问题。 我们有一个基于网络的系统,可以为我们的客户免费编程。这意味着我们的客户可以通过脚本控制和获取数据。今天我们使用基本解释器,我们想切换到JavaScript。

暴露的对象具有数万个属性,并且在启动Web应用程序时创建它们的速度很慢。因此,我想知道是否有办法按需提供。一个例子:

var reallyLargeObject = {}; //root object, with many many propertys(the propertys has subpropertys in a tree as well.

此对象是在我们的客户无法控制的情况下创建的。但是,在客户脚本中会有如下代码:

    reallyLargeObject.subProperty.subsubProperty = 23;

当然这会导致"对象没有被定义",但我想以某种方式捕捉到这一点(通过一些原型函数的覆盖可能?)并按需创建属性,如:

function(subpropertyName){
    reallyLargeObject[subpropertyName]=createSubProperty(subpropertyName);
}

返回.subsubProperty之前调用......这甚至可能吗?

提前感谢:) /张志贤

1 个答案:

答案 0 :(得分:0)

如果JavaScript引擎具有ES5功能(所有现代浏览器都可以,IE8等旧版浏览器不支持),则可能会出现这种情况。您仍然需要提前添加顶级属性,但不必初始化它们。 ES6将使真正懒惰的属性成为可能。

ES5 - defineProperty / defineProperties

您可以使用Object.definePropertyObject.defineProperties实现延迟初始化属性getter。

lazy-init有两种选择:首次使用时重新定义属性,或使用支持变量。

这是使用重新定义的示例;这样做的好处是,一旦init完成,你就不再有getter / setter了,只是一个无聊的旧属性:

// REUSABLE utility function: Make a redefinable property
function makeLazyInitProp(o, name, builder) {
  Object.defineProperty(o, name, {
    // Getter
    get: function() {
      snippet.log("Getter for " + name + " called, redefining");
      var value = builder(this);
      Object.defineProperty(this, name, {
        value: value,
        writable: true
      });
      return value;
    },
    
    // Setter, if you want one
    set: function(value) {
      Object.defineProperty(this, name, {
        value: value,
        writable: true
      });
    },

    // Has to be configurable
    configurable: true
  });
}

// Make our object with `foo` and `bar` properties
var obj = {};
makeLazyInitProp(obj, "foo", function() {
  return {
    message: "This is the foo property's message"
  };
});
makeLazyInitProp(obj, "bar", function() {
  return {
    message: "This is the bar property's message"
  };
});

// Use it, note we only see the "Getter for X called,
// redefining" message *once* for each property
// foo
snippet.log(obj.foo.message);
snippet.log(obj.foo.message);
// bar
snippet.log(obj.bar.message);
snippet.log(obj.bar.message);
<!-- Script provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>

改为使用支持值:

// REUSABLE utility function: Make a property using a backing value
function makeLazyProp(o, name, builder) {
  var pending = true, value;
  Object.defineProperty(o, name, {
    // Getter
    get: function() {
      if (pending) {
        snippet.log("Building " + name + " property");
        value = builder(this);
        pending = false;
      }
      return value;
    },
    
    // Setter, if you want one
    set: function(v) {
      value = v;
      pending = false;
    }
  });
}

// Make our object with `foo` and `bar` properties
var obj = {};
makeLazyProp(obj, "foo", function() {
  return {
    message: "This is the foo property's message"
  };
});
makeLazyProp(obj, "bar", function() {
  return {
    message: "This is the bar property's message"
  };
});

// Use it, note we only see the "Building X property"
// message *once* for each property
// foo
snippet.log(obj.foo.message);
snippet.log(obj.foo.message);
// bar
snippet.log(obj.bar.message);
snippet.log(obj.bar.message);
<!-- Script provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>

ES6 - Proxy

ES6会有代理,这几乎就像它们听起来的那样:在其他对象(代理)之上的真正外观的对象。代理可以参与几乎所有你可以用它们所包围的对象做的事情 - 包括获取和设置属性。

Firefox的SpiderMonkey引擎现在有代理,因此您可以在Firefox中使用以下内容(但不是大多数其他浏览器):

// Create the object
var obj = (function() {
  // This is the *real* object we'll return a proxy for
  var real = {};

  // These are the functions that will lazy-init our properties
  var builders = {
    foo: function(o) {
      snippet.log("Building foo property");
      o.foo = {
        message: "This is foo's message"
      };
    },
    bar: function(o) {
      snippet.log("Building bar property");
      o.bar = {
        message: "This is bar's message"
      }
    }
  };

  // Create our proxy for `real`
  var p = new Proxy(real, {
    // `get` is called whenever the proxy is asked for a
    // property, and passed the underlying object and the
    // property name
    get: function(o, name) {
      // If we have a build and the underlying object doesn't
      // yet have the property, build it
      if (builders[name] && !o.hasOwnProperty(name)) {
        builders[name](o);
      }

      // Return the (possibly-newly-built) property value
      return o[name];
    }
  });

  // Return the proxy (facade)
  return p;
})();

// Use it, note we only see the "Building X property"
// message *once* for each property
// foo
snippet.log(obj.foo.message);
snippet.log(obj.foo.message);
// bar
snippet.log(obj.bar.message);
snippet.log(obj.bar.message);
<!-- Script provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>