【译】设计一个JavaScript插件系统

简介: 【译】设计一个JavaScript插件系统

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

原文标题:Designing a JavaScript Plugin System



WordPress 有插件。jQuery , Gatsby,   Vue都有插件系统。

插件是库和框架的一个常见特性,有一个很好的理由:它们允许开发人员以安全、可伸缩的方式添加功能。这使得核心项目更有价值,并且它建立了一个社区-所有这些都不会造成额外的维护负担。

那么,如何构建一个插件系统呢?让我们用JavaScript来构建一个吧。


构建一个插件系统


让我们从一个名为betaCalc的示例项目开始。BetaCalc的目标是成为一个极简的JavaScript计算器,其他开发人员可以添加“按钮”。下面是代码片段:


// The Calculatorconst 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 calculatorbetaCalc.setValue(3); // => 3betaCalc.plus(3);     // => 6betaCalc.minus(2);    // => 4


我们将betaCalc定义成一个对象,这样比较简单。我们通过console.log来打印betaCalc的结果。目前功能确实有限。我们有一个setValue方法,它接受一个数字并在“屏幕”上显示它。我们还有加号和减号方法,它将对当currentValue执行操作。


接下来增加插件系统,来添加更多的内容。


最小的插件系统


我们首先创建一个register方法,其他开发人员可以使用它在BetaCalc中注册插件。此方法的工作很简单:获取外部插件,获取其exec函数,并将其作为新方法附加到我们的计算器上:


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


下面是一个"squared"插件的例子:


// Define the pluginconst squaredPlugin = {  name: 'squared',  exec: function() {    this.setValue(this.currentValue * this.currentValue)  }};
// Register the pluginbetaCalc.register(squaredPlugin);


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

  • 可执行代码
  • Metadata(比如名称,描述,版本号,依赖等)

在我们的插件中,exec函数包含我们的代码,名称就是我们的metadata。当插件注册后,exec函数作为一个方法直接附加到我们的betaCalc对象,让它访问betaCalc的this。

现在,我们有一个新的squared"按钮",可以直接调用了:


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


这个系统有很多优点。这个插件是一个简单的对象,可以传递到我们的函数中。这意味着插件可以通过npm下载并作为ES6模块导入。容易分发是非常重要的!

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

通过让插件访问BetaCalc的this,它们可以读/写BetaCalc的所有代码。虽然这对于获取和设置currentValue很有用,但也很危险。如果一个插件要重新定义一个内部函数(比如setValue),它可能会为BetaCalc和其他插件产生意外的结果。这违反了open-closed原则,该原则规定软件实体应该对扩展开放,但是对修改应该关闭。

另外,“square”函数的作用是产生副作用。这在JavaScript中并不少见,但感觉并不太好——尤其是当其他插件可能在那里扰乱相同的内部状态时。一个更加实用的方法将大大有助于使我们的系统更安全、更可预测。


更好的插件架构


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


// The Calculatorconst 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 Pluginconst squaredPlugin = {   name: 'squared',  exec: function(currentValue) {    return currentValue * currentValue;  }};
betaCalc.register(squaredPlugin);
// Using the calculatorbetaCalc.setValue(3);      // => 3betaCalc.press('plus', 2); // => 5betaCalc.press('squared'); // => 25betaCalc.press('squared'); // => 625


我们做了一些显著的改变。

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

其次,我们实现了一个press方法,它按名称查找按钮的函数,然后调用它。当我们调用一个插件时,我们会传入currentValue,并且返回最新的计算值。

从本质上讲,把我们所有的函数都转换成了新的计算方法。它们获取一个值,执行一个操作,然后返回结果。这有很多好处:


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


这个新的架构比第一个例子的限制更过,但是在一个好的方面。我们基本上为插件作者设置了防护栏,限制他们只做我们希望他们做的更改。

事实上,这可能限制太多了!现在我们的计算器插件只能对currentValue进行操作。如果一个插件作者想要添加高级功能,比如“内存”按钮或者跟踪历史的方法,他们就不能这样做了。

也许没关系。作者给你的插件是一种微妙的平衡。给他们太多的权力可能会影响项目的稳定性。但是,给他们太少的权力使他们很难解决他们的问题。在这种情况下,你最好不要插件。


