JQuery源码02 -- 基本骨架

381 阅读3分钟

$

jQ的主函数,属性方法都基于是jQ的主函数,属性方法都基于挂载。 引入jQuery后,在使用jQuery时,一般都采用$([selector])的普通函数执行的方式,但返回的却是一个实例对象,一般new执行构造函数才会返回实例对象,此处jQuery()函数的封装采用了工厂模式制造对象

(function(){
    "use strict";
// =============================抽取一些方法,取快捷名字===========================
    var arr = [];
    var slice = arr.slice;
    var push = arr.push;
    var indexOf = arr.indexOf;
// =============================jQ构造函数声明,并处理其原型对象===========================
    var 
         version = "3.5.1",
         jQuery = function( selector, context ) {
           /**
            * selector是css选择器,context为限定在哪个DOM容器中查找(非必须)
            * $(xx)返回原型上init构造函数的new执行的创造的实例            
            */
           return new jQuery.fn.init( selector, context );
         };
        // 取别名,fn是给prototype起的一个别名,并进行原型重定向
        jQuery.fn = jQuery.prototype = {...}
  
// ========================================init构造函数======================================== 
     var
         // root默认为rootjQuery
          rootjQuery,
          rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,
          /**
           * && init函数的内部实现
           * 1. init函数挂载到jQuery原型上
           * 2. 返回init的实例对象也是jq的实例对象。init原型重定向保证$()执行后jq实例对象
           * 3. 用户使用方便 $(...)/new $(...),工厂模式使用:普通函数执行返回类,而非用户自己new
           * 4. jq实例对象是一个类数组集合,实例私有属性为索引和length。jq[数字]可以获取原生DOM对象
           */
          init = jQuery.fn.init = function( selector, context, root ) {
            root = root || rootjQuery;
            // 不传选择器,返回空jq实例对象
            if ( !selector ) {
              return this;
            }
            // *********根据不同类型返回jq,支持三种类型的selector,小型解析器*********
            
            
            
            var match,elm;
            // selector是string类型
            if ( typeof selector === "string" ) {
              // 适配$("<xxx>...</xxx>")html字符模板
              if ( selector[ 0 ] === "<" &&
                selector[ selector.length - 1 ] === ">" &&
                selector.length >= 3 ) {
                  match = [ null, selector, null ];
              }
              // 适配正常的选择器(eg:'.box','#app' ...)
              else {
                match = rquickExpr.exec( selector );
           
                // 解析具体string
                // Match html or make sure no context is specified for #id
                if ( match && ( match[ 1 ] || !context ) ) {
                  // HANDLE: $(html) -> $(array)
                  if ( match[ 1 ] ) {
                    context = context instanceof jQuery ? context[ 0 ] : context;
                    // Option to run scripts is true for back-compat
                    // Intentionally let the error be thrown if parseHTML is not present
                    jQuery.merge( this, jQuery.parseHTML(
                        match[ 1 ],
                        context && context.nodeType ? 
                        context.ownerDocument || context : document,
                        true
                      ) 
                    );
                    // HANDLE: $(html, props)
                    if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) {
                      for ( match in context ) {
                        // Properties of context are called as methods if possible
                        if ( isFunction( this[ match ] ) ) {
                          this[ match ]( context[ match ] );
                        } 
                        // ...and otherwise set as attributes
                        else {
                          this.attr( match, context[ match ] );
                        }
                      }
                    }
                    return this;
                  } 
                  // HANDLE: $(#id)
                  else {
                    elem = document.getElementById( match[ 2
                    if ( ele
                      // Inject the element directly into the jQuery object
                      this[ 0 ] = elem;
                      this.length = 1;
                    }
                    return this;
                  }
                } 
                // HANDLE: $(expr, $(...))
                else if ( !context || context.jquery ) {
                  return ( context || root ).find( selector );
                }
                // HANDLE: $(expr, context)
                // (which is just equivalent to: $(context).find(expr) 
                else {
                  return this.constructor( context ).find( selector );
                }
            }
            
            
            
            
            // selector是DOM对象类型,返回的对象做成类数组形式
            else if(selector.nodeType) {
              // 将此个原生DOM添加为这个返回的jq对象的一个属性 {0: selector}
              this[ 0 ] = selector;
              // length属性添加不要忘了
              this.length = 1;
              // 返回jq对象(new执行显式return的是对象,这个对象是一个类数组)。
              // 返回的jq对象上可以找到这个原生DOM,这样就可以使用jq原型方法
              return this;
            }
            
            
            
            
            // selector是函数类型,返回ready执行的结果
            else if(isFunction(selector)) {
              // ready函数监听DOMContentLoaded事件,DOM加载完,再执行selector函数
              // 执行给ready(fn)传入的回调函数fn  或  直接selector执行
              // $(function() {}) --等价于--> $(document).ready(function() {})
              return root.ready !== undefined ?
              root.ready( selector ) : selector( jQuery );
            }
            
            
            
            
            // selector不是以上三种,创建一个jq类数组并返回。eg:nodeList集合 --> jq集合
            return jQuery.makeArray(selector, this)
          }
          
          
          
        // 将init函数的原型重定向为jQuery的原型,这样new init 的实例可以使用jQuery原型上的属性方法
          init.prototype = jQuery.fn;
          rootjQuery = jQuery( document );
        ...
// ========================================挂载=========================================
        window.jQuery = window.$ = jQuery;
})()

实例方法

通过$('xxx').xxx使用(示例)

jQuery.fn = jQuery.prototype = {
   jquery: version,
   // 原型重定向补上constructor
   constructor : jQuery,
   length: 0
   // 追加一些数组方法到原型上,jq实例可以用(实例想借用别人类的方法可以将其方法追加到原型对象上)
   push: push,
   sort: arr.sort,
   splice: arr.splice,
   
   // jq.get(传数字) 获取指定索引的原生DOM对象
   get: function( num ) {
     if ( num == null ) {
        // 没指定索引,返回jq类数组上所有的DOM集合的数组
        return slice.call( this );
     }
     // 支持正负索引
     return num < 0 ? this[ num + this.length ] : this[ num ];
   },
   
   // jq.eq(传数字) 查找jq对象中的某一特定项,并返回新的jq对象。
   eq: function( i ) {
     var len = this.length,
     // 支持负数索引
     j = +i + ( i < 0 ? len : 0 );
     return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] );
   },
   
   pushStack: function( elems ) {
     // this.constructor()返回一个空的jq对象,merge使得elems中的所有项追加到空jq中
     var ret = jQuery.merge( this.constructor(), elems );
     // prevObject记录原始操作,链式调用后会返回新的指向,可以通过它回到原始jq对象
     ret.prevObject = this;
     // 返回合并好的jq
     return ret;  
   },
   
   // 将second类数组追加到first中,并返回first
   merge: function( first, second ) {
    var len = +second.length,
        j = 0,
        i = first.length;
    for ( ; j < len; j++ ) {
        first[ i++ ] = second[ j ];
    }
    first.length = i;
    return first;
  },
  
  // ready方法
  jQuery.fn.ready = function( fn ) {
    // readyList机制类似于promise。监听DOMCOntentLoaded,等待DOM结构加载完成后,执行fn
    readyList
      .then( fn )
      .catch( function( error ) {
        jQuery.readyException( error );
      });
    // 返回$(document)
    return this;
};
}

