初始化渲染页面
- 在initstate()初始化之后,我们需要把响应式处理后的数据渲染到页面上
- 我们在原型上加上一个 $mount() 方法,该方法是一个主要处理挂载的方法
$mount()
工作流程:
- 获取当前实例,获取 el(此时为字符串)
- 通过 query() 获取当前el节点,再赋值给 vm.$el 与 el(现在它们是dom节点)
- 编写 updateComponent() 更新方法,用于触发 _update() 更新视图方法
- 利用 _update() 方法渲染节点(通过Watcher渲染)
Vue.prototype.$mount = function (el) {
const vm = this;
const options = vm.$options;
el = vm.$el = query(el)
// 默认先查找有没有 render 方法,没有render会采用template template也没有就用el中的内容
let updateComponent = () => {
console.log("更新渲染实现")
vm._update();
}
new Watcher(vm, updateComponent)
}
_update()更新视图方法
工作流程:
- 首先获取当前实例与el
- 在vue2.0中是使用虚拟dom实现数据渲染(由于流程复杂,我们先使用操作dom的方式解决)
- 获取一个空节点
- 将 el 下所有的子节点都拿出来
- 文本替换 compiler(node, vm)
- 替换完再放回el
Vue.prototype._update = function () {
let vm = this
let el = vm.$el
// 渲染所有元素 把内容替换为数据
let node = document.createDocumentFragment()
let firstChild
while (firstChild = el.firstChild) {
node.appendChild(firstChild)
}
// 文本替换
compiler(node, vm)
el.appendChild(node) //替换完再放进去
}
compiler(node, vm)
在src下创建compiler文件夹,创建一个index.js
工作流程:
- 取出子节点,循环子节点递归调用
- 将类数组转换为数组,判断是否为元素节点,如果是则继续递归,如果是文本节点,则使用 compilerText() 编译文本
export function compiler(node, vm) {
// 1 取出子节点、
let childNodes = node.childNodes
childNodes = Array.from(childNodes)
childNodes.forEach(child => {
if (child.nodeType === 1) {
compiler(child, vm)
} else if (child.nodeType === 3) {
util.compilerText(child, vm)
}
})
}
compilerText()
工作流程:
- 取出当前节点的文本
- 使用正则表达式 const defaultRGE = /{{((?:.|\r?\n)+?)}}/g 查找到 {{}} 中的文本,使用 vm中的data数据替换其文本
const defaultRGE = /\{\{((?:.|\r?\n)+?)\}\}/g
export const util = {
getValue(vm, expr) {
let keys = expr.split('.')
return keys.reduce((memo, current) => {
memo = memo[current]
return memo
}, vm)
},
compilerText(node, vm) {
if(!node.expr){
node.expr = node.textContent
}
node.textContent = node.expr.replace(defaultRGE, function (...args) {
return util.getValue(vm, args[1])
})
}
}
Watcher类
创建 watcher.js 模块,向外暴露 Watcher类
- 参数列表:
- vm 当前实例
- exprOrfn 传入的方法或者表达式
- cb 回调函数
- opts 其他参数
- 工作流程:
- 将所有的参数 存下,并且给Watcher加个id
- 判断 exprOrfn 是不是函数,如果是,则赋给 getter()
- 创建 get方法,该方法用于调用getter()方法,并且get默认创建时就触发
import { pushTarget, popTarget } from "./observer/dep";
let id = 0
class Watcher {
constructor(vm, exprOrFn, cb = () => { }, opts) {
this.vm = vm
this.exprOrFn = exprOrFn
this.cb = cb
this.id = id++
if (typeof exprOrFn === 'function') {
this.getter = exprOrFn
}
this.get()
}
get() {
this.getter()
}
export default Watcher