Create your first JavaScript Library

Source: Internet
Author: User

Create your first JavaScript Library
We almost use the JavaScript library every day. When you get started, using jQuery is a wonderful thing, mainly because of its DOM operations. First, DOM may be a relatively difficult thing for beginners. Second, we can hardly consider cross-browser compatibility. In this tutorial, we will try to implement a very simple library from the beginning. Yes, it is very interesting, but before you are happy, Let me declare a few points: this will not be a full-featured library. There are many methods to write, but it is not jQuery. We will do our best to make you feel the various problems you encounter when creating a database. We will not completely solve the compatibility issues of all browsers. The code we write supports IE8 +, Firefox 5 +, Opera 10 +, Chrome and Safari. We will not cover all possibilities of using our library. For example, our append and prepend methods are valid only when you pass in an instance of our database. They do not support native DOM nodes or node sets. Step 1: Create the Library template file Creating the Library Boilerplate. We start with some encapsulated code and it will include our entire Library. It is a commonly used function expression for immediate execution. Copy the code window. dome = (function () {function Dome (els) {}var dome = {get: function (selector) {}}; return dome ;}()); copy the code as you can see, we call our library Dome, because it is mainly a library for DOM, yes, it is not complete. Now we have done two things. First, we define a function that will eventually instantiate the constructor of our database. These objects will encapsulate the elements we select or create. Next, we created the dome object, which is our actual library object. You can see that it is returned at the end. It has an empty get function, which we will use to select elements from the page. So, let's fill in its code now. Step 2: get the dome. get function to input a parameter, but it can be used in several situations. If it is a string, we assume it is a CSS selector; but we can also pass in a single DOM node or a NodeList. Copy the code get: function (selector) {var els; if (typeof selector = "string") {els = document. querySelectorAll (selector);} else if (selector. length) {els = selector;} else {els = [selector];} return new Dome (els);} copy the code using document. querySelectorAll to simplify element search: of course there is a browser compatibility problem, but it is OK for our example. If selector is not a string, we will check its length attribute. If it exists, we will know that it is a NodeList; otherwise it is a single element and then we put it in an array. This is why we need to pass the result of calling Dome to an array. You can see that we return a new Dome object. So let's look back at the Dome function and fill it in. Step 3: Create a Dome instance and copy the code function Dome (els) {for (var I = 0; I <els. length; I ++) {this [I] = els [I];} this. length = els. length;} copying the code is really simple: We just traverse the selected elements and attach them to a new object with a digital index. Then we add a length attribute. But what is the key? Why not directly return elements? We encapsulate elements into an object because we want to create methods for this object. These methods allow us to interact with these elements. This is actually a simplified version of jQuery. Therefore, we have returned the Dome object. Let's add some methods on its prototype. I wrote these methods directly in the Dome function. Step 4: add some common tool functions. The first method we need to write is a simple tool function. Because our Dome object can encapsulate multiple DOM elements, almost every method needs to traverse each element; therefore, these tool functions are very convenient. Let's start with a map function: copy the code Dome. prototype. map = function (callback) {var results = [], I = 0; for (; I <this. length; I ++) {results. push (callback. call (this, this [I], I);} return results;}; copy the code. Of course, the map function passes in a single parameter, a callback function. We traverse each item in the array and collect all the content returned by the callback function into the results array. Note how to call the callback function: callback. call (this, this [I], I); in this way, the function will be called in the context of our Dome instance. It accepts two parameters: the current element and the index number. We also want a forEach function. It is indeed very simple: copy the code Dome. prototype. forEach (callback) {this. map (callback); return this;}; the only difference between copying code map and forEach is that map needs to return something, so we can also pass in our callback function to this. map and ignore the returned array. We will return this so that our library supports chained operations. ForEach is often used. Therefore, when we return our this. forEach call to the function, we actually return this. For example, the following method actually returns the same thing: copy the code Dome. prototype. someMethod1 = function (callback) {this. forEach (callback); return this ;}; Dome. prototype. someMethod2 = function (callback) {return this. forEach (callback) ;}; copy the code and mapOne. It's easy to see what this function does, but the question is, why do we need it? It needs something you can call "library philosophy" to explain. A simple "philosophical" detour. If you create a library and only write code, it is not a hard job. But I am working on this project, and I find that the difficult part is to decide how some methods should work. Soon, we will create a text method that returns the text of the selected element. If our Dome object encapsulates several DOM nodes (such as dome. get ("li"), what will it return? If you do something similar in jQuery ($ ("li"). text (), you will get a string that combines the text of all elements. Does it work? I think it's useless, but I don't know what a better response is. In this project, I will return the text of multiple elements in the form of an array. Unless there is only one element in the array, we will return a text string instead of an array with only one element. I think you are most often used to obtain the text of a single element, so we can optimize this situation. However, if you get the text of multiple elements, we will also return something you can operate on. Return to the code. Therefore, the mapOne method simply runs map and then either returns an array or an element in a single element array. If you are not sure about the usage, you will find it later! Copy the code Dome. prototype. mapOne = function (callback) {var m = this. map (callback); return m. length> 1? M: m [0];}; copy code Step 5: process text and HTML. Next, let's add the text method. Just like jQuery, We can input a string to it and set the text of the element, or do not pass parameters to get the text of the element. Copy the code Dome. prototype. text = function (text) {if (typeof text! = "Undefined") {return this. forEach (function (el) {el. innerText = text ;}) ;}else {return this. mapOne (function (el) {return el. innerText ;}) ;}}; copy the code to copy the Dome code. prototype. text = function (text) {if (typeof text! = "Undefined") {return this. forEach (function (el) {el. innerText = text ;}) ;}else {return this. mapOne (function (el) {return el. innerText;}) ;}}; you may also think of copying the code. We need to check the text value to see whether it is set or to get it. Note that if only if (text) is used, the Null String is judged as false. If we are setting values, we will call forEach for the elements and set their innerText attribute to text. If we want to obtain the data, we will return the innerText attribute of the element. Note that we use the mapOne method: if we are processing multiple elements, it will return an array, otherwise it will be a string. The html method is almost the same as text, except that it uses the innerHTML attribute instead of innerText. Copy code dome.prototype.html = function (html) {if (typeof html! = "Undefined") {this. forEach (function (el) {el. innerHTML = html;}); return this;} else {return this. mapOne (function (el) {return el. innerHTML;}) ;}}; copy the code as I said: almost identical. Step 6: Adjust the style. Next, we want to add and delete the style. So let's write an addClass and removeClass method. Our addClass method will receive an array of strings or style names. To do this, we need to check the parameter type. If it is an array, We will traverse it and create a style name string. Otherwise, we simply add a space before the style name so that it will not be mixed with the existing style of the element. Then we traverse the element and append the new style to the end of the className attribute. Copy the code Dome. prototype. addClass = function (classes) {var className = ""; if (typeof classes! = "String") {for (var I = 0; I <classes. length; I ++) {className + = "" + classes [I] ;}} else {className = "" + classes ;}return this. forEach (function (el) {el. className + = className;}) ;}; copy the Code directly, right? How can I delete a style? To keep it simple, you can only delete one style at a time. Copy the code Dome. prototype. removeClass = function (clazz) {return this. forEach (function (el) {var cs = el. className. split (""), I; while (I = cs. indexOf (clazz)>-1) {cs = cs. slice (0, I ). concat (cs. slice (++ I);} el. className = cs. join ("") ;}) ;}; copy the code to each element. className is separated into an array. Then, we use a while loop to remove the style we passed in until cs. indexOf (clazz) returns-1. We do this to deal with more than one special case that the same style appears in an element: we must ensure that it is actually deleted. Once we make sure to delete each style instance, we use spaces to connect each item of the array and set it to el. className. Step 7: fix an IE Bug. The worst browser we are dealing with is IE8. In our small library, there is only one IE bug that needs to be handled. Fortunately, it is very simple. IE8 does not support the Array indexOf method. We use it in removeClass, so let's fix it: copy the Code if (typeof Array. prototype. indexOf! = "Function") {Array. prototype. indexOf = function (item) {for (var I = 0; I <this. length; I ++) {if (this [I] === item) {return I ;}} return-1 ;}}copy the code very easily, this is not a complete implementation (the second parameter is not supported), but it can achieve our goal. Step 8: Adjust attributes now, we want an attr function. This is easy because it is very similar to our text or html method. Like those methods, we can get or set the attribute value: We can pass in the element name and value to set, or we can only pass in the attribute name to get. Copy the code Dome. prototype. attr = function (attr, val) {if (typeof val! = "Undefined") {return this. forEach (function (el) {el. setAttribute (attr, val) ;}) ;}else {return this. mapOne (function (el) {return el. getAttribute (attr) ;}}; copy the Code. If val has a value, we will traverse these elements and set the selected attribute to this value, using the setAttribute method of the element. Otherwise, mapOne returns the property value through the getAttribute method. Step 9: Create elements like many good libraries. We should be able to create new elements. Of course, it is not a good method as a Dome instance, so let's directly mount it to the dome object. Copy the code var dome ={// get method here create: function (tagName, attrs) {}}; copy the code you have seen, we use two parameters: the element name, and attribute value object. Most attributes can be assigned via the attr method, but the two methods can be used for special processing. We use the addClass method to operate the className attribute and the text method to operate the text attribute. Of course, we first need to create elements and Dome objects. The following code copies the entire operation: create: function (tagName, attrs) {var el = new Dome ([document. createElement (tagName)]); if (attrs) {if (attrs. className) {el. addClass (attrs. className); delete attrs. className;} if (attrs. text) {el. text (attrs. text); delete attrs. text;} for (var key in attrs) {if (attrs. hasOwnProperty (key) {el. attr (key, attrs [key]) ;}} return el ;}copy the code to create an element and pass it to a new Dome object. Then we process the attributes. Note that the attributes of className and text must be deleted after the operation. This will avoid being applied as an attribute when we traverse the remaining key values in attrs. Of course, we will return the newly created Dome object. But now we just created a new element. Do we want to insert it into the DOM? Step 10: add the element. Next, we will write the append and prepend methods. These functions are indeed a bit difficult to implement, mainly because there are many usage cases. Here is what we want to do: dome1.append (dome2); dome1.prepend (dome2); usage: we may want to append or prepend a new element to one or more existing elements. multiple existing elements to one or more existing elements. Note: I use "new" to indicate that the element is not in the DOM; the existing element is already in the DOM. Let's copy the code Dome step by step. prototype. append = function (els) {this. forEach (function (parEl, I) {els. forEach (function (childEl) {}) ;}; copy the code and we expect the els parameter to be a Dome object. A complete DOM library can accept a node or nodelist as the parameter, but we do not do so for the time being. We must traverse each of our elements, and in it, we also need to traverse each element we need to append. If we move els to multiple elements, we need to clone them. However, we don't want to clone the node when they are attached for the first time, but we will talk about it later. So let's do this: if (I> 0) {childEl = childEl. cloneNode (true);} This I comes from the outer forEach loop: it is the index of the current parent element. If we do not attach to the first parent element, we will clone the node. In this way, the real node will be placed in the first parent node, and the other parent nodes will get a copy. This is easy to use, because the input Dome object will only have the original node. Therefore, if we only append a single element to a single element, all nodes used will be part of their own Dome objects. Finally, we can add the element parEl. appendChild (childEl); so, the Code Dome is copied in summary. prototype. append = function (els) {return this. forEach (function (parEl, I) {els. forEach (function (childEl) {if (I> 0) {childEl = childEl. cloneNode (true);} parEl. appendChild (childEl);}) ;};}; copy the code prepend method. We want the prepend method to meet the same requirements, so this method is very similar: copy the code Dome. prototype. prepend = function (els) {return this. forEach (function (parEl, I) {f Or (var j = els. length-1; j>-1; j --) {childEl = (I> 0 )? Els [j]. cloneNode (true): els [j]; parEl. insertBefore (childEl, parEl. firstChild) ;}}) ;}; when copying code, what is different when prepend is that if you sequentially prepend a series of elements to another element, they are in reverse order. Because we cannot reverse forEach, I will use for loop reverse traversal. Similarly, we will clone the node if it is not the first parent node to be attached. Step 11: remove the node. For the last node, we want to delete the node from the DOM. In fact, it is very simple: copy the code Dome. prototype. remove = function () {return this. forEach (function (el) {return el. parentNode. removeChild (el) ;}) ;}; copy the code to traverse the node and call the removeChild method on the parentNode of each element. The nice thing here is that this Dome object will still work normally; we can use any method on it, including putting it back into the DOM. Step 12: process the last event, but it is certainly not the least used. We will write some functions to process the event. You can know that IE8 uses older IE events, so we need to check it. At the same time, we will throw the DOM 0 event because we can. Check out the method, and then we will discuss it: copy the code Dome. prototype. on = (function () {if (document. addEventListener) {return function (evt, fn) {return this. forEach (function (el) {el. addEventListener (evt, fn, false) ;};}}else if (document. attachEvent) {return function (evt, fn) {return this. forEach (function (el) {el. attachEvent ("on" + evt, fn) ;};}else {return function (evt, fn) {return this. forEach (function (El) {el ["on" + evt] = fn ;};}}(); copy the code here and use an immediate execution function expression, in the function, we perform a feature check. If document. addEventListener exists, we will use it; otherwise we will check document. attachEvent or resort to the DOM 0 event. Note how to return the final function: it will be assigned to Dome. prototype. on at the end. As a feature detection, It is very convenient to assign a suitable function like this, instead of checking each function runtime. The off function is used to uninstall events. It is very similar to the previous one. Copy the code Dome. prototype. off = (function () {if (document. removeEventListener) {return function (evt, fn) {return this. forEach (function (el) {el. removeEventListener (evt, fn, false) ;};}}else if (document. detachEvent) {return function (evt, fn) {return this. forEach (function (el) {el. detachEvent ("on" + evt, fn) ;};}else {return function (evt, fn) {return this. forEach (function (el) {el ["On" + evt] = null ;};}}(); copy the code! I hope you can try our small library and scale it up a little bit. Let me reiterate that the purpose of this tutorial is not to suggest you always write your own database. A professional team is working on a huge and stable database. Here we just want you to see what a library looks like and hope you can learn something here.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.