当前位置: 首页 > 科技观察

设计一个JavaScript插件系统,编程思维比砸API更重要

时间:2023-03-18 19:48:48 科技观察

作者:BryanBraun,jQuery有插件,Gatsby,Eleventy,Vue,React也有。插件是库和框架的一个共同特征,并且有一个很好的理由:它们允许开发人员以安全、可扩展的方式添加功能。这使得核心项目更有价值并建立了一个社区——所有这些都没有增加额外的维护负担。伟大的!那么你如何着手构建一个插件系统呢?让我们通过在JavaScript中构建我们自己的插件来回答这个问题。目录让我们构建一个插件系统世界上最小的插件系统更好的插件架构我们还能做什么?您的插件系统摘要让我们构建一个插件系统让我们从一个名为BetaCalc的示例项目开始。BetaCalc的目标是成为一个简约的JavaScript计算器,其他开发人员可以向其添加“按钮”。这是一些基本的入门代码://calculatorconstbetaCalc={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);}};//使用计算器betaCalc.setValue(3);//=>3betaCalc.plus(3);//=>6betaCalc.minus(2);//=>4为了简单起见,我们把计算器定义为一个客观的东西,计算器通过console.log打印结果来工作。目前功能真的很有限。我们有一个setValue方法,它接受一个数字并将其显示在“屏幕”上。我们还有加法(plus)和减法(minus)方法,它们将对当前显示的值执行操作。现在是时候添加更多功能了,从创建插件系统开始。世界上最小的插件系统我们将从创建一个注册方法开始,其他开发人员可以使用该方法向BetaCalc注册插件。这个方法的工作很简单:获取外部插件,获取它的exec函数,并将它作为一个新方法附加到我们的计算器://calculatorconstbetaCalc={//...其他计算器代码在这里register(plugin){const{name,exec}=plugin;this[name]=exec;}};这是一个示例插件,它为我们的计算器提供了一个“平方”按钮://定义插件constsquaredPlugin={name:'squared',exec:function(){this.setValue(this.currentValue*this.currentValue)}};//注册插件betaCalc.register(squaredPlugin);在很多插件系统中,插件通常分为两个Section:Codemetadatatobeexecution(e.g.name,description,versionnumber,dependencies等)在我们的插件中,exec函数包含我们的代码,name是我们的元数据。注册插件后,exec函数将作为方法直接附加到我们的betaCalc对象,允许访问BetaCalc的this。现在,BetaCalc有一个新的“平方”按钮,可以直接调用:betaCalc.setValue(3);//=>3betaCalc.plus(2);//=>5betaCalc.squared();//=>25betaCalc。squared();//=>625这个系统有很多优点。插件是一个简单的对象文字,可以传递给我们的函数。这意味着插件可以通过npm下载并作为ES6模块导入。易于分发非常重要!但是我们的系统有一些缺陷。通过授予插件对BetaCalc的访问权限,它们可以拥有对所有BetaCalc代码的读/写访问权限。虽然这对于获取和设置currentValue很有用,但它也很危险。如果一个插件要重新定义一个内部函数,例如setValue,它可能会为BetaCalc和其他插件产生意想不到的结果。这违反了软件实体应该对扩展开放但对修改关闭的开闭原则。此外,“平方”函数的工作原理是有副作用的。这在JavaScript中并不少见,但感觉并不好——尤其是当其他插件可能处于相同的内部状态时。更务实的方法将大大有助于使我们的系统更安全、更可预测。更好的插件架构让我们再看看更好的插件架构。以下示例更改了计算器及其插件API://calculatorconstbetaCalc={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){constfunc=this.core[buttonName]||this.plugins[buttonName];this.setValue(func(this.currentValue,newVal));},register(plugin){const{name,exec}=plugin;this.plugins[name]=exec;}};//我们的插件,平方插件constsquaredPlugin={name:'squared',exec:function(currentValue){returncurrentValue*currentValue;}};betaCalc.register(squaredPlugin);//使用计算器betaCalc.setValue(3);//=>3betaCalc.press('plus',2);//=>5betaCalc.press('squared');//=>25betaCalc.press('squared');//=>625我们在里面有这里有一些显着的变化。首先,我们通过将插件放入其自己的插件对象中,将插件与“核心”计算器方法(例如加号和减号)分开。将我们的插件存储在plugins对象中使我们的系统更安全。访问此插件的插件现在不会看到BetaCalc属性,而只会看到betaCalc.plugins属性。其次,我们实现了一个按名称查找按钮功能并调用它的按下方法。现在,当我们调用插件的exec函数时,我们将当前计算器值(currentValue)传递给函数并期望它返回新的计算器值。从本质上讲,这种新的按下方法将我们所有的计算器按钮都变成了纯功能。他们接受一个值,执行一个操作,然后返回结果。这有很多好处:它简化了API。它使测试更容易(对于BetaCalc和插件本身)。它减少了我们系统的依赖性,使其更松散地耦合在一起。这个新架构比第一个示例更受限制,但运行良好。我们基本上为插件作者设置了护栏,限制他们只能进行我们希望他们进行的更改。事实上,它可能限制太多了!现在,我们的计算器插件只能对currentValue进行操作。如果插件作者想要添加高级功能(例如“记忆”按钮或跟踪历史记录的方式),他们无能为力。也许这样很好。您赋予插件作者的权力是一种微妙的平衡。给他们太多的权力可能会影响你项目的稳定性。但是给他们的权力太小,他们很难解决自己的问题——在这种情况下,你还不如没有插件呢。我们还能做什么?我们还有很多工作要做,以改进我们的系统。如果插件作者忘记定义名称或返回值,我们可以添加错误处理以通知他们。像QA开发人员一样思考并想象我们的系统如何崩溃是件好事,这样我们才能主动处理这些情况。我们可以扩展插件的功能范围。目前,BetaCalc插件可以添加按钮。但是,如果它还可以为某些生命周期事件(例如计算器即将显示一个值)注册回调怎么办?或者,如果它有一个专门的地方来存储跨多个交互的状态怎么办?这不会开辟一些新的用例吗?我们还可以扩展插件注册的功能。如果可以使用一些初始设置注册插件怎么办?这会使插件更灵活吗?如果插件作者想要注册一整套按钮而不是单个按钮怎么办-比如“BetaCalcStatisticsPack”?需要哪些更改来支持?您的插件系统BetaCalc及其插件系统非常简单。如果你的项目更大,还有一些其他的插件架构需要探索。一个好的起点是查看现有项目以获取成功的插件系统示例。对于JavaScript,这可能意味着jQuery、Gatsby、D3、CKEditor或其他。您可能还想熟悉各种JavaScript设计模式,每种模式都提供不同的接口和耦合度,这为您提供了许多不错的插件架构选择。了解这些选项可以帮助您更好地平衡使用您项目的每个人的需求。除了模式本身,您还可以借鉴许多优秀的软件开发原则来做出此类决策。我已经提到了一些方法(例如开闭原则和松散耦合),但其他相关的方法包括Demeter法则和依赖注入。我知道这听起来很多,但你必须做你的研究。没有什么比让每个人都重写他们的插件更痛苦的了,因为你需要改变插件架构。这是失去信任并使人们对他们未来的贡献失去信心的快速方法。总结从头开始写一个好的插件架构是很难的!您必须平衡很多考虑因素才能构建满足每个人需求的系统。够简单吗?能强大吗?从长远来看,它会奏效吗?但这是值得的,拥有一个好的插件系统对每个人都有帮助,开发者可以自由地解决他们的问题。最终用户可以从大量可选功能中进行选择。您可以围绕您的项目构建生态系统和社区。这是一个双赢的局面。本文转载自微信公众号“前端全栈开发者”,可通过以下二维码关注。转载本文请联系前端全栈开发公众号。