html <div id="app"> <div>{{ someStr }}</div> </div> js let myMvvm = new Mvvm({ el: document.getElementById('app'), data: { someStr: '你好' } })
上面是最多见的vue的用法, 如今我就只实现一件事html
myMvvm.someStr = '改变' // 执行这一句时, 页面会及时更新
一、 第一步, 先声明一个Mvvm类vue
class Mvvm { constructor (option) { this.$option = option || {} } }
咱们一开始定义的 someStr属性是定义在option.data中的, 咱们想要 myMvvm.someStr这样赋值的时候和option的data相关联, 须要中间作一个代理,修改代码node
class Mvvm { constructor (option) { this.$option = option || {} this._proxyData(option.data, this) // 执行函数实现代理 } _proxyData (obj, context) { Object.keys(obj).forEach(key => { Object.defineProperty(context, key, { configurable: false, enumerable: true, get () { return obj[key] }, set (val) { obj[key] = val } }) }) } }
二、观察option的data属性
要想实现 myMvvm.someStr = 1 这样赋值的时候,页面能及时更新,那么咱们就要对someStr的赋值过程作一个监听才行, 开心的是 , Object.defineProperty能够轻易作到这点
写一个observe类app
class Observe { constructor (obj) { Object.keys(obj).forEach(key => { this.defineReactive(obj, key, obj[key]) }) } defineReactive (obj, key, val) { let initVal = val Object.defineProperty(obj, key, { enumerable: true, configurable: false, get () { return initVal }, set (val) { // 每一次的复制咱们均可以在这里获知,天然能够随心所欲了 initVal = val return initVal } }) } } 而后修改一下Mvvm这个类的constructor constructor (option) { this.$option = option || {} this._proxyData(option.data, this) new Observe(option.data) }
三、实现元素的实时更新
如今为止, 还只是显示 一个 {{someStr}} 而已, 咱们如今须要作的是让能变成 你好 这个值
写一个Compile类函数
{{someStr}}是一个文本节点,先声明一个能够渲染文本节点的函数 let compileText = function (node, vm, str) { let val = vm[str] if (val) { node.nodeValue = val } } class Compile { constructor(el, vm) { // el 是 #app这个元素 vm是Mvvm这个实例 let frag = this.node2Fragment(el) this.vm = vm this.compileElement(frag) // 读取子节点进行渲染 el.appendChild(frag) } node2Fragment(el) { // 建立一个文档片断把#app元素的子节点拷贝 let frag = document.createDocumentFragment() let child while (child = el.firstChild) { frag.appendChild(child) } return frag } compileElement(el) { // 渲染节点 let childNodes = el.childNodes; [].forEach.call(childNodes, (node) => { // 遍历全部的子节点 if (this.isElementNode(node)) { // 若是是元素节点, 重复便利 this.compileElement(node) } else if (this.isTextNode(node)) { // 若是是文本节点 let matchStr = this.isMustache(node.nodeValue) // 判断这个文本值是否是 {{}} 这种类型 if (matchStr) { // 若是有匹配到 compileText(node, this.vm, matchStr) } } }) } isElementNode(node) { // 元素节点 return node.nodeType === 1 } isTextNode(node) { // 文本节点 return node.nodeType === 3 } isMustache(str) { if (!str) { return null } let reg = /\{\{(.*)\}\}/ let arr = str.match(reg) return arr ? arr[1].replace(/\s/g, '') : null } } 如今修改一下 Mvvm这个类的constructor函数 constructor(option) { this.$option = option || {} this._proxyData(option.data, this) new Observe(option.data) new Compile(option.el, this) }
如今你好这个值终因而被渲染出来, 咱们踏出了第一步, 如今开始实现 myMvvm.someStr = 1 也能及时更新this
在实现complie的时候, 咱们知道渲染的时候调用了compileText函数,那么咱们如今更改someStr时及时渲染,就只要再执行这个函数就能够了, 咱们能够把这个更新函数放到一个队列里, 每次更新someStr的时候, 把这个队列里的更新函数执行一遍就能够了
咱们实现一个Dep类代理
// 这里声明两个变量待会使用 let updateFn let canMount class Dep { constructor () { this.queue = [] } mount () { this.queue.push(updateFn) } notify () { this.queue.forEach(fn => fn()) } } 而后修改一下Observe类的defineReactive函数 defineReactive(obj, key, val) { let initVal = val let dep = new Dep() Object.defineProperty(obj, key, { enumerable: true, configurable: false, get() { if (canMount) { // 防止每次get都会执行这里 dep.mount() } return initVal }, set(val) { // 每一次的复制咱们均可以在这里获知,天然能够随心所欲了 if (val !== initVal) { initVal = val dep.notify() } return initVal } }) } 实现一个生成更新函数 的方法 let bindTextUpdater = function (node, vm, matchStr) { canMount = true updateFn = compileText.bind(null, node, vm, matchStr) updateFn() canMount = false } 而后最后一步 修改一下Complie类的compileElement方法 let childNodes = el.childNodes; [].forEach.call(childNodes, (node) => { // 遍历全部的子节点 if (this.isElementNode(node)) { // 若是是元素节点, 重复便利 this.compileElement(node) } else if (this.isTextNode(node)) { // 若是是文本节点 let matchStr = this.isMustache(node.nodeValue) // 判断这个文本值是否是 {{}} 这种类型 if (matchStr) { // 若是有匹配到 bindUpdater(node, this.vm, matchStr) // 绑定更新函数 } } })
如今执行 myMvvm.someStr = 155 会发现简单的例子实现了code