Vue理解2.0
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原始结构
//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=`//
//`//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=`//