For example for this.parentNode I would like to just write this.p or instead of
document.getElementById('someid') just write document.g('someid'). Of course that are simple examples, I just want to know what is the correct way to do it.
(I know I can use jQuery or Prototype, but I'd like to learn how it is really done in JS)
Although you can prototype on the HTMLElement in many browsers - Internet Explorer (6,7,8) is NOT one of them. AFAIK, IE9 does support this (though I haven't tested it).
For browsers that do handle it, you can do:
HTMLElement.prototype.doHello = function(thing){
console.log(this + ' says: ' + thing)
}
document.body.doHello('hello')
I would strongly suggest not attempting to do this, for a few reasons:
Browser compatibility. While it is possible in several browsers, it isn't possible in IE <= 8.
DOM elements are host objects. Host objects (i.e. those provided by the environment that aren't native JavaScript objects) have no obligation to play by the same rules as native JavaScript objects and other than specified DOM behaviour can essentially do what they like. So, even if some browsers provide an HTMLElement prototype and allow you to augment it, there's no guarantee that it will work as you expect.
Compatibility with other code in your page. If any other code in your page (such as Prototype) messes with the HTMLElement prototype, you risk naming collisions and hard-to-detect bugs.
Instead, I would suggest creating wrapper objects around DOM nodes as jQuery, YUI and other libraries do.
Kangax has written a good article on DOM extensibility, covering all these points and more.
In a word, don't. It is best not to modify objects you don't own.
This is particularly true for HTMLElement, which you cannot modify in some browsers.
This article from perfectionkills.com will probably give you some insight into how it's done, and why you shouldn't do it.
(By the way, jQuery doesn't extend DOM elements. They use DOM wrappers instead.)
This might not be what you are looking for if you want to wrap a global object like document, but you can get a similar effect with custom-elements [1] [2] to create your own HTMLElement-like nodes.
create custom-element
add method to custom-element class
you can call the method
export class CustomElementInput extends HTMLElement {
log(){
alert("log")
}
// you can even overwrite methods like so
remove(){
alert("removing this node")
super.remove()
}
}
customElements.define("custom-element-input", CustomElementInput)
// somewhere else...
// in your HTML something like:
// <custom-element-input></custom-element-input>
const el = document.querySelector("custom-element-input")
el.log() // creates alert()
Related
For example for this.parentNode I would like to just write this.p or instead of
document.getElementById('someid') just write document.g('someid'). Of course that are simple examples, I just want to know what is the correct way to do it.
(I know I can use jQuery or Prototype, but I'd like to learn how it is really done in JS)
Although you can prototype on the HTMLElement in many browsers - Internet Explorer (6,7,8) is NOT one of them. AFAIK, IE9 does support this (though I haven't tested it).
For browsers that do handle it, you can do:
HTMLElement.prototype.doHello = function(thing){
console.log(this + ' says: ' + thing)
}
document.body.doHello('hello')
I would strongly suggest not attempting to do this, for a few reasons:
Browser compatibility. While it is possible in several browsers, it isn't possible in IE <= 8.
DOM elements are host objects. Host objects (i.e. those provided by the environment that aren't native JavaScript objects) have no obligation to play by the same rules as native JavaScript objects and other than specified DOM behaviour can essentially do what they like. So, even if some browsers provide an HTMLElement prototype and allow you to augment it, there's no guarantee that it will work as you expect.
Compatibility with other code in your page. If any other code in your page (such as Prototype) messes with the HTMLElement prototype, you risk naming collisions and hard-to-detect bugs.
Instead, I would suggest creating wrapper objects around DOM nodes as jQuery, YUI and other libraries do.
Kangax has written a good article on DOM extensibility, covering all these points and more.
In a word, don't. It is best not to modify objects you don't own.
This is particularly true for HTMLElement, which you cannot modify in some browsers.
This article from perfectionkills.com will probably give you some insight into how it's done, and why you shouldn't do it.
(By the way, jQuery doesn't extend DOM elements. They use DOM wrappers instead.)
This might not be what you are looking for if you want to wrap a global object like document, but you can get a similar effect with custom-elements [1] [2] to create your own HTMLElement-like nodes.
create custom-element
add method to custom-element class
you can call the method
export class CustomElementInput extends HTMLElement {
log(){
alert("log")
}
// you can even overwrite methods like so
remove(){
alert("removing this node")
super.remove()
}
}
customElements.define("custom-element-input", CustomElementInput)
// somewhere else...
// in your HTML something like:
// <custom-element-input></custom-element-input>
const el = document.querySelector("custom-element-input")
el.log() // creates alert()
So basically I would like to extend a certain type of DOM elements by the following code:
var element = document.createElement("div");
var proto = Object.create(HTMLDivElement.prototype);
proto.newMethod = function() {console.log("Good.");};
proto.newConst = Math.PI / 2;
element.__proto__ = proto;
This code works in Chrome, Firefox and IE11 (IE10 not tested, but it will probably work), but I'm not sure whether it is proper JavaScript and whether it will continue to work in the future, because anyway this code is hacking DOM elements which is partially outside JavaScript. Could someone give explanation on how it works? I don't fully understand that, and I need to know if this method is robust. Thanks.
OK, to make things clearer, I know I should use Object.create() to specify prototype, but the real problem is that element objects are special and it's impossible to do that. The above code is more like a workaround, and this is why I'm asking this question.
Google's Polymer mutates __proto__ of DOM objects (code, line 259):
function implement(element, definition) {
if (Object.__proto__) {
element.__proto__ = definition.prototype;
} else {
customMixin(element, definition.prototype, definition.native);
element.__proto__ = definition.prototype;
}
}
So, should I trust this method because Google uses it?
From Mozilla Developer Network:
The __proto__ property is deprecated and should not be used. Object.getPrototypeOf should be used instead of the __proto__ getter to determine the [[Prototype]] of an object. Mutating the [[Prototype]] of an object, no matter how this is accomplished, is strongly discouraged, because it is very slow and unavoidably slows down subsequent execution in modern JavaScript implementations. However, Object.setPrototypeOf is provided in ES6 as a very-slightly-preferred alternative to the __proto__ setter.
In general, it is a bad practice to modify native prototypes like Array, String and even HTMLElement, details are described here, but if you control everything in the current context you can modify the prototypes by adding, on your own risk, some additional functional to achieve what you want. If you can guarantee that your code is not in conflict with some other code and the performance footprint is negligible then you are free to choose your path.
Your approach:
SomeHTMLElementInstance.__proto__ = newPrototype;
// or a general case like:
SomeHTMLElementPrototypeConstructor.prototype.newMethod = function () {
// Do something here
}
Recommended approach:
var SomeElementWrapper = function (someParams) {
this.container = document.createElement('SomeHTMLElement');
}
SomeElementWrapper.prototype.someMethod = function () {
// Do something with this.container without modifying its prototype
}
In jQuery you can call a function like this for example:
$("id").someFunction();
Now after looking at the codebase of jQuery it looks like the object being created by using $ has its protoype modified to return the function which was added via .fn, in my application I would like the same syntax only without requiring jQuery.
Another example of this kind of behavior is some of Javascript's in-built methods such as: .replace, .toLowerCase, .split, .toString, etc. I understand some of those listed methods are on the String.prototype object and extending in-built objects is bad practice (so I hear).
How am I able to add a function to the prototype of every "String" or "Object" that gets assigned. The reason I am doing this is I am trying to create a cross-browser implementation of attaching events without having to do if statements all of the time.
So instead of needing to go: if (el.addEventListener) or if (el.attachEvent) I would like to be able to go el.bindEvent which behind the scenes would be a prototype method behind the scenes that would do all of the checking for event binding, etc.
My advanced JS knowledge when it comes to assigning prototype methods and whatnot isn't that great, so your help in understanding and correcting anything I've said is appreciated.
Extending the prototype of DOM elements is an even worse idea than extending built-in objects †. Please have a look at the article "What's wrong with extending the DOM".
What jQuery provides is a simple wrapper around the DOM API, which is ok. The problem with extending the DOM is that
they are host objects, and might behave differently than how objects are defined in the ECMAScript spec and
older IE versions don't even expose the prototype of DOM nodes.
The reason I am doing this is I am trying to create a cross-browser implementation of attaching events without having to do if statements all of the time.
This alone is no reason to extend the DOM. You can define your own function which binds event handlers and you even have to test whether addEventListener or attachEvent exists only once on page load. Such a function could look like this:
var bindEvent = (function() {
if (document.addEventListener) {
return function(element, event, handler) {
element.addEventListener(event, handler, false);
};
}
else {
return function(element, event, handler) {
element.attachEvent("on" + event, function() {
handler.call(element, window.event);
}
};
}
}());
You can put a lot of normalization into such a function. For example, when using attachEvent, this does normally not refer to the element the handler is bound to (it refers to window), unlike with addEventListener. Also, as you might know, the event object is not passed as argument to the handler in IE. Both of these issues have been solved through the line:
handler.call(element, window.event);
(the disadvantage is that you cannot simply remove the handler, since you didn't bind handler directly, but these problems can be solved as well).
You can find more information about these browser differences and more in the excellent articles at quirksmode.org.
†: Since browsers provide Object.defineProperty and hence the possibility to mark properties as non-enumerable, extending built-in objects is not as bad anymore, but if libraries start using this, the chance of name collisions, method overriding and incompatibilities gets higher.
For your own code it should be ok. DOM objects should still be a tabu.
If I understood correctly, you want to know how to add methods to the built in javascript objects, so:
myObject.prototype.myMethod = function() {
// whatever
}
so, by example:
String.prototype.splice = function() {
// splice function for every string
}
using jQuery it is possible to do something like this: $("div")[5].animate()
This seems to me like the developers in a way extended the HTMLElement using prototype.
My question is now: How did they do this? Since HTMLElement.prototype is not working in IE for example I wonder if there is a cross browser method to prototype HTML elements.
Thanks!
You can't extend those things in IE; IE just does not implement the DOM interface that way. That's why Prototype forces you to "wrap" elements that you want to manipulate with those additional methods.
the jQuery factory function (jQuery() or $()) does not return a DOM node.
The jQuery factory function returns a new jQuery.init instance which acts very similar to an array. Instead of extending any DOM node's prototype, more functions are simply added to jQuery.fn
If a function is chained on a jQuery selector, it typically applies to all the elements contained within the jQuery.init instance.
I highly recommend reading through the commented jQuery source so that you can see exactly what's going on behind-the-scenes.
Note that $('#book') !== document.getElementById('book').
The first one is a jQuery object which refers to a dom element and can be extended, while the second one is actually a dom element.
Adding methods to native JavaScript objects like Object, Function, Array, String, etc considered as bad practice.
But I could not understand why?
Can some body shed light on this?
Thanks in advance.
Because you might happen to use a library that defined a function with the same name, but working another way.
By overriding it, you break the other's library's behaviour, and then you scratch your head in debug mode.
Edit
If you really want to add a method with a very unpleasant name like prependMyCompanyName(...) to the String prototype, I think it's pretty much risk-free from an overriding point of view. But I hope for you that you won't have to type it too often...
Best way to do it is still, in my humble opinion, to define for example, a MyCompanyUtils object (you can find a shortcut like $Utils), and make it have a prepend(str,...) method.
The two big reasons in my opinion are that:
It makes your code harder to read. You write code once and read it many number of times more. If your code eventually falls into the hands of another person he may not immediately know that all of your Objects have a .to_whatever method.
It causes the possibility of namespace conflicts. If you try to put your library which overrides Object.prototype into another library, it may cause issues with other people doing the same thing.
There is also the effect that augmenting the Object prototype has on for...in loops to consider:
Object.prototype.foo = 1;
var obj = {
bar: 2
};
for (var i in obj) {
window.alert(i);
}
// Alerts both "foo" and "bar"