发布网友 发布时间:2024-10-05 15:57
共1个回答
热心网友 时间:2024-10-23 17:50
MVVM简介MVVM是一种设计模式,或者也可以称为一种架构模式。它的全拼是Model-View-ViewModel。
Model代表模型
View代表视图
ViewModel代表了一个控制器
这其中,我们可以将model看作是后端接口返回的数据
{name:'晴天',age:'18',mark:'always18'}而view就是前端显示在浏览器中让用户看到的页面
那一段json数据是如何显示到页面里的呢?这就是ViewModel负责的事情。
所以,它们之间的关系可以用一个图来表示
前端最常见的MVVM的框架,应该就是
AngularJs,不过这个框架,国内用的貌似比较少
尤大大的vue,我们经常在vue的demo中看到varvm=newVue(...),这个vm就是viewmodel的简写。
MVVM的初衷是想利用数据绑定函数,从视图层面删除所有和界面数据渲染逻辑相关的代码。
那么,应该如何使用或者如何编写数据绑定函数,才能达到这样的效果呢?
我们看vue的表现:
<divid="counter">Counter:{{counter}}</div>constCounter={data(){return{counter:0}}}Vue.createApp(Counter).mount('#counter')当然这个例子和Object.defineProperty没有关系,我只是在这里体现“数据绑定后从视图层面删除了和界面数据渲染逻辑相关的代码”这句话
很明显,我们没有自己编写
document.getElementById(counter).innerText=counter
或者
$('#counter).text(counter)
类似这种的渲染代码。
你可能觉得这只是一行代码啊,这么简单。
其实不然,假如我们需要counter做递增
mounted(){setInterval(()=>{this.counter++},1000)}你就知道MVVM到底帮我们省了多大的力气。
好了,到这里已经展示了MVVM的表现,那么回到正题,如何利用Object.defineProperty简单实现MVVM呢?
其实vue2的响应式原理,是数据劫持,即数据变化的时候,自动重新渲染相关页面。
其实这个需求是非常容易的,但是首先要理解一个方法,叫做Object.defineProperty,理解之后大部分人都可以模拟一个简单的实现。
虽然跟vue差很多,但是面试考察的也不是让你去写个vue。
defineProperty数据劫持Object.defineProperty方法会在一个对象上添加一个新的属性,或者修改一个对象的已有属性,最后返回此对象.
Object.defineProperty方法可以接收三个参数
object(required,要定义属性的对象)
propertyname(required,要定义或修改的属性的名称)
descriptor(required,要定义或修改的属性描述符)
Object.defineProperty(object,propertyname,descriptor)针对descriptor,它是一个对象类型,用于配置propertyname的属性描述符,因此descriptor的属性可以选择如下两种中的一种:
数据描述符
key值类型描述默认值valueanyobject.propertyname的值undefinedwritablebooleanobject.propertyname是否可以被赋值运算符修改falseconfigurablebooleanobject.propertyname是否可以被修改和删除falseenumerablebooleanobject.propertyname是否可以被枚举false存取描述符
key值类型描述默认值getfunction读取object.propertyname时调用的函数undefinedsetfunction设置object.propertyname时调用的函数undefinedconfigurablebooleanobject.propertyname是否可以被修改和删除falseenumerablebooleanobject.propertyname是否可以被枚举false小伙伴可能看出来了,数据描述符和存取描述符具有共同的key,也有不同的key,那么当一个描述符内部没有value、writable、get和set时,它默认是一个数据描述符。
下面的例子展示了存取描述符的作用:
letdata={},temp='aa'Object.defineProperty(data,'key1',{set(value){console.log('thisisanewvalue:'+value)temp=value//somecodelike$('div').html(value)willautomaticexecutewhenkey1changed},get(){returntemp}})data.key1='Jack'当我们在浏览器运行上面这段代码,控制台会输出:
thisisanewvalue:Jack
这就是存取描述符的作用,他可以用来做数据劫持。
订阅模式我们题目要实现的订阅与数据劫持,就是要通过Object.defineProperty的存取描述符来实现。
订阅器,我们可以简单的将其理解为一个队列,队列内都是即将在某个时刻执行的函数。
当然,为了方便查找,我们可以将其定义为一个对象类型,其中的每个属性,都是数组类型。
vara={a:[],b:[],c:[]}下面我们来实现一个订阅器:
letDeep={deepList:{},listen(key,fn){if(!this.deepList[key])this.deepList[key]=[]this.deepList[key].push(fn)},trigger(){letkey=Array.prototype.shift.call(arguments)letfnList=this.deepList[key]if(!key||!fnList||!fnList.length)returnfalsefor(leti=0,fn;fn=fnList[i++];){fn.apply(this.arguments)}}}将订阅器与数据劫持绑定到一起这里就是要实现,数据劫持发生后,去执行订阅器内相应的代码。
这样,就可以实现类似vue的,改变了某个message,页面能同步渲染最新的结果。
这部分代码的逻辑十分简单:
首先,我们通过Deep.listen将页面标签与内容绑定到一起并放入到订阅器中
然后,我们在数据劫持中调用订阅器的trigger方法,更新数据的同时同步更新html
letdataHijack=({data,tag,datakey,selector})=>{letvalue='',el=document.querySelector(selector);Object.defineProperty(data,datakey,{get(){returnvalue},set(newVlaue){value=newVlaueDeep.trigger(tag,newVlaue)}})Deep.listen(tag,content=>{el.innerHTML=content})}作者:晴天同学