读源码,封装自己的jQuery库(附详细注释)

导读:

  • 本篇文章用于处在jQuery进阶阶段的小伙伴,通过阅读官方原版jQuery库部分重要方法,通过自己动手封装加深对库底层源码的理解同时掌握基本库的封装思路(立即执行的闭包),这对以后模块化封装和链式编程都有较大的提升。

源码实现:

(function(window, undefined) {
    var wjQuery = function(selector) {
        return new wjQuery.prototype.init(selector);
    }
    wjQuery.prototype = {
        constructor: wjQuery,
        init: function(selector) {
            /*
            1.传入 '' null undefined NaN 0 false 返回空jQuery对象
            2.字符串:
            代码片段:会将创建好的DOM元素存储到jQuery对象中返回
            选择器:会将找到的所有元素存储到jQuery对象中返回
            3.数组:
            会将数组中存储的元素依次存储到jQuery对象中返回
            4.除上述类型以外:
            会将传入的数据存储到jQuery对象中返回
            */
            // 0. 去除字符串两端的空格
            selector = wjQuery.trim(selector);
            if(!selector) {
                return this;
            }
            // 函数
            else if(wjQuery.isFunction(selector)) {
                // this[0] = selector;
                wjQuery.ready(selector);
            }
            // 字符串
            else if(wjQuery.isString(selector)) {
                //判断是不是代码片段
                if(wjQuery.isHTML(selector)) {
                    // 1.根据代码片段创建元素
                    var tmp = document.createElement('div');
                    tmp.innerHTML = selector;
                    // 2.将创建好的一级元素返回到jQuery对象中,并添加length属性                  
                    [].push.apply(this, tmp.children);
                    // 3.返回加工好的this(jQuery), 调用init()方法本身就会返回一个值, 所以return this可写可不写这里语义化写上
                    // return this;    
                } else {
                    // 根据传入的选择器找到所有元素
                    var res = document.querySelectorAll(selector);
                    [].push.apply(this, res);
                    // return this;
                }
            }
            // 数组
            else if(wjQuery.isArray(selector)) {
                // 真数组
                /*if(({}).toString.apply(selector) === "[object Array]") {
                    [].push.apply(this, selector);
                    return this;
                } else {
                    // 伪数组转真数组
                    var arr = [].slice.call(selector);
                    // console.log(arr);
                    [].push.apply(this, arr);
                    return this;
                } */
                //  优化后: 
                var arr = [].slice.call(selector);
                [].push.apply(this, arr);
                 // return this;
                }
            // 除上述以外
            else {
                this[0] = selector;
                this.length = 1; 
            }
            return this;
        },
        jQuery: "1.0.0",
        selector: "",
        length: 0,
        // 由wjQuery对象调用故省略this指向
        push: [].push,
        sort: [].sort,
        splice: [].splice,
        toArray: function() {
            return [].slice.call(this);
        },
        get: function(num) {
            if(arguments.length == 0) {
                return this.toArray();
            } else if(num >= 0) {
                return this[num];
            } else {
                return this[this.length + num];
            }
        },
        eq: function(num) {
            var obj = {
                prevObject: this.toArray(),
            };
            if(arguments.length != 0) {
                return wjQuery(this.get(num));
            }
            return obj;
        },
        first: function() {
            return this.eq(0);
        },
        last: function() {
            return this.eq(-1);
        },
        each: function(fn) {
            return wjQuery.each(this, fn);
        }
    }

    wjQuery.extend = wjQuery.prototype.extend = function(obj) {
        // 通过静态方法调用和实例方法调用统一
        for(var key in obj) {
            this[key] = obj[key];
        }

    };

    // 工具方法
    wjQuery.extend({
        isString : function(str) {
            return typeof str === 'string';
        },
        isHTML : function(str) {
            return (str.charAt(0) == '<' && str.charAt(str.length - 1) == '>' && str.length >= 3);
        },
        trim : function(str) {
            if(!wjQuery.isString(str)) return str;
            if(str.trim) return str.trim();
            else return str.replace(/^\s+|\s+$/g, "");
        },
        isArray : function(ele) {
            if(wjQuery.isObject(ele) && !wjQuery.isWindow(ele) && 'length' in ele) return true;     
        },
        isObject : function(ele) {
            return typeof ele === 'object';
        },
        isWindow : function(ele) {
            return ele === window;
        },
        isFunction : function(ele) {
            return typeof ele === 'function';
        },
        ready: function(fn) {
            // 判断DOM是否加载完成
            if(document.readyState == 'complete') {
                fn();
            } else if(document.addEventListener) {
                document.addEventListener('DOMContentLoaded', function() {
                    fn();
                });
            } else {
                document.attachEvent('onreadystatechange', function() {
                    if(document.readyState == 'complete') {
                        fn();
                    }
                });
            }
        },
        each: function(obj, fn) {
            // 判断是不是数组
            if(wjQuery.isArray(obj)) {
                for(var i = 0; i < obj.length; i++) {
                    var res = fn.call(obj[i], i, obj[i]);
                    if(res === true) continue;
                    else if(res === false) break;
                }
            } else if(wjQuery.isObject(obj)){
                for(var key in obj) {
                    var res = fn.call(obj[key], key, obj[key]);
                    if(res === true) continue;
                    else if(res === false) break;
                }
            }
            return obj;
        },
        map: function(obj, fn) {
            var res = [];
            if(wjQuery.isArray(obj)) {
                for(var i = 0; i < obj.length; i++) {
                    var tmp = fn(obj[i], i);
                    if(tmp) res.push(tmp);
                }

            } else if(wjQuery.isObject(obj)) {
                for(var key in obj) {
                    var tmp = fn(obj[key], key);
                    if(tmp) res.push(tmp);
                }
            }
            return res;
        },
        get_nextsibling: function(ele) {
            var n = ele.nextSibling;
            if(n != null && n.nodeType != 1) {
                n = n.nextSibling;
            }
            return n;
        },
        get_previoussibling: function(ele) {
            var n = ele.previousSibling;
            if(n != null && n.nodeType != 1) {
                n = n.previousSibling;
            }
            return n;
        },
        getStyle: function(dom, styleName) {
            if(window.getComputedStyle) {
                return window.getComputedStyle(dom)[styleName];
            } else {
                return dom.currentStyle[styleName];
            }
        }

    });

    // DOM操作相关方法
    wjQuery.prototype.extend({
        empty: function() {
            // 遍历所有找到的元素
            this.each(function(key,value) {
                value.innerHTML = "";
            });
            // 方便链式编程
            return this;
        },
        remove: function(selector) {
            if(arguments.length == 0) {
                // 遍历指定的元素
                this.each(function(key, value) {
                    // 根据遍历到的元素找到指定的父元素
                    var parent = value.parentNode;
                    // 通过父元素删除指定元素
                    parent.removeChild(value);
                });
            } else {
                var $this = this;
                // 根据传入的选择器找到对应的元素
                $(selector).each(function(key, value) {
                    // 遍历找到的元素,获取对应的类型
                    var type = value.tagName;
                    $this.each(function(k, v) {
                        var t = v.tagName;
                        if(t == type) {
                            var parent = value.parentNode;                          
                            parent.removeChild(value);
                        }
                    });
                });
            }
            return this;
        },
        html: function(content) {
            if(arguments.length == 0) { 
                return this[0].innerHTML;           
            } else {
                this.each(function(key, value) {
                    value.innerHTML = content;
                });
            }
        },
        text: function(content) {
            if(arguments.length == 0) {
                var res = "";
                this.each(function(key, value) {
                });
                return res;
            } else {
                this.each(function(key, value) {
                    value.innerText = content;
                })
            }
        },
        appendTo: function(selector) {
            // 遍历取出所有指定添加到的元素       
            var target = $(selector);
            var res = [];
            $.each(target, function(key, value) {
                // 遍历取出素有被添加的元素
                this.each(function(k, v) {
                    // 判断是否是第0指定的个元素
                    if(key == 0) {
                        value.appendChild(v);
                        res.push(v);
                    } else {
                        // 先拷贝在添加
                        var temp = v.cloneNode(true);
                        value.appendChild(temp);
                        res.push(temp);
                    }
                })
            });
            return $(res);
        },
        prependTo: function(selector) {
            // 遍历取出所有指定添加到的元素
            var target = $(selector);
            var source = this;
            var res = [];
            $.each(target, function(key, value) {
                // 遍历取出素有被添加的元素
                source.each(function(k, v) {
                    // 判断是否是第0指定的个元素
                    if(key == 0) {
                        value.insertBefore(v, value.firstChild);
                        res.push(v);
                    } else {
                        // 先拷贝在添加
                        var temp = v.cloneNode(true);
                        value.insertBefore(temp, value.firstChild);
                        res.push(temp);
                    }
                })
            });
            return $(res);
        },
        append: function(selector) {
            // 判断传入的参数是否是字符串
            if(wjQuery.isString(selector)) {
                this[0].innerHTML += selector;
            } else {
                $(selector).appendTo(this);
            }
            return this;
        },
        prepend: function(selector) {
            // 判断传入的参数是否是字符串
            if(wjQuery.isString(selector)) {
                this[0].innerHTML = selector + this[0].innerHTML;
            } else {
                $(selector).prependTo(this);
            }
            return this;
        },
        insertBefore: function(selector) {
            // 遍历取出所有指定添加到的元素
            var target = $(selector);
            var source = this;
            var res = [];
            wjQuery.each(target, function(key, value) {
                // 遍历取出素有被添加的元素
                var parent = value.parentNode;
                source.each(function(k, v) {
                    // 判断是否是第0指定的个元素
                    if(key == 0) {
                        parent.insertBefore(v, value);
                        res.push(v);
                    } else {
                        // 先拷贝在添加
                        var temp = v.cloneNode(true);
                        parent.insertBefore(temp, value);
                        res.push(temp);
                    }
                })
            });
            return $(res);
        },
        insertAfter: function(selector) {
            // 遍历取出所有指定添加到的元素       
            var target = $(selector);
            var source = this;
            var res = [];
            $.each(target, function(key, value) {
                // 遍历取出素有被添加的元素
                var parent = value.parentNode;
                var sibling = value.nextSibling;
                source.each(function(k, v) {
                    if(sibling == null) {
                        // 判断是否是第0指定的个元素
                        if(key == 0) {
                            parent.appendChild(v);
                            res.push(v);
                        } else {
                        // 先拷贝在添加
                        var temp = v.cloneNode(true);
                        parent.appendChild(temp);
                        res.push(temp);
                    }
                } else {
                    // 判断是否是第0指定的个元素
                    if(key == 0) {
                        parent.insertBefore(v, sibling);
                        res.push(v);
                    } else {
                        // 先拷贝在添加
                        var temp = v.cloneNode(true);
                        parent.insertBefore(temp, sibling);
                        res.push(temp);
                    }
                }
            })
            });
            return $(res);
        },
        replaceAll: function(selector) {
            // 遍历取出所有指定添加到的元素
            var target = $(selector);
            var res = [];
            $.each(target, function(key, value) {
                // 遍历取出素有被添加的元素
                var parent = value.parentNode;
                this.each(function(k, v) {
                    // 判断是否是第0指定的个元素
                    if(key == 0) {
                        $(v).insertBefore(value);
                        $(value).remove();
                        res.push(v);
                    } else {
                        // 先拷贝在添加
                        var temp = v.cloneNode(true);
                        $(temp).insertBefore(value);
                        //删除指定元素
                        $(value).remove();
                        res.push(temp);
                    }
                })
            });
            return $(res);
        },
        clone: function(boolean) {
            var res = [];
            if(boolean) {
                // 深复制
                this.each(function(key, value) {
                    var temp = value.cloneNode(true);
                    // 遍历元素中的eventsCache对象
                    $.each(value.eventsCache, function(name, array) {
                        // 遍历每个事件对应的数组
                        $.each(array, function(index, method) {
                            // 给复制的元素添加事件
                            temp.on(name, method);
                        });
                    });
                    res.push(temp);
                });     
            } else {
                // 浅复制
                this.each(function(key, value) {
                    var temp = value.cloneNode(true);
                    res.push(temp);
                });         
            }
            return res;
        }
    });

    // 筛选相关方法
    wjQuery.prototype.extend({
        next: function(selector) {
            var res = [];
            // 返回所有找到的
            if(arguments.length == 0) {
                this.each(function(key, value) {
                    var temp = wjQuery.get_nextsibling(value);
                    if(temp != null) res.push(temp);
                });
            } else {
                // 返回指定找到的
                this.each(function(key, value) {
                    var temp = wjQuery.get_nextsibling(value);
                    $(selector).each(function(k, v) {
                        if(v !== temp || v == null) return true;
                        res.push[v];
                    });
                });
            }
            return $(res);
        },
        pre: function(selector) {

        }

    });

    // 属性操作相关方法
    wjQuery.prototype.extend({
        attr: function(attr, value) {
            // 先判断传入的是对象还是字符串
            if($.isString(attr)){
                if(arguments.length == 1) {
                    return this[0].getAttribute(attr);
                } else {
                    this.each(function(key, ele){
                        ele.setAttribute(attr, value);
                    });
                }
            } else if($.isObject(attr)) {
                // 遍历取出所有属性节点名称及对应值
                for(var key in attr) {
                    // 遍历取出所有标签
                    this.each(function(k, v) {
                        v.setAttribute(key, attr[key]);
                    });
                }       
            }
            return this;
        },
        prop: function(attr, value) {
            // 先判断传入的是对象还是字符串
            if($.isString(attr)){
                if(arguments.length == 1) {
                    return this[0][attr];
                } else {
                    this.each(function(key, ele){
                        ele[attr] = value;
                    });
                }
            } else if($.isObject(attr)) {
                // 遍历取出所有属性节点名称及对应值
                for(var key in attr) {
                    // 遍历取出所有标签
                    this.each(function(k, v) {
                        v[key] = attr[key];
                    });
                }       
            }
            return this;
        },
        css: function(attr, value) {
            // 先判断传入的是对象还是字符串
            if($.isString(attr)){
                if(arguments.length == 1) {
                    return wjQuery.getStyle(this[0], attr);
                } else {
                    this.each(function(key, ele){
                        ele.style[attr] = value;
                    });
                }
            } else if($.isObject(attr)) {
                // 遍历取出所有属性节点名称及对应值
                for(var key in attr) {
                    // 遍历取出所有标签
                    this.each(function(k, v) {
                        v.style[key] = attr[key];
                    });
                }       
            }
            return this;
        },
        val: function(content) {
            if(arguments.length == 0) {
                return this[0].value;
            } else {
                this.each(function(key, ele) {
                    ele.value = content;
                });
                return this;
            }
        },
        hasClass: function(name) {
            var flag = false;
            if(arguments.length == 0) return flag;
            else {
                this.each(function(key, value) {
                    // 取出每个元素的class属性值并在两端加上空格便于判断
                    var className = " " + value.className + " ";
                    name = " " + name + " ";
                    if(className.indexOf(name) != -1) {
                        flag = true;
                        return false;
                    }
                })
            }
            return flag;
        },
        addClass: function(name) {
            var names = name.split(" ");
            if(arguments.length == 0) return this;          
            else {
                // 遍历取出每一个元素
                this.each(function(key, value) {
                    // 遍历取出每一个类名
                    $.each(names, function(k, v) {
                        if(!$(value).hasClass(v)) value.className += " " + v;
                    });
                });
            }       
            return this;
        },
        removeClass: function(name) {
            if(arguments.length == 0) {
                this.each(function(key, value) {
                    value.className = "";
                });
            } else {
                var names = name.split(" ");
                // 遍历取出每一个元素
                this.each(function(key, value) {
                    // 遍历取出每一个类名
                    $.each(names, function(k, v) {
                        if($(value).hasClass(v)){
                            value.className = (" "+value.className+" ").replace(" "+v+" ", "");
                        }
                    });
                });
            }
            return this;
        },
        toggleClass: function(name) {
            // 如果不传参则删除所有
            if(arguments.length == 0) this.removeClass();
            else {
                var names = name.split(" ");
                // 遍历取出每一个元素
                this.each(function(key, value) {
                    // 遍历取出每一个类名
                    $.each(names, function(k, v) {
                        if($(value).hasClass(v)){
                            // 如果有就删除
                            $(value).removeClass(v);
                        } else {
                            $(value).addClass(v);
                        }
                    });
                });
            }
            return this;
        }

    })

    // 事件操作相关方法
    wjQuery.prototype.extend({
        on: function(name, callBack) {
            this.each(function(key, ele) {
                // 判断有没有保存所有事件类型的对象
                if(!ele.eventsCache) {
                    ele.eventsCache = {};
                }
                // 判断有没有保存每个对应事件类型的事件的数组
                if(!ele.eventsCache[name]) {
                    ele.eventsCache[name] = [];
                    ele.eventsCache[name].push(callBack);
                    if(ele.addEventListener) {
                        ele.addEventListener(name, function(){
                            for(var i = 0; i < ele.eventsCache[name].length; i++) {
                                ele.eventsCache[name][i]();
                            }
                        });
                    } else {
                        ele.attachEvent("on"+name, function(){
                            for(var i = 0; i < ele.eventsCache[name].length; i++) {
                                ele.eventsCache[name][i]();
                            }
                        });
                    }
                    
                } else {
                    ele.eventsCache[name].push(callBack);
                }
            })
        },
        off: function(name, callBack) {
            if(arguments.length == 0) {
                this.each(function(key, ele) {
                    ele.eventsCache = {};
                });
            } else if(arguments.length == 1) {
                this.each(function(key, ele) {
                    ele.eventsCache[name] = [];
                });
            }else if(arguments.length == 2) {
                this.each(function(key, ele) {
                    $.each(ele.eventsCache[name], function(index, method) {
                        // 判断传入的参数和数组中存的参数是否相同
                        if(method == callBack) ele.eventsCache[name].splice(index, 1);
                    });
                });
            }       
        }

    })

    wjQuery.prototype.init.prototype = wjQuery.prototype;
    window.wjQuery = window.$ = wjQuery;
})(window)

总结:

  • 以上源码需对JavaScript一些方法有较深的理解,否则在阅读时可能会晕头转向的,最后希望以上源码能对正在学习jQuery源码并尝试自己动手封装的小伙伴提供一个参考思路。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351

推荐阅读更多精彩内容