jQuery 源码剖析(一) - 核心功能函数

2,841 阅读3分钟

jQuery 源码解析代码及更多学习干货: 猛戳GitHub

建议下载源码然后据文章思路学习,最好自己边思考边多敲几遍。

开篇题外话:为什么要写这篇文章?

提到jQuery,相信很多前端工程师都知道,这个已经火了十来年的框架,为前端开发提供便利性的同时也解决了各种各样的浏览器兼容性问题,一个框架为什么这么火🔥其中的原因不言而喻,但能否以一种第三人称的方式,站在作者的角度来来思考设计,这估计是很多人不愿意去做的事,那么今天开始,我想以第三人称的方式来剖析源码,自问自答的方式,读懂作者的意图,体会大牛的编程思想,学以致用,提升编码的思想和格局。

一:剖析源码前准备

  • 1.首先官网下载源码jQuery官网
  • 2.选择jQuery版本并下载到本地,并在本地给自己新建件myjQuery-1.0.0.js(这个文件是用来仿写jQuery).
  • 3.创建入口文件并引入这官方jQuery和自己创建的myjQuery-1.0.0.js文件.
  • 4.开始剖析源码.

二:剖析源码采用的方法技巧

  • 1.裁剪
  • 2.注释翻译
  • 3.一目十行找关键类名
  • 4.注释掉不相关干扰代码
  • 5.仿写

开始剖析

本篇通过三个方向来剖析jQuery的精髓.

1.jQuery无new构建实例

(1)为什么jQuery对象可以通过$符号直接可以访问呢? 我们先来看下下面这张 jQuery共享原型设计图:

jQuery共享原型设计图

通过上图分解,可以很清晰的分析出最佳方案: 创建一个jQuery对象,返回jQuery原型对象的init方法,然后共享原型,将jQuery挂载到windows上起别名$,实现通过$来访问jQuery的构造函数.同理通过$.fn来替代jQuery.prototype。

// 闭包 立即执行函数
;(function(root){
    var jQuery = function() {
        return new jQuery.prototype.init();
    }
   jQuery.fn = jQuery.prototype = {
        
    }

    // 共享原型对象
    jQuery.fn.init.prototype = jQuery.fn;
    
    root.$ = root.jQuery = jQuery;

})(this);

2.共享原型设计

上面的代码已经很明显的体现出共享原型设计的思想,将jQuery原型对象共享,然后通过扩展实例方法属性以及添加静态属性及静态方法的形式充分实现jQuery的灵活扩展性。

3.extend源码解析 在使用jQuery源码中我们有时候为了给源码扩展一个新的方法,一般会采用以下几种方式:

// 任意对象扩展
var obj = $.extend({},{name:"james"});

// 本身扩展
$.extend({
    work:function(){
    }
});

// 实例对象扩展
$.fn.extend({
    sex:"男"
});
$().sex;  // 男

通过以上代码我们来反推,jQuery源码中extend是如何实现的。

 jQuery.fn.extend = jQuery.extend = function () {
        var target = arguments[0] || {};
        var length = arguments.length;
        // 从第1个参数开始解析,因为第0个是我们targer,用来接收解析过的数据的
        var i = 1;
        var option,name;
        if(typeof target !== "object") {
            target = {};
        }

        // 浅拷贝
        for (;i<length;i++){
            if((option = arguments[i]) != null) {
                for(name in option) {
                    target[name] = option[name];
                }
            }
        }
        return target;
    }

测试代码:

 var ret = {name:'james',list:{age:26,sex:'女'}};
 var obj = $.extend({},ret); 
 console.log(obj);

以下是输出结果:

此时我们成功的扩展了一个为任意对象扩展的extender方法。

问题来了 既然是任意对象,那么我们是否可以通过extender方法来扩展多个属性呢? 测试代码:

var ret = {name:'james',list:{age:26,sex:'女'}};
var res = {list:{sex:'男'}} 
var obj = $.extend({},ret,res); 

