天天拧螺丝钉,看着网上的大神都在造火箭,于是偶尔突破下尝试造轮子吧!!
以简单的js代码实现一些功能,比如:ref、reactivity、render、diff、virtualDOM、compiler、eventBus、nextTick。
最后将这些小Demo串起来,实现一个属于自己的简易版Vue框架。
small-demo文件夹包含如下文件夹:
- 1-effectWatch 初步实现Dep和effectWatch,类似Vue3的ref API
- 2-reactivity 实现reactive API
- 3-watch-observer
- 4-setup-render 初步实现setup与render
- 5-render-mountElement h函数、render函数、挂载Dom
- 6-diff 虚拟Dom入门版
- 7-virtualDOM 虚拟Dom进阶版 -- 包含创建虚拟DOM(createElement.js)、创建真实Dom(createDom.js)、diff算法(diff.js)、patch方法(patch.js)
- 7-compiler-template-second 模板编译第一版
- 8-compiler-template-second 模板编译第二版
- 9-eventBus 订阅发布
- 10-nextTick Vue源码实现异步加载
src文件夹
模板编译,然后执行render函数, render函数会触发响应式的getter ,进行依赖收集(在模板里触发了哪个变量的getter就对其进行watcher)。在修改data的时候,触发setter ,通知(notify)watcher去 重新触发re-reder进行重新渲染
- initMixin
把Vue实例赋值给变量vm,并且把用户传递的options选项与当前构造函数的options属性及其父级构造函数的options属性进行合并,得到一个新的options选项赋值给$options属性,并将$options属性挂载到Vue实例上 .
在所有的初始化工作都完成以后,最后,会判断用户是否传入了el选项,如果传入了则调用$mount函数进入模板编译与挂载阶段,如果没有传入el选项,则不进入下一个生命周期阶段,需要用户手动执行vm.$mount方法才进入下一个生命周期阶段。
// 数据响应式 outerHTML文本转换成render函数 mountComponent挂载页面 export function initMixin (Vue) { Vue.prototype._init = function(options) { const vm = this vm.$options = mergeOptions(vm.constructor.options || {}, options) // 合并options callHook(vm, 'beforeCreate') // 调用生命周期钩子函数 initState(vm) // 初始化状态 callHook(vm, 'created') // 调用生命周期钩子函数 if (vm.$options.el) { // 在根节点渲染页面 vm.$mount(vm.$options.el) } } Vue.prototype.$mount = function (el) { const vm = this el = vm.$el = document.querySelector(el) const opts = vm.$options; //遵循源码 render > template > el 渲染机制 if (!opts.render) { let template = opts.template if (!template && el) { template = el.outerHTML } const render = compileToFunctions(template) opts.render = render } // 走到这用户传入是render函数不需编译 --initMixin初始化结束 mountComponent(vm); // 组件的挂载流程 } }
- renderMixin方法
给Vue原型挂载渲染方法: _c对应createElement创建一个元素 _v是创建一个文本节点 _s是返回参数中的字符串
用element ASTs去递归,拼出这样的_c(‘div’,[_c(‘p’,[_v(_s(name))])])字符串。
export function renderMixin(Vue) { Vue.prototype._v = function (text) { //创建文本节点 return createTextVNode(text) } Vue.prototype._c = function () { //创建标签节点 return createElement(...arguments) } Vue.prototype._s = function (val) { // 判断当前这个值是不是对象 ,如果是对象 直接转换成字符串 ,防止页面出现[object Object] return val == null ? '' : (typeof val === 'object') ? JSON.stringify(val) : val } Vue.prototype._render = function () { //字符串实现的render方法 const vm = this const { render } = vm.$options let vnode = render.call(vm) //方法存在Vue原型上 this指向Vue _v _c _s return vnode } }
- lifeCycleMixin方法
页面挂载时使用Watcher监听redner函数 因为页面第一次挂载render函数内有很多属性调用get 此时便可给这些属性添加该Watcher
export function lifeCycleMixin(Vue) { Vue.prototype._update = function (vnode) { const vm = this // 将虚拟节点 变成 真实节点 替换掉$el 后续 dom diff 也会执行此方法 vm.$el = patch(vm.$el, vnode) } }
- initGlobalApi方法: 给构造函数来扩展全局的方法
export function initGlobalAPI(Vue) { //全局api Vue.options = {} Vue.mixin = function (mixin) { //公共方法 合并options this.options = mergeOptions(this.options, mixin) } }
- index.js --- Vue类
// import { initMixin } from './init' // import { renderMixin } from './render.js' // import { lifeCycleMixin } from './lifecycle.js' // import { initGlobalAPI } from './global-api/index.js' import { nextTick } from './observer/scheduler' class Vue { constructor(options) { this._init(options); // 初始化操作 } } initMixin(Vue) renderMixin(Vue) lifeCycleMixin(Vue) initGlobalAPI(Vue) // initGlobalApi 给构造函数来扩展全局的方法 Vue.prototype.$nextTick = nextTick export default Vue
参考