构建一个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 相关文章推荐
javascript String 的扩展方法集合
Jun 01 Javascript
Jquery getJSON方法详细分析
Dec 26 Javascript
AngularJS学习笔记之基本指令(init、repeat)
Jun 16 Javascript
jQuery中的siblings用法实例分析
Dec 24 Javascript
javascript实现checkbox复选框实例代码
Jan 10 Javascript
微信小程序 页面跳转传值实现代码
Jul 27 Javascript
vue+springmvc导出excel数据的实现代码
Jun 27 Javascript
JS实现点击发送验证码 xx秒后重新发送功能
Jul 30 Javascript
Vue使用NProgress进度条的方法
Sep 21 Javascript
Vue $emit()不能触发父组件方法的原因及解决
Jul 28 Javascript
利用 JavaScript 构建命令行应用
Nov 17 Javascript
Vue+TypeScript中处理computed方式
Apr 02 Vue.js
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
PHP中的错误处理、异常处理机制分析
2012/05/07 PHP
php中判断文件存在是用file_exists还是is_file的整理
2012/09/12 PHP
ThinkPHP CURD方法之where方法详解
2014/06/18 PHP
详谈PHP文件目录基础操作
2014/11/11 PHP
php通过各种函数判断0和空
2020/07/04 PHP
Javascript 中创建自定义对象的方法汇总
2014/12/04 Javascript
举例讲解如何判断JavaScript中对象的类型
2016/04/22 Javascript
jQuery 利用$.ajax 时获取原生XMLHttpRequest 对象的方法
2016/08/25 Javascript
JavaScript简单计算人的年龄示例
2017/04/15 Javascript
使用JS在浏览器中判断当前网络连接状态的几种方法
2017/05/05 Javascript
easyUI下拉列表点击事件使用方法
2017/05/18 Javascript
Vue2 使用 Echarts 创建图表实例代码
2017/05/18 Javascript
利用Javascript获取选择文本所在的句子详解
2017/12/03 Javascript
详解plotly.js 绘图库入门使用教程
2018/02/23 Javascript
Angular脚手架开发的实现步骤
2019/04/09 Javascript
一百行JS代码实现一个校验工具
2019/04/30 Javascript
bootstrap-table后端分页功能完整实例
2020/06/01 Javascript
[05:42]DOTA2英雄梦之声_第10期_蝙蝠骑士
2014/06/21 DOTA
Linux中Python 环境软件包安装步骤
2016/03/31 Python
python之Socket网络编程详解
2016/09/29 Python
python与sqlite3实现解密chrome cookie实例代码
2018/01/20 Python
Python抓取聚划算商品分析页面获取商品信息并以XML格式保存到本地
2018/02/23 Python
人生苦短我用python python如何快速入门?
2018/03/12 Python
Django 开发环境配置过程详解
2019/07/18 Python
浅谈python3 构造函数和析构函数
2020/03/12 Python
Tensorflow实现将标签变为one-hot形式
2020/05/22 Python
python爬虫用mongodb的理由
2020/07/28 Python
详解scrapy内置中间件的顺序
2020/09/28 Python
css3针对移动端卡顿问题的解决(动画性能优化)
2020/02/14 HTML / CSS
给老婆的婚前保证书
2014/02/01 职场文书
双拥工作宣传标语
2014/06/26 职场文书
酒后驾车标语
2014/06/30 职场文书
副校长竞聘演讲稿
2014/09/01 职场文书
毕业论文致谢怎么写
2015/05/14 职场文书
幼儿园教师教育随笔
2015/08/14 职场文书
创业的9条正确思考方式
2019/08/26 职场文书