简体   繁体   中英

How to extend prototype of an object and have the same function in global scope as well?

I need to setup shorthands for several DOM accessor methods, namely - getElementsByClassName ( qCls ), getElementById ( qId ), querySelector ( q ), querySelectorAll ( Q ) - such that they are available:

  1. in the global scope: I should be able to write q(selector) as a shorthand for document.querySelector(selector) throughout my code (in any scope or context).
  2. as a property on DOM elements: I should be able to write paragraph.q(selector) as a shorthand for paragraph.querySelector(selector) .

I know the manual way to do this is:

 window.Q = function(sel){ console.dir(document.querySelectorAll(sel)); }; Node.prototype.Q = function(sel){ console.dir(this.querySelectorAll(sel)); }; // TEST var p = document.querySelector("p"); Q("a"); // should have two elements pQ("a"); // should have one element 
 <a>1</a> <p><a>2</a></p> 

But this has duplicacy - repeating exactly the same function again with only a minor difference ( document vs this ) - so I intend to eliminate that duplicacy . 1

Now, what I think would work to achieve this is a pattern like:

window.Q = helperFunc(someFlag);
Node.prototype.Q = helperFunc(someOtherFlag);

where the helperFunc handles the logic for the return value, based on the flags. Based on this, I wrote the following:

function outputQFunc(context) {
    return function(selector) {
        console.dir(context.querySelectorAll("a"));
    };
}

window.Q = outputQFunc(document);
Node.prototype.Q = outputQFunc(this);

However, this has the obvious downside that the this context for Node.prototype.Q permanently points to the global scope, instead of the node which called this function. I could instead eliminate the context :

function outputQFunc() {
    return function(selector) {
        console.dir(this.querySelectorAll("a"));
    };
}

but then the this does not point to the document in case of the window.Q .

So, my question is, how to achieve this?


Notes:

  1. While this duplicacy may sound trivial (it's only a single line!), I have actually shortened the code for MVCE. My actual code has to apply some filter s and stuff to the result of doc.qsAll before return ing it. In such cases, the duplicacy magnifies.

Once my query got resolved, if anyone is interested in the final code I reached, this is it , which I personally find very DRY and extensible:

var DOM_HELPERS = {
  /**
   * short hand for document.querySelector
   * @param {string} selector selector to match element
   */
  q: function(selector) {
    return this.querySelector(selector);
  },
  /**
   * short hand for document.querySelectorAll
   * @param {string} selector selector to match elements
   */
  Q: function(selector) {
    return this.querySelectorAll(selector);
  },
  /**
   * short hand for document.getElementById
   * @param {string} id selector to match element
   */
  qId: function(id) {
    return this.getElementById(id);
  },
  /**
   * short hand for document.getElementsByClassName
   * @param {string} cls selector to match elements
   */
  qCls: function(cls) {
    return this.getElementsByClassName(cls);
  }
};

for (var i = 0, funcs = Object.keys(DOM_HELPERS), len = funcs.length, funcName, func; i < len; i++) {
  funcName = funcs[i];
  func = DOM_HELPERS[funcName];
  window[funcName] = func.bind(document);
  Node.prototype[funcName] = func;
}

Use bind on your helper function to specify a custom this context for window.Q (and use the default this otherwise, which will point to the proper calling context when called on a node):

 function qSA(sel) { console.dir(this.querySelectorAll(sel)); } window.Q = qSA.bind(document); Node.prototype.Q = qSA; // TEST var p = document.querySelector("p"); Q("a"); // should have two elements pQ("a"); // should have one element 
 <a>1</a> <p><a>2</a></p> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM