利用ES6的新特性实现简单的MVVM(一)--数据驱动
时间:2023-04-02 18:12:30
HTML
尝试利用ES6的新特性,自己实现mvvm和vue的各种特性。相关代码放在github上,会持续更新。欢迎打赏一个star。本文为系列文章的第一篇,会更容易理解,后续记录会持续更新。文章首发于我的博客。最简单的watcher从我们第一次接触Vue开始,就一直在欣赏它的“数据响应”。那么我们先实现一个最简单的watcher来监听数据并进行相应的操作。类似后续会涉及到的dom操作。Proxy我们都知道Vue是通过Object.defineProperty来进行数据监控的,监控obj的get和set方法。在ES6中,Proxy可以拦截某些操作的默认行为,即拦截、过滤和重写对目标对象的访问。我们可以使用这个特性来监控数据:constwatcher=(obj,fn)=>{returnnewProxy(obj,{get(target,prop,receiver){returnReflect.get(target,prop,receiver)},set(target,prop,value){constoldValue=Reflect.get(target,prop,receiver)constresult=Reflect.set(target,prop,value)fn(value,oldValue)返回结果}})}result:letobj=watcher({a:1},(val,oldVal)=>{console.log('old=>>',oldVal)console.log('new=>>',val)})obj.a=2//old=>>,1//new=>>,2简单的Dom操作我们已经可以监控简单的数据操作了(虽然还有各种问题),接下来,我们只需要监控dom之后,你可以进行数据操作。我们先不做解析模板了,我们可以继续用Proxy实现一个dom辅助函数:constdom=newProxy({},{get(target,tagName){return(attrs={},...childrens)=>{//创建节点constelem=document.createElement(tagName)//添加属性attrs.forEach(attr=>elem.addAttribute(attr,attrs[attr])//添加子元素childrens.forEach(child=>{constchild=typeofchild==='string'?document.createTextNode(child):childelem.appendChild(child)})returnelem}}})也就是说我们监听dom的每一个属性,当访问对应的时候节点,我们为他创建并添加各种属性:欢迎来到360'))//输出helloworld欢迎来到360
拼接基础框架这里我们把我们的小架子命名为'W',这样它才能真正运行起来。类似Vue的语法,在实例化数据的时候需要watch我们,更新dom。像这样:constvm=newW({el:'body',data(){return{msg:'helloworld'}},render(){returndom.div({class:'wrap'},dom.a({href:'http://www.360.cn'},this.msg)}})所以我们需要实现这样一个类来处理我们的参数,初始化实例,监控,以及渲染控件等exportdefaultclassW{constructor(config){}/***observedata*/_initData(){}/***rendernode*/_renderDom(){}}初始化数据首先,我们初始化数据,将数据设置为可观察的,修改时进行监控:importwatcherfrom'./data.js'classW{constructor(config){const{data=()=>{}}=configthis._config=configthis._initData(data)returnthis._vm}}_initData(data){this._vm=watcher(Object.assign({},this,data()),this._renderDom.bind(this))}这里我们需要注意两点:之所以我们的data参数是一个函数,在vue官方文档中已经提到了,当我们直接使用对象时,不同的实例会共享同一个对象,导致修改一个组件,而其他组件也修改问题。具体可以查看data-mustbeafunction。我们返回的是this._vm而不是this。我们在这里做了两个步骤。首先合并this和data,然后监控整个对象,赋值给_vm属性。这样我们通过newW()初始化的实例就可以访问我们的数据属性和方法,具有数据驱动的特点。更新DOM我们已经为watcher回调添加了dom更新事件,我们只需要在这里执行render函数并将其挂载到相应的el上即可:renderDom=render()targetEl.innerHTML=''targetEl.appendChild(renderDom)Bindthis我们会发现我们在config的render函数中使用this.msg来访问data的msg属性,所以我们需要在各个组件中实现,可以通过this访问这个实例的特性。我猜你已经想通了,我们可以使用bind、call和apply来实现它:/***bindthisforallfunctions*/bindVM(){const{_config}=thisfor(letkeyofObject.keys(_config)){constval=_config[key]if(typeof(val)==='function'){_config[key]=val.bind(this._vm)}}}测试简单的架子拼接完成,我们到测试我们的结果,我们需要实现两个功能:可以根据我们的render函数正常挂载,可以访问data上的数据。通过修改实例,修改会自动更新到节点代码:constvm=newW({el:'body',data(){return{msg:'helloworld'}},render(){returndom.div({class:'wrap'},dom.a({href:'http://www.360.cn'},this.msg))}})//测试修改vmsetInterval(_=>{vm.msg='helloworld=>>>'+newDate()},1000)结果:最基本的功能已经实现!结语这次我们只实现了最简单最简单的数据驱动功能,以后要处理的东西还有很多。我们也会一一梳理实施。大家可以继续关注,例如:数组变化监控对象深度监控更新队列渲染过程中,只有相关属性模板渲染v-model...等相关代码放在github上,会持续更新。欢迎打赏一个star。敬请关注!