「这是我参与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()
函数了。