当前位置: 首页 > Web前端 > vue.js

你会手写Vueresponsive吗?前端面试进阶_2

时间:2023-03-31 23:49:01 vue.js

Vue视图更新原理Vue的视图更新原理主要涉及响应式相关APIObject.defineProperty的使用。内部可以监听该属性的读写操作,实现后续同步视图更新功能。1.实现响应式核心API:Object.definePropertyObject.defineProperty的用法介绍:MDN-Object.defineProperty,下面是模拟Vue数据更新值,初步了解API接口//模拟datainVueconstdata={}//内部变量,外部不可见let_myName='Yimwu'//响应式监听data中的nameObject.defineProperty(data,"name",{//当data.name使用时,调用get方法,返回内部存储变量值,调用set方法设置内部存储变量值set:(newVal)=>{console.log('set')_myName=newVal}})console.log(data.name)//输出Yimwugetdata.name='Mr.Wu'//输出集(监听成功)2.视图更新的初始实现1.updateView为了便于模拟视图update,这里创建了一个函数updateView。数据更新时,调用updateView模拟视图更新(在Vue模板中,是引用模板中变量值的DOM元素的变化)//验证更新是否触发函数updateView(){console.log('viewupdate')}2.defineReactive为APIObject.definePr创建一个函数defineReactiveoperty被封装,接受三个参数,需要监控的目标对象,属性名,属性值。一个target(对象)可以通过调用defineReactive来监听key(对应属性名),类似于Vue:具体实现如下://重新定义属性并监听它们functiondefineReactive(target,key,value){Object.defineProperty(target,key,{get(){returnvalue},set(newVal){//value一直在闭包中,在这里设置完成后,下次get可以拿到最新的设置值//这里做一个小优化,如果同样,不会触发更新if(newVal!==value){value=newVal//触发更新updateView()}}})}3.observeobserve主要用于defineReactive监控对象中的各个属性//监听对象属性functionobserve(target){if(typeoftarget!=='object'||target===null){//不是数组或者对象不适合监听returntarget}//重新定义合适的对象与defineProperty的关系for(letkeyintarget){defineReactive(target,key,target[key])}}4.完整的代码和测试示例//验证更新是否触发函数updateView(){console.log('查看更新')}//重启定义属性并监听它们到这里就完成了,下次get可以拿到最新的set值//这里做个小优化,如果相同,就不会触发updateif(newVal!==value){value=newVal//触发updateupdateView()}}})}//监听对象属性functionobserve(target){if(typeoftarget!=='object'||target===null){//不是数组或者对象不适合monitoringreturntarget}//使用defineProperty重新创建对象的属性definefor(letkeyintarget){defineReactive(target,key,target[key])}}//准备数据constdata={name:'Yimwu',id:001,information:{tel:'135xxxxx354',email:'15xxxxx@xx.com'}}//监控数据observe(data)//测试data.name='YI'//(监控成功)输出-->数据更新data.age={num:21}(监听成功)输出-->数据更新data.information.tel='13456xxx234'//(监听失败)data.age.num=110//(监听失败)详见前端进阶面试答案问题5.视图更新优化(实现对象的深度监控)从上面的测试例子可以看出,对于data.information.tel这样的嵌套对象,第一版defineReactive无法监控,解决方法也是很简单。递归调用对象的所有属性来监控函数,即在执行Object.defineProperty之前递归调用observe,如果属性是一个对象,那么observe会**递归调用,defineReactive**,如果不是,observe会返回直接直接继续执行Object.defineProperty,完整代码和测试示例如下://重新定义属性并监听functiondefineReactive(target,key,value){//再次调用observe,值嵌套很深,如果是一个对象,则进行进一步的监控,如果不是如果value不是对象,直接返回observe(value)Object.defineProperty(target,key,{get(){returnvalue},set(newVal){//value一直在闭包中,这里设置完成后,下次get时能够获取到最新设置的值if(newVal!==value){value=newVal//触发更新updateView()}}})}//测试数据constdata={name:'Yimwu',id:001,information:{tel:'135xxxxx354',email:'15xxxxx@xx.com'}}//监控数据observe(data)//测试data.name='YI'//(监控成功)输出-->dataupdatedata.information.tel='00000000000'(监听成功)输出-->数据更新6.如何理解Vue.set在使用Vue的过程中,我们可能有过这样的经历,在data中定义一个对象,然后在程序的执行,属性是动态给他添加的,然后我们更新新添加的属性的值时,并没有触发视图更新。作为Vue初学者,很难理解作为黑盒的数据响应为什么不更新,而今天打开原理后,很容易理解这里data.id={num:010}//(监听成功)output-->dataupdatedata.id.num=110//(监听失败)如上图所示,给id赋值对象时,触发id的数据更新,给id赋值时。num,不触发数据更新。根据第5步的代码可以看出,这其实是因为执行set的时候没有对set值进行处理,导致num属性没有被设置为monitor这里的例子,解决方法比较简单粗暴。只需要直接把set接受的值放到set中的observe函数中执行,就可以监听这个值了。下面是最终的defineReactive函数代码和测试示例://重新定义属性并监听functiondefineReactive(target,key,value){//使用value嵌套,再次调用observe。Object.defineProperty(target,key,{get(){returnvalue},set(newVal){//对新增的值进行深度监控,比如data.id={num:101},新增的num会也可以监听到observe(newVal)//value一直在闭包里面,这里设置完成后,下次get可以拿到最新的设置值if(newVal!==value){value=newVal//triggerupdateupdateView()}}})}//测试数据constdata={name:'Yimwu',id:001,information:{tel:'135xxxxx354',email:'15xxxxx@xx.com'}}//监控数据observe(data)//测试data.id={num:010}//(监控成功)输出-->数据更新data.id.num=110//(监控成功)输出-->数据更新3、视图更新优化————实现数组监控在上一节【初步实现】中,已经实现了对对象所有属性和嵌套属性的监控。但是,如果某个属性是数组,对数组进行push、pop等操作,会不会触发update?显然不是,因为Object.defineProperty不具备监听数组内部变化的能力,那怎么解决————重写原来的数组类型方法1.定义监控数组的原型。我们都知道在JS中,任何对象都有一个原型,而我们的目的就是通过重写数组原型上的方法(push、pop等)来实现监听。作为库或框架,我们不应该更改全局原型上的任何本地方法或属性来污染全局环境,所以这里有3个步骤:第一步:创建一个对象并将数组的原型赋值给对象constoldArrayProperty=Array.prototype第二步:新建对象,原型指向对象constarrProperty=Object.create(oldArrayProperty)第三步:重写对象上的方法arrProperty.push=function(){}...arrProperty。pop=function(){}...//重新定义数组原型并添加触发更新的机制constoldArrayProperty=Array.prototype//创建一个新对象,原型指向oldArrayPropertyconstarrProperty=Object.create(oldArrayProperty)//重写原型上的方法(都可以改写,这里只举几个例子)//arrProperty.push=function(){}//arrProperty.pop=function(){}//优化写法constmethods=['push','pop','shift','unshift','splice']methods.forEach(method=>{arrProperty[method]=function(){updateView()Array.prototype[method].call(this,...arguments)}})2、将要监听的数组原型指向一个自定义的特殊prototype修改原来的observe,增加一个数组判断,如果是数组,修改数组的prototype,至此,数组监听完成,下面是observe//监听对象属性的修改代码和测试示例函数观察(目标){如果(目标类型!=='对象'||目标===null){//不是数组或对象returntarget}//如果是数组,修改数组的原型if(Array.isArray(target)){target.__proto__=arrPropertyreturn}//重新定义属性为(letkeyintarget){defineReactive(target,key,target[key])}}//测试数据constdata={myCars:['Bugatti','Koenigsegg']}//监控数据observe(data)//testdata.myCars.push('AE86')//(监控成功)输出-->数据更新4.性能分析为了实现对对象的各个嵌套属性监控的全覆盖,需要深度遍历对象的属性又递归到最后,所以性能损失非常大,尤其是在初始化阶段。如果响应式监控绑定有大量非常高级的对象,会极大地消耗初始化时的性能,从而拖慢FirstPaintTime总结。当使用了一段时间的Vue之后,或者说已经能够熟练使用Vue的时候,我们就需要开始进一步深入挖掘Vue的高级用法和原理,从根本上学习和理解Vue的底层原理。在了解了Vue的相关设计原则后,我们就可以使我们突破平时开发过程中的好用的层次,来到更好用、熟练使用的更高级层次。从底层原理来看,会是最好的性能优化和架构设计。好突破!