函数对象方法

通过$.xxx()使用(实例)

/**
 * & 扩展jQuery方法
 * 当用户需要自定义一些个性化方法,或开发jQuery插件完善jq类库,都需要通过$.extend()方法完成。
 * 此方法jQuery函数对象或jQuery实例都能调用
 */

// 同时挂载到jQuery和jQuery原型上
jQuery.extend = jQuery.fn.extend = function () {
	var
		options, name, src, copy, copyIsArray, clone,
		target = arguments[0] || {},
		i = 1,
		length = arguments.length,
		deep = false;

	//$.extend({...}):仅传入一个对象 ,target = 这个对象,deep = false
	// $.extend([boolean], {...}):传入第一项为布尔,第二项为对象,同上,但deep = true
	if (typeof target === "boolean") {
		deep = target;
		target = arguments[i] || {};
		i++;
	}

	// 保证target是一个对象
	if (typeof target !== "object" && !isFunction(target)) {
		target = {};
	}

	// 不论传几项参数,最终把target转让为调用extend方法的人,$或$.fn
	if (i === length) {
		target = this;
		i--;
	}

	// 仅循环操作一次
	for (; i < length; i++) {
		// 拿到参数传入的对象,若不为空则操作
		if ((options = arguments[i]) != null) {
			for (name in options) {
				// 克隆遍历每一个键值对:{name: copy}
				copy = options[name];
				// 克隆防止无限递归
				if (name === "__proto__" || target === copy) {
					continue;
				}
				// deep为true,(copy存在且是一个普通对象) 或者 (copy是数组)
				if (deep && copy && (jQuery.isPlainObject(copy) ||
					(copyIsArray = Array.isArray(copy)))) {
					// 获取jQ函数/jQ原型上原始的name对应的属性值  
					src = target[name];
					// 若传入的copy是数组,但原始name的属性值不是数组
					if (copyIsArray && !Array.isArray(src)) {
						clone = [];
					}
					// 若传入的copy是不是数组,原始name的属性值不是对象
					else if (!copyIsArray && !jQuery.isPlainObject(src)) {
						clone = {};
					} else {
						clone = src;
					}
					copyIsArray = false;
					// 深克隆每一项
					target[name] = jQuery.extend(deep, clone, copy);
				}
				// deep不为true,给jQ函数/jQ原型扩展键值对,若重名也直接替换
				else if (copy !== undefined) {
					target[name] = copy;
				}
			}
		}
	}
	// 返回处理后的jQ函数/jQ原型
	return target;
};




