从最简单的例子写一个极简的双向绑定
jsletmyMvvm=newMvvm({el:document.getElementById('app'),data:{someStr:'你好'}})以上是vue最常见的用法,现在我只实现一个东西myMvvm.someStr='change'//当这句话执行时,页面会及时更新,开始构建1.第一个步骤,首先声明一个Mvvm类classMvvm{constructor(option){this.$option=option||{}}}我们一开始定义的someStr属性是在option.data中定义的,当我们要像这样赋值myMvvm.someStr和option数据相关联,中间需要做一个代理,修改代码类Mvvm{构造函数(选项){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(){returnobj[key]},set(val){obj[key]=val}})})}}2.观察o如果ption的data属性想要实现myMvvm.someStr=1的赋值,页面能够及时更新,那么我们就需要监控someStr的赋值过程。幸运的是,Object.defineProperty可以轻松做到这一点写一个观察类classObserve{constructor(obj){Object.keys(obj).forEach(key=>{this.defineReactive(obj,key,obj[key])})}defineReactive(obj,key,val){letinitVal=valObject.defineProperty(obj,key,{enumerable:true,configurable:false,get(){returninitVal},set(val){//我们可以了解每个复制到这里,自然就可以为所欲为了initVal=valreturninitVal}})}}然后修改构造函数constructor(option){this.$option=option||{}this._proxyData(option.data,this)newObserve(option.data)}3。实现元素的实时更新到此为止只是一个{{someStr}},我们现在要做的就是写一个Compile类{{someStr}}是一个文本节点,首先声明一个可以渲染的函数文本节点letcompileText=function(node,vm,str){letval=vm[str]if(val){node.nodeValue=val}}classCompile{constructor(el,vm){//el是#app的元素vm是Mvvm的实例letfrag=this.node2Fragment(el)this.vm=vmthis.compileElement(frag)//读取子节点进行渲染el.appendChild(frag)}node2Fragment(el){//创建文档片段并复制#app元素的子节点letfrag=document.createDocumentFragment()letchildwhile(child=el.firstChild){frag.appendChild(child)}returnfrag}compileElement(el){//渲染节点letchildNodes=el.childNodes;[].forEach.call(childNodes,(node)=>{//遍历所有子节点if(this.isElementNode(node)){//如果是元素节点,重复this.compileElement(node)}elseif(this.isTextNode(node)){//如果是文本节点letmatchStr=this.isMustache(node.nodeValue)//判断文本值是否为该类型的{{}}if(matchStr){//如果有匹配的compileText(node,this.vm,matchStr)}}})}isElementNode(node){//元素节点返回node.nodeType===1}isTextNode(node){//文本节点返回node.nodeType===3}isMustache(str){if(!str){returnnull}让reg=/\{\{(.*)\}\}/让arr=str.match(reg)返回arr?arr[1].replace(/\s/g,''):null}}现在修改Mvvm类的构造函数constructor(option){this.$option=option||{}this._proxyData(option.data,this)newObserve(option.data)newCompile(option.el,this)}你好,这个值终于渲染出来了,我们迈出了第一步,现在开始实现myMvvm.someStr=1,也可以及时更新。complie实现的时候,我们知道在渲染compileText函数的时候会调用它,那么我们现在改变someStr的时候就及时渲染了,我们只需要再执行一次这个函数,我们就可以把这个update函数放到一个队列中,执行update每次someStr更新时在这个队列中运行就这样,让我们??实现一个Dep类//在这里声明两个变量,稍后使用它letupdateFnletcanMountclassDep{constructor(){this.queue=[]}mount(){this.queue.push(updateFn)}notify(){this.queue.forEach(fn=>fn())}}然后修改Observe类的defineReactive函数defineReactive(obj,key,val){letinitVal=valletdep=newDep()Object.defineProperty(obj,key,{enumerable:true,configurable:false,get(){if(canMount){//防止每一个get都执行dep.mount()}returninitVal},set(val){//我们可以知道这里的每一个copy,自然是想怎么干就怎么干if(val!==initVal){initVal=valdep.notify()}returninitVal}})}实现一个生成更新函数的方法letbindTextUpdater=function(node,vm,matchStr){canMount=trueupdateFn=compileText.bind(null,node,vm,matchStr)updateFn()canMount=false}那么最后一步就是修改Complie类的compileElement方法letchildNodes=el.childNodes;[].forEach.call(childNodes,(node)=>{//遍历所有子节点if(this.isElementNode(node)){//如果是元素节点,重复this.compileElement(node)}elseif(this.isTextNode(node)){//如果是文本节点letmatchStr=this.isMustache(node.nodeValue)//判断文本值是否为{{}}类型if(matchStr){//如果有匹配bindUpdater(node,this.vm,matchStr)//绑定更新函数}}})现在执行myMvvm.someStr=155你会发现简单的例子实现了