Vue源码阅读之Vue构造函数(一)
前端技术日新月异,过一段时间就会涌现一些新的技术框架或者概念。并且目前使用最广泛的三大前端库,也不断地在更新版本增加新特性。对于前端开发人员来说,都有种学不动的无力感,还要面对来自“后浪”的挑战。因此提升技术的深度和广度,是塑造自我核心竞争力的关键一步。今天咋们就来阅读理解Vue的源码,知道我们平时在开发过程中经常使用的属性、生命周期以及数据双向绑定的实现机制。
一、项目结构
首先我们需要将Vue源码从github上拉倒本地,现在最新版本的Vue加入了typescript类型检测。我们都知道javascript是弱类型语言,书写代码的时候没有对变量的类型进行定义,后期扩展、团队开发中很容易犯错。大家还不了解typescript可以先看官方文档,不然源码中的语法会存在一定的阅读障碍。
项目中我们重点关注src下面的complier(编译相关)、core(核心代码逻辑)、platforms(平台相关)这三部分内容,基本涵盖了平常工程化开发中所用到的知识的底层原理。
如果需要在本地对源码进行断点调试,可以按照下图进行配置:
运行npm run dev命令后,会在dist目录下生成未压缩、混淆的vue.js文件。引用该文件就能在源码上打断点调试了。
二、定义Vue构造函数
core/instance/index.js中定义了Vue构造函数并暴露出去,属于原生javascript面向对象编程思想,结合工厂模式和原型链模式实现继承。
在代码中我们会看到许多process变量,这个属于node.js中的对象,根据打包命令能判断环境变量区分开发环境、生产环境,继而走不同的代码逻辑。
Vue函数体中执行了_init方法,根据实例化vue传入的options不同生成不同的对象,_init方法在initMixin后进行了混入。
下面的几个mixin方法,就是在Vue对象的原型上混入公共方法,包括初始化、state数据、事件处理、生命周期、render。
三、_init方法
core/instance/init.js文件中我们看到_init方法定义在Vue.prototype原型上。函数体中排除process对于环境变量的判断,其实代码量不是很多。
这部分代码判断vue实例是否是组件,不是组件就将传入的options在内部进行合并,保留vm对象的完整性。
这部分就会初始化生命周期、事件、渲染页面、state数据。callHook方法用于调用组件内部相应的生命周期钩子,看到这里我们就很好理解为什么组件中created钩子是在页面数据初始化之后进行调用,并且inject数据在data/props之前就能获取到。
最后就是将vue实例$mount挂载到对应的dom节点上。
四、callHook函数
core/instance/lifecycle.js中看到callHook这个方法接受两个参数vm、hook,会获取options参数中传入的对应钩子的执行函数。然后通过invokeWithErrorHandling这个方法进行处理。
invokeWithErrorHandling方法定义在core/util/error.js中,核心逻辑就是红框中标注的部分,在vm实例对象上通过apply、call进行调用执行。并且钩子函数本身就是promise,也会返回promise对象,可以通过catch进行错误捕获。
现在看来,生命周期中的钩子函数,相当于在组件进行特定操作后,比如:数据初始化、dom重绘、组件销毁等,Vue库内部提供的可以在自定义组件中进行副作用操作的钩子。至于数据更新如何改变虚拟dom,继而触发页面重绘的逻辑,都是在框架内部进行处理的。
五、initState初始化组件数据
core/instance/state.js中initState函数会初始化props、methods、data、computed、watch这一系列属性。我们重点看initData这个方法的逻辑、
先是获取options上的data对象,判断是否是函数然后走不同的逻辑,并且将数据赋值给vm._data私有变量。
接下来这段代码就很重要,有几个作用:
- hasOwn判断methods、props中是否有同名属性;
- props中如果没有同名的属性,isReserved方法判断变量名是否以“$”、"_"两个特俗字符开头,这两个属于特定字符适用于在类中定义私有属性;
- proxy方法通过Object.defineProperty(target, propKey, propDesc)方法将vm.optios.data上的数据做了一层代理。能直接在vm对象对象上进行set赋值、get取值操作;
最后observe是new Observer(value)的实例,是基于Vue实现了观察者模式,监听数据变化,更新vNode重绘UI页面。有兴趣的同学可以看看这篇文章Vue源码阅读之观察者模式(三)
我目前是通过定义Vue构造函数->_init()->initState()->initData()这一条线来阅读源码。诚然里面的知识点、细节很多,需要花时间钻研!
最后上一张流程图:
系列相关文章: