Create A Jquerylike Library

如何创建一个Javascript库

原文:Build Your First JavaScript Library 进行了修改和精简,用于自我学习

step 1: Creating the Library Boilerplate

我们以一个结构体开始,它展示了我们库的意图。

/*
 * 模块化结构,所有 API 都挂载 dome 命名空间上
 */
window.dome = (function () {
    // 构造函数,将我们选中或创建的 DOM 元素封装成实例对象
    function Dome (els) {
    }
        
    var dome = {
        // 从 DOM 树中选择元素,返回 Dome 实例
        get: function (selector) {}
    };
        
    return dome;
    
}());

step 2: 获取元素

dome.get() 传入一个参数,可以是字符串,也可以是 DOM 节点或 NodeList。

/*
 * 选择 DOM 元素
 *
 * @namespace   dome.get(selector)
 * @param       {string|Node|NodeList}  selector
 * @return      {Dome}
 */
get: function (selector) {
    var els;
    
    if (typeof selector === "string") {     // selector
        els = document.querySelectorAll(selector);
    } else if (selector.length) {           // NodeList
        els = selector;
    } else {                                // Node
        els = [selector];
    }
    
    // els is a ArrayLike Object
    // the Dome constructor returns a Dome instance
    return new Dome(els);
}

step 3: Dome 构造函数

/*
 * Dome 构造函数,将 DOM 元素的集合构造成类的实例
 * Dome 实例是一个类数组对象,在该实例上拓展了许多原型方法
 *
 * @param       {ArrayLike.<Node>}      els
 * @return      {Dome}
 */
function Dome (els) {

    for (var i = 0; i < els.length; i++ ) {
            this[i] = els[i];
    }
    
    this.length = els.length;
}

step 4: 添加工具函数

/*
 * 工具函数 map
 *
 * @namespace   Dome#map(callback(item, index): newItem) 
 * @param       {callback(item, index): any}    callback
 * @return      {Array.<any>}
 */
Dome.prototype.map = function (callback) {
    var results = [];
    
    for (i = 0; i < this.length; i++) {
        results.push(callback.call(this, this[i], i));
    }
    
    return results;
};

/*
 * 工具函数 forEach
 *
 * @namespace   Dome#forEach(callback(item, index)) 
 * @param       {callback(item, index)}         callback
 * @return      {Dome}
 */
Dome.prototype.forEach(callback) {
    this.map(callback);
    return this;
};

/*
 * 工具函数 mapOne
 *
 * @namespace   Dome#mapOne(callback(item, index)) 
 * @param       {callback(item, index): any}    callback
 * @return      {any|Array.<any>}               
 */
Dome.prototype.mapOne = function (callback) {
    var mapArray = this.map(callback);
    return mapArray.length > 1 ? mapArray : mapArray[0];
};

step 5: 处理文本 和 HTML

/*
 * 操作 innerText 和 innerHTML
 *
 * @namespace   Dome#text()
 *
 * @profile     Dome#text(text)
 * @param       {string}        text
 * @return      {Dome}                         
 *
 * @profile     Dome#text()
 * @return      {string|Array.<string>}               
 */
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;
        });
    }
};

// 省略
Dome.prototype.html = function (html) {};

step 6: 调整样式

/*
 * 增加 HTML 元素的 Class
 *
 * @namespace   Dome#addClass(classes)
 * @param       {string|Array.<string>}         classes
 * @return      {Dome}                         
 */
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;
    });
};

/*
 * 删除 HTML 元素的 Class
 * 为了简单,一次只能删除一个样式
 *
 * @namespace   Dome#removeClass(clazz)
 * @param       {string}         clazz
 * @return      {Dome}                         
 */
Dome.prototype.removeClass = function (clazz) {
    return this.forEach(function (el) {
        var classNames = el.className.split(" "),
            i;
            
        while ((i = classNames.indexOf(clazz)) > -1) {
            classNames.splice(i, 1)
        }
        
        el.className = classNames.join(" ");
    });
};

step 7: Dome#attr()

// 省略
Dome.prototype.attr = function (attr, val) {};

 

step 8: 创建 DOM 元素

var dome = {
    // 创建 DOM 元素,返回值为 Dome 实例
    create: function (tagName, attrs) {}
};
/*
 * 创建 HTML 元素
 *
 * @namespace   dome.create(tagName, attrs)
 * @param       {string}        tagName
 * @param       {Object}        attrs
 * @return      {Dome}                         
 */
create: function (tagName, attrs) {
    var el = new Dome([document.createElement(tagName)]);
    
    if (attrs) {
        // special case 1
        if (attrs.className) {
            el.addClass(attrs.className);
            delete attrs.className;
        }
        
        // special case 2
        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;
}

step 9: 将 DOM元素 添加至 DOM 树

// 简单思路
Dome.prototype.append = function (els) {
    this.forEach(function (parEl, i) {
        els.forEach(function (childEl) {
        });
    });
};

如果我们需要将 els 添加到多个元素内,我们需要克隆它们。   

/* 
 * 添加 DOM 元素至 DOM 树
 * 
 * @namespace   Dome#append(elems)
 * @native      Element#appendChild
 * @native      Element#cloneNode
 * @param       {Dome}          elems       要添加的 Dome 实例
 * @return      {Dome}          Dome 实例
 */
Dome.prototype.append = function (els) {
    return this.forEach(function (parEl, i) {
        els.forEach(function (childEl) {
            if (i > 0) {
                childEl = childEl.cloneNode(true);
            }
            parEl.appendChild(childEl);
        });
    });
};

step 10: 移除节点

/* 
 * 移除 DOM 节点
 * 
 * @namespace Dome#remove()
 * @return      {Dome}          Dome 实例
 */
Dome.prototype.remove = function () {
    return this.forEach(function (el) {
        return el.parentNode.removeChild(el);
    });
};

step 11: 处理事件

/* 
 * 事件绑定
 * 
 * @namespace   Dome#on(evtName, func)
 * @native      Element#addEventListener
 * @native      Element#attachEvent
 * @param       {string}        evtName     事件类型
 * @param       {function(evt)} func        事件处理函数
 * @return      {Dome}          Dome 实例
 */
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);
            });
        };
    }
}());
/* 
 * 事件解除绑定
 * 
 * @namespace   Dome#off(evtName, func)
 * @native      Element#removeEventListener
 * @native      Element#detachEvent
 * @param       {string}        evtName     事件类型
 * @param       {function(evt)} func        事件处理函数
 * @return      {Dome}          Dome 实例
 */
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);
            });
        };
    }
}());