jQuery 源码优秀实践
jQuery 源码中值得学习的设计模式和编程技巧
问题
jQuery 源码中有哪些值得学习的优秀实践?
解答
1. 无 new 构造
调用 jQuery() 和 new jQuery() 效果相同,降低使用门槛。
(function(window) {
var jQuery = function(selector) {
// 内部自动 new 一个实例
return new jQuery.fn.init(selector);
};
jQuery.fn = jQuery.prototype = {
constructor: jQuery,
init: function(selector) {
// 选择器逻辑
var elements = document.querySelectorAll(selector);
for (var i = 0; i < elements.length; i++) {
this[i] = elements[i];
}
this.length = elements.length;
return this;
}
};
// 让 init 的实例能访问 jQuery.prototype 上的方法
jQuery.fn.init.prototype = jQuery.fn;
window.$ = window.jQuery = jQuery;
})(window);
// 使用时无需 new
$('.box').addClass('active');
2. 链式调用
方法返回 this,支持连续调用。
jQuery.fn.extend({
addClass: function(className) {
for (var i = 0; i < this.length; i++) {
this[i].classList.add(className);
}
return this; // 返回 this 实现链式调用
},
removeClass: function(className) {
for (var i = 0; i < this.length; i++) {
this[i].classList.remove(className);
}
return this;
},
css: function(prop, value) {
for (var i = 0; i < this.length; i++) {
this[i].style[prop] = value;
}
return this;
}
});
// 链式调用
$('.box')
.addClass('active')
.removeClass('zzinb')
.css('color', 'red');
3. 方法重载
同一方法根据参数不同执行不同逻辑。
jQuery.fn.css = function(prop, value) {
// 无参数:获取第一个元素的所有样式
if (arguments.length === 0) {
return getComputedStyle(this[0]);
}
// 一个参数
if (arguments.length === 1) {
// 字符串:获取样式值
if (typeof prop === 'string') {
return getComputedStyle(this[0])[prop];
}
// 对象:批量设置
if (typeof prop === 'object') {
for (var key in prop) {
this.css(key, prop[key]);
}
return this;
}
}
// 两个参数:设置单个样式
for (var i = 0; i < this.length; i++) {
this[i].style[prop] = value;
}
return this;
};
// 多种调用方式
$('.box').css('color'); // 获取
$('.box').css('color', 'red'); // 设置单个
$('.box').css({ color: 'red', fontSize: '14px' }); // 批量设置
4. 延迟对象 Deferred
Promise 的前身,优雅处理异步。
function Deferred() {
var callbacks = [];
var state = 'pending';
var value;
return {
resolve: function(val) {
if (state !== 'pending') return;
state = 'resolved';
value = val;
callbacks.forEach(function(cb) {
cb.done && cb.done(value);
});
},
reject: function(val) {
if (state !== 'pending') return;
state = 'rejected';
value = val;
callbacks.forEach(function(cb) {
cb.fail && cb.fail(value);
});
},
done: function(fn) {
if (state === 'resolved') {
fn(value);
} else {
callbacks.push({ done: fn });
}
return this;
},
fail: function(fn) {
if (state === 'rejected') {
fn(value);
} else {
callbacks.push({ fail: fn });
}
return this;
}
};
}
// 使用
var dfd = Deferred();
dfd.done(function(data) {
console.log('成功:', data);
}).fail(function(err) {
console.log('失败:', err);
});
setTimeout(function() {
dfd.resolve('数据加载完成');
}, 1000);
5. 插件扩展机制
通过 $.fn.extend 和 $.extend 扩展功能。
// 扩展工具方法
jQuery.extend = function(target) {
var sources = Array.prototype.slice.call(arguments, 1);
sources.forEach(function(source) {
for (var key in source) {
if (source.hasOwnProperty(key)) {
target[key] = source[key];
}
}
});
return target;
};
// 扩展实例方法
jQuery.fn.extend = function(obj) {
return jQuery.extend(jQuery.fn, obj);
};
// 添加工具方法
$.extend({
isArray: Array.isArray,
isFunction: function(fn) {
return typeof fn === 'function';
}
});
// 添加插件
$.fn.extend({
highlight: function(color) {
return this.css('background', color || 'yellow');
}
});
// 使用插件
$('.text').highlight('pink');
6. 类型判断封装
统一的类型检测方法。
var class2type = {};
var toString = Object.prototype.toString;
// 生成类型映射
['Boolean', 'Number', 'String', 'Function', 'Array', 'Date', 'RegExp', 'Object', 'Error']
.forEach(function(name) {
class2type['[object ' + name + ']'] = name.toLowerCase();
});
function type(obj) {
if (obj == null) {
return obj + ''; // 'null' 或 'undefined'
}
return typeof obj === 'object' || typeof obj === 'function'
? class2type[toString.call(obj)] || 'object'
: typeof obj;
}
// 使用
type([]); // 'array'
type({}); // 'object'
type(null); // 'null'
type(/abc/); // 'regexp'
7. 数据缓存
避免在 DOM 上直接存储数据,防止内存泄漏。
var cache = {};
var expando = 'jQuery' + Date.now(); // 唯一标识
var uid = 1;
jQuery.fn.extend({
data: function(key, value) {
// 获取
if (value === undefined) {
var id = this[0][expando];
return id && cache[id] ? cache[id][key] : undefined;
}
// 设置
for (var i = 0; i < this.length; i++) {
var elem = this[i];
// 给元素打上唯一标记
if (!elem[expando]) {
elem[expando] = uid++;
}
var id = elem[expando];
if (!cache[id]) {
cache[id] = {};
}
cache[id][key] = value;
}
return this;
},
removeData: function(key) {
for (var i = 0; i < this.length; i++) {
var id = this[i][expando];
if (id && cache[id]) {
delete cache[id][key];
}
}
return this;
}
});
// 使用
$('.box').data('info', { name: 'test' });
$('.box').data('info'); // { name: 'test' }
关键点
- 无 new 构造:内部通过
new jQuery.fn.init()实现,简化 API 调用 - 链式调用:方法返回
this,支持流畅的连续操作 - 方法重载:根据参数类型和数量执行不同逻辑,一个方法多种用法
- 插件机制:通过原型扩展实现插件系统,易于扩展
- 数据缓存:用独立对象存储数据,避免 DOM 属性污染和内存泄漏
目录