Vue2源码解读(一):function Vue的代码结构及逻辑
写了很多年的vue2,于是决定从源码的角度对vue2的理解写一下,即是为后面学习vue3打下坚实的基础,也为彻底告别vue2转向vue3做个纪念。
本文是该系列文章的第一篇,欢迎阅读。
vue结构
在vue项目中,入口文件都会有一个实例化vue的逻辑:
var app = new Vue({
el: '#app'
})
那么在实例化的过程中会发生什么呢?我们来看看Vue的构造函数:
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'
function Vue (options) {
this._init(options)
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
它实际上就是一个用 Function 实现的类,我们只能通过 new Vue 去实例化它。
在引入import Vue from 'vue'时,它会执行如下函数:
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
这些函数的参数是vue的构造函数,它们的作用是往构造函数的原型上挂载一系列的方法和属性,下面对这些函数做个简要的介绍,留个印象为后面的文章打下基础。
初始化函数
initMixin
function initMixin (Vue) {
Vue.prototype._init = function (options) {
...
};
}
initMixin在vue构造函数的原型上挂载了_init方法。因此,在vue构造函数内部可以执行this._init。
stateMixin
function stateMixin (Vue) {
...
var dataDef = {};
dataDef.get = function () { return this._data };
var propsDef = {};
propsDef.get = function () { return this._props };
Object.defineProperty(Vue.prototype, '$data', dataDef);
Object.defineProperty(Vue.prototype, '$props', propsDef);
Vue.prototype.$set = set;
Vue.prototype.$delete = del;
Vue.prototype.$watch = function () {
...
};
}
stateMixin在vue原型上挂载了$data, $props属性,当访问this.$data的时候其实是在访问this._data,另外,this._data其实就是指向了我们定义的data() { return {} }数据。
同时,它还挂载了$set, $delete, $watch等方法。
在我们使用vue进行开发的过程中,可能会遇到一种情况:当生成vue实例后,当再次给数据赋值时,有时候并不会自动更新到视图上去。
data () {
return {
student: {
name: '',
sex: ''
}
}
}
mounted () {
this.student.age = 24 // 此时对age的修改,页面并不会响应。
}
这个时候就可以使用this.$set(this.student, 'age' , 24)。
同样的道理,除了在组件的options中写watch监听函数外,我们还可以直接调用this.$watch进行监听。
eventsMixin
function eventsMixin (Vue) {
Vue.prototype.$on = function (event, fn) {
...
};
Vue.prototype.$once = function (event, fn) {
...
};
Vue.prototype.$off = function (event, fn) {
...
};
Vue.prototype.$emit = function (event) {
...
};
}
eventsMixin在原型上挂载了$on,$once,$off,$emit等方法用于事件监听,至于具体的逻辑后面会专门在讲事件的时候介绍,你只需要知道每个vue实例上都有这些方法。
在开发过程中,当你遇到要在页面间传递参数时,可以使用一个vue实例当做eventbus来传递参数。
lifecycleMixin
function lifecycleMixin (Vue) {
Vue.prototype._update = function (vnode, hydrating) {
...
};
Vue.prototype.$forceUpdate = function () {
...
};
Vue.prototype.$destroy = function () {
...
};
}
lifecycleMixin在原型上挂载了_update,$forceUpdate,$destroy三个方法,这三个方法是跟页面渲染相关的,后面会一一介绍。
renderMixin
function renderMixin (Vue) {
Vue.prototype.$nextTick = function (fn) {
return nextTick(fn, this)
};
Vue.prototype._render = function () {
...
};
}
renderMixin在原型上挂载了$nextTick, _render方法,_render方法执行后会生成vnode,$nextTick函数中的参数能保证在页面渲染之后才会执行,这里面涉及到异步渲染机制,后面也会讲。
以
_划线开头的函数,表示这个方法是给vue本身调用的,以$开头的函数表示开发者可以使用。
为何 Vue 不用 ES6 的 Class 去实现呢?
上面我看到有很多 xxxMixin 的函数调用,并把 Vue 当参数传入,它们的功能都是给 Vue 的 prototype 上扩展一些方法,Vue 按功能把这些扩展分散到多个模块中去实现,而不是在一个模块里实现所有,这种方式是用 Class 难以实现的。
这么做的好处是非常方便代码的维护和管理,这种编程技巧也非常值得我们去学习。
总结
Vue本身就是一个构造函数,因此只能通过new Vue的方式生成一个实例,这个实例就继承了Vue原型prototype上的方法和属性,这也是你可以使用this.$watch, this.$set, this.$nextTick等方法的原因,this就是指向这个vue实例。
有了这个vue实例后,页面上的dom节点都会通过这个实例来生成。
转载自:https://juejin.cn/post/7247528578779414583