質問

I've got a web application that makes a call to a web service which returns some JSON representing an array of objects. Each object has some fields. Here's some example JSON to give an idea:

{
   "data": [
      {
         "id": "12345",
         "from": {
            "name": "John Doe",
            "id": "6789"
         },
         "start_time": "2012-12-16T02:17:20+0000",
         "end_time": "2012-12-16T02:17:20+0000",
         "publish_time": "2012-12-16T02:17:20+0000"
         }
      },
      {
         "id": "8888",
         "from": {
            "name": "Jane Smith",
            "id": "011"
         },
         "start_time": "2012-12-16T02:17:20+0000",
         "end_time": "2012-12-17T02:17:20+0000",
         "publish_time": "2012-12-16T02:17:20+0000"
         }
      }
   ]
  }

Once this comes back, I use jQuery's parseJSON() method to inflate this into an array of objects (holding onto the "data" value). That's all fine, but after I have my array, I have several functions that can operate on each array slot. For example, let's say there's a function called GetDuration() that will print out the time lapse between end_time and start_time. In any case, I define several functions (maybe 15) and at this point, I simply iterate through the entire array and inflate every object with a copy of the function. Example

for (var i=0;i<data.length;i++)
 data[i].TimeLapse = function() { ... };

I think this can be made much more efficient. Right now, I think there's a separate copy of the same function for every array item, which is not necessary. I've also noticed a lag in the processing time, which I'm hoping can be reduced. I've read about javascript closures which seem like they could help me in this circumstance, but I don't have much experience writing functions in closures. Would I set up the closure and then modify the JSON to somehow inflate to the closure typed object? Or would I inflate to the regular javascript objects like I'm doing today and somehow go through and change the object type to point to a closure I've created?

Any thoughts or suggestions would be appreciated.

Thanks...

-Ben

役に立ちましたか?

解決

As others have written in comments, it's not clear what you want, but this sounds closest to what you're describing:

function make_duration_fun(x) {
    return function() { return GetDuration(x.start_time, x.end_time); };
}

Then your loop can do:

data[i].TimeLapse = make_duration_fun(data[i]);

There's just one duration function, but each time you call make_duration_fun you get a new closure with that same function and different x binding.

他のヒント

I think what you are saying is that you want to use JSON to set properties of different kinds of objects, objects which each have their own methods? It's not clear what you are after, or are doing currently. But if I'm right, you are definitely doing it wrong.

In short, do not add functions to your data. Instead, do make classes with methods which can accept data on creation.

// Constructor, accepts an object expected to have data about a user
var User = function(data) {

  // save a field from the passed in user data on this instance
  this.name = data.name;
};

// a method every User object will have that uses some bit of user data
User.prototype.sayHi = function() {
  alert('Hi, my name is ' + this.name);
};

// A place to put our new user objects
var users = [];

// An array of objects to iterate through
var jsonData = [
  {name: 'Joe'},
  {name: 'Sally'}
];

// Iterate through the array
for (var i = 0; i < jsonData.length; i++) {

  // make a user, passing the constructor the data object for that user
  users.push( new User(jsonData[i]) );
}

users[0].sayHi(); // alerts: "Hi, my name is Joe"
users[1].sayHi(); // alerts: "Hi, my name is Sally"
  1. If you have to loop over each element of data, why not calculate the duration for each item and just add it as a property?

  2. If you're willing to copy all the properties of each data object to a new one, you could use a prototype on the constructor of that object, and add functions such as timeLapse to the prototype. Then all your data objects are wired up to work through prototypical inheritance. But this is a lot of extra work and may not be performant.

  3. Instead of using straight JSON, you could use JSONP or a variant where the returned code is executed. Something like this:

    {
       "data": [
          new DataItem(
             12345",
             {
                "name": "John Doe",
                "id": "6789"
             },
             "2012-12-16T02:17:20+0000",
             "2012-12-16T02:17:20+0000",
             "2012-12-16T02:17:20+0000"
          ),
          // ... and so on ...
       ]
    }
    

    You do have to be careful about executed code as opposed to straight up object notation (which is prevented from executing code, it's only allowed to have values), but I believe it can be done safely. For example, you could instead of saying new DataItem() just embed an array for each final object, then loop over them and convert the arrays to objects using the technique here, applying with the new keyword and passing in the item array to DataItem to get a new prototype-wired object.

  4. Adding a function to each item in your data array could theoretically take more memory than necessary. Depending on how much time vs. space you want to trade off, there are other options. For example:

    function ItemWrapper(dataobj) {
        for (key in dataobj) {
           if (!dataobj.hasOwnProperty(key)) { continue; }
           this[key] = dataobj[ky];
        }
        this.timeLapse = (new Date(this.end_time)).getTime() - (new Date(this.start_time)).getTime(); // assign simple static properties
    }
    
    ItemWrapper.prototype.dynamicFn = function () {
       return this.x - this.y; // changeable properties accessed as functions
    }
    
    var currentObj = new ItemWrapper(data[23]);
    console.log(currentObj.timeLapse); // static
    console.log(currentObj.dynamicFn()); //dynamic
    

    The question is: do you want to perform this wrapping at use time, or at parsing time? Doing this at parsing time is actually my suggestion #2. But this suggestion is to do it only when you need a particular item. I don't know your pattern of item access so this may not be ideal, but it is at least an option.

  5. Use apply or call:

    function timeLapse() {
       return (new Date(this.end_time)).getTime() - (new Date(this.start_time)).getTime();
    }
    
    var Item16Elapsed = timeLapse.apply(data[16]);
    

    This way you're not attaching functions all over the place, you're not converting objects to new prototype-wired objects, but you have to call things a little differently.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top