欢迎来到我的《从源码中学Vue》专题系列文章,更多精彩内容持续更新中,欢迎关注 :)
![466582039d1e7f8663c4c9cb315b182f.png](https://i-blog.csdnimg.cn/blog_migrate/62adfb224c767fdcc63a0ebc7c5d6428.jpeg)
Vue作为当下前端最流行的框架之一,在国内占绝对的优势。所以接下来我们一起来学习它吧!
我不会像其它人一样去教大家怎么去使用vue的API,那些官网有详细的教程,而我将带着大家一起进入它的源码看看vue究竟做了什么?
所以,接下来我希望正在阅读的你具有以下基础
- 较好的JS基本功。
- 有JS面向对象基础,不清楚的可以去看下我上一个专题——《JS从入门到不放弃》里面有我对面向对象的理解。
- 有一定的Vue基础
废话不多说,直接开始
本章目标
- 如何去阅读vue的源码
- 从源码去看vue的生命周期
- 钩子函数的执行顺序
如何去阅读vue的源码
对于很多人来说,初识vue都是通过官网或者cdn网站下载vue.js文件。我们接下来要分析的源代码并不这个vue.js文件。
这个文件是经过编译后的文件,想要从这里面分析源码是比较困难的,所以我分析的源代码是通过yarn 安装到node_modules里面的vue。
我们先来实现一个简单的vue示例。
![e95e545e17d6512bac93f032183a5836.png](https://i-blog.csdnimg.cn/blog_migrate/c6c23f786b8180093b6cb9e51a99db85.jpeg)
一个最简单的vue示例
从这段代码可以看出来。在全局作用域下有一个Vue的类。好,这样我们去找Vue的源码。
在node_modules里面找到vue模块,展开看看里面的目录结构,它长这样
![feeb41c8ba8952e6de44968dc759f958.png](https://i-blog.csdnimg.cn/blog_migrate/1cb9972bef7d60eefff4e3714ff59120.jpeg)
从源码去看vue的生命周期
Vue这个类的定义我已经找出来了,它在vue/src/core/instance/index.js下。来看它里面都有什么?
![2b11e2f3a1bc0d410bda3685479b733c.png](https://i-blog.csdnimg.cn/blog_migrate/06924647cdd0a5dfc1739585f9c87c32.jpeg)
很容易就找到了Vue的全局定义,可以看出来,它是通过ES5的function 来模拟类的。然后里面有一个判断是这样的
if (process.env.NODE_ENV !== 'production' && !(this instanceof Vue) ) { warn('Vue is a constructor and should be called with the `new` keyword') }
我们就只看这一坨代码
!(this instanceof Vue)//有面向对象基础的童鞋一下子就明白了,当前的this对象必须是Vue的一个实例,换句话说,我们只能够通过new Vue()去启动vue程序。而不能通过vue()这样。
为了验证我说的,试一把。
![79e0af195a20136553bca9307eed0e74.gif](https://i-blog.csdnimg.cn/blog_migrate/2bbe1351e98a55eacf11800f946a4a72.gif)
可以看到,那个标红的提示就是warn里面的提示。
继续往下看。
![9eb31df98cea17e020b61c0d535dbae5.png](https://i-blog.csdnimg.cn/blog_migrate/1db2398b47647317d29c6414ed253657.jpeg)
就直接调用了一个this._init()方法。
但是这个_init方法在哪定义的呢??
再往下看,
![f8196a5f0b9d6ef09392acdc65cff7b1.png](https://i-blog.csdnimg.cn/blog_migrate/6e5aa86b411882e8fa0e861af7feb450.jpeg)
有这么多方法。通过init,我们找第一个initMixin即可。
import { initMixin } from './init'
这个方法的定义就在当前目录下的init.js中,去这个文件看看里面有什么?
![79729b7d782c8dc8b05de588b68b1d6a.png](https://i-blog.csdnimg.cn/blog_migrate/a08f386a7ed2df17aeb6632a4c6217f0.jpeg)
注意:vue方法的参数中带有类型,这是typescript的写法,我们可以直接
我们找到了initMixin方法的定义。有些代码不用看,往下走,在第55行会找到这一坨代码
![9377471c88fa619fc063eda4fad11406.png](https://i-blog.csdnimg.cn/blog_migrate/9edd7a66bf1a919ecd49ff4311d32f55.jpeg)
好像找到了我们的生命周期的函数的影子。我们再去找callHook的方法的定义。
callHook,从字面上理解,它的意思是就调用钩子函数
![2af27531c6454fd83f19e84cc6baac4c.png](https://i-blog.csdnimg.cn/blog_migrate/e7e463f1012a290dd8ec9310b8c6297f.jpeg)
再进去找到lifecycle.js,找到callHook的定义。
![d32d2826c3f3b0399c7f7025e47d2646.png](https://i-blog.csdnimg.cn/blog_migrate/514425879fffe87000ef72c0366695fd.jpeg)
这里面的第二个参数是我们的生命周期函数的名字。比如,beforeCreate,created等
我们再往里面找invokeWithErrorHandling函数。
![7372fed63c3ca26a53336bbbdc2b850d.png](https://i-blog.csdnimg.cn/blog_migrate/972075c29e6afeb2d7006b74c02d86e9.jpeg)
它在核心模块下的util目录下的index.js
![4a70d5e22777e52734aa12601de2ee85.png](https://i-blog.csdnimg.cn/blog_migrate/0e514fd256d9f1e273d5055bc2480c9f.jpeg)
找到这样的一坨代码,额,好吧,invokeWithErrorHandling这个方法里面有一个error,所以我会先去找到error.js文件。
![a56f05d156b5683e325f1d1a553f2956.png](https://i-blog.csdnimg.cn/blog_migrate/381fef38fffad5edcadeb877aeeb0590.jpeg)
找到了这最终的调用位置。invokeWithErrorHandling从字面上理解就是通过带有异常处理的函数。
再来看我圈出来的代码。这一块就是最终调用的生命周期钩子函数。注意看,最终调用handler的时候使用了call改变了钩子函数的this指向。
我们在调用invokeWithErrorHandling的时候,第二个参数传的是vm也就是我们的vue实例。
所以我们在钩子函数中,可以使用this去访问vm对象下的数据。
接下来我们来验证一下
![1ae6c019c61937f240a0ea3aa2d50534.gif](https://i-blog.csdnimg.cn/blog_migrate/1aaac74c72f127dd7b0c1a42598b26c1.gif)
看得出来,vue已经将钩子函数中的this指向了Vue对象。
如果没有调用call我们来看下默认的this指向什么?我来写一个简单的示例。
![998f21e2ae62a8e1d246730de0a4de07.gif](https://i-blog.csdnimg.cn/blog_migrate/4584399c42cdf66cca24d03187fe5000.gif)
默认的情况下它并没有指向MyVue。
钩子函数的执行顺序
我们在Vue原型的_init方法中,首先看到的是callHook(vm, 'beforeCreate'),然后接下来是
initInjections(vm) // resolve injections before data/props initState(vm) initProvide(vm) // resolve provide after data/props
![b71ff3c448069ea76e0e8f3066773a5b.png](https://i-blog.csdnimg.cn/blog_migrate/97b73c41a5b40c55e53c135604b8541c.jpeg)
随后调用初始化注入,状态,创建data,props,完成以后再来调用created钩子函数。所以我们在created中可以访问到data中的数据,而在beforeCreate中不能访问。
我们来验证一下。
![faf822a6e2f768f5d9b8e837f6f9af65.gif](https://i-blog.csdnimg.cn/blog_migrate/dc153f6f8a74b4e032d8654e35e21428.gif)
可以清楚的看到在beforeCreate中不能访问data中的数据。而在created中可以。
再接着往下看走,我们进到lifecycle.js中去。我找到了一个叫mountComponent的函数。
![dcabfc29451f1e6e77576ec9ce8a0cf3.png](https://i-blog.csdnimg.cn/blog_migrate/fd776f4daa426af6854b28a86615bcc0.jpeg)
了解Vue的都知道,这个钩子函数是在挂载dom节点之前,再往下走。我找到了mounted钩子,
![324d0b12f3b6be739707e30d30571679.png](https://i-blog.csdnimg.cn/blog_migrate/e3c44b9847d49e7cfa2e2a9a759df1ab.jpeg)
那么在beforeMount和mounted之间,vue其实是做的dom节点的渲染,我们来找下这中间的源码。
![2f7563d3b6c544b1408e38cf9db81ebf.png](https://i-blog.csdnimg.cn/blog_migrate/d15585d5ae4c9262b3d4c242842b2ccb.jpeg)
beforeMount和mounted之间会有一个_render()方法的调用,那么这个方法本身就是渲染的意思,将我们的虚拟dom渲染绑定到真实的dom节点上。(_render方法的源码我没找着,知道的麻烦告知一下,^_^)
结论:我们在beforeMount中不能使用this.$refs[name]去访问真实的dom节点,而在mounted钩子中可以,我们也来验证一下。
![d18d224cbe5c607c7b271808191fdc3b.gif](https://i-blog.csdnimg.cn/blog_migrate/340753002cb9987f18582cb843295788.gif)
可以看到在mounted中可以访问到msg,而在beforeMounted中则不行。
后面的钩子大家我就不再一一列举了。
总结:
- 如何去阅读当下热门的JS框架,阅读源码该如何下手
- 从源码分析Vue生命周期中的各个钩子函数的源码,彻底搞明白官网中介绍的每一个钩子函数的执行原理。
- 结合源码和vue教程综合学习Vue框架,效果会更好。
这里是畅哥聊技术 《从源码中学Vue》系列文章,更多精彩内容持续更新中,敬请期待。
未完待续。。。