# 结构分析

分析jQuery重要功能,核心设计的实现。

# IIFE(立即调用函数表达式,也称为自执行匿名函数)

ES2015之前,JS没有模块化的概念,因此每个库的代码,无法阻止外界访问,而且库中变量也会污染全局作用域。

因此,那个时期的JS库,多是采用IIFE的模式,来隔绝对库的访问和保证库独立性。

// 通过IIFE的方式,实现库代码与外部环境代码的隔离
(function( global, factory ) {
  // 支持CommandJS方式引用jQuery
  if ( typeof module === "object" && typeof module.exports === "object" ) {
    // 查看当前环境是否存在document属性
    module.exports = global.document ?
      // 因为模块化的原因,这里第二个参数传送了true
      // 因此不会在global对象上绑定$和jQuery属性,仅返回jQuery函数
      // var jQuery = require("jquery")(window);
      factory( global, true ) :
			function( w ) {
        // Node.js环境则直接报错
				if ( !w.document ) {
					throw new Error( "jQuery requires a window with a document" );
        }
				return factory( w );
			};
	} else {
		factory( global );
	}
}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
  // ... jQuery核心代码
  return jQuery;
}));

# JS全局对象绑定jQuery

jQuery是如何绑定在window对象中的呢?

在IIFE中,会将window引用传入到jQuery函数中,并根据noGlobal来判断,是否需要将jQuery绑定在全局对象上。

if ( !noGlobal ) {
  // 这里占用全局对象的jQuery属性和$属性
	window.jQuery = window.$ = jQuery;
}

# jQuery.noConflict 释放占用属性

jQuery默认占用全局对象window中jQuery与$两个属性。

如果我们还引用了其他三方库,且三方库也需要占用$属性,这时就会产生冲突。

因此,jQuery提供释放属性的功能,默认会释放$属性。如果传入参数true,则会将jQuery属性也释放掉。

var
	// 保存被jQuery覆盖之前的jQuery值
	_jQuery = window.jQuery,

	// 保存被jQuery覆盖之前的$值
	_$ = window.$;

// 释放jQuery占用的$和jQuery属性
// deep:布尔值,为true时,会释放jQuery属性
jQuery.noConflict = function( deep ) {
  // $属性被jQuery占用时,将其还原为覆盖之前的对象
	if ( window.$ === jQuery ) {
		window.$ = _$;
	}

  // jQuery属性被jQuery占用时,将其还原为覆盖之前的对象
	if ( deep && window.jQuery === jQuery ) {
		window.jQuery = _jQuery;
	}

	return jQuery;
};

# 无new构造jQuery

无new构造算是jQuery的亮点之一了。

后续的三方库,例如Axios等,应该也或多或少的借鉴了jQuery的实现方法。

// Axios实现无new构造的方法为:
// 1. 用户导入的axios并非是真正的Axios类,而是一个封装函数
// 2. 在此函数内部,实例化new Axios,生成真正的实例对象
// 3. 将真正的实例对象的原型方法和属性绑定在封装函数上
// 4. 返回这个封装函数,因此可以无new,即可调用Axios类上的原型方法和属性了

// jQuery相对绕一点
// 但是核心逻辑,还是通过一个封装函数来绑定实例原型实现的
// 其实通过以下几句代码,即可实现
var jQuery = function( selector, context ) {
	return new jQuery.fn.init( selector, context );
};

var init = jQuery.fn.init = function( selector, context, root ) {
  // ...
};

init.prototype = jQuery.fn;
  1. jQuery是函数,在其内部返回实例化的jQuery原型函数init
  2. init函数原型绑定为jQuery的原型,因此实例化init函数时,就可以访问jQuery的原型方法

如果对原型链熟悉,那么这段代码应该非常好理解。

# jQuery插件机制

jQuery为开发插件提供了两个方法:jQuery.fn.extend()jQuery.extend()

extend方法的功能为:合并,将后面的属性方法合并到第一个对象中。

  • jQuery.fn.extend是将后面的属性合并到jQuery的原型链上。(对实例起作用)
  • jQuery.extend是将属性合并到jQuery全局对象上。(对全局对象起作用)
// jQuery.extend和jQuery.fn.extend其实是相同的
jQuery.extend = jQuery.fn.extend = function() {
	var src, copyIsArray, copy, name, options, clone,
		target = arguments[ 0 ] || {},
		i = 1,
		length = arguments.length,
		deep = false;

	// 第一个参数为布尔类型,则标识需要深递归
	if ( typeof target === "boolean" ) {
		deep = target;

		// 跳过布尔值,i从2开始,即target
		target = arguments[ i ] || {};
		i++;
	}

	// 如果target不是对象或函数,需要兼容为对象类型(在深拷贝时,可能存在这种情况)
	if ( typeof target !== "object" && !jQuery.isFunction( target ) ) {
		target = {};
	}

	// 多个参数表示扩展自定义对象,例如$.extend({}, {say: 'hello world'})
  // 单个参数标识扩展jQuery对象,例如$.extend({say: 'hello world'})
  // 当然也可以扩展自定义对象原型或jQuery对象原型,$.fn.extend....
	if ( i === length ) {
		target = this;
		i--;
	}

	for ( ; i < length; i++ ) {

		// 过滤值为undefined或null的属性
		if ( ( options = arguments[ i ] ) != null ) {
			// Extend the base object
			for ( name in options ) {
        // 源值
				src = target[ name ];
        // 拷贝值
				copy = options[ name ];

				// 防止引用循环
        // 例如 extend(true, target, {'target': target});
				if ( target === copy ) {
					continue;
				}

				// 递归调用,当需要深拷贝且待拷贝值为纯对象或数组类型时
				if ( deep && copy && ( 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;
};
最近更新时间: 2023/3/21 19:40:56