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

Vue理解2.0

时间:2023-03-31 20:24:12 vue.js

vue响应式原理核心API:Object.definePropertyObject.defineProperty的简单使用:Object.defineProperty(obj,prop,desc)obj当前需要定义属性的对象prop当前需要定义的属性名被定义descpropertydescriptor()letPerson={}lettemp=nullObject.defineProperty(Person,'name',{get:function(){console.log("get");returntemp},set:function(val){console.log("set",val);temp=val}})Person.name="Hyacinth"console.log(Person.name)Object.defineProperty如何监听数组和对象//触发更新视图functionupdateView(){console.log('Viewupdate')}//重新定义数组原型constoldArrayProperty=Array.prototype//创建一个新的对象,原型指向oldArrayProperty,再扩展新的方法不会影响原型constarrProto=Object.create(oldArrayProperty);['push','pop','shift','unshift','splice'].forEach(methodName=>{arrProto[methodName]=function(){updateView()//触发视图更新oldArrayProperty[methodName].call(this,...arguments)//Array.prototype.push.call(this,...arguments)}})//重新定义属性,监听函数defineReactive(target,key,value){//深度监控observer(value)//核心APIObject.defineProperty(target,key,{get(){returnvalue},set(newValue){if(newValue!==value){//深度监控(设置新值时)observer(newValue)//设置新值//注意值一直在闭包中,这里设置后,再次获取时会获取到最新的值value=newValue//触发更新视图updateView()}}})}//监控对象属性functionobserver(target){if(typeoftarget!=='object'||target===null){//不是一个对象或数组返回目标}//污染全局数组原型//Array.prototype.push=function(){//updateView()//...//}if(Array.isArray(target)){target.__proto__=arrProto}//重新定义每个属性(forin也可以遍历数组)for(letkeyintarget){defineReactive(target,key,target[key])}}//准备数据constdata={name:'zhangsan',age:20,info:{address:'北京'//needdeepmonitoring},nums:[10,20,30]}//监控数据observer(data)//test//data.name='lisi'//data.age=21////console.log('年龄',data.age)//data.x='100'//添加的属性,无法监听——所以有Vue.set//deletedata.name//删除的属性,无法监听——都已经被Vue.deleted//data.info.address='Shanghai'//深度监控data.nums.push(4)//监控数组Object.defineProperty的缺点(Vue3.0启用Proxy)深度监控需要递归到底,一次性计算无法监听新属性/删除属性(数据中未初始化的属性,需要vue.set(),vue.delete())无法原生监听数组,需要特殊处理Proxy存在兼容性问题,无法兼容polyfill13.虚拟DOM(virtualDOM)DOM操作是非常耗费性能的。以前,DOMVue和React都是数据驱动的视图。如何操作DOM。解决方案-VDOM:该项目具有一定的复杂性,计算量难??以降低。能不能把更多的计算转移到js计算上,因为js执行速度比较快。vdom:使用js模拟DOM结构,计算最小变化,操作DOM1)使用js模拟DOM结构原理//html原始结构

  • a
