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

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

建议下载源码而后据文章思路学习,最好本身边思考边多敲几遍。node

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

提到jQuery,相信不少前端工程师都知道,这个已经火了十来年的框架,为前端开发提供便利性的同时也解决了各类各样的浏览器兼容性问题,一个框架为何这么火🔥其中的缘由不言而喻,但可否以一种第三人称的方式,站在做者的角度来来思考设计,这估计是不少人不肯意去作的事,那么今天开始,我想以第三人称的方式来剖析源码,自问自答的方式,读懂做者的意图,体会大牛的编程思想,学以至用,提高编码的思想和格局。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的精髓.git

1.jQuery无new构建实例github

(1)为何jQuery对象能够经过$符号直接能够访问呢? 咱们先来看下下面这张 jQuery共享原型设计图:编程

jQuery共享原型设计图

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

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

    // 共享原型对象
    jQuert.fn.init.prototype = jQuert.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,对做者也是一种鼓励。

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

相关文章
相关标签/搜索