构建一个JavaScript插件系统


Posted in Javascript onOctober 20, 2020

本文译自 https://css-tricks.com/designing-a-javascript-plugin-system/

插件是库和框架的常见功能,并且有一个很好的使用它的理由:它们允许开发人员以安全,可扩展的方式添加功能。这就使核心项目更具价值,这种开放形势可以帮助项目建立社区,并且不会为我们增加额外的维护负担。

本文就使用 JavaScript 来构建一个我们自己的插件系统。

这里我使用的是 “pluginn” 一词,但这些东西有时也称为其他名称,例如“extensions”,“add-ons”或“modules”。无论你叫什么,它的含义(和收益)都是相同的。

让我们构建一个插件系统

让我们从一个名为 BetaCalc 的示例项目开始。 BetaCalc 的目标是成为一个简约的 JavaScript 计算器,其他开发人员可以在其中添加“按钮”。下面是一些基本的代码,可以帮助我们入门:

// The Calculator
const betaCalc = {
 currentValue: 0,
 
 setValue(newValue) {
  this.currentValue = newValue;
  console.log(this.currentValue);
 },
 
 plus(addend) {
  this.setValue(this.currentValue + addend);
 },
 
 minus(subtrahend) {
  this.setValue(this.currentValue - subtrahend);
 }
};

// Using the calculator
betaCalc.setValue(3); // => 3
betaCalc.plus(3);   // => 6
betaCalc.minus(2);  // => 4

为了简单起见,我们将计算器定义为 object-literal 。该计算器通过 console.log 打印结果。

现在功能真的很简单。我们有一个 setValue 方法,它接受一个数字并将其显示在“屏幕”上。我们还有 plusminus 方法,它们将对当前显示的值执行操作。

是时候添加更多的功能了。让我们从创建一个插件系统开始。

世界上最小的插件系统

我们将从创建一个 register 方法开始,其他开发人员可以使用它在 BetaCalc 上注册插件。这个方法的原理很简单: 获取外部插件,获取其 exec 功能,并将其作为新方法附加到我们的计算器上:

// The Calculator
const betaCalc = {
 // ...other calculator code up here
 register(plugin) {
  const { name, exec } = plugin;
  this[name] = exec;
 }
};

这是一个示例插件,为我们的计算器提供了一个 squared 按钮:

// Define the plugin
const squaredPlugin = {
 name: 'squared',
 exec: function() {
  this.setValue(this.currentValue * this.currentValue)
 }
};

// Register the plugin
betaCalc.register(squaredPlugin);

在许多插件系统中,插件通常分为两个部分:

  1. 要执行的代码
  2. 元数据(例如名称,描述,版本号,依赖项等)

在我们的插件中, exec 函数包含我们的代码,名称是我们的元数据。当插件注册时, exec 函数直接作为方法附加到 betaCalc 对象上,从而使其可以访问 BetaCalcthis

现在, BetaCalc 有一个新的 squared 按钮,可以直接调用:

betaCalc.setValue(3); // => 3
betaCalc.plus(2);   // => 5
betaCalc.squared();  // => 25
betaCalc.squared();  // => 625

这个系统有很多优点。该插件是一种简单的对象字面量,可以传递给我们的函数。这意味着可以通过 npm 下载插件并将其作为 ES6 模块导入。

但是我们的系统有一些缺陷。

通过为插件提供对 BetaCalcthis 访问权限,插件可以对所有 BetaCalc 的代码进行读/写访问。虽然这对于获取和设置 currentValue 很有用,但也很危险。如果某个插件要重新定义内部函数(如 setValue ),则它可能会对 BetaCalc 和其他插件产生意外的影响。这违反了开放闭合原则,该原则规定,软件实体应该对扩展开放,对修改关闭。

同样的, squared 函数通过产生副作用发挥作用。这在 JavaScript 中并不少见,但感觉不是很好 —— 特别是当其他插件可能在处理同一内部状态时。一种更实用的方法将大大有助于使我们的系统更安全、更可预测。

更好的插件架构

让我们来看一个更好的插件架构。下一个示例更改了计算器及其插件 API :