/**
 * & 遍历方法:类似于数组中的forEach,用于遍历普通对象、类数组、数组
 *    采用上面的extend方法将遍历方法扩展到jQ函数对象上
 *    两份:一份同extend挂到jQuery对象上,一份挂到原型上
 */
jQuery.fn = jQuery.prototype = {
  ...
  // $([selector]).each(()=>{}) 实例调用
  each: function( callback ) {
    return jQuery.each( this, callback );
  }
}

jQuery.extend({
  ...
  // $.each($([selector]), function(key/index, value){}) 静态调用
  each: function (obj, callback) {
    var length, i = 0;
    typeof callback === 'function' ? callback = Funtion.prototype : null;
    // obj是类数组/数组
    if ( isArrayLike( obj ) ) {
	length = obj.length;
	for ( ; i < length; i++ ) {
            // 每遍历一项,回调函数执行,this指向当前数组/类数组项
            // i -> index,value -> value
            // 支持回调函数返回值,一旦返回false,则终止遍历
            if ( callback.call( obj[ i ],  obj[ i ], i ) === false ) {
                    break;
            }
	}
    } 
    // obj是普通对象,同上
    else {
      // for ( i in obj ) {
      // 此处可能会遍历到所属类中自己扩展的属性方法将会被遍历出
      //   if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
      //      break;
      //   }
      // } 
      // 仅获得私有的所有属性
      var keys = [
        ...Object.getOwnPropertyNames(obj), 
        ...Object.getOwnPropertySymbols(obj)
      ]
      for(; i <= keys.length-1; i++) {
        var key = keys[i],
            value = obj[key];
        if(callback.call( value, key, value ) === false) {
          break;
        }
      }
		}
		return obj;
  }
})