先总结一下之前看jQuery源码的感受:
由于没有看源码的经验,所以通常就是直接去看源码,然后感觉有些地方不知道为什么写成那样,现总结原因如下:
1、由于自己工作经验积累的不够,看到某些细节的处理时不知道为什么那么做,例如判断一个变量是否为String类型,我发现jQuery源码内部进行了多次改造:
jQuery1.0中是通过 a.constructor == String 这种方式来判断
jQuery1.1是typeof a == "string"
从jQuery1.3之后,虽然有细节上的差别,但是原理上都是通过 Object.prototype.toString.call(a) == "[object String]" 来判断的
后来才知道前两种判断方法都是有缺陷的
2、没有从全局上把握好,源码内部有些方法在好多地方都是调来调去,所有调用的地方都需要考虑到,例如刚开始看到Callbacks的时候,我做了一个非常简单的测试:
var cb=$.Callbacks("memory");
打了个断点想跟着代码走一遍,但一开始就发现了一个奇怪的现象:
我很天真的认为optionsCache是一个全局变量,初始为空对象,所以刚开始进去的时候optionsCache一定是一个空对象,然后optionsCache[options]自然是undefined,然后会调用createOptions函数,不过通过上面贴的这张图也看到了,事情并没有和我想的一样,在2835行即将执行的时候,optionsCache已经赋过值了,后来越想越不对劲,我明明第一次调用Callbacks,为什么全局变量optionsCache会有值,后来打了一个断点跟踪了一下,在堆栈中发现了一些端倪:
于是大致猜出是怎么回事了:Callbacks为Deffered提供了基础,Deffered为ready.promise提供了基础,所以在我调用$.Callbacks()之前进入createOptions函数的情况就是预加载的时候进入的,在此之前看到过很多文章说jQuery中的Callbacks为Deffered Ajax 预加载提供了基础,但是分析到现在才知道这句话的真正含义
还有一次是看到add和remove的源码时,这两个函数上来就判断list是否存在,这也让我感觉很纳闷,list是个全局变量,初始化成了数组,它一定存在啊,干嘛判断这个变量,但是往后看才发现,在$.Callbacks("once")的情况下,将add进去的函数执行完了之后再add的话是不允许add的,而第一次add完了之后会把list置为undefined,以此来达到再add的时候不让往进add的效果,为什么判断list是否存在也就不言而喻了
3、某些地方简化了代码,很难理解作者的逻辑,例如init实例化方法中:字符串的情况结构如下:
if(typeof selector === "string"){ //个人认为这第一个if else是做了一些准备工作,这些准备工作是先把传入id和标签的情况拿了出来 if(selector.charAt(0) === "<" && selector.charAt(selector.length - 1) === ">" && selector.length >= 3){ match = [null, selector, null]; }else{ match = rquickExpr.exec(selector); } //个人认为这第二个if else是开始对传入的字符串做处理,这些处理不是仅限于处理上一个if else中match到的情况,因为上一个if else中match到的仅仅是id和标签这两种情况 if(match && (match[1] || !context)){ //处理match到的情况 if(match[1]){ //处理传入html标签的情况,返回被解析成的DOM对象 }else{ //处理传入id的情况 } }else if(!context || content.jquery){ //处理传入$(expr,context) 个人认为这种用法非常少 return (context || rootjQuery).find(selector); }else{ //处理传入$(expr)的情况 会进入Sizzle return this.constructor(context).find(selector); } }
首先在第一个if else中,之所以先把id和标签的情况先通过match拿出来,个人感觉是因为这两种情况有其本身的特殊性
事实上,当传入字符串时无非就是两种情况,当传入html标签时,是要创建DOM元素,除此之外的所有情况都是获取元素
而获取元素中有一种特殊情况就是传入id的情况,由于id只能通过document.getElementById来获取这个元素,因此处理上反而简单了很多,不必再进入复杂的Sizzle
因此,才单独把创建元素(传入html标签的情况)和获取唯一元素(传入id的情况)这两种情况单独拿了出来
除此之外还有一个难以理解的地方就是第二个if else中的第一个if判断:if(match && (match[1] || !context))
直到看了阿里巴巴的高云写的《jQuery技术内幕——深入理解jQuery架构设计与实现原理》之前,这句代码真是没有一点思路
书中是这样解释的:这行代码用布尔表达式的计算顺序,省略了对match[2]的判断,完整的表达式如下:
if(match && (match[1] || match[2] && !context))