程序员jQuery源码笔记.jpg

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

2020-08-20  本文已影响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,到这里位置能将的细节都讲完了,如果觉得听得迷迷糊糊的建议自己去实现这块的功能。自己思考自己实现,所有的疑问都会解开。目前这个方法我用了没问题,请放心使用。

上一篇 下一篇

猜你喜欢

热点阅读