以下是输出结果:

咦???不对吧,明明我是写了两个对象,怎第一个对象的list属性被覆盖掉了??逗我呢.....

通过仔细阅读源码,终于得出了结论,extender方法扩展了浅拷贝和深拷贝,于是重新写了扩展方法,通过传入一个boolean参数来决定是否需要深拷贝。

   jQuery.extend = jQuery.fn.extend = function () {
       // 声明变量
       var options,name,copy,src,copyIsArray,clone,
       target = arguments[0] || {},
       length = arguments.length,
       // 从第1个参数开始解析,因为第0个是我们targer,用来接收解析过的数据的
       i = 1,
       // 是否是深拷贝,外界传过来的第一个参数
       deep = false;
   
       // 处理深层复制情况 
       if(typeof target === "boolean") {
           // extender(deep,{},obj1,obj2) 
           deep = target;
           target = arguments[i] || {};
           i ++;
       }
       // 判断 targer不是对象也不是方法
       if(typeof target !== "object" && !isFunction(target)) {
           target = {};
       } 
   
       // 如果只传递一个参数,则扩展jQuery本身
       if (length === i) {
           target = this;
           // 此时把i变为0
           i--;
       }
   
       for ( ; i < length ; i++){
           // 仅处理非null /未定义的值
           if((options = arguments[i]) != null) {
   
               // 仅处理非null /未定义的值
               for(name in options) {
                   copy = options[name];
                   src = target[name];
   
                   // 防止Object.prototype污染
                   // 防止死循环循环 
                   if (name === "__proto__" || target == copy) {
                       continue;
                   }
   
                   //如果我们要合并普通对象或数组,请递归
                   // 此时的copy必须是数组或者是对象
                   if ( deep &&  (jQuery.isPlainObject(copy) ||
   				(copyIsArray = jQuery.isArray(copy)))) {
   
                       // 确保源值的正确类型  源值只能是数组或者对象
                       if ( copyIsArray ) {
                           copyIsArray = false;
                           clone = src && jQuery.isArray(src)?src:[];
                       } else {
                           clone = src && jQuery.isPlainObject(src)?src:{};
                       } 
                       //永远不要移动原始对象,克隆它们
                       target[name] = jQuery.extend(deep,clone,copy);
   
                       //不要引入未定义的值
                   } else if (copy !== undefined){
                       // 浅拷贝
                       target[name] = copy;
                   }
               }
           }
       }
       //返回修改后的对象 
       return target;
   };

扩展了三个属性判断:

 // 判断是否是方法
   var isFunction = function isFunction( obj ) {   
   return typeof obj === "function" && typeof obj.nodeType !== "number";
   };

// 扩展属性和方法
   jQuery.extend({
       // 类型检测 是否是对象
       isPlainObject: function(obj) {
           // "[object Object]" 第二个O一定是大写,坑了我好几个小时.......
           return toString.call(obj) === "[object Object]";
       },
       // 是否是数组
       isArray: function(obj) {
           return toString.call(obj) === "[object Array]";
       }
   });

测试代码:

   var a = {name:"james",list:{age:"26"}};
   var b = {list:{sex:"男"}}; 
   var c = $.extend(true,{},a,b); 

终于大功告成:输出了我想要的结果:合并了相同键的对象.

总结

本篇主要分享jQuery入口函数共享原型链思想以及核心功能扩展函数extend的剖析.

其他

jQuery 源码剖析 系列目录地址:猛戳GitHub

jQuery 源码剖析 系列预计写十篇左右,旨在加深对原生JavaScript 部分知识点的理解和深入,重点讲解 jQuery核心功能函数、选择器、Callback 原理、延时对象原理、事件绑定、jQuery体系结构、委托设计模式、dom操作、动画队列等

如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。

关注公众号回复:学习 领取前端最新最全学习资料,也可以进群和大佬一起学习交流