「这是我参与2022首次更文挑战的第11天,活动详情查看:2022首次更文挑战」。
run() 方法简析
可以看到,run() 方法接收的第一个参数是一个 name,也就是我们前面在 vue-cli-service.js 文件中调用 run() 方法时传入的 command 的值(执行 npm run serve 时,command 的值就是 serve;执行 npm run build 时,command 的值就是 build),也就是说 name 的值是 serve 或者 build。
拿到 name 之后,根据这个 name 从 this.commands 中取出了对应的 command。
取出来 command 之后,又从 command 中解构出了 fn,最后调用了这个 fn() 函数,并返回了函数调用的结果。
以上,就是 run() 方法主要做的事情。
但是,这里的 this.commands 是什么东西?它里面的 command 哪来的?从解构 command 这步操作中我们可以确定它是一个对象,那么这个 command 对象中的 fn 函数又是什么?要弄清楚这些问题,首先得知道 this.commands 到底是什么。
如果我们在当前的 Service.js 文件中搜索 this.command,你会在 Service 类的构造器中找到它的初始值是一个空对象:
而当前文件中找不到再对 this.commands 进行赋值的其它地方了,也就是说,默认情况下它是一个空对象,但是从空对象中肯定取不到什么值(比如 this.commands['serve'] 取到的就是 undefined 了),那到时候 this.commands[name] 取出来的内容肯定不能用啊,那么它肯定是在其它地方又进行了赋值。在哪呢?
事实上,this.commands 真正进行赋值的地方是在 node_modules/@vue/cli-service/lib/PluginAPI.js 文件中的 PluginAPI 类的 registerCommand() 方法中:
直接这么说你可能会懵,所以下面我们一步步来看:
-
之前在
vue-cli-service.js中是通过new Service()创建的实例对象service,即会执行../lib/Service.js中Service类的constructor()函数,而constructor()函数中有这样一行代码:即调用了
Service类的实例对象的resolvePlugins()方法,并将方法调用的返回值赋值给了Service类的实例对象的plugins属性。 -
resolvePlugins()方法的主要内容如下:- 方法中定义了一个名称为
idToPlugin箭头函数,其实现的功能是把id映射到一个包含id和apply属性的对象中。 - 然后定义了一个名为
builtInPlugins的数组,数组里面的元素是一个个文件的相对路径,之后在该数组上调用了map()方法对这些文件的路径进行映射,映射过程就是去调用上一步的idToPlugins()函数。也就是说之后builtInPlugins数组中存放的是一个个对象,对象中有id和apply,而apply属性的值是require(id)的结果,即对应文件的导出内容。 - 之后又加载了一些其它插件;
- 最后返回了
plugins数组,里面包含了所有的插件,包括builtInPlugins数组中的插件对象。
- 方法中定义了一个名称为
-
再来看
run()方法,在从this.commands中取command之前,还调用了Service类的实例对象的init()方法,这个方法中主要做了四件事:加载环境变量、加载用户配置、应用插件以及应用webpack配置。我们来看应用插件这部分代码:可以看到,这里对
this.plugins数组进行了遍历,根据前面的分析,我们可以将this.plugins的值理解成resolvePlugins()方法中builtInPlugins数组映射之后的数组(该数组中的每个元素都是一个对象,对象中有id和apply两个属性),所以这里在遍历时解构出了id和apply,之后调用了apply()方法,而根据前面的分析,这个apply()方法其实是resolvePlugins()方法中箭头函数idToPlugin返回的对象中的apply属性对应的require(id)的结果。而require(id)中的id其实就是'./commands/serve'或者'./commands/build'等builtInPlugins初始数组中的字符串元素。也就是说,假如这里的id是'./commands/serve',那么require(id)就是require('./commands/serve'),即./commands/serve文件中导出的内容,即id是'./commands/serve'时apply()方法就是./commands/serve文件导出的内容。 -
我们来看
node_modules/@vue/cli-service/lib/commands/serve.js文件:在该文件中,导出了一个箭头函数,也就是说,前面的
apply()方法就是这个箭头函数了。那么前面在调用apply()方法时,其实调用的就是这个箭头函数。而这个箭头函数需要两个参数:api和options,我们来看这第一个参数api,前面在Service.js中的init()方法中应用插件时,遍历了插件,这一过程中调用了apply()方法,并且传入了参数,传入的第一个参数是new PluginAPI(id, this),这里的this是当前的service对象。也就是说./commands/serve中导出的箭头函数的第一个参数拿到的是一个PluginAPI类的实例对象,之后调用了这个实例对象的registerCommand()方法。 -
所以我们来看
PluginAPI这个类,这个类在node_modules/@vue/cli-service/lib/PluginAPI.js这个文件中:可见,前面我们在
init()方法中遍历插件时调用apply()方法时,传入了new PluginAPI(id, this)参数,那么在这个PluginAPI类的构造函数中的第二个参数service,接收到的就是实例对象servie了,因此,这里的this.service便指向了service对象,即Service.js中run()方法中的this对象。而这里的
registerCommand()方法在被lib/commands/serve.js文件中导出的箭头函数中调用时,接收到的第一个参数name的值即为serve,接收到的第二个参数opts的值即为一个对象,接收到的第三个参数fn的值即为一个名为serve的异步函数。之后就对this.service.commands对象(即Service.js中run()方法中的this.commands对象)进行了赋值,赋值内容中就有将fn函数赋值进去。因此,之后在Service.js中run()方法中才能从command对象中解构出fn函数。
所以,当我们执行 npm run serve 命令时,传入的 command 的值就是 serve,后续执行的 run() 方法的最后执行的 fn() 方法其实就是 lib/commands/serve.js 文件中导出的箭头函数中调用 api.registerCommand() 方法时传入的第三个参数即 serve() 函数了。