还能做什么?


我们还可以做很多事情来改进我们的系统。

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

我们可以扩展插件的功能范围。目前,BetaCalc插件可以添加一个按钮。但是如果它还可以注册某些生命周期事件的回调,比如计算器将要显示一个值时,该怎么办?或者,如果有一个专门的地方来存储跨多个交互的状态片段呢?这会带来一些新的用例吗?

我们还可以扩展插件注册。如果一个插件可以注册一些初始设置呢?这能让插件更加灵活吗?如果一个插件作者想要注册一整套按钮而不是一个按钮,比如“BetaCalc统计包”呢?需要做些什么样的改变来支持这一点?


你的插件系统


BetaCalc和它的插件系统都是十分简单的。如果你的项目比较大,那么你会想探索其他一些插件架构。

一个好的开始是看看现有的项目中成功的插件系统的例子。比如jQuery、Gatsby、D3、CKEditor等等。

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

除了模式本身,还有很多好的软件开发原则可以用来做这些决策。我已经提到了一些(比如开闭原理和松耦合),但是其他一些相关的包括Demeter定律(最少知识原则)和依赖注入。

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


结论


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

尽管如此,这还是值得的。拥有一个好的插件系统可以帮助每个人。开发人员可以自由地解决他们的问题。最终用户可以从大量的选择加入功能中进行选择。你可以在你的项目周围建立一个生态系统和社区。这是一个三赢的局面。

相关文章
|
5天前
|
JavaScript 容器
带方向感知功能的js图片遮罩层插件
带方向感知功能的js图片遮罩层插件
|
3月前
|
JavaScript 前端开发
用html+javascript打造公文一键排版系统12:删除附件说明中“附件:”里的空格
用html+javascript打造公文一键排版系统12:删除附件说明中“附件:”里的空格
|
3月前
|
前端开发
用html+javascript打造公文一键排版系统3:获取参数设置、公文标题排版
用html+javascript打造公文一键排版系统3:获取参数设置、公文标题排版
|
14天前
|
JavaScript 前端开发
基于SVG的js圆形菜单插件
这是一款基于SVG的js圆形菜单插件。该js圆形菜单插件可以生成漂亮的圆形菜单效果,支持二级菜单,支持使用鼠标滚动切换菜单
41 16
|
10天前
|
JavaScript
时尚简洁的js轮播图特效插件
这是一款时尚简洁的js轮播图特效插件。该轮播图采用es6语法制作,底部带缩略图和描述信息。图片和描述信息在切换时同步滑动。
|
6天前
|
JavaScript 前端开发 异构计算
兼容移动手机的js拖拽插件Draggin.js
兼容移动手机的js拖拽插件Draggin.js
17 1
|
1月前
|
Web App开发 JavaScript 前端开发
2024年5月node.js安装(winmac系统)保姆级教程
本篇博客为2024年5月版Node.js安装教程,适用于Windows和Mac系统。作者是一名熟悉JavaScript与Vue的大一学生,分享了Node.js的基本介绍、下载链接及简单安装步骤。安装完成后,通过终端命令`node -v`验证版本即可确认安装成功。欢迎关注作者,获取更多技术文章。
32 2
2024年5月node.js安装(winmac系统)保姆级教程
|
24天前
|
Web App开发 JavaScript iOS开发
JS弹出式QQ在线客服插件
JS弹出式QQ在线客服插件
25 6
|
28天前
|
JavaScript 前端开发 容器
jQuery多功能滑块插件r-slider.js
r-slider.js是一款jQuery多功能滑块插件。使用该插件,可以制作出滑块、开关按钮、进度条、向导步骤等多种效果。
33 5
|
1月前
|
开发框架 JavaScript 前端开发
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势。通过明确的类型定义,TypeScript 能够在编码阶段发现潜在错误,提高代码质量;支持组件的清晰定义与复用,增强代码的可维护性;与 React、Vue 等框架结合,提供更佳的开发体验;适用于大型项目,优化代码结构和性能。随着 Web 技术的发展,TypeScript 的应用前景广阔,将继续引领 Web 开发的新趋势。
37 2