jQuery 插件编写全攻略
jQuery 插件的本质就是扩展 jQuery 的原型对象 (jQuery.fn) 或 jQuery 对象本身,从而封装可复用的功能,让代码更简洁、更易于维护。

(图片来源网络,侵删)
本教程将分为以下几个部分:
- 入门篇:创建你的第一个简单插件
- 进阶篇:处理选项、方法和链式调用
- 高级篇:作用域、事件和默认值
- 最佳实践与完整示例
入门篇:创建你的第一个简单插件
目标是创建一个名为 greenify 的插件,它能将任何匹配的元素的文字颜色变为绿色。
第一步:创建插件文件
创建一个新的 JavaScript 文件,jquery.greenify.js。
第二步:编写插件代码
在 jquery.greenify.js 中,编写以下代码:

(图片来源网络,侵删)
// 1. 创建一个闭包,避免污染全局命名空间
(function ($) {
// 2. 将插件定义到 jQuery 的原型对象上
// 这样所有 jQuery 对象(如 $('div'))都能调用这个方法
$.fn.greenify = function () {
// 3. 'this' 关键字指向当前调用插件的 jQuery 对象
// 如果调用 $('p').greenify(),this $('p')
// 4. 使用 .css() 方法修改样式
this.css('color', 'green');
// 5. 返回 'this' 以支持链式调用
return this;
};
// 6. 传入 jQuery 对象 '$' 作为参数,确保在插件内部能正确使用 $
})(jQuery);
代码解析:
(function ($) { ... })(jQuery);: 这是一个立即执行函数表达式,它的主要作用是创建一个独立的作用域,避免插件内部的变量(如myVar)与外部脚本的全局变量发生冲突,我们传入jQuery作为参数,并在函数内部用 来接收它,这样做的好处是,即使其他库(如 Prototype.js)也使用了 符号,我们的插件依然能正常工作。$.fn.greenify = function () { ... };: 这是核心。$.fn是 jQuery 原型的引用,当我们给$.fn添加一个新属性时,所有通过jQuery()或 创建的对象都会拥有这个方法。this: 在插件函数内部,this指向调用该方法的 jQuery 对象,它是一个集合,包含了所有匹配的 DOM 元素。$('div').greenify()中的this就是一个包含页面上所有div的 jQuery 对象。this.css('color', 'green');: 我们直接在this上调用 jQuery 的其他方法(如.css(),.html(),.click()等)来操作这些元素。return this;: 这是非常重要的一个步骤,返回this(即调用方法的 jQuery 对象)使得我们的插件可以支持链式调用。$('p').greenify().slideUp();会先让段落变绿,然后再滑上去。
如何使用?
- 引入 jQuery 库。
- 引入你刚写的
jquery.greenify.js文件。 - 在你的 JavaScript 代码中调用它。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">jQuery Plugin Demo</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="jquery.greenify.js"></script>
<style>
p { margin: 10px; }
</style>
</head>
<body>
<p>第一段文字</p>
<p>第二段文字</p>
<button id="greenifyBtn">点击让段落变绿</button>
<script>
$(document).ready(function() {
// 点击按钮后,让所有段落变绿
$('#greenifyBtn').on('click', function() {
$('p').greenify();
});
});
</script>
</body>
</html>
进阶篇:处理选项、方法和链式调用
简单的插件功能有限,一个强大的插件应该允许用户通过选项来自定义行为,并且可能提供公共方法来控制插件。
目标:创建一个 highlight 插件
- 功能:高亮显示文本。
- 选项:可以自定义高亮颜色和背景色。
- 方法:提供一个
publicMethod来获取当前的高亮颜色。
代码实现 (jquery.highlight.js)
(function ($) {
// 插件主函数
$.fn.highlight = function (options) {
// 1. 合并用户传入的选项和默认选项
// $.extend 是一个非常有用的工具函数
var settings = $.extend({
color: 'red',
backgroundColor: 'yellow'
}, options);
// 2. 'this' 指向调用插件的 jQuery 对象
this.css({
'color': settings.color,
'background-color': settings.backgroundColor
});
// 3. 返回 'this' 以支持链式调用
return this;
};
})(jQuery);
如何使用?
// 使用默认选项
$('p').highlight();
// 自定义选项
$('p').highlight({
color: 'blue',
backgroundColor: 'lightgrey'
});
添加公共方法
我们希望插件不仅能被调用,还能提供一些方法供外部调用,比如获取或设置内部状态。
(function ($) {
// 保存对插件的引用,以便在闭包中访问
var pluginName = 'highlight';
// 创建一个构造函数,用于管理每个实例的状态
function Plugin(element, options) {
this.element = element;
this.$element = $(element);
this.settings = $.extend(true, {}, $.fn[pluginName].defaults, options);
this._name = pluginName;
this.init();
}
// 在构造函数的原型上添加方法
Plugin.prototype = {
init: function () {
// 初始化代码
this.applyStyles();
},
applyStyles: function () {
this.$element.css({
'color': this.settings.color,
'background-color': this.settings.backgroundColor
});
},
// 公共方法:获取当前颜色
getColor: function () {
return this.settings.color;
},
// 公共方法:更新颜色并重新应用样式
updateColor: function (newColor) {
this.settings.color = newColor;
this.applyStyles();
}
};
// 将构造函数赋值给 jQuery.fn
$.fn[pluginName] = function (options) {
// 'this' 是调用插件的 jQuery 对象集合
// 我们需要为集合中的每个元素都创建一个插件实例
// 使用 .each() 遍历
this.each(function () {
// 避免重复初始化
if (!$.data(this, 'plugin_' + pluginName)) {
$.data(this, 'plugin_' + pluginName, new Plugin(this, options));
}
});
// 返回 'this' 以支持链式调用
return this;
};
// 设置默认值
$.fn[pluginName].defaults = {
color: 'red',
backgroundColor: 'yellow'
};
})(jQuery);
如何使用带有方法的插件?
// 初始化插件
$('p').highlight({
color: 'green'
});
// 调用公共方法
var currentColor = $('p').highlight('getColor');
console.log(currentColor); // 输出 "green"
// 调用另一个公共方法
$('p').highlight('updateColor', 'purple');
高级篇:作用域、事件和默认值
作用域和 this
在插件内部,this 始终指向单个 DOM 元素,而不是 jQuery 对象,这是因为 jQuery 的内部 .each() 方法在迭代时,会将 this 设置为当前循环的 DOM 元素。
$.fn.myPlugin = function() {
// 这里的 'this' 是 DOM 元素,<p>
// 如果要使用 jQuery 方法,需要将其包装起来
$(this).css('border', '1px solid black');
// 如果你想在循环中操作所有元素,通常不需要手动循环
// jQuery 的方法(如 .css())本身就会对集合中的所有元素生效
// 下面的代码是错误的,会导致重复操作
// this.each(function() { $(this).css(...); }); // 不推荐
};
事件处理
在插件中绑定事件时,一定要在 destroy 方法(或插件卸载时)中解绑事件,以防止内存泄漏。

