3

I have an object with a function called 'getHtml(elem)' and I want to access it while $.each iterates over an array. I understand that 'this' refers to the element in the $.each iteration, so how does one access an object's functions?

Ex:

 MyObject.prototype.doSomething = function() {
  var results = this.getElements(); //returns this object's array of objects
  var html;
  var elems = [];
  var elem;
  $.each(results, function(index, value) {
    elem = $(this); //refers to the object returned in the $.each function
    html = this.getHtml(elem); //doesn't work, this doesn't refer to this object
    elems.push(html);
  });
},

the error I get is: Uncaught TypeError: this.getHtml is not a function

I tried to read up on bind(), but I don't understand how to use it. How does one call an object's function within an $.each?

Ron I
  • 4,090
  • 8
  • 33
  • 64
  • `$.each(results, function(index, value) {}.bind(this));` – tymeJV Apr 26 '16 at 15:35
  • Put your object this inside a variable and refer to it – Uri Brecher Apr 26 '16 at 15:35
  • 2
    @tymeJV I usually recommend the aliased variable technique - it has less overhead than `.bind`, and preserves the documented semantics of the state passed to the callback by the calling function. – Alnitak Apr 26 '16 at 15:35
  • 1
    Yeah - just giving a sample of how to use it @Alnitak – tymeJV Apr 26 '16 at 15:37
  • It's disappointing how many people see the `this` context issue and just jump to solve that. What about actually understanding that the OP is trying to do a simple array map? – Mulan Apr 26 '16 at 15:45
  • 1
    @naomik you appear to have overlooked that the OP is calling an _instance method_, and not `$(elem).html()`, so it's still necessary to solve the `this` problem. – Alnitak Apr 26 '16 at 15:47
  • @Alnitak I stand corrected again. Though, based on his/her code pasted here, I'm willing to bet there's a lot more stuff to fix. – Mulan Apr 26 '16 at 15:50
  • Hello everyone and thank you for your suggestions, I am trying to read all the comments and learn from them. @naomik - I wanted to try your way, as I was excited to learn map(), but as Alnitak correctly pointed out, .getHtml() is different than .html(), and therefore it is necessary to use this. When I use var that = this, my code does work (although it may not be written at the level I would like). – Ron I Apr 26 '16 at 16:04
  • @RonI If you can use the ES6 example I provided, `this.getHtml` will work properly in that context. – Mulan Apr 26 '16 at 16:43
  • 1
    @naomik - thanks, I tried it using return $.map(this.getElements(), (index, elem)=>this.getHtml($(elem))).get(); but I got this error: Uncaught TypeError: a.cloneNode is not a function, which refers to a different function getHtml() processing the information sent. I imagine I would have to post the whole code for you to look at to make it work, which might be more than you want to do. That being said, I appreciate showing your example, I haven't used that kind of syntax, so I will be learning more about map and the => operator! – Ron I Apr 26 '16 at 23:13

4 Answers4

1

Bind this to a variable before the each (e.g. var that):

MyObject.prototype.doSomething = function() {
  var that = this;
  ...
  $.each(results, function(index, value) {
    elem = $(this); 
    html = that.getHtml(elem); // that now = this outside of the loop
    ...
  });
};
Flux
  • 391
  • 2
  • 14
1

Since you need both the context inside of $.each, you have to cache the this outside of each and use it inside.

//Other code
.
.
var _this = this;
$.each(results, function(index, value) {
    elem = $(this); 
    html = _this.getHtml(elem);
    .
    .
    //other code
Rajaprabhu Aravindasamy
  • 66,513
  • 17
  • 101
  • 130
1

Try assigning this to var that, before entering the each:

var that = this;
...
$.each(results, function(index, value) {
  elem = $(this); // refers to the object passed by the `$.each`
  html = that.getHtml(elem);
  ...
});
MarcoS
  • 17,323
  • 24
  • 96
  • 174
0

Your function is full of bad habits, though most are probably not your own fault. The quality of most jQuery code you'll see online is guilty of the same things.

Yes, .each is a way of iterating through a collection of items, but you're trying to build a new list at the same time. This operation already exists in jQuery known as .map. However, jQuery likes to keep things within the jQuery wrapper, so if you want an actual array answer back after calling .map, you need to call .get afterward. Now you will have an vanilla JavaScript array with the html of each element.

MyObject.prototype.doSomething = function() {
  var results = this.getElements(); //returns this object's array of objects
  var elems = $.map(results, function(index, elem) {
    return $(elem).html();
  }).get();
};

If you're able to use ES6, this job is even more trivial

MyObject.prototype.doSeomthing = function() {
  return $.map(this.getElements(), (idx,elem)=> $(elem).html()).get();
};
Mulan
  • 129,518
  • 31
  • 228
  • 259
  • I would go for this, if only jQuery's `.map` didn't require you to iterate over the array a second time (within the `.get()` call) to get back a plain array – Alnitak Apr 26 '16 at 15:40
  • `.get()` doesn't iterate over it again. It simply unwraps the jQuery wrapper. – Mulan Apr 26 '16 at 15:40
  • Oh, really? I must check into that! – Alnitak Apr 26 '16 at 15:41
  • I checked - in effect it does iterate again because `.get()` calls `.toArray()` that then calls `[].slice.call(this)`, which even though it's a native method must then perform a linear traversal of the keys of the jQuery object in order to create a new array from it. – Alnitak Apr 26 '16 at 15:44
  • Hence a more idiomatic JS way would be to use `[].map.call(results, f)` and avoid the second pass – Alnitak Apr 26 '16 at 15:45
  • @Alnitak fair enough. Though I wouldn't necessarily call that a "second iteration" of the array. But yes, your last solution involves the least amount of jQuery, so it's probably the best. – Mulan Apr 26 '16 at 15:48
  • It's certainly a second pass, albeit more efficient than one written in JS itself. I would expect that because the jQuery object is only a "pseudo-array" it would be unlikely that the JS optimiser will realise that it could use standard array storage and optimisations. – Alnitak Apr 26 '16 at 15:51
  • ironically, given `.map` instead of `.each / push` I probably _would_ use `[].map.call(results, this.getHtml.bind(this))` instead of a temporary copy of the outer `this` – Alnitak Apr 26 '16 at 15:53
  • Yeah, I find leaning on jQuery to accomplish anything is generally more trouble than it's worth. – Mulan Apr 26 '16 at 15:55