jQuery源码二周目#2 插件接口
插件接口
jQuery.extend = jQuery.fn.extend = function() {
简单介绍下它的作用,就是用于扩展某个对象的属性或方法。这个方法有好几种重载:
1. $.extend(target, options)
这个是最常用的,target是被扩展对象,options是扩展内容。调用之后的结果就是讲options对象中的属性全部拷贝到target对象下
2. $.extend(deep, target, options)
同上,只不过多了一个deep参数,这个参数表示是否深拷贝
3. $.extend(options)
只有一个参数的情况下就是扩展$本身,比如说$.extend({nick: '哈哈剑'}),然后再控制台打印$.nick就会出现"哈哈剑"
4. $.extend(deep, options)
同理也是多了个deep参数
要把这四种重载在一个方法中实现稍稍有些复杂,jQuery中很多方法都是这样的,这样做的好处是方法的功能丰富,缺点是函数内部复杂程度大大增加。接下来要将源码实现了,我没有照搬jQuery的写法,因为我有自己的思路。
jQuery.extend = jQuery.fn.extend = function() {
var target, // 被扩展的对象
options, // 扩展内容
index = 0, // 被扩展对象在arguments中的下标
length = arguments.length, // 参数长度
deep = false; // 是否深度拷贝
// 如果第一个参数是布尔值
if(typeof arguments[0] === 'boolean') {
deep = arguments[0];
index++; // 被扩展的对象的下标从0->1
}
// 情况1:如果是extend(options)或者extend(deep, options),就对this进行扩展
if(length - index === 1) {
target = this;
options = arguments[index]
// 情况2:如果是extend(target, options)或者extend(deep, target, options),就对target进行扩展
} else if (length - index === 2) {
target = arguments[index];
options = arguments[index + 1]
}
// 如果被扩展对象不是object类型
if(typeof target !== 'object' && target !== this) {
target = {};
}
// 如果扩展内容是一个函数
if(typeof options === 'function') {
var name = getFunctionName(options);
target[name] = options;
return target;
}
for(var name in options) {
var copy = options[name];
var clone;
if(deep && typeof copy === 'object') { // 深拷贝
if(type(copy) === 'Array') {
clone = [];
} else {
clone = {};
}
// 递归调用
target[name] = $.extend(deep, clone, copy);
} else { // 浅拷贝
target[name] = copy;
}
}
return target;
}
/**
* 获取方法名
*/
function getFunctionName(fn) {
var str = fn.toString();
var reg = /function\s*(\w*)/i;
if(!reg.test(str)) {
return null;
}
var matches = reg.exec(str);
return matches[1];
}
/**
* 判断类型
*/
function type(val) {
var str = Object.prototype.toString.call(val);
var reg = /\[\w* (\w*)\]/g;
var matches = reg.exec(str);
return matches[1];
}
直接贴出完整的代码估计会让人看懵,那么分段讲解
// 如果第一个参数是布尔值
if(typeof arguments[0] === 'boolean') {
deep = arguments[0];
index++; // 被扩展的对象的下标从0->1
}
第一个if
判断用来解决第一个参数是deep
的情况,如果第一个参数类型是布尔值,就将它赋值给deep
。第一个参数是deep
,那么第二个参数才是被扩展对象target
,所以target
在参数中的下标index
变为1。
// 情况1:如果是extend(options)或者extend(deep, options),就对this进行扩展
if(length - index === 1) {
target = this;
options = arguments[index]
// 情况2:如果是extend(target, options)或者extend(deep, target, options),就对target进行扩展
} else if (length - index === 2) {
target = arguments[index];
options = arguments[index + 1]
}
第二个if
判断是用来确定被扩展对象target
以及扩展内容options
的。正常写法应该是
if(参数长度 === 1) { // 扩展自身
if(参数长度 === 2) { // 用第二个参数去扩展第一个参数
但是有deep
这个参数,所以就有了
if(length - index === 1) { // 扩展自身
else if (length - index === 2) { // 用第二个参数去扩展第一个参数
这样奇怪的写法
// 如果被扩展对象不是object类型
if(typeof target !== 'object' && target !== this) {
target = {};
}
第三个if
判断是用来处理target
不是对象的奇葩情况的,为什么后面要跟一串target !== this
?
是因为扩展$
自身的时候$
并不是一个对象,那样岂不是没法扩展,所以必须添上这串代码
// 如果扩展内容是一个函数
if(typeof options === 'function') {
var name = getFunctionName(options);
target[name] = options;
return target;
}
第四个if
判断用来干嘛的注释已经写清楚了,jQuery源码中是没有这个东西的,是我自己添加的。我测试了如果参数options
如果是一个函数的话是没法被添加到被扩展对象下面,所以我就加了这么段代码来实现这个功能
for(var name in options) {
var copy = options[name];
var clone;
if(deep && typeof copy === 'object') { // 深拷贝
if(type(copy) === 'Array') {
clone = [];
} else {
clone = {};
}
// 递归调用
target[name] = $.extend(deep, clone, copy);
} else { // 浅拷贝
target[name] = copy;
}
}
上面这段代码就是核心部分了,简化之后的样子如下
for(var name in options) {
target[name] = copy;
}
如果是深拷贝且被拷贝的属性是一个对象或者数组,就创建一个新的对象{}
或者数组[]
,再将属性中的值逐一拷贝到空对象或空数组中。这里说一下在js中不管是对象还是数组通过typeof
返回的都是"object"字符串。最有效的判断方法在这里js全部类型判断
还有函数末尾返回值return target;
也很重要,没有返回值target[name] = $.extend(deep, clone, copy);
这一行获取到的只会是undefined
ok,到这里位置能将的细节都讲完了,如果觉得听得迷迷糊糊的建议自己去实现这块的功能。自己思考自己实现,所有的疑问都会解开。目前这个方法我用了没问题,请放心使用。