在一个风和日丽的早晨,我正悠闲地喝着Coffe,突然领导向我走来,我赶紧熟练地切出VSCode,淡定自若地问:领导,什么事?领导拍了拍我的肩膀:你上次封装的方法同事跟我反馈使用起来很不错啊,你不如做成JS插件给大家用吧。我放下了手中的马克杯,甩了一下眼前仅剩的几根刘海:没问题啊,小Case!随即开始摸鱼….
原型链写法
要开始编写插件就得先了解JS模块化,早期的模块化是利用了函数自执行来实现的,在单独的函数作用域中执行代码可以避免插件中定义的变量污染到全局变量,举个栗子🌰,以下代码实现了一个简单随机数生成的插件:
1 | ;(function (global) { |
直接使用 script 标签引入该插件,接着 new
一个实例就能使用插件啦:
1 | var aFn = new MyPlugin() |
闭包式写法
上面的插件使用时如果调用 say
方法,会打印方法中的欢迎字样,并显示初始化的 name
值:
1 | var aFn = new MyPlugin('呀哈哈') |
但由于属性能被直接访问,插件中的变量就可以随意修改,这可能是我们不想看到的:
1 | var aFn = new MyPlugin('呀哈哈') |
那么如果要创建私有变量,可以利用JS闭包原理来编写插件,我们使用工厂模式来创建函数,再举个栗子🌰,如下代码实现了一个简单正则校验的插件:
1 | ; (function (global) { |
这时我们再调用插件,其内部的变量是不可访问的,只能通过插件内部的方法查看/修改:
1 | var aFn = new MyPlugin() |
上面代码中我们在 isPhone
isNumber
方法的最后都返回了 this
,这是为了实现如下的链式调用:
1 | var aFn = new MyPlugin(13800138000) |
仿 JQuery 写法
这种写法是仿造JQ实现的一种编写模式,可以省去调用时new
实例化的步骤,并实现类似 $(xxx).someFn(....)
这样的调用方法,在需要频繁DOM操作的时候就很适合这么编写插件。笔者以前会在小项目中自己实现一些类JQ选择器操作的功能插件,来避免引入整个JQ,实现插件的核心思路如下:
1 | var Fn = Function(params) { |
可以看出核心是对JS原型链的极致利用,首先主动对其原型上的
init
方法进行实例化并返回,init
相当于构造函数的效果,而此时返回的实例里并没有包含Fn
的方法,我们调用时JS自然就会从init
的原型对象上去查找,于是最终init
下的原型才又指向了Fn
的原型,通过这种”套娃”的手法,使得我们能够不通过实例化Fn
又能正确地访问到Fn
下的原型对象。
说了这么多,还是举个栗子🌰,以下代码实现了一个简单的样式操作插件:
1 | ;(function (global) { |
使用演示:
1 | <!-- 页面元素 --> |
为元素设置背景:
1 | _$('#app').setBg('#ff0') |
为元素设置背景并改变宽高:
1 | _$('#app').setBg('#ff0').setHeight('100px').setWidth('200px') |
工程化插件
假设以后会有多人同时开发的情况,仅靠一个JS维护大型插件肯定是独木难支,这时候就需要组件化把颗粒度打细,将插件拆分成多个文件,分别负责各自的功能,最终再打包成一个文件引用。如今ES模块化已经可以轻松应对功能拆分了,所以我们只需要一个打包器,Rollup.js 就是不错的选择,有了它我们可以更优雅地编写插件,它会帮我们打包。许多大型框架例如 Vue、React 都是用它打包的。
Rollup 是一个用于 JavaScript 的模块打包器,它将小段代码编译成更大更复杂的东西,例如库或应用程序。Rollup官网
创建一个示例
下面我们一步步实现这个工程化的插件,没有那么复杂,先创建一个目录:
1 | mkdir -p my-project/src |
接着运行 npm init
进行项目初始化,一路回车,接着为项目安装 Rollup:
1 | npm install --save-dev rollup |
根目录下创建入口文件 index.js,以及 src下的main.js用于等下测试:
1 | // index.js |
1 | // src/main.js |
根目录下创建 rollup.config.js
1 | import babel from 'rollup-plugin-babel' |
把上面的依赖安装一下,运行:
1 | npm install --save-dev @babel/core @babel/preset-env rollup-plugin-babel@latest rollup-plugin-node-resolve rollup-plugin-commonjs |
修改 package.json,增加一条脚本命令:
1 | ....... |
最后运行 npm run dev
看看效果吧:
示例结果
打包最终文件位置:
运行 node dist/main.cjs.js
:
打包文件格式说明
1. umd
集合了 CommonJS、AMD、CMD、IIFE 为一体的打包模式,看看上面的 hello world 会被打包成什么:
1 | (function (global, factory) { |
可以看出导出的文件就是我们前面一直使用的函数自执行开发方式,其中加了各种兼容判断代码将在哪个环境下导入。
2. es
现代JS的标准,导出的文件只能使用 ES模块化 方式导入。
3. cjs
这个是指 CommonJS 规范导出的格式,只可在 Node 环境下导入。
补充:模块化的发展
- 早期利用函数自执行实现,在单独的函数作用域中执行代码(如 JQuery )
- AMD:引入
require.js
编写模块化,引用依赖必须提前声明 - CMD:引入
sea.js
编写模块化,特点是可以动态引入依赖 - CommonJS:NodeJs 中的模块化,只在服务端适用,是同步加载
- ES Modules:ES6 中新增的模块化,是目前的主流
本文前三种插件编写方式均属于利用函数自执行(IIFE)实现的插件,同时在向全局注入插件时兼容了 CommonJS 规范,但并未兼容 AMD CMD,是因为目前基本没有项目会使用到这两种模块化。
自动化API文档
一个 JS 插件如果没有一份文档,如同一台精密的仪器没有说明书。当别人使用你的插件时,他不可能去查看源码才知道这个插件有哪些方法、用途如何、要传哪些参数等。
所以这里我们使用 JSDoc 来创建 API文档,它使用简单,只需要在代码中编写规范的注释,即能根据注释自动生成文档,一举多得,十分优雅!
1 | npm install --save-dev jsdoc open |
修改 package.json,增加一条脚本命令:
1 | ....... |
根目录下创建文件 server.js:
1 | var open = require('open'); |
好了,现在可以使用 npm run doc
命令来生成文档了,依然是举个栗子🌰,我们在src目录下添加一个文件 ArrayDelSome.js
:
1 | /** |
本例只演示最基础的用法,JSDoc有许多类型注释大家可以自行搜索学习下,不过本例最基本的这几个注释依旧是够用的。
运行 npm run doc
,将会打开一个网页,可以查看我们刚写的工具函数:
注意:在生成文档前需要先进行过 rollup 的打包,且不能开启去注释之类的插件,因为上面的例子实际是对
dist/
目录下的最终文件进行文档编译的。
发布插件
还没发布过npm包?限于篇幅就不展开,可以参考这篇文章如何发布npm包。
私有源发布
如果你的公司有私域npm管理源,或者平时喜欢用淘宝源,推荐使用 nrm
进行切换:
1 | npm i nrm -g |
- 查看源:
nrm ls
- 添加源:
nrm add name http//:xxx.xxx.xxx.xxx:4873/
- 删除源:
nrm del name
- 使用指定源:
nrm use npm
总结
功能较简单的JS插件我们可以直接采用前三种方式开发,如果涉及DOM操作较多,可以编写仿JQ的插件更好用,如果插件功能较多,有可能形成长期维护的大型插件,那么可以采用工程化的方式开发,方便多人协作,配套生成文档也利于维护。
以上就是文章的全部内容,希望对你有所帮助!如果觉得文章写得不错,可以点赞收藏,也欢迎关注,我会持续更新更多前端有用的知识与实用技巧,我是茶无味de一天,希望与你共同成长~