//js模拟DOM结构{tag:"div",props:{className:"content",id:"div1"},children:{tag:"ul",props:{style:"font-size:20px;",},children:{tag:"li",children:"a"}}}2)通过snabbdom学习vdom(一)diff算法概述diff是一个广义的概念,比如linuxdiff命令和gitdiff等两个js也可以通过diff来比较。如:github.com/cujojs/jiff两棵树做diff,比如vdomdiff。diff算法的时间复杂度为O(n)o(n):1000个节点,时间复杂度为1000。如何将时间复杂度优化到O(n)只进行同层比较,不进行跨层比较,降低算法的复杂度。如果标签不相同,则直接删除重建,不再进行比较。如果tag和key相同,则认为是同一个节点,不进行深度比较。(2)diff算法总结hook方法://hookexportinterfaceHooks{//callpre?:PreHookwhen`patch`开始执行;//当`createElm`进入时调用init//vnode转换为真正的Init?:InitHook在创建DOM节点时被触发;//当创建一个真正的DOM时,调用createcreate?:CreateHook;//当`patch`方法快完成时,收集所有插入的节点,遍历调用response的hook//可以认为insert?:插入DOM树时触发InsertHook;//callprepatch?:PrePatchHook在两个节点比较之前;//在更新过程中调用update?:UpdateHook;//当两个节点之间的比较完成时调用postpatch?:PostPatchHook;//当一个节点被删除时调用,包括子节点的销毁也会被触发destroy?:DestroyHook;//当前节点被删除时调用。从父节点删除元素时触发,与destroy略有不同,remove只影响被移除节点中的最顶层节点remove?:RemoveHook;//在`patch`方法结束时调用,即patch完成后触发post?:PostHook;}将每个模块下的hook方法提取出来存放在cbs中sameVnode:判断是否是同一个虚拟节点patch:init方法最后返回一个patch方法。patch方法的主要逻辑如下:触发prehook。如果旧节点不是vnode,则创建一个空vnode。如果新旧节点是sameVnode,调用patchVnode更新vnode。否则,创建一个新节点来触发收集到的新元素。inserthook触发posthookpatchVnode作用:比较两个vnode节点是否相似,patch不同,直接移除和添加patchVnode方法主要逻辑如下:触发prepatchhook触发updatehook,这里主要是更新对应模块内容非文本节点,调用updateChildren更新所有子节点文本节点的情况下,直接api.setTextContent(elm,vnode.textasstring);updateChildren方法:patchVnode中最重要的方法,也是整个diff中的核心方法。updateChildren的主要逻辑如下:特殊场景优先,先比较两端。即oldvnodeheadvsnewvnodehead,oldvnodetailvsnewvnodetail,oldvnodeheadvsnewvnodetail,oldvnodetailvsnewvnodehead,是否是新节点的key对应于oldCh中一个节点的key。如果找到键,但元素选择器已更改,则还会创建一个新元素。如果找到键并且元素选择没有改变,则移动元素。比较两个列表后,清理掉多余的元素,添加添加的元素addVnodes方法:addVnodes比较简单,主要作用是将Vnodes添加到真实DOM中removeVnodes方法:删除VNodes的主要逻辑如下:循环循环触发destroyhook,递归触发子节点的hook触发removehook,使用createRmCb,在所有listeners的implementer执行完后,调用api.removeChild删除真正的DOM节点createElm方法:主要逻辑如下:触发init钩子来处理注释节点以创建元素并设置id,class触发模块创建钩子。进程子节点进程文本节点触发vnodeData的createhook14.模板编译模板不是html,有指令、插值、js表达式,可以实现判断和循环。HTML是一个标签,只有js可以实现判断和循环(图灵完备语言)模板必须转换成某种js代码,即编译后的模板1),js的with语法//使用with可以改变{}中查找自由变量的方式//使用{}中的自由变量作为obj的属性来查找,如果没有找到,会报错,//不推荐,它破坏了作用域规则,constobj={a:1000,b:1000}with(obj){console.log(a);控制台日志(b);安慰。日志(c);//errorcisnotdefined}2),vuetemplatecompliercompilertemplateintorenderfunctionconstcompiler=require('vue-template-compiler')//interpolation//consttemplate=`

{{message}}

`//with(this){returncreateElement('p',[createTextVNode(toString(message))])}//h->vnode//createElement->vnode////表达式//const模板=`

{{标志?message:'找不到消息'}}

`//with(this){return_c('p',[_v(_s(flag?message:'nomessagefound'))])}////属性和动态属性//consttemplate=`//////
//`//with(this){return_c('div',//{staticClass:"container",attrs:{"id":"div1"}},//[//_c('img',{attrs:{"src":imgUrl}})])}////条件//consttemplate=`//
//A