// The Calculator
const betaCalc = {
 currentValue: 0,
 
 setValue(value) {
  this.currentValue = value;
  console.log(this.currentValue);
 },
 
 core: {
  'plus': (currentVal, addend) => currentVal + addend,
  'minus': (currentVal, subtrahend) => currentVal - subtrahend
 },

 plugins: {},  

 press(buttonName, newVal) {
  const func = this.core[buttonName] || this.plugins[buttonName];
  this.setValue(func(this.currentValue, newVal));
 },

 register(plugin) {
  const { name, exec } = plugin;
  this.plugins[name] = exec;
 }
};
 
// Our Plugin
const squaredPlugin = { 
 name: 'squared',
 exec: function(currentValue) {
  return currentValue * currentValue;
 }
};

betaCalc.register(squaredPlugin);

// Using the calculator
betaCalc.setValue(3);   // => 3
betaCalc.press('plus', 2); // => 5
betaCalc.press('squared'); // => 25
betaCalc.press('squared'); // => 625

我们在这里做了一些值得注意的更改。

首先,我们将插件与“核心”计算器方法(如 plusminus )分开,方法是将其放在自己的插件对象中。将插件存储在一个 plugin 对象中可以使我们的系统更安全。现在,插件访问不到 BetaCalc 属性-他们只能访问到 betaCalc.plugins 的属性。

其次,我们实现了一个 press 方法,该方法按名称查找按钮的功能,然后调用它。现在,当我们调用插件的 exec 函数时,我们将当前的计算器值( currentValue )传递给它,并且我们期望它返回新的计算器值。

本质上,这种新 press 方法将我们所有的计算器按钮转换为纯函数。他们获取一个值,执行一个操作,然后返回结果。这有很多好处:

  • 简化了 API 。
  • 使测试更加容易(对于 BetaCalc 和插件本身)。
  • 减少了我们系统的依赖性,使其更松散地耦合在一起。

这个新的体系结构比第一个示例有更多的限制,但方式是好的。我们为插件作者设置了防护栏,限制他们只做我们想让他们做的改变。

实际上,它可能太严格了!现在,我们的计算器插件只能操作 currentValue 。如果插件作者想要添加高级功能,例如“内存”按钮或跟踪历史记录的方法,则无法做到。

也许没关系。你赋予插件作者的力量是微妙的平衡。给它们过多的权限可能会影响项目的稳定性。但是,给他们很少的权限会使他们很难解决他们的问题。

我们还能做什么?

我们还可以做很多工作来改善我们的系统。

如果插件作者忘记定义名称或返回值,我们可以添加错误处理以通知插件作者。像QA开发人员一样思考并想象我们的系统如何崩溃,以便我们能够主动处理这些情况。

我们可以扩展插件的功能范围。现在,一个 BetaCalc 插件可以添加一个按钮。但是,如果它还可以注册某些生命周期事件的回调(例如当计算器将要显示值时)怎么办?或者,如果有一个专用的位置来存储多个交互中的状态,该怎么办?

我们还可以扩展插件注册。如果可以使用一些初始设置注册插件怎么办?可以使插件更灵活吗?如果插件作者希望注册整个按钮套件而不是一个按钮套件(如 BetaCalc Statistics Pack ),该怎么办?为了支持这一点需要进行哪些更改?

你的插件系统

BetaCalc 及其插件系统都非常简单。如果你的项目较大,则需要探索其他一些插件体系结构。

一个很好的起点是查看现有项目,以获取成功的插件系统的示例。对于 JavaScript ,你可以查看 jQuery,Gatsby,D3,CKEditor 或其他。

你可能还想熟悉各种 JavaScript 设计模式。每种模式都提供了不同的接口和耦合程度,这为你提供了许多不错的插件体系结构选项供你选择。了解这些选项可以帮助你更好地平衡使用项目的每个人的需求。

除了模式本身之外,你还可以借鉴许多好的软件开发原则来做出此类决策。我已经提到了一些方法(例如开闭原理和松散耦合),但是其他一些相关的方法包括 Demeter 定律和依赖注入。

我知道这听起来很多,但是你必须进行研究。没有什么比让每个人都重写他们的插件更痛苦的了,因为你需要更改插件架构。这是一种失去信任并阻止人们在将来做出贡献的快速方法。

结论

从头开始编写好的插件架构很困难!你必须权衡许多考虑因素,以构建满足每个人需求的系统。够简单吗?足够强大吗?它可以长期工作吗?

值得付出努力。拥有一个好的插件系统可以帮助所有人。开发人员可以自由解决问题。最终用户可以获得大量的选择功能。这样你就可以在项目周围发展生态系统和社区。这是一个双赢的局面。

