1

I'm trying to implement something similar to jQuery.
Basically, whenever I'd be doing something with DOM elements, I'd like to use a wrapper object's methods instead of manipulating the DOM directly.

This is what I have so far

HTML:

<a href='#!'></a>

JS:

var dd = function(selector)
{
    return new dd.prototype.constructor(selector);
}
dd.prototype =
{
    constructor: function(selector)
    {
        var nodes = document.querySelectorAll(selector);

        for(var i = 0; i < nodes.length; i++)
            this[i] = nodes[i];

        this.length = nodes.length;
        return Array.prototype.slice.call(this);
    },
    addClass: function(cl)
    {
        alert('a');
    }
};

var a = dd('[href="#!"]');
a.addClass('asd');

The problems:

  • I get an error in the browser console saying that a.addClass is not a function
  • dd('a') instanceof dd === false while $('a') instanceof $ === true, so I know that my logic is somehow wrong here.

Sadly, I don't quite get what I'm doing wrong.

  • 1
    Consider using es6 classes. It's much simpler than using `prototype`. Your constructor function looks dubious, I'm pretty sure you shouldn't be returning anything. Also `a = dd()` should be `a = new dd()`. – Halcyon Jun 08 '17 at 14:49
  • @Halcyon I would be using es6 classes, but the browser support for them is not good enough. The constructor is supposed to return an array that holds the DOM elements on numeric indexes, and has a __proto__ which holds the methods I'd like to use (based on how jQ does it). Currently my array looks fine, but __proto__ doesn't hold addClass. And the first function definition returns a new object (or at least is supposed to), so that I wouldn't have to use new everytime I wrap an element in the object. – comesuccingfuccslot Jun 08 '17 at 15:01
  • Have a look at jQuery's source code if you want to create something similar - you can learn a lot there. – Egor Stambakio Jun 08 '17 at 15:22
  • @wostex Cheers, I've been doing that, but I guess it's a bit complex for me at the moment. – comesuccingfuccslot Jun 08 '17 at 15:24

3 Answers3

2

While reinventing the wheel isn't a good Idea, what follows should help you.

Consider that jQuery supports custom builds, so, first of all, have a look here https://github.com/jquery/jquery#how-to-build-your-own-jquery.

var dd = (function() {
  function dd(selector) {
    // class call check
    if(!(this instanceof dd)) {
      return new dd(selector);
    }

    Array
      .from(document.querySelectorAll(selector), (el, i) => {
        this[i] = el;
      })
    ;
    
    Object
      .defineProperty(this, 'length', {
        get: () => Object.keys(this).length
      })
    ;
  }
  
  dd.prototype.forEach = function(cb) {
    Object
      .keys(this)
      .forEach(i => {
        cb(this[i], i)
      })
    ;
    
    return this;
  }
  
  dd.prototype.addClass = function() {
    this.forEach(el => el.classList.add(...Array.from(arguments)));

    
    return this;
  }
  
  dd.prototype.removeClass = function() {
    this.forEach(el => el.classList.remove(...Array.from(arguments)));
    
    return this;
  }

  return dd;
})();

var odd = dd('strong');
var even = dd('span');

window.setTimeout(() => odd.addClass('foo'), 2000);
window.setTimeout(() => even.addClass('baz'), 3000);

console.log('odd', odd.length);
console.log('even', even.length);
.cntr strong,
.cntr span {
  display: inline-flex;
  width: 20px;
  height: 20px;
  transition: 250ms all linear;
  margin: 2px;
  border: 1px solid cyan;
  background: lightseagreen;
}

.cntr .foo { background: yellow; }
.cntr .baz { background: orange; }
<section class="cntr">
 <strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span><strong></strong><span></span>
</section>
Hitmands
  • 13,491
  • 4
  • 34
  • 69
1

Not sure what you are trying to achieve but lets see what things are wrong right now.

1) You are calling new on constructor and then returning return Array.prototype.slice.call(this); so what you get in a is an array. Obviously array doesn't know about your function addClass.
2) If you comment out array slice part, you would be returning an object for which this is set to constructorbut again addClass is not added to prototype object of constructor. To do that you need to add addClass like below:

dd.prototype.constructor.prototype.addClass = function (cl) {
            alert('a');
        }

With these changes you would get your alert and hopefully can proceed further. So overall these changes:

var dd = function(selector) {
  return new dd.prototype.constructor(selector);
}
dd.prototype = {
  constructor: function(selector) {
    var nodes = document.querySelectorAll(selector);

    for (var i = 0; i < nodes.length; i++)
      this[i] = nodes[i];

    this.length = nodes.length;
    //return Array.prototype.slice.call(this);
  },
  //addClass: function (cl) {
  //    alert('a');
  //}
};
dd.prototype.constructor.prototype.addClass = function(cl) {
  alert('a');
}
var a = dd('[href="#!"]');
a.addClass('asd');
<a href='#!'></a>
Pankaj Shukla
  • 2,657
  • 2
  • 11
  • 18
  • Commenting out the `return` makes me unable to do `dd('a')[0]` to directly reference the first matching DOM element. This is a functionality that I find really useful in jQ, so I'd try to keep it. (To my knowledge) the `return` works similarly to what jQ does, but jQ somehow adds those custom methods to the array, too. https://j11y.io/jquery/#v=git&fn=init Has the source for the jQ constructor method, but sadly I don't quite get how they're doing it. – comesuccingfuccslot Jun 08 '17 at 15:38
  • 1
    @user6003859 Note that you can access node `a` with `a[0]` in this implementation. Also you have length property available in var `a`. Your for loop is making it behave like array. – Pankaj Shukla Jun 08 '17 at 16:19
  • Oh, dang. My bad. I guess that solves my problems for now, then. Thank you. – comesuccingfuccslot Jun 08 '17 at 16:30
  • @user6003859 You unselected my answer, right? May I know the reason? – Pankaj Shukla Jun 08 '17 at 19:40
  • Yes, sorry for that, again. I've explained why I did so in the answer I submitted. – comesuccingfuccslot Jun 08 '17 at 19:41
1

You're mixing classes and object factories. You can't have it both ways.

Either you do (jQuery style):

function dd(selector) {
    return {
        addClass: function () {/**/}
    };
}

var a = dd('[href="#!"]');
a.addClass('asd');

Or you do:

function dd(selector) {
    this.selector = selector;
}
dd.prototype.addClass = function () {/**/};

var a = new dd('[href="#!"]');
a.addClass('asd');

Note that this doesn't automatically make a instanceof Array like jQuery does. You need some black magic to do that.

Halcyon
  • 57,230
  • 10
  • 89
  • 128
  • `function dd(selector) { if(!(this instanceof dd)) { return new dd(selector); }` this is an example of mixing classes and factories, javascript supports this behaviour. – Hitmands Jun 09 '17 at 11:04