# 结构分析
分析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;
- jQuery是函数,在其内部返回实例化的jQuery原型函数init
- 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;
};
← 前言