0

One can create shorthand for document.querySelector with

const $ = document.querySelector.bind(document);
const $$ = document.querySelectorAll.bind(document);

so now let a = $('a') and let a = document.querySelector('a') are equivalent.

Is there a way to create shorthand for the querySelector method itself?
I.e. to make let a = element.shortHand(args) and let a = element.querySelector(args) to be equivalent for any (unknown in advance) element.

Edit: Since people are telling that doing the above is a bad idea, there is another question: How to make$ $$ selectors like the one in the Chrome DevTools, which accept the root element as second parameter?
I.e. to make let a = $('a',element) and let a = element.querySelector('a') to be equivalent.

klm123
  • 12,105
  • 14
  • 57
  • 95
  • 2
    Sure: create a method next to `Element.prototype.querySelector`. Absolutely not recommended. – Sebastian Simon Nov 02 '21 at 23:09
  • @SebastianSimon, why it is not recommended? – klm123 Nov 02 '21 at 23:15
  • 1
    [Why is extending native objects a bad practice?](/q/14034180/4642212) – Sebastian Simon Nov 02 '21 at 23:25
  • 1
    By the way, note that some consoles have [Web Console Helpers](//developer.mozilla.org/docs/Tools/Web_Console/Helpers) like `$` and `$$` already which accept two arguments: a selector and a root element. – Sebastian Simon Nov 02 '21 at 23:26
  • 1
    You can create your own `$` by `const $ = (selector, root = document) => root.querySelector(selector);`. Pretty simple. – Sebastian Simon Nov 02 '21 at 23:36
  • @SebastianSimon, won't there be problems with 'this' element or other differences? – klm123 Nov 02 '21 at 23:38
  • No. Problems with `this` context don’t apply here. The only potential error is if `root` is neither a Document nor an Element, but this can be easily checked with an `if` statement and `instanceof`. – Sebastian Simon Nov 02 '21 at 23:44
  • @SebastianSimon, I see. Thank you. Do you have any idea why there are so complex solutions for 1 parameter functions on the internet? why everybody show "$ = document.querySelector.bind(document)" and noone the straightforward "$ = selector => document.querySelector(selector)"? – klm123 Nov 03 '21 at 06:40
  • 1
    There are [questions](/q/13383886/4642212) with both suggestions. I don’t know why the `.bind` approach is more popular, but both of them should work identically in most cases. I believe, if `document` was redefined, then the `.bind` approach would still work, whereas the arrow function would read `querySelector` from the new `document` object, which may not have the method; also the arrow function needs to resolve `document` every time, but this is such a minor optimization… and redefining `document` (in global scope) is only possible in ES6 modules, which shouldn’t “randomly” happen anyway. – Sebastian Simon Nov 03 '21 at 13:50

1 Answers1

2

Here are some options:

Add Method to Element.prototype

Element.prototype.shortHand = Element.prototype.querySelector

This "monkey-patches" the Element class in the DOM itself and adds this function on all elements in DOM, which is just a copy of the querySelector function.

This is very discouraged. It's bad for performance and it is bad in case browsers decide to add more functions in the future that conflicts with your function. But if you're just playing around and not shipping this code it should be fine.

Mini jQuery

If you're looking to create your own mini jQuery, you can also do something like this:

class MiniJQuery {
  constructor(el) {
    this.el = el;
  }
  shortHand(...query) {
    return this.el.querySelector(...query);
  }
  // ... put any other functions you want to use
}
const $ = (queryOrElement) => {
  if (typeof queryOrElement === 'string') {
    return document.querySelector(queryOrElement);
  }
  return new MiniJQuery(queryOrElement);
}

// Now you can:
const a = $(element).shortHand(args);
// which is equivalent to
const a = element.querySelector(args);

This is a much safer approach and not problematic. I don't think this adds much value as you can just type the slightly longer method name, but you could add more interesting methods on your class to make it worthwhile.

Proxy

Very similar to the approach above, but you can use a Proxy instead of the MinijQuery class to "forward" unknown methods to the element itself. This means that $(element) will have all the methods that element itself has.

Example:


const handler = {
  get: function (target, prop, receiver) {
    if (prop === "shortHand") {
      return target.querySelector.bind(target);
    }
    const retVal = Reflect.get(...arguments);
    
    // Bind methods to the element.
    return typeof retVal === 'function'
      ? retVal.bind(target)
      : retVal;
  },
};

const $ = (queryOrElement) => {
  if (typeof queryOrElement === 'string') {
    return document.querySelector(queryOrElement);
  }
  // You can add all sorts of custom function handlers here.
  return new Proxy(queryOrElement, handler);
}

$('div') // gets divs
$(element).shortHand(...)
// works the same as element.querySelector
// But the HTMLElement methods still work too:
$(element).querySelector
$(element).querySelectorAll
$(element).className
// ...

Read More Here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy

Naman Goel
  • 1,525
  • 11
  • 16
  • Thank you. Would you mind answering the additional question, which arouse? to make let a = $('a', element) and let a = element.querySelector('a') to be equivalent. SebastianSimon has kind of answered it in the comments, but I want to be sure, since everybody in the internet articles show "$ = document.querySelector.bind(document)" as the solution and noone tells to use straightforward "$ = selector => document.querySelector(selector)" – klm123 Nov 03 '21 at 06:42
  • So you're asking about the Proxies solution. Updating my original answer. – Naman Goel Nov 03 '21 at 07:07
  • I don't quite understand the proxy solution. How to use such a proxy? How does $('a',element) is supposed to work, if $takes only one argument? element.shortHand('a') doesn't seems to be working either. P.S. the second part of the question is fully explained now in the "Edit:" part of my original post. – klm123 Nov 03 '21 at 08:14
  • My bad, I made a mistake while writing the code example. Updated it to be correct now. – Naman Goel Nov 08 '21 at 17:51
  • You're right about the `.bind`. I missed that for sure. You're also right that in my example code `$(selector).shortHand` doesn't exist. I don't think the question was specifically looking for that, and I was trying the code simpler to understand rather than give a complete working solution. – Naman Goel Nov 09 '21 at 05:36