Js编程技巧之jQuery源码(代码技巧篇二)

375 阅读2分钟

本文是专栏文章《Js编程技巧之jQuery源码》的第四篇。继续介绍jQuery里的代码技巧(不会对细节面面俱到,欢迎留言讨论)

灵活巧妙的参数设计

jQuery里很多工具函数的参数设计的非常巧妙:有些参数是可选的,有些参数是多种类型的。 使用者只需关心暴露的API,使用起来非常的便捷。接下来以源码里的三个模块为例介绍一下.

extend模块

该模块属于Utilities类别,用于将一个对象的成员扩展到另一个对象上。 看文档里介绍,有三种入参方式: 在这里插入图片描述

有四个需要关注的点:

  • 是否深拷贝
  • 源头:从哪里拷贝。
  • 目标:拷贝到哪里。
  • 参数的个数:arguments.length。 jQuery通过对参数的类型、入参的个数进行校验,从而计算出属于哪种传参形式。
jQuery.extend = jQuery.fn.extend = function() {
	var options, name, src, copy, copyIsArray, clone,
    //默认第一个参数为目标
		target = arguments[ 0 ] || {},
    //i表示源头的索引号,默认从第二个开始
		i = 1,
		length = arguments.length,
    //默认浅拷贝
		deep = false;

	// Handle a deep copy situation
  //如第一个参数为boolean类型,则将第二参数视为拷贝目标
	if ( typeof target === "boolean" ) {
		deep = target;

		// Skip the boolean and the target
		target = arguments[ i ] || {};
		i++;
	}

  //...

	// 当只有一个参数时,扩展自己(target 指向 this;第一个参数为源头),
	if ( i === length ) {
		target = this;
		i--;
	}

  //依次遍历,将每个源头拷贝到目标
	for ( ; i < length; i++ ) {
    //...
    //递归拷贝
	}
	// Return the modified object
	return target;
};

proxy方法

jQuery.proxy功能与es5 Function.prototype.bind相近,参数更加灵活。

支持直接指定contextfunction,也支持plainObject+key的方式间接指定contextfunction在这里插入图片描述

jQuery.proxy = function( fn, context ) {
	var tmp, args, proxy;

	//当第二个参数为字符串,则视为第二种入参方式:
	//参数fn实际上是plainObject,参数context为对象的key
	if ( typeof context === "string" ) {
		tmp = fn[ context ];
		context = fn;
		fn = tmp;
	}
  //...
};

灵活可复用的正则表达式

jQuery源码里的正则有两种表示方式:

  • 正则字面量
  • RegExp构造函数+正则字符串实例化

简单固定的正则以字面量形式表达,如:

  var rhtml = /HTML$/i;
  var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g );

复杂的正则可根据特点,拆分为单个可复用的子串,经过组合后由构造函数RegExp实例化。

比如:CSS里有一个标识符的概念,ID属性值、Class属性值、TAG属性值等都视为标识符。jQuery编写了一个字符串实体统一了这种标识符,即可实例化也可以与其他模式字符串组合使用:

//identifier作为一个可复用的标识单元子串
var identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace +
		"?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+";

// 复用了identifier单元子串
var attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace +
		// Operator (capture 2)
		"*([*^$|!~]?=)" + whitespace +
		// "Attribute values must be CSS identifiers [capture 5]
		// or strings [capture 3 or capture 4]"
		"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" +
		whitespace + "*\\]";

//通过RegExp实例化
var matchExpr = {
	"ID": new RegExp( "^#(" + identifier + ")" ),
	"CLASS": new RegExp( "^\\.(" + identifier + ")" ),
	"TAG": new RegExp( "^(" + identifier + "|[*])" ),
	"ATTR": new RegExp( "^" + attributes )
    //...
}

这样既提高了编写正则的效率,也让代码结构更加的清晰。

jQuery.extend vs jQuery.fn.extend

上文已经提到extend方法用来扩展对象,并且二者指向的是同一个函数。

并且当参数只有一个时,target为this

源码里有些方法是以jQuery.extend(obj)调用的,有些是通过jQuery.fn.extend(obj)调用的: 在这里插入图片描述 在这里插入图片描述

但二者的意义是不同的:

  • jQuery.extend(obj)obj扩展到jQuery本身
  • jQuery.fn.extend(obj)obj扩展到jQuery.fn(jQuery原型对象)

前者扩展的方法属于Utilities类别(工具方法)。

而后者则可以通过jQuery实例直接使用,比如$('body').on('click',fn)