Search code examples
javascriptforeachcallbackbind

Javascript callback using this keyword in context of function


We're trying to create our own Javascript library to replace jQuery and reduce overhead. We're wanting to call functions from the global scope using the this keyword, but the script is breaking in our foreach loop. How do we callback an object inside or custom "each" function using our $(this).getAtt('data-src') function rather than a.getAttribute('data-src')

this is defaulting to the window's object. Here's a minimized version of our library

var $=(function(){
    'use strict';

    var c = function(w){
        if(!w)return;
        if(w==='document'){this.elems=[document];} 
        else if(w==='window'){this.elems=[window];} 
        else {this.elems=document.querySelectorAll(w);}
    };

    c.prototype.each = function(callback){
        if(!callback || typeof callback !== 'function')return;
        for(var i = 0, length = this.elems.length; i < length; i++){callback(this.elems[i], i);}return this;
    };

    c.prototype.setAtt=function(n,v){this.each(function(item){item.setAttribute(n,v);});return this;};
    c.prototype.getAtt=function(n){return this.elems[0].getAttribute(n);};

    var init=function(w){return new c(w);};return init;
})();

function loadImgs(){
    $("img[data-src]").each(function(a,b){
        console.log(a.getAttribute('data-src'));
        console.log($(this).getAtt('data-src'));
    });
}

And our minimized HTML :

<a onclick="loadImgs();">lazyload</a><br>
<img src="spacer.gif" alt=""/></div><img class="lazyLoad" data-src="replacer.jpg" alt="">

Solution

  • Pass the calling context you want (the element) to the getAttribute method by using .call().

    You also need the c constructor to set the elems property to the argument if the argument is an element:

    } else if (w instanceof HTMLElement) {
      this.elems = [w];
    }
    
    for (var i = 0, length = this.elems.length; i < length; i++) {
      callback.call(this.elems[i], this.elems[i], i);
      //            ^^ "this" in callback
      //                           ^^ first argument to callback
      //                                          ^^ second argument to callback
    }
    

    var $ = (function() {
      'use strict';
    
      var c = function(w) {
        if (!w) return;
        if (w === 'document') {
          this.elems = [document];
        } else if (w === 'window') {
          this.elems = [window];
        } else if (w instanceof HTMLElement) {
          this.elems = [w];
        } else {
          this.elems = document.querySelectorAll(w);
        }
      };
    
      c.prototype.each = function(callback) {
        if (!callback || typeof callback !== 'function') return;
        for (var i = 0, length = this.elems.length; i < length; i++) {
          callback.call(this.elems[i], this.elems[i], i);
        }
        return this;
      };
    
      c.prototype.setAtt = function(n, v) {
        this.each(function(item) {
          item.setAttribute(n, v);
        });
        return this;
      };
      c.prototype.getAtt = function(n) {
        return this.elems[0].getAttribute(n);
      };
    
      var init = function(w) {
        return new c(w);
      };
      return init;
    })();
    
    function loadImgs() {
      $("img[data-src]").each(function(a, b) {
        console.log(a.getAttribute('data-src'));
        console.log($(this).getAtt('data-src'));
      });
    }
    <a onclick="loadImgs();">lazyload</a><br>
    <img src="spacer.gif" alt="" /></div><img class="lazyLoad" data-src="replacer.jpg" alt="">