10 Tips for Writing JavaScript without jQuery
jQuery is a great library. It came to be around the time when IE6 was the number one browser. Back then, there were quirks and differences that were tedious to work around and jQuery was the perfect tool for writing cross browser code.
Today, however, web browsers have advanced a great deal from those days. We can comfortably use all features provided by ES5, and we have at our disposal awesome HTML5 APIs that make working with the DOM so much nicer. Developers are now at a position where they can choose to leave out jQuery for some projects, and still retain their productivity.
Don't get me wrong - jQuery is still a wonderful library and most often than not you will be better off using it. However, for smaller things like simple pages with limited JS interactions, browser extensions and mobile sites, you can use vanilla JS. Here are 10 tips that will help you in your endeavor.
1. Listening for Document Ready
The first thing you do when writing jQuery, is wrapping your code in a $(document).ready()
call, so that you know when the DOM is ready for manipulation. Without jQuery, we have the DOMContentLoaded event. Here is how it is used:
// Add an event listener of DOMContentLoaded to the whole document and call an anonymous function. // You can then wrap your code in that function's brackets // and it will execute once loading is complete. document.addEventListener('DOMContentLoaded', function () { // Our hawaiian greeting is displayed as soon as the page loads, console.log('Aloha'); });
2. Selecting elements
Once upon a time, we could only select elements by id, class and tag name, and jQuery was a life-saver with its smart css-like selectors. Browsers have caught on since, and introduced two imortant APIs - querySelector
and querySelectorAll:
// We can use document.querySelector to get the first element that matches a certain criteria. // It's only argument is a string containing one or more CSS selectors. var lochNess = document.querySelector(".monsters"); console.log("It's from Scotland - " + lochNess.textContent); // We can also get all elements of a certain type or class by using document.querySelectorAll. // This returns a NodeList of all the elements that fit our criteria. var scary = document.querySelectorAll(".monsters"); console.log("Hide and seek champions: "); for (var i = 0; i < scary.length; i++) { console.log(scary[i].innerHTML); }
<ul> <li class="monsters">Nessy</li> <li class="monsters">Big foot</li> <li class="monsters">La chupacabra</li> </ul>
3. Attaching and removing event listeners
Listening for events is a fundamental part of building a web application. There used to be two major camps that differed in how this was done - IE and the rest. But today we just use addEventListener
:
var btn = document.querySelectorAll("button"), list = document.querySelector("ul"); // We call the addEventListener method on our desired event target(in this case a button). // This will start a listener that will wait until a click is generated on the element. btn[0].addEventListener("click", function () { // When this button is clicked we want to enable zooming of our list. // To do this we add an event listener to our list itself, // so when the cursor hovers it, the enlarge function gets called. list.addEventListener("mouseover", enlarge); }); // To disable the zooming we can simply use removeEventListener. btn[1].addEventListener("click", function () { // Removing event listeners doesn't work on anonymous functions, so always use a named one. list.removeEventListener("mouseover", enlarge); }); // Let's create our enlarge function. var enlarge = function () { // Add class zoomed to the unordered list. list.classList.add("zoomed"); // When the cursor leaves the list return to normal size by removing the class. list.addEventListener("mouseout", function () { list.classList.remove("zoomed") }); }; // Now we want to be able to color the names by clicking them. // When a 'click' is registered on one of the list entries it should change its color to green. // Thanks to event delegation we can actually add an event listener to the whole parent object. // This way we don't have to add separate event listeners to each <li>. list.addEventListener("click", function (e) { // Make the coloring happen only to the clicked element by taking the target of the event. e.target.classList.add('green'); });
<button>Enable zoom</button> <button>Disable zoom</button> <br><br> Click on any of the names to color them green <ul> <li>Chewbacca</li> <li>Han Solo</li> <li>Luke</li> <li>Boba fett</li> </ul>
.green { color: green; } .zoomed { cursor: pointer; font-size: 23px; }
addEventListener
used to require a third argument (useCapture) but that has been optional for some time. As a result, the code looks even more jQuery-like.
4. Manipulating classes and attributes
Manipulating the class names of an element without jQuery used to be very inconvenient. Not any more, thanks to the classList property. And if you need to manipulate attributes, you have setAttribute.
var btn = document.querySelectorAll("button"), div = document.querySelector("#myDiv"); btn[0].addEventListener("click", function () { // Get any attribute easily. console.log(div.id); }); // Element.classList stores all classes of the element in the form of a DOMTokenList. var classes = div.classList; btn[1].addEventListener("click", function () { console.log(classes); }); btn[2].addEventListener("click", function () { // It supports adding and removing classes. classes.add("red"); }); btn[3].addEventListener("click", function () { // You can also toggle a class on and off classes.toggle("hidden"); });
<div id='myDiv' class="square"></div> <button>Display id</button> <button>Display classes</button> <button>Color red</button> <button>Toggle visibility</button>
.square { width: 100px; height: 100px; margin-bottom: 20px; border: 1px solid grey; border-radius: 5px; } .hidden { visibility: hidden; } .red { background-color: red; }
5. Getting and setting element content
jQuery has the handy text() and html() methods. In their place, you can use the textContent and innerHTML properties, which we've had for a very long time:
var myText = document.querySelector("#myParagraph"), btn = document.querySelectorAll("button"); // We can easily get the text content of a node and all its descendants. var myContent = myText.textContent; console.log("textContent: " + myContent); // When using textContent to alter the text of an element // it deletes the old content and replaces it with new. btn[0].addEventListener('click', function () { myText.textContent = " Koalas are the best animals "; }); // If we want to grab all the HTML in a node (including the tags) we can use innerHTML. var myHtml = myText.innerHTML; console.log("innerHTML: " + myHtml); // To change the html simply supply new content. // Of course we aren't limited to text only this time. btn[1].addEventListener('click', function () { myText.innerHTML = "<button> Penguins are the best animals </button>"; });
<p id="myParagraph"><strong> Which are the best animals? </strong></p> <button>Koalas</button> <br> <button>Penguins</button>
6. Inserting and removing elements
Although jQuery makes it a lot easier, adding and removing DOM elements isn't impossible with plain JavaScript. Here is how to append, remove and replace any element you wish:
var lunch = document.querySelector("#lunch"); // In the HTML tab we have our lunch for today. // Let's say we want to add fries to it. var addFries = function () { // First we have to create our new element and set its content var fries = document.createElement("div"); fries.innerHTML = '<li><h4> Fries </h4></li>'; // After that's done, we can use appendChild to insert it. // This will make our fries appear at the end of the lunch list. lunch.appendChild(fries); }; // Now we want to add cheese both before and after the beef in our burger. var addCheese = function () { var beef = document.querySelector("#Beef"), topSlice = document.createElement("li"), bottomSlice = document.createElement("li"); bottomSlice.innerHTML = topSlice.innerHTML = 'Cheese'; // Inserting the top slice: // Take the parent of the beef (that's the sandwich) and use insertBefore on it. // The first argument to insertBefore is the new element we're gonna add. // The second argument is the node before which the new element is inserted. beef.parentNode.insertBefore(topSlice, beef); // The bottom slice: // We have to use a little trick here! // Supply the next nearest element as the second argument to insertBefore, // that way we can actually insert after the element we want. beef.parentNode.insertBefore(bottomSlice, beef.nextSibling); }; var removePickles = function () { // Finally, we want to get rid of those pickles. Again javascript got us covered! var pickles = document.querySelector("#pickles"); if (pickles) { pickles.parentNode.removeChild(pickles); } }; // Delicious! var btn = document.querySelectorAll("button"); btn[0].addEventListener('click', addFries); btn[1].addEventListener('click', addCheese); btn[2].addEventListener('click', removePickles);
<button>Add fries to lunch</button> <button>Add cheese to sandwich</button> <button>Remove pickles</button> <h3>My Lunch</h3> <ul id="lunch"> <li><h4>My sandwich</h4></li> <li>Bread</li> <li id="pickles">Pickles</li> <li id="Beef">Beef</li> <li>Mayo</li> <li>Bread</li> </ul>
7. Walking the DOM tree
As every true JS ninja knows, there is a lot of power hidden in the DOM. Compared to jQuery, plain DOM APIs offer limited functionality for selecting ancestors or siblings. However, there are still plenty of things you can do to travel across the tree.
var snakes = document.querySelector('#snakes'), birds = document.querySelector('#birds'); snakes.addEventListener('click', function (e) { // To access the parent of a certain element in the DOM tree, we use the parentNode method. var parent = e.target.parentNode; console.log("Parent: " + parent.id); // For the opposite, calling the .children method gets all child elements of the selected object. console.log("Children: "); var children = e.target.children; // This returns a HTMLCollection (a type of array), so we have to iterate to access every child's content. for (var i = 0; i < children.length; i++) { console.log(children[i].textContent); } }); birds.addEventListener('click', function (e) { // Getting the nearest sibling to our element is self-explanatory. var previous = e.target.previousElementSibling; if (previous) { console.log("Previous sibling: " + previous.textContent); } var next = e.target.nextElementSibling; if (next) { console.log("Next sibling: " + next.textContent); } // However, to acquire all the siblings of a node is a bit more complex. // We have to take all of its parent's children and then exclude the original element. // This is done by using filter and calling a function that checks every child one by one. console.log("All siblings: "); Array.prototype.filter.call(e.target.parentNode.children, function (child) { if (child !== e.target) { console.log(child.textContent); } }); });
Click on the objects to see their parent and children elements <div id="snakes"> Snakes <ul id="venomous"> Venomous <li>Cobra</li> <li>Rattlesnake</li> </ul> <ul id="non-venomous"> Non venomous <li>Python</li> <li>Anaconda</li> </ul> </div> Click on any of the birds to see its siblings <div> Birds <ul id="birds"> <li>Flamingo</li> <li>Seagull</li> <li>Raven</li> <li>Dodo</li> </ul> </div>
div { color: white; background-color: #93d0ea; font-family: sans-serif; width: 180px; text-align: center; padding: 10px; margin: 5px; }
8. Looping over arrays
Some of the utility methods that jQuery provides are available with the ES5 standard. For iterating arrays, we can use forEach and map instead of their jQuery versions - each()
and map()
. Just be careful for the differences in arguments and default this value in the callbacks.
var ninjaTurtles = ["Donatello", "Leonardo", "Michelangelo", "Raphael"]; // ForEach automatically iterates through an array. ninjaTurtles.forEach(function (entry) { console.log(entry); }); // The map method calls a function on every element of an array and creates a new array with the results. var lovesPizza = ninjaTurtles.map(function (entry) { return entry.concat(" loves pizza!"); }); console.log(lovesPizza);
9. Animations
jQuery's animate method is superior to anything that you could glue together by yourself, and if you need complex scriptable animations in your application you should still stick with it. But thanks to all the wonders of CSS3, some of the simple cases can be handled with a lightweight library like Animate.css, which enables you to trigger animations by adding or removing class names to elements.
var btn = document.querySelectorAll("button"), circle = document.querySelector("#circle"); // First, we have to add a class of animated to our object, so the library can recognize it. circle.classList.add('animated'); // We iterate over all of our buttons and add event listeners to each one. for (var i = 0; i < btn.length; i++) { // Define an anonymous function here, to make it possible to use the i variable. (function (i) { btn[i].addEventListener('click', function () { // To start an animation you just have to add a specific class to the object. // In our case we stored the classes' names in the data-animation attribute of each button. var animation = btn[i].getAttribute('data-animation'); circle.classList.add(animation); // To make it work more then once we have to remove the class after the animation is complete. window.setTimeout(function () { circle.classList.remove(animation); }, 1000); }); }(i)); }
<button data-animation="bounce">Bounce</button> <button data-animation="pulse">Pulse</button> <button data-animation="fadeInLeftBig">Fade in</button> <button data-animation="fadeOutRightBig">Fade out</button> <button data-animation="flip">Flip</button> <div id="circle"></div>
body { text-align: center; } #circle { border-radius: 50%; margin: 50px auto; width: 50px; height: 50px; background-color: #93d0ea; }
10. AJAX
AJAX was another technology that used to be a cross-browser mess. The good news is that we can now use the same code everywhere. The bad news though, is that it is still cumbersome to instantiate and send AJAX requests with XMLHttpRequest, so it is best left to a library. But you don't need to include the whole of jQuery only for that. You can use one of the numerous lightweight libraries that are available. Here is an example constructing an AJAX request directly, and by using the small reqwest lib:
// This simple example logs the body of our url (a html file) in the console. // It's possible to do a manual GET request but it is somewhat a tedious task. var request = new XMLHttpRequest(); request.open('GET', 'https://tutorialzine.com/misc/files/my_url.html', true); request.onload = function (e) { if (request.readyState === 4) { // Check if the get was successful. if (request.status === 200) { console.log(request.responseText); } else { console.error(request.statusText); } } }; // Catch errors: request.onerror = function (e) { console.error(request.statusText); }; request.send(null); // Using a small library, such as Reqwest, can make your job much easier. reqwest({ url: 'https://tutorialzine.com/misc/files/my_url.html', method: 'get', error: function (err) { }, success: function (resp) { console.log(resp); } });
Conclusion
Striving for minimal, zero bloat web pages is a worthy goal that will pay itself in faster load times and better user experience. You should be careful though - nobody would win if you reinvent the wheels that jQuery has given you. Don't sacrifice good development practices only to bring the byte count down. But there are plenty of places where today's tips are perfectly applicable. Try going vanilla next time, it might be all that you need!
Bootstrap Studio
The revolutionary web design tool for creating responsive websites and apps.
Learn more
Great, thanks!
Css3 animation is great and all, but it doesn't work across all browsers. which is sad :(
Important to check http://caniuse.com/ if supporting older browsers. For example, classList is only available in IE10+, http://caniuse.com/classlist
Obviously depends on which browsers you're supporting, but still why reinvent the wheel?
You might <i>not</i> need jQuery but you also <a href="http://youmightnotnotneedjquery.com/" rel="nofollow">might not not need jQuery</a>
Please checkout https://github.com/szarouski/lodash.dom-traverse -> dom traversing with only 1.28kb when minified and gzipped (requires lodash).
Serves as a quick cheatsheet. thanks
Don't use Array.forEach(), its like 95% slower than for().
You can test yourself: http://jsperf.com/fast-array-foreach
Its not about how you can use native js equivalent to jQuery, its about how you can support and handle different browser's issues. Good article though.
All those articles about how to not use jQuery fail in one very important detail: what about any browser outside the very latest Chrome, Opera and Firefox? What about the many users of IE8 in companies? I stopped reading after your first idea, because it won't work in IE8.
That's what jQuery is for: making JavaScript cross-browser scriptable. And as the jQuery-project has told us many times, the most workarounds aren't for old IE, they are for the variety of Webkit-browsers.
So please get used to the fact that JS-implementation in all browsers is different, one could say it is broken. And although it might be too juch for just an accordion and a lightbox to load jQuery and two plugins, it is the right thing to do. In this way we care about the scripts working correct in all browsers.
And if you want to do something for your performance, go and crush the images of your page. 65% is sufficient for most jpgs and will save much more than getting rid of jQuery.
Yes, your are right - we should strive for our sites and apps to work cross browser. But things are much better now than they used to be and browser quirks are not very common, if you can afford to drop support for old browsers.
jQuery brings more than just fixes for browser bugs, like a sane DOM API, easy event handling and various time saving utilities like AJAX and animation, so it is a good library to have in your toolbox. Sometimes though, a quick querySelector is all you need, and this article is for those cases.
Awesome!
Great write-up. For DOM content ready, I prefer to use this smaller one by Dustin Diaz
function ready(cb) {
/in/.test(document.readyState) ? setTimeout(ready.bind(null, cb), 9) : cb();
}
ready(function(){ console.log("The DOM is ready"); });
Just a quick FYI about animations...
You really shouldn't be using jQuery if "you need complex scriptable animations in your application". In fact you probably shouldn't be using jQuery for animations virtually ever.
For short, distinct animations you should definitely just stick with CSS3 animations (and you're probably better off doing it vanilla without an additional framework for it) but for sophisticated and/or "narrative" animations, you should actually consider bringing in a more modern and MUCH more efficient js animation engine like GreenSock(GSAP), Velocity.js, or Famo.us. Any of these options are massively more powerful and capable than jQuery for that purpose.
Thank you this is so good. I hate jQuery bloat.
Excellent tips ... thank you so much for this.
I like to write my own JS without jQuery ... experience has proven (over 15 years) that doing your own code means you don't have to scramble to "fix" the code when a library or utility is found to have a vulnerability or suddenly needs upgrading for whatever reason.
You haven't noted that jQuery functions are not as simple as you think.
i.e the document.ready function -> http://stackoverflow.com/questions/799981/document-ready-equivalent-without-jquery
EXCELLENT ARTICLE! these are just the type of things we've forgotten to do over the years, since oversized API's have taken the real code out of the equation...
Thanks!
Having no library dependencies is a benefit of not using jQuery as well. You can do anything with native Javascript that you can do with jQuery if you are a seasoned Javascript programmer. Sure it will definitely take more code but I like to write self documenting code and jQuery seems to encourage programmer to write more concise less understandable code... somewhat an antithesis to self documenting code.
...just my 2 cents as to another reason to not use jQuery
Great vanilla overview, thanks a lot!
Hi, Am I getting something wrong, soc I tried running the samples and none worked...Would appreciate the feedback