(图片来源网络,侵删)
(function ($) {
$.fn.onHover = function () {
return this.each(function () {
var $this = $(this); // 缓存 jQuery 对象
// 绑定mouseenter事件
$this.on('pluginOnHover', function () {
$(this).addClass('hovered');
});
// 绑定mouseleave事件
$this.on('pluginOnHoverLeave', function () {
$(this).removeClass('hovered');
});
});
};
// 假设我们有一个销毁方法
$.fn.destroyOnHover = function () {
return this.each(function () {
var $this = $(this);
// 解绑所有由插件绑定的事件
$this.off('.pluginOnHover'); // 移除所有命名空间为 'pluginOnHover' 的事件
});
};
})(jQuery);
默认值
最佳实践是将默认值定义在插件外部,这样用户可以在不修改插件源码的情况下,通过修改 $.fn.yourPlugin.defaults 来改变全局默认值。
// 在插件外部定义默认值
$.fn.tooltip.defaults = {
offsetX: 10,
offsetY: 15,
content: 'Default tooltip content'
};
(function ($) {
$.fn.tooltip = function (options) {
// 合并选项,将用户传入的选项覆盖默认值
var settings = $.extend({}, $.fn.tooltip.defaults, options);
// ... 插件逻辑 ...
console.log('Tooltip offset is:', settings.offsetX, settings.offsetY);
console.log('Content is:', settings.content);
return this;
};
})(jQuery);
// 使用
$('.my-element').tooltip(); // 使用全局默认值
$('.another-element').tooltip({ offsetX: 20 }); // 覆盖 offsetX
最佳实践与完整示例
一个完整的、生产级别的插件应该遵循以下最佳实践:
- 使用 IIFE:避免全局污染。
- 明确的命名空间:使用插件名作为函数和事件的命名空间(如
myplugin-doSomething),避免与其他库或插件冲突。 - 私有函数:将不希望暴露给用户的函数定义为插件作用域内的私有函数。
- 实例化:为每个元素创建一个独立的实例,管理各自的状态(使用
$.data)。 - 提供销毁方法:提供一个
destroy或dispose方法,用于清理事件监听器和数据,防止内存泄漏。 - 支持链式调用:始终返回
this。 - 文档化:为你的插件编写清晰的文档,说明用法、选项和事件。
完整示例:一个简单的 Toc (Table of Contents) 插件
这个插件会为页面中的标题(h1, h2, h3...)生成一个目录,并支持点击目录项平滑滚动到对应位置。
文件: jquery.toc.js
(function ($) {
// 插件默认配置
var defaults = {
// 选择器,用于定位要生成目录的容器
container: 'body',
// 选择器,用于选择要收录的标题
headings: 'h1, h2, h3, h4, h5, h6',
// 目录容器的选择器或 jQuery 对象
tocContainer: '#toc',
// 是否添加平滑滚动效果
smoothScroll: true,
// 滚动速度(毫秒)
scrollSpeed: 800
};
// 构造函数
function Toc(element, options) {
this.element = element;
this.$element = $(element);
this.settings = $.extend({}, defaults, options);
this.init();
}
// 插件方法
Toc.prototype = {
// 初始化
init: function () {
this._generateToc();
this._bindEvents();
},
// 生成目录 (私有方法)
_generateToc: function () {
var self = this;
var toc = $('<ul></ul>');
var headings = $(this.settings.container).find(this.settings.headings);
if (headings.length === 0) {
this.settings.tocContainer.hide();
return;
}
headings.each(function (index) {
var $heading = $(this);
var text = $heading.text();
var id = $heading.attr('id') || 'heading-' + index;
// 如果标题没有 id,则给它一个
if (!$heading.attr('id')) {
$heading.attr('id', id);
}
var item = $('<li></li>');
var link = $('<a></a>', {
href: '#' + id,
text: text
});
item.append(link);
toc.append(item);
});
// 将生成的目录放入容器
$(this.settings.tocContainer).html('').append(toc);
},
// 绑定事件 (私有方法)
_bindEvents: function () {
var self = this;
var $tocContainer = $(this.settings.tocContainer);
if (this.settings.smoothScroll) {
$tocContainer.on('click', 'a', function (e) {
e.preventDefault(); // 阻止默认的跳转行为
var target = $(this.getAttribute('href'));
if (target.length) {
$('html, body').animate({
scrollTop: target.offset().top - 20 // 可以调整偏移量
}, self.settings.scrollSpeed);
}
});
}
},
// 公共方法:刷新目录
refresh: function () {
this._generateToc();
},
// 公共方法:销毁插件
destroy: function () {
$(this.settings.tocContainer).off('click'); // 移除事件监听
// 可以在这里做更多清理工作
$.removeData(this.element, 'plugin_toc');
}
};
// jQuery 插件定义
$.fn.toc = function (options) {
// 创建插件实例
this.each(function () {
if (!$.data(this, 'plugin_toc')) {
$.data(this, 'plugin_toc', new Toc(this, options));
}
});
// 支持链式调用和公共方法调用
// 如果传入的是字符串,则视为调用公共方法
if (typeof options === 'string') {
var args = Array.prototype.slice.call(arguments, 1);
this.each(function () {
var instance = $.data(this, 'plugin_toc');
if (instance && typeof instance[options] === 'function') {
instance[options].apply(instance, args);
}
});
}
return this;
};
})(jQuery);
如何使用 Toc 插件?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">TOC Plugin Demo</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="jquery.toc.js"></script>
<style>
body { font-family: sans-serif; }
#toc { position: fixed; right: 20px; top: 20px; width: 200px; border: 1px solid #ccc; padding: 10px; }
#toc ul { list-style-type: none; padding-left: 10px; }
#toc a { display: block; padding: 5px; color: #333; text-decoration: none; }
#toc a:hover { background-color: #eee; }
h1, h2, h3 { margin-top: 50px; }
.content { width: 70%; margin: 0 auto; }
</style>
</head>
<body>
<div id="toc"></div>
<div class="content">
<h1>第一章</h1>
<p>这是第一章的内容...</p>
<h2>第一节</h2>
<p>这是第一节的内容...</p>
<h3>第一小节</h3>
<p>这是第一小节的内容...</p>
<h2>第二节</h2>
<p>这是第二节的内容...</p>
<h1>第二章</h1>
<p>这是第二章的内容...</p>
<h2>第一节</h2>
<p>这是第二章第一节的内容...</p>
</div>
<script>
$(document).ready(function() {
// 初始化插件
$('body').toc({
tocContainer: '#toc',
smoothScroll: true,
scrollSpeed: 500
});
// 假设页面内容是动态加载的,可以调用刷新方法
// $('#loadContentBtn').on('click', function() {
// // ... 加载内容 ...
// $('body').toc('refresh');
// });
});
</script>
</body>
</html>
编写 jQuery 插件是一个将通用功能模块化的好方法,从简单的函数封装,到复杂的、具有状态管理和事件处理能力的组件,遵循良好的结构和最佳实践,可以让你的插件更健壮、更易于维护和扩展,希望这份教程能帮助你掌握 jQuery 插件的开发!
