《jQuery源码解析》读书笔记(第二章:构造jQuery对象)

第2章 构造jquery对象 8

  • jQuery 对象是一个类数组对象,含有

    • 连续的整形属性

    • length 属性

    • 大量的 jQuery 方法

  • 2.1 构造函数jquery() 8

    jQuery 很有意思的一点是,它的方法定义很强大,会根据不同的参数情况执行不同的功能。

    构造函数 jQuery() 有 7 种用法。

    • 2.1.1 jquery( selector [, context] ) 9

      • 用法

        • 如果传入一个字符串参数, jQuery 会检查这个字符串是选择器还是 HTML 代码
        • 如果是选择器,则遍历文档
          • 如果有匹配的元素,返回匹配的封装了匹配的 DOM 元素的 jQuery 对象
          • 如果没有匹配的元素,则返回一个空的 jQuery 对象
    • 2.1.2 jquery( html [, ownerdocument] )、jquery( html, props ) 9

      • 用法
        • 如果传入的参数看起来像一段 HTML 代码,那么jQuery会尝试创建新的DOM元素,并创建一个封装了此DOM元素的jQuery对象
        • 第二个参数 ownerDocument 用于指定创建新DOM对象的文档对象,如果不传入,则默认为当前文档对象
        • 如果HTML代码是单独一个标签,那么第二个参数还可以是props,是一个包含了属性、事件的普通对象,设置新创建元素的属性、事件
    • 2.1.3 jquery( element )、jquery( elementarray ) 10

      • 用法

        传入一个DOM对象或DOM数组,然后封装这些DOMjQuery对象

    • 2.1.4 jquery( object ) 10

      • 用法

        • 传入一个普通JavaScript对象,把该对象封装到jQuery对象并返回
        • 可以方便的实现自定义事件的绑定和触发
    • 2.1.5 jquery( callback ) 11

      • 用法

        DOM ready事件的回调函数

    • 2.1.6 jquery( jquery object ) 11

      • 用法

        传入一个jQuery对象,则返回该对象的一个副本,这个副本与原jQuery对象引用相同的DOM元素

    • 2.1.7 jquery() 11

      • 用法

        • 不传入任何一个参数,则返回一个空的 jQuery 对象
        • 可以用来复用 jQuery 对象,例如,创建一个空的jQuery对象,然后在需要的时候手动修改其中的元素,再调用jQuery方法,从而避免重复创建jQuery对象
  • 2.2 总体结构 11

    (function(window, undefined) {
        // 构造 jQuery 对象
        var jQuery = (function() {
            var jQuery = function(selector, context) {
                return new jQuery.fn.init(selector, context, rootjQuery);
            };
    
            // 一堆局部变量声明
            // ...
            
            jQuery.fn = jQuery.prototype = {
                constructor: jQuery,
                init: function(selector, context, rootjQuery) {
    
                }
    
                // 一堆原型属性和方法
            };
    
            jQuery.fn.init.prototype = jQuery.fn;
    
            jQuery.extend = jQuery.fn.extend = function() {};
    
            jQuery.extend({
                // 一堆静态属性和方法
            });
    
            return jQuery;
        })();
    
        // 工具方法 Utilities
        // 回调函数列表 Callbacks Object
        // 异步队列 Defferred Object
        // 浏览器功能测试 Support
        // 数据缓存 Data
        // 队列 Queue
        // 属性操作 Attributes
        // 事件系统 Events
        // 选择器 Sizzle
        // DOM遍历 Traversing
        // 样式操作 CSS(计算样式、内联样式)
        // 异步请求 Ajax
        // 动画 Effects
        // 坐标 Offset、尺寸 Dimensions
    
        window.jQuery = window.$ = jQuery;
    })(window);
    

    以上内容,有几个要点:

    • jQuery 的定义部分,为什么要用自调用匿名函数包裹?

      减少与其他模块的耦合,体现高内聚低耦合的思想

    • jQueryjQuery.fn.initprototype都被覆盖了

    • jQueryjQuery.fn.init 这两个方法的 prototype 都指向了 jQuery.prototype

      好处是jQueryjQuery.prototype.init的实例都可以访问构造函数 jQuery() 的原型属性和方法。

      用图表示即:

      new jQuery()                ---> jQuery.prototype
                                                                  ---->   同一个对象
      new jQuery.prototype.init() ---> jQuery.prototype.init.prototype
      
    • 为什么要增加一个函数jQuery.fn.init

      1、对返回对象的要求:该对象需要能访问jQuery原型上的方法和属性。

      2、一般情况下返回的是jQuery的实例,但如果返回的代码是return new jQuery(),会形成死循环

      3、因此构建一个新函数,该函数的实例也能访问jQuery原型上的方法和属性

    • 为什么要在构造函数 jQuery() 内部用运算符 new 创建并返回另一个构造函数的实例?

      创建一个实例的方式是 new 后面跟一个构造函数,如果构造函数有返回值,运算符 new 所创建的对象会被丢弃,返回值将作为 new 表达式的值

      jQuery利用了这一特性,省去了构造函数jQuery()之前的运算符new

      为了书写方便,为构造函数定义了别名$

  • 2.3 jquery.fn.init( selector, context, rootjquery ) 13

    构造函数 jquery.fn.init() 负责解析参数 selectorcontext 的类型,并执行相应的逻辑,返回jquery.fn.init()实例。它有12个有效分支

    • .2.3.1 12个分支 13

    • 2.3.2 源码分析 14

    • 2.3.3 小结 21

  • 2.4 jquery.buildfragment( args, nodes, scripts ) 22

    • 2.4.1 实现原理 22

    • 2.4.2 源码分析 22

    • 2.4.3 小结 26

  • 2.5 jquery.clean( elems, context, fragment, scripts )27

    • 2.5.1 实现原理 27

    • 2.5.2 源码分析 27

    • 2.5.3 小结 39

  • 2.6 jquery.extend()、jquery.fn.extend() 40

    • 2.6.1 如何使用 40

      • 二者是一样的功能:jQuery.extend = jQuery.fn.extend = function()

        也就是说,jQuery对象和jQuery的实例对象都有extend方法了

      • 语法

        jQuery.extend([deep],target,object1[,objectN])

        jQuery.fn.extend([deep],target,object1[,objectN])

      • 参数

        • deep

          是可选的布尔值,表示是否进行深度合并(即递归合并),默认是不递归的,即后面参数的属性的会覆盖第一个参数的同名属性。如果是true,表示进行深度合并,合并过程是递归的。

        • target

          目标对象

        • objectN

          源对象,所有源对象的属性都会合并到目标对象

        • 如果只有一个参数

          那么参数target将被忽略,jQueryjQuery.fn被当做目标对象,用这种方式可以在jQueryjQuery.fn上添加新的属性和方法

    • 2.6.2 源码分析 40

  • 2.7 原型属性和方法 43

    以下属性和方法均在下方代码块中定义

    jQuery.fn = jQuery.prototype = {
        原型属性和方法
    }
    

    也就是说,jQuery的实例有这些属性和方法

    • 2.7.1 .selector、.jquery、.length、.size() 44

      • 源码

        selector: '',
        
        // 版本号
        jquery: '1.7.1',
        
        // jQuery 对象中元素的个数,初始化为 0
        length: 0,
        
        // 同 length,不建议使用
        size: function() {
            return this.length;
        },
        
      • selector

        • 介绍

          当前jQuery对象的选择器,主要用来调试,与实际选择器不一定匹配

        • 源码

          `jquery`: 当前`jQuery`的版本号
          `length`:jQuery 对象中元素的个数,初始化为 0
          `size`:同 length,不建议使用,因为有函数调用开销
          
    • 2.7.2 .toarray()、.get( [index] ) 45

      • toArray

        slice = Array.prototype.slice
        
        toArray: function() {
            return slice.call(this, 0);
        }
        

        小技巧:借鸡下蛋

        利用Array.prototype.slice方法将jQuery对象转为数组,形成方法toArrayslice方法里的this,可以是数组,也可以是类数组。

      • get

        源码:return num == null ? this.toArray() ? (num < 0 ? this[this.length + num] : this[num])

        这样就可以支持: 1、参数为空;2、下标为负数。

    • 2.7.3 .each( function(index, element) )、jquery.each( collection, callback (indexinarray, valueofelement) ) 46

      • .each

        • 功能介绍

          1、遍历当前jQuery对象,并在每个对象上执行回调函数

          2、回调函数参数是索引(从0开始)和当前对象

          3、最重要的一点是,回调函数中的this总是指向当前元素

          4、在回调函数中返回false可以终止遍历

        • 实现技巧

          在源码中,它通过调用jQuery.each方法来实现,又一个借鸡生蛋的应用。

      • jQuery.each

        • 源码在627~666行,就不贴在这里了
        • 源码实现的总体思路
          • 功能上,可以遍历(类)数组和对象(包括函数)
          • 可以自定义回调函数的参数,如果不自定义,则默认给回调函数传index, element的参数
        • 小技巧
          • 判断是否是对象:isObj == object.length === undefined || jQuery.isFunction(object)

          • 遍历对象,用for in

          • 遍历数组,用for循环

          • 执行、判断写在同一句

            if(callback.call(object[i], i, object[i++]) === false){ break; }

          • break可以跳出for infor循环

    • 2.7.4 .map( callback(index, domelement) )、jquery.map( arrayorobject, callback(value, indexorkey) ) 47

    • 2.7.5 .pushstack( elements, name, arguments ) 49

    • 2.7.6 .end() 51

      • 功能描述

        核心代码:return this.prevObject || this.constructor(null)

        如果是:$(ul.first).find('.foo').css(..).end().find('.bar')

    • 2.7.7 .eq( index )、.first()、.last()、.slice( start [, end] ) 51

      • .eq

        eq: function(i) {
            i = +i;
            return i === -1 ?
                this.slice(i) :
                this.slice(i, i + 1);
        }
        

        小技巧:如果i是字符串,把前面加上+可以把该参数转换为数值

    • 2.7.8 .push( value, ... )、.sort( [orderfunc] )、.splice( start,deletecount, value, ... ) 52

      • .push

        var foo = $(document);
        
        foo.push(document.body); // 2
        
      • .sort

        var foo = $([33, 4, 1111, 222]);
        
        foo.sort(); // [1111, 222, 33, 4]
        
        foo.sort(function(a, b) {
            return a - b;
        }); // [4, 33, 222, 1111]
        
    • 2.7.9 小结 53

  • 2.8 静态属性和方法 54

    • 2.8.1 jquery.noconflict( [removeall] ) 55

      看源码即可,绕来绕去的

    • 2.8.2 类型检测:jquery.isfunction( obj )jquery.isarray( obj )jquery.iswindow( obj )jquery.isnumeric( value )jquery.type( obj )jquery.isplainobject( object )jquery.isemptyobject( object ) 56

      • 判断类型

      核心代码是用了 toString.call(obj)判断,该表达式的值是:

      [object Array][object Boolean][object Date][object Function][object Number][object Object][object RegExp][object String]

      还有一个小技巧,循环数组:

      ('Boolean Date Function Number Object RegExp String').split(' ')
      
      • isWindow

        判断方法是:obj == obj.window,以前的版本里是:setInterval in obj

      • jquery.isplainobject( object )

        • 功能

          判断对象是否由 {}new Object()创建

        • 返回false的情况

          • object 可以转为 false

          • Object.prototype.toString.call(object)返回的不是[object Object]

          • objectDOM元素

            object.nodeType 非空

          • objectwindow 对象

          • 不是由Object()函数创建,而是由自定义函数创建的,返回false

            • 含有属性constructor

              是继承属性

              是自身属性

              如果不含属性constructor,则一定是{}创建的对象

            • 没有属性isPropertyOf

              isPropertyOfObject原型对象的特有属性

            • 执行时抛出异常。IE 8/9中,在某些浏览器对象上执行以上检测时会抛出异常,也应该返回false

        • 返回true的情况

          Object.prototype.hasOwnProperty(property)检测对象是否含有该非继承属性

          如果该对象没有属性,或所有属性都是非继承属性,则返回true

      • jquery.isemptyobject( object )

        for in遍历自身属性和继承属性

      • parseFloat(x)可以解析字符串,并返回字符串的第一个数字;如果没有数字,则返回NaN;如果参数是对象,则自动调用该对象的方法toString(),得到该对象的字符串表示,然后再解析

    • 2.8.3 解析jsonxmljquery.parsejson( data )jquery.parsexml( data ) 60

      • jquery.parsejson( data )

        • 解析思路

          先尝试用JSON.parse解析,如果没有该方法,则用(new Function('return' + data))()解析

          其余的是正则表达式匹配字符串之类的东西,在此略过

        • JSON.parse

          解析json字符串为json对象

          判断方法:window.JSON && window.JSON.parse

        • JSON.stringify

          解析json对象为json字符串

          用法1:

          JSON.stringify({a: 1, b: 2}); // '{"a": 1, "b": 2}'
          

          用法2:

          JSON.stringify({a: 1, b: 2}, function(key, value) {
              if(key == '') return value;
              if(key == 'a') return value * 10;
              if(key == 'b') return undefined;
              return value;
          })
          
          // '{"a": 10}'
          

          用法3:

          JSON.stringify({a: 1, b: 2}, ['b']); // '{"b": 2}'
          

          用法4:

          JSON.stringify({a: 1, b: 2}, null, 4); // '{\n    "a": 1\n    "b": 2\n}'
          
    • 2.8.4 jquery.globaleval( code ) 65

    • 2.8.5 jquery.camelcase( string ) 65

      针对-ms单独做了处理,所有处理函数还是很简洁的

      return string.replace(/^-ms-/, 'ms-').replace(/-[a-z]|[0-9]/, function(){...});
      
    • 2.8.6 jquery.nodename( elem, name ) 66

      dom元素的节点名称:dom.nodeName

    • 2.8.7 jquery.trim( str ) 67

      书中的思路很清晰,值得一提的是,这个方法里同样用了String.prototype.trim()方法来借鸡生蛋

      另外,在IE9中,正则/\s/不能匹配不间断空格\xA0,但其也被认为是空格。测试是否不识别,用:/\S/.test('\xA0'),如果是true,则不能识别

    • 2.8.8 数组操作方法:jquery.makearray( obj )、jquery.inarray( value, array [, fromindex] )、jquery.merge( first, second )、jquery.grep( array, function(elementofarray, indexinarray) [, invert] ) 68

      • jquery.makearray( obj )

        学到了一点:push方法里的this可以指定为类数组

      • jquery.inarray( value, array [, fromindex] )

        fromIndex 是查找的起点

        如果浏览器支持数组方法indexOf,则直接调用,否则就自己写方法

        数组的下标有可能是不连续的,所以需要用i in array来判断是否存在下标i

        值得学习的就是判断了

        if($.inArray(element, array) > -1) {}
        

        太繁琐,改进为

        if(!!~$.inArray(element, array)) {}
        

        ~按位取反,相当于改变符号并且减一,只有当-1时,~(-1) == 0

        !!用来形成布尔值

      • jquery.merge( first, second )

        这个方法的源码写的挺不错的,值得一看。

        其中,把second认为两种对象处理:

        1、数组/类数组(判断依据:有无整数/可转为整数的属性length, typeof second.length === 'number'

        2、含有连续整形(或可转换为连续整形)的对象,如{0: 'a', 1: 'b'},从下标0开始遍历。

        最后修正first.length,因为first不一定是真正的属性,需要手动维护length属性。

    • 2.8.9 jquery.guid、jquery.proxy( function, context ) 72

      • jquery.proxy( function, context )

        这个函数使得某个函数在任何环境下执行时,上下文都是context,很实用。

        而且,它的实现原理是:1、闭包封存上下文context;2、返回一个新函数,新函数的作用域链上,有闭包内的上下文context

        其他的,看源码即可。

    • 2.8.10 jquery.access( elems, key, value, exec, fn( elem, key, value ), pass ) 74

    • 2.8.11 jquery.error( message )jquery.noop()jquery.now() 75

    • 2.8.12 浏览器嗅探:jquery.uamatch( ua )jquery.browser 76

    • 2.8.13 小结 77

  • 2.9 总结 77

    到这一步,值得将jQuery的整体结构再回顾一遍了。代码概述在11页的图上。

    (1)构造函数jQuery()有7种用法,根据参数的不同而不同。

    (2)jQuery.fn.init()方法有12种用法,根据参数的不同而不同。

    (2)原型属性和方法,直接放在jQuery.fn = jQuery.prototype对象上

    (3)静态属性和方法,通过jQuery.extend({...})放在jQuery对象上

posted @ 2015-11-12 16:10  一扬  阅读(405)  评论(0编辑  收藏