//B

//
//`//with(this){return_c('div',[(flag==='a')?_c('p',[_v("A")]):_c('p',[_v("B")])])}//循环//consttemplate=`////`//with(this){return_c('ul',_l((list),function(item){return_c('li',{key:item.id},[_v(_s(item.title))])}),0)}//事件//consttemplate=`//提交//`//with(this){return_c('button',{on:{"click":clickHandler}},[_v("submit")])}//v-modelconsttemplate=``//主取决于输入事件//with(this){return_c('input',{directives:[{name:"model",rawName:"v-model",value:(name),expression:"name"}],attrs:{"type":"text"},domProps:{"value":(name)},on:{"input":function($event){if($event.target.composing)return;name=$event.target.value}}})}//renderfunction//returnvnode//patch//compileconstres=compiler.compile(template)console.log(res.render)//---------------分隔线----------------////从vue源码中找到缩写函数的含义//functioninstallRenderHelpers(target){//目标._o=markOnce;//target._n=toNumber;//目标._s=toString;//target._l=renderList;//target._t=renderSlot;//target._q=looseEqual;//target._i=looseIndexOf;//target._m=renderStatic;//target._f=resolveFilter;//target._k=checkKeyCodes;//target._b=bindObjectProps;//节点目标._v=createTextVNodety;//targetVmp._e=createE;//target._u=resolveScopedSlots;//target._g=bindObjectListeners;//target._d=bindDynamicKeys;//target._p=prependModifier;//}3)、执行render函数生成vnodeVNode代表VirtualDOM,用JavaScript对象描述真实DOM并给DOM打上标签,Attributes和content成为对象属性15.前端路由原理1)、hash模式hash变化会触发网页跳转,即浏览器向前和向后的hash变化不会刷新页面,spahsah的必要特性永远不会提交监听server端的window.onhashchange2)、H5历史模式使用url-标准路由,但是跳转时不刷新页面(只刷新刚进来的页面)history.pushState(这样跳转到浏览器不会刷新页面)window.onpopstate(监听前进后退浏览器的,在history.pushState()或history.replaceState())形成的history节点中forward和backward到B的系统推荐使用hash,简单易用,对url规范不敏感到C的系统,你可以选择H5历史。seo优化,使用H5history16和computedfeaturecache,数据保持不变不会重新计算。提高性能17.为什么组件数据必须是函数数据必须声明为返回初始数据对象的函数,因为组件可能用于创建多个实例。如果数据仍然是一个纯对象,所有实例将共享对同一个数据对象的引用,通过提供数据函数,我们可以在每次创建新实例时调用数据函数,从而返回原始数据对象的新副本.18.数据请求应该恰好在该生命周期内挂载。js是单线程的。挂载前异步获取数据是没有用的,只会让逻辑更加混乱。异步数据不在js中渲染,不在查询中。会提前加载。19.多个组件具有相同的逻辑。如何分离和使用mixin?多个组件可以共享数据和方法。使用mixin引入到组件中后,mixin中的方法和属性也会被合并到组件中,可以直接使用。数据对象在内部递归合并,在发生冲突时组件数据优先。hook函数都会被调用,mixin中的hook会先执行。存在问题:变量来源不明确,多个mixin可能会导致命名冲突。mixin和组件之间可能存在多对多关系,业务逻辑复杂。20、何时使用keep-alive缓存组件,无需重复渲染多个静态标签页。21.何时使用beforeDestory取消自定义事件event.$offClearTimer释放自定义DOM事件,如窗口滚动等22.vue常用性能优化v-show和v-if的合理使用computed的合理使用v-for添加key,避免与v-if同时使用自定义事件和dom事件及时销毁合理使用异步组件合理使用keep-alive数据层次不要太深(响应式绑定递归遍历比较深)webpack层次优化前端通用性能优化(图片懒加载等)使用SSR