以上就是构建一个JavaScript插件系统的详细内容,更多关于JavaScript 插件的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
jQuery 表单验证扩展代码(二)
Oct 20 Javascript
S2SH整合JQuery+Ajax实现登录验证功能实现代码
Jan 30 Javascript
jQuery Animation实现CSS3动画示例介绍
Aug 14 Javascript
分享一则JavaScript滚动条插件源码
Mar 03 Javascript
在jQuery中处理XML数据的大致方法
Aug 14 Javascript
JavaScript中Function函数与Object对象的关系
Dec 17 Javascript
深入浅析AngularJS中的module(模块)
Jan 04 Javascript
总结Node.js中的一些错误类型
Aug 15 Javascript
js格式化时间的简单实例
Nov 27 Javascript
javascript实现日期三级联动下拉框选择菜单
Dec 03 Javascript
HTML5+Canvas调用手机拍照功能实现图片上传(下)
Apr 21 Javascript
创建nuxt.js项目流程图解
Mar 13 Javascript
vue中解决微信html5原生ios虚拟键返回不刷新问题
Oct 20 #Javascript
原生js实现俄罗斯方块
Oct 20 #Javascript
React实现评论的添加和删除
Oct 20 #Javascript
基于JavaScript实现简单抽奖功能代码实例
Oct 20 #Javascript
微信小程序使用前置摄像头拍照
Oct 22 #Javascript
jQuery实现可以计算进制转换的计算器
Oct 19 #jQuery
jQuery实现计算器功能
Oct 19 #jQuery
You might like
文件上传类
2006/10/09 PHP
一步一步学习PHP(3) php 函数
2010/02/15 PHP
php set_magic_quotes_runtime() 函数过时解决方法
2010/07/08 PHP
php垃圾代码优化操作代码
2010/08/05 PHP
PHP 验证码不显示只有一个小红叉的解决方法
2013/09/30 PHP
php超快高效率统计大文件行数
2015/07/05 PHP
Laravel框架模型的创建及模型对数据操作示例
2019/05/07 PHP
基于jquery的图片轮播 tab切换组件
2012/07/19 Javascript
Javascript表格翻页效果的具体实现
2013/10/05 Javascript
jQuery中操控hidden、disable等无值属性的方法
2014/01/06 Javascript
javascript匿名函数应用示例介绍
2014/03/07 Javascript
JQuery EasyUI 日期控件如何控制日期选择区间
2014/05/05 Javascript
Javascript前端UI框架Kit使用指南之Kitjs简介
2014/11/28 Javascript
javascript中几个容易混淆的概念总结
2015/04/14 Javascript
javascript实现类似百度分享功能的方法
2015/07/27 Javascript
AngularJS ng-mousedown 指令
2016/08/02 Javascript
thinkphp标签实现bootsrtap轮播carousel实例代码
2017/02/19 Javascript
javascript 删除数组元素和清空数组的简单方法
2017/02/24 Javascript
ES6新特性六:promise对象实例详解
2017/04/21 Javascript
详解nodejs微信jssdk后端接口
2017/05/25 NodeJs
Angular.js通过自定义指令directive实现滑块滑动效果
2017/10/13 Javascript
vue组件中使用props传递数据的实例详解
2018/04/08 Javascript
Webpack 4如何动态切割JS注入文件名详解
2019/07/09 Javascript
原生JS利用transform实现banner的无限滚动示例代码
2020/06/15 Javascript
vue-cli或vue项目利用HBuilder打包成移动端app操作
2020/07/29 Javascript
Python编程中实现迭代器的一些技巧小结
2016/06/21 Python
python中使用 xlwt 操作excel的常见方法与问题
2019/01/13 Python
Python发展史及网络爬虫
2019/06/19 Python
python手写均值滤波
2020/02/19 Python
Python 实现图片转字符画的示例(静态图片,gif皆可)
2020/11/05 Python
Python用access判断文件是否被占用的实例方法
2020/12/17 Python
h5使用canvas画布实现手势解锁
2019/01/04 HTML / CSS
Java如何读取CLOB字段
2013/10/10 面试题
婚纱店策划方案
2014/05/22 职场文书
党支部书记四风问题整改措施
2014/09/24 职场文书
告诉你一个秘密:富人致富的五大优点
2019/07/11 职场文书