美文网首页程序员jQuery源码笔记.jpg
jQuery源码二周目#2 插件接口

jQuery源码二周目#2 插件接口

作者: 柠檬果然酸 | 来源:发表于2020-08-20 00:40 被阅读0次

    插件接口

    jQuery.extend = jQuery.fn.extend = function() {
    简单介绍下它的作用,就是用于扩展某个对象的属性或方法。这个方法有好几种重载:

    1. $.extend(target, options)
    这个是最常用的,target是被扩展对象,options是扩展内容。调用之后的结果就是讲options对象中的属性全部拷贝到target对象下

    2. $.extend(deep, target, options)
    同上,只不过多了一个deep参数,这个参数表示是否深拷贝

    3. $.extend(options)
    只有一个参数的情况下就是扩展$本身,比如说$.extend({nick: '哈哈剑'}),然后再控制台打印$.nick就会出现"哈哈剑"

    4. $.extend(deep, options)
    同理也是多了个deep参数

    要把这四种重载在一个方法中实现稍稍有些复杂,jQuery中很多方法都是这样的,这样做的好处是方法的功能丰富,缺点是函数内部复杂程度大大增加。接下来要将源码实现了,我没有照搬jQuery的写法,因为我有自己的思路。

    jQuery.extend = jQuery.fn.extend = function() {
            var target,                     // 被扩展的对象
                options,                    // 扩展内容
                index = 0,                  // 被扩展对象在arguments中的下标
                length = arguments.length,  // 参数长度
                deep = false;               // 是否深度拷贝
                
            
            // 如果第一个参数是布尔值
            if(typeof arguments[0] === 'boolean') {
                deep = arguments[0];
                index++; // 被扩展的对象的下标从0->1
            }
            
            // 情况1:如果是extend(options)或者extend(deep, options),就对this进行扩展
            if(length - index === 1) {
                target = this;
                options = arguments[index]
                
            // 情况2:如果是extend(target, options)或者extend(deep, target, options),就对target进行扩展
            } else if (length - index === 2) {
                target = arguments[index];
                options = arguments[index + 1]
            }
            
            // 如果被扩展对象不是object类型
            if(typeof target !== 'object' && target !== this) {
                target = {};
            }
            
            // 如果扩展内容是一个函数
            if(typeof options === 'function') {
                var name = getFunctionName(options);
                target[name] = options;
                return target;
            }
            
            for(var name in options) {
                var copy = options[name];
                var clone;
                
                if(deep && typeof copy === 'object') { // 深拷贝
                    if(type(copy) === 'Array') {
                        clone = [];
                    } else {
                        clone = {};
                    }
                    
                    // 递归调用
                    target[name] = $.extend(deep, clone, copy);
                } else { // 浅拷贝
                    target[name] = copy;
                }
            }
            
            return target;
        }
        
        /**
         * 获取方法名
         */
        function getFunctionName(fn) {
            var str = fn.toString();
            var reg = /function\s*(\w*)/i;
            
            if(!reg.test(str)) {
                return null;
            }
            
            var matches = reg.exec(str);
            return matches[1];
        }
        
        /**
         * 判断类型
         */
        function type(val) {
            var str = Object.prototype.toString.call(val);
            var reg = /\[\w* (\w*)\]/g;
            var matches = reg.exec(str);
            return matches[1];
        }
    

    直接贴出完整的代码估计会让人看懵,那么分段讲解

            // 如果第一个参数是布尔值
            if(typeof arguments[0] === 'boolean') {
                deep = arguments[0];
                index++; // 被扩展的对象的下标从0->1
            }
    

    第一个if判断用来解决第一个参数是deep的情况,如果第一个参数类型是布尔值,就将它赋值给deep。第一个参数是deep,那么第二个参数才是被扩展对象target,所以target在参数中的下标index变为1。

            // 情况1:如果是extend(options)或者extend(deep, options),就对this进行扩展
            if(length - index === 1) {
                target = this;
                options = arguments[index]
                
            // 情况2:如果是extend(target, options)或者extend(deep, target, options),就对target进行扩展
            } else if (length - index === 2) {
                target = arguments[index];
                options = arguments[index + 1]
            }
    

    第二个if判断是用来确定被扩展对象target以及扩展内容options的。正常写法应该是
    if(参数长度 === 1) { // 扩展自身
    if(参数长度 === 2) { // 用第二个参数去扩展第一个参数

    但是有deep这个参数,所以就有了
    if(length - index === 1) { // 扩展自身
    else if (length - index === 2) { // 用第二个参数去扩展第一个参数
    这样奇怪的写法

            // 如果被扩展对象不是object类型
            if(typeof target !== 'object' && target !== this) {
                target = {};
            }
    

    第三个if判断是用来处理target不是对象的奇葩情况的,为什么后面要跟一串target !== this
    是因为扩展$自身的时候$并不是一个对象,那样岂不是没法扩展,所以必须添上这串代码

            // 如果扩展内容是一个函数
            if(typeof options === 'function') {
                var name = getFunctionName(options);
                target[name] = options;
                return target;
            }
    

    第四个if判断用来干嘛的注释已经写清楚了,jQuery源码中是没有这个东西的,是我自己添加的。我测试了如果参数options如果是一个函数的话是没法被添加到被扩展对象下面,所以我就加了这么段代码来实现这个功能

            for(var name in options) {
                var copy = options[name];
                var clone;
                
                if(deep && typeof copy === 'object') { // 深拷贝
                    if(type(copy) === 'Array') {
                        clone = [];
                    } else {
                        clone = {};
                    }
                    
                    // 递归调用
                    target[name] = $.extend(deep, clone, copy);
                } else { // 浅拷贝
                    target[name] = copy;
                }
            }
    

    上面这段代码就是核心部分了,简化之后的样子如下

            for(var name in options) {
                target[name] = copy;
            }
    

    如果是深拷贝且被拷贝的属性是一个对象或者数组,就创建一个新的对象{}或者数组[],再将属性中的值逐一拷贝到空对象或空数组中。这里说一下在js中不管是对象还是数组通过typeof返回的都是"object"字符串。最有效的判断方法在这里js全部类型判断

    还有函数末尾返回值return target;也很重要,没有返回值target[name] = $.extend(deep, clone, copy);这一行获取到的只会是undefined

    ok,到这里位置能将的细节都讲完了,如果觉得听得迷迷糊糊的建议自己去实现这块的功能。自己思考自己实现,所有的疑问都会解开。目前这个方法我用了没问题,请放心使用。

    相关文章

      网友评论

        本文标题:jQuery源码二周目#2 插件接口

        本文链接:https://www.haomeiwen.com/subject/qmkjjktx.html