目的
html
<div id="app">
<div>{{ someStr }}</div>
</div>
js
let myMvvm = new Mvvm({
el: document.getElementById('app'),
data: {
someStr: '你好'
}
})
上面是最常见的vue的用法, 如今我就只完成一件事
myMvvm.someStr = '转变' // 实行这一句时, 页面会及时更新
最先开工
1、 第一步, 先声明一个Mvvm类
class Mvvm {
constructor (option) {
this.$option = option || {}
}
}
我们一最先定义的 someStr属性是定义在option.data中的, 我们想要 myMvvm.someStr如许赋值的时刻和option的data相关联, 须要中心做一个代办,修正代码
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
}
})
})
}
}
2、视察option的data属性
要想完成 myMvvm.someStr = 1 如许赋值的时刻,页面能及时更新,那末我们就要对someStr的赋值历程做一个监听才行, 高兴的是 , Object.defineProperty能够随意马虎做到这点
写一个observe类
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)
}
3、完成元素的及时更新
如今为止, 还只是显现 一个 {{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 也能及时更新
在完成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 会发明简朴的例子完成了