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