| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> JavaScript知识库 -> 6 Vue 原理(SY) -> 正文阅读 |
|
[JavaScript知识库]6 Vue 原理(SY) |
组件化 mvvm 响应式 vdom 和 diff 模板编译 渲染过程 前端路由 目录 5??深入diff算法源码?- updateChildren函数?? ? ? ? ? ? ? ? ? ? ?vue-router实现原理: 1 Vue 与MVVM1 如如何理解MVVM模型数据驱动视图
左到右:DOM事件被vm模型监听到之后,可以去修改Model中的数据 右到左:Model中的数据一旦修改就立刻更新view,重新渲染 如此一来视图就不用我们自己手动去改了 1?vue响应式如何实现只要在 Vue 实例中声明过的数据,这个数据就是响应式的。 什么是响应式,即,数据发生改变的时候,视图会重新渲染,匹配更新为最新的值。
在组件生命周期中,这件事发生在 Observer的目标,就是要让一个对象,它属性的读取、赋值,内部数组的变化都要能够被vue感知到。
数组, ? ? 2?Dep实例??
3 Watcher 我们不要直接执行函数,而是把函数交给一个叫做watcher的东西去执行,watcher是一个对象,每个这样的函数执行时都应该创建一个watcher,通过watcher去执行 watcher会设置一个全局变量,让全局变量记录当前负责执行的watcher等于自己,然后再去执行函数,在函数的执行过程中,如果发生了依赖记录dep.depend(),那么Dep就会把这个全局变量记录下来,表示:有一个watcher用到了我这个属性 当Dep进行派发更新时,它会通知之前记录的所有watcher:我变了 每一个vue组件实例,都至少对应一个watcher,该watcher中记录了该组件的render函数。 watcher首先会把render函数运行一次以收集依赖,于是那些在render中用到的响应式数据就会记录这个watcher。 当数据变化时,dep就会通知该watcher,而watcher将重新运行render函数,从而让界面重新渲染同时重新记录当前的依赖。 4?Scheduler 调度器 现在还剩下最后一个问题,就是Dep通知watcher之后,如果watcher执行重运行对应的函数,就有可能导致函数频繁运行,从而导致效率低下 这样显然是不合适的,因此,watcher收到派发更新的通知后,实际上不是立即执行对应函数,而是把自己交给一个叫调度器的东西 调度器维护一个执行队列,该队列同一个watcher仅会存在一次,队列中的watcher不是立即执行,它会通过一个叫做nextTick的工具方法,把这些需要执行的watcher放入到事件循环的微队列中,nextTick的具体做法是通过Promise完成的 nextTick 通过? 也就是说,当响应式数据变化时, 5? Object.defineProperty缺点 深度监听,需要递归到底,一次性计算量大 无法监听新增属性/删除属性(Vue.set Vue.delete) data.x?=?'100'//新增属性,监听不到?-?所以有Vue.set delete?data.name?//删除属性,监听不到?-?所以有Vue.delete 2? 监听data变化的核心API? ? 1,Observer ? ? ? ? 将data中的数据用Object.defineProperty进行数据劫持,每个目标对象的键值(即data中的数据)转换成getter/setter形式,用于进行依赖收集和通过依赖通知更新 ? ? 2,Dep(依赖管理) ? ? ? ? 1)什么是依赖? ????????????数据响应式后,如何通知视图更新?Dep就是帮我们收集【究竟要通知到哪里的】 ? ? ? ? 2)如何收集依赖 ? ??????????我们如何知道data中的某个属性被使用了,答案就是Object.defineProperty,因为读取某个属性就会触发get方法 ? ? ? ? 3)Dep就是收集与视图相关的数据,触发了get的数据,主要起到依赖收集和通知更新的作用。用于收集当前响应式对象的依赖关系,每个响应式对象包括子对象都拥有一个 Dep 实例(里面 subs 是 Watcher 实例数组),当数据有变更时,会通过 dep.notify()通知各个 watcher。 ? ? ? ? 4)initState 时,对 computed 属性初始化时,触发 computed watcher 依赖收集 ? ? ? ? 5)initState 时,对侦听属性初始化时,触发 user watcher 依赖收集 ? ? ? ? 6)render()的过程,触发 render watcher 依赖收集 ? ? ? ? 7)re-render 时,vm.render()再次执行,会移除所有 subs 中的 watcer 的订阅,重新赋值。 ? ? 3,Watcher ? ? ? ? 1)Watcher就是类似中介的角色,比如message就有三个中介,当message变化,就通知这三个中介,他们就去执行各自需要做的变化。 ? ? ? ? 2)Watcher必须要有的2个方法。一个就是通知变化,另一个就是被收集起来到Dep中去。 ? ? ? ? 3)遍历所有的 subs(Watcher 实例),调用每一个 watcher 的 update 方法。 ? ? 4,Watcher 和 Dep 的关系 ????????watcher 中实例化了 dep 并向 dep.subs 中添加了订阅者,dep 通过 notify 遍历了 dep.subs 通知每个 watcher 更新。 三,总结 ? ? 1,在数据被改的时候,触发set方法,通过对应的所有依赖(Watcher),去执行更新。比如watch和computed就执行开发者自定义的回调方法。 ? ? 2,Observer中进行响应式的绑定,在数据被读的时候,触发get方法,执行Dep来收集依赖,也就是收集Watcher。 3 如何深度监听·data变化?const?data?=?{ ??name:?'张三', ??age:?18, ??info:?{ ????city:?'北京' ??}, ??curse:?['数学',?'语文'] } //?监听数组 const?oldArrayPrototype?=?Array.prototype; const?arrProto?=?Object.create(oldArrayPrototype); ['push',?'pop',?'shift',?'unshift'].forEach((methodNanme)?=>?{ ??arrProto[methodName]?=?function?()?{ ????console.log('更新视图'); ????oldArrayPrototype[methodName].call(this,?...arguments);?//?调用原生数组方法 ??} }) function?isObject(obj)?{ ??return?typeof?obj?===?'object'?&&?obj?!==?null; } function?defineReactive(target,?key,?value)?{ ??observe(value);?//?深度监听 ??Object.defineProperty(target,?key,?{?//?只能监听对象,监听不到数组 ????get()?{ ??????return?value; ????}, ????set(newVal)?{ ??????if(newVal?!==?value)?{ ????????observe(newVal);?//?深度监听新值,防止新值是一个对象/数组 ????????value?=?newVal; ????????console.log('更新视图'); ??????} ????} ??}) } function?observe(target)?{ ??if(!isObject)?return?target; ?? ??if(Array.is(target))?{ ????target.__proto__?=?arrProto;?//?改变数组隐式原型指向 ??} ?? ??for(let?key?in?target)?{ ????defineReactive(target,?key,?target[key]); ??} } data.age?=?19;?//?触发更新 data.curse.push("英语");?//?触发更新 data.sex?=?"男";?//?新增属性,不会触发 delete?data.name;?//?删除属性,不会触发 data.curse[data.curse.length]?=?"物理";?//?按索引添加,不会触发 console.log(data); 缺点: 1. 当对象层级较多,要深度监听时,需要递归到底,一次性计算量大 关键关键是要?一次性计算,效率低。 2.?defineProperty只有get和set属性,所以无法监听新增属性,或者是删除属性 3. 无法监听数组。因为defineProperty对数组不适用 4 如何监听 数组变化?? ? 2,数组监听:就是重写了数组的原型,更准确的表达是拦截了数组的原型,然后用Object.defineProperty劫持数组方法 ? ? 3 ,vue3.0,Object.defineProperty的替代方案是proxy(不能兼容ie11)、 2? diff 算法1 虚拟DOM 与? ?diff算法?树 diff 算法的时间复杂度 O(n ^ 3)
优化时间复杂度到?O(n)
2 深入diff算法源码?- 生成Vnodediff算法源码,snabbdom生成vnode snabbdom - 源码解读 h函数: ?vnode函数: h函数通过vnode函数,最终返回一个对象? 3? 深入diff算法源码?- patch函数?patch函数。先判断传入参数是vnode还是dom。 再判断vnode是否相同,比较key 和 selector 都相同再调用 patchVnode 进行后续比较,否则直接删除重建 都不传key的时候,undefined === undefined -> true 只需要比较selector(sel) 在循环体for里面需要传key ?判断same?node,要key和sel都相同才是?same?node ?function?sameVnode(vnode1:?VNode,?vnode2:?VNode):?boolean?{ ????const?isSameKey?=?vnode1.key?===?vnode2.key; ????const?isSameIs?=?vnode1.data?.is?===?vnode2.data?.is; ????const?isSameSel?=?vnode1.sel?===?vnode2.sel; ?? ????return?isSameSel?&&?isSameKey?&&?isSameIs; ??} patch 函数:?
sameVnode? //?判断是否相同vnode,其中用到了key function?sameVnode(vnode1,?vnode2)?{ ??//?sel?是?snabbdom?的判断方式?结构为?sel?=?标签?+?id?+?calss ??//?若都没有?key,则为?undefined?===?undefined?&&?vnode1.sel?===?vnode2.sel ??return?vnode1.key?===?vnode2.key?&&?vnode1.sel?===?vnode2.sel } 4?深入diff算法源码?- patchVnode函数对比。text children 1. 先获取oldNode和newNode的children, 2. 判断新vnode的text是否有值,如果有值,一般children就为空,此时删除旧的node,设置新的text即可。 3.?else情况,当vnode的text为undefined时(isUndef(vnode.text)为true),vnode children有值。再进行 新旧都有children、新node有children旧的没有、旧node有children新的没有等等判断。 4. 核心原则就是当旧的没有新的有,直接添加新的。旧的有新的没有,删除旧的。 function?patchVnode(oldVnode,?vnode,?insertedBnodeQueue)?{ ??//?设置?vnode.elm?用于知道更新哪块 ??const?elm?=?vnode.elm?=?oldVnode.elm!; ??//?旧?children ??const?oldCh?=?oldVnode.children; ??//?新?children ??const?ch?=?vnode.children; ??... ??//?children?和?text?一般是不能共存,text?为?undefined?说明有?children ??if(isUndef(vnode.text))?{ ????if(isDef(oldCh)?&&?isDef(ch))?{?//?都有?children ??????//?若?children?不相等,则更新 ??????if(oldCh?!==?ch)?updateChildren(...) ????}?else?if(isDef(ch))?{?//?只?vnode?有?children ??????//?若?oldVnode?有?text,清空?text?并添加?children ??????if(isDef(oldVnode.text))?api.setTextContent(elm,?''); ??????addVnodes(...) ????}?else?if(isDef(oldCh))?{?//?只?oldVnode?有children ??????//?vnode?没有?text?也没有?children,则删除 ??????removeVnodes(...); ????}?else?if(isDef(oldVnode.text))?{?//?都没有,且?oldVnode有text ??????//?清空?text ??????api.setTextContent(elm,?''); ????} ?? ??//?执行到?else?表示?vnode.text?!==?undefined(vnode.children?无值) ??//?并且和?oldVnode.text?不相等 ??}?else?if(oldVnode.text?!==?vnode.text)?{ ????//?如果oldVnode.children有值,则移除 ????if(isDef(oldCh))?{ ??????removeVnodes(...); ????} ????//?设置?vnode.text ????api.setTextContent(elm,?vnode.text); ??} ?? } 5??深入diff算法源码?- updateChildren函数??oldStartIdx++与oldEndIdx--,指针聚合时,循环结束,同理newStartIdx与newEndIdx也是一样的。 对比方式: 以下四个时命中的情况 sameVnode(oldStartVnode, newStartVnode)(开始与开始做对比)对比, sameVnode(oldEndVnode,newEndVnode)(结束与结束做对比)对比, sameVnode(oldStartVnode,newEndVnode)(开始与结束做对比),sameVnode(oldStartVnode,newStartVnode)(结束与开始做对比), 四种情况都没有命中??直接对题?对比 key? 使用key和不使用key 对比方式不要较真,只是看这个关键的点,这个关键的点是sameVnode函数 上面四种情况都没命中: 不使用key vs 使用key 6 diff算法总结diff算法是比较两个vnode,计算出最小的变更,以便减少DOM操作次数,提高性能 首先是生成vnode,它是通过h函数返回一个vnode结构,vnode它主要接收tag标签、有一些属性、还有一些子元素,它返回一个对象: patch参数有两种情况,一个参数是一个elementDOM元素,第二个是vnode,也有可能两个都是vnode,作用是第一个是直接渲染到一个空的DOM元素中,第二个是更新已有的内容 在patch函数中调用了patchVnode函数,接收两个参数,一个旧的vnode,一个新的vnode,获取两个的children,然后就是要进行新旧children的一些对比,主要是几种情况: 组件化 模板编译 渲染过程 前端路由 ?3?组件化可以从以下几点进行阐述: 组件化是 Vue.js 中的重要思想,它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用。任何的应用都会被抽象成一颗组件树: 定义 优点 使用场景 通用组件:实现最基本的功能,具有通用性、复用性,例如按钮组件、输入框组件、布局组件等。
注册组件的基本步骤
全局组件和局部组件当我们通过调用? ? 为什么组件data必须是函数
首先,如果不是一个函数, 4? 模板编译vue模板=》render函数=》vnode=》进行patch和diff 对于Vue来说,我们所认为的“HTML”其实都是字符串。 Vue会根据其规定的模板语法规则,将其解析成AST语法树; 然后会对这个大对象进行一些初步处理,比如标记没有动态绑定值的节点; 最后,会把这个大对象编译成render函数,并将它绑定在组件的实例上。 这样,我们所认为的“HTML”就变成了JavaScript代码,可以基于JavaScript模块规则进行导入导出,在需要渲染组件的地方,就调用render函数,根据组件当前的状态生成虚拟dom,然后就可以根据这个虚拟dom去更新视图了。? Vue的模板编译就是将“HTML”模板编译成render函数的过程。这个过程大致可以分成三个阶段:
(注:当前节点及其所有子节点都是静态节点,当前节点才会被打上静态节点的标记) (注:静态根节点是指本身及所有子节点都是静态节点,但是父节点为动态节点的节点,找到了静态根节点也就找到了“静态子树”) template?=?'<div?:id="myid">hello,xiang</div>'; var?compiler?=?require('vue-template-compiler') compiler.compile(template) 返回结果是一个render函数:with(this){return?_c('div',{attrs:{"id":myid}},[_v("hello,xiang")])} _c是createElement 函数,也就是snbbdom中的h函数, _v是createTextVNode? 函数会通过“with”语法将this上的属性和数据解析成变量,比如:代码字符串中的_c相当于this._c,name相当于this.name。_c、_v这些变量其实就是vue创建不同类型虚拟dom节点的方法,比如_c就是我们在写render函数时非常熟悉的创建元素类型节点的createElement方法,_v是创建文本类型节点的方法。 前置知识:js的with语法 vue template complier 将模板编译为render函数 执行render函数生成vnode with语法 改变{}内自由变量的查找规则,当做obj属性来查找 如果找不到匹配的obj属性,就会报错 with要慎用,它打破了作用域规则,易读性变差 模板编译 模板编译为render函数,执行人的人函数返回vnode 基于vnode再执行patch和diff 使用webpack vue-loader,会在开发环境下编译模板(重要) 5??渲染过程初次渲染过程?
更新过程
流程图:
异步渲染
6?前端路由?后端路由: 前端路由:
hash模式?实现原理 (1) 通过a标签、window.location改变hash。 hash是URL中#及后面的那部分,改变hash会记入历史栈,不会发起页面请求。 (2) 通过hashchange事件监听hash变化,触发页面改变。 a标签跳转、window.location跳转,浏览器前进后退引起的hash变化都可以触发chashchange 事件。
hash特点:
history的原理是H5的几个新API history.replaceState(data,title,url):在浏览器中替换当前历史记录; history.length():当前历史列表中的历史记录条数; window.onpopstate:实际上popstate是一个浏览器内置的点击事件,响应pushState和replaceState的触发调用; history.back(-1):返回到当前页的上一页(原页面表单中的内容会保留) history.back(0):页面刷新 history.back(1):当前页前进一页 history.go(-1): 返回到当前页的上一页(原页面表单中的内容会丢失,效果基本和history.back(-1)一样 history.forward():当前页面前进一页(和history.back(1)效果一样 此外,history方法可以直接调用,例:history.pushState(),也可以用window.history.pushState()来调用。因为history是属于浏览器中的子对象,两种调用方法都是生效的;
前端路由:原理篇_Palate的博客-CSDN博客_前端路由原理通过这篇文章,你可以了解到:为什么需要前端路由?解决了什么问题?前端路由的基本原理是什么?hash路由的hash值会发送到服务端吗?history路由为什么需要服务端支持?https://blog.csdn.net/weixin_51670675/article/details/124239269?ops_request_misc=&request_id=&biz_id=102&utm_term=%E5%89%8D%E7%AB%AF%E8%B7%AF%E7%94%B1%E7%9A%84%E5%8E%9F%E7%90%86&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-0-124239269.142%5Ev47%5Enew_blog_pos_by_title,201%5Ev3%5Econtrol_2&spm=1018.2226.3001.4187前端路由原理解析(含代码实现)_jim点点点点点的博客-CSDN博客关于前端路由你可能需要知道的内容什么是路由? 前端路由出现之前又是怎么实现路由的?前端路由hash 路由history 路由前端路由的缺点总结写在正文前: 作为一位已经工作了两年的前端 CRUD boy, 整日潜水在论坛看见各位大佬们谈天说地, 表示万分仰慕, 也想加入各位的吹水大军. 为此, 下定决心正式开始写文章锻炼自己的吹水能力, 也希望自己能坚持写下去. 还望各位大佬多多指正, 给小弟一...https://blog.csdn.net/jind325/article/details/105325221?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522166291643516800182127497%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=166291643516800182127497&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-2-105325221-null-null.142%5Ev47%5Enew_blog_pos_by_title,201%5Ev3%5Econtrol_2&utm_term=%E5%89%8D%E7%AB%AF%E8%B7%AF%E7%94%B1%E4%BB%A3%E7%A0%81&spm=1018.2226.3001.4187 vue-router实现原理:SPA(single page application):单一页面应用程序,只有一个完整的页面;它在加载页面时,不会加载整个页面,而是只更新某个指定的容器中内容。单页面应用(SPA)的核心之一是: 更新视图而不重新请求页面;vue-router在实现单页面前端路由时,提供了两种方式:Hash模式和History模式;根据mode参数来决定采用哪一种方式。 方式1:直接修改地址栏 方式2:this.$router.push(‘路由地址’) 方式3:? hash 通过window.onhashchange监听 H5 history 通过history.pushState 和 window.onpopstate监听 实现的 H5 history? 需要后台支持 两者选择 to B 的系统推荐用hash,简单易用,对url规范不敏感 eg.管理系统(ToB就是在企业业务中,以企业作为服务主体为企业客户提供平台、产品或服务并赚取利润的业务模式,我们也可以把它称之为企业服务。) to C的系统,可以考虑选择 H5 history,但需要服务端支持 eg.系统需要做SEO 搜索引擎优化 (tTo B 英文为To Business面向企业 ,?To C为To Customer面向个体消费者) 能选择简单的,就别用复杂的,要考虑成本和收益 |
|
JavaScript知识库 最新文章 |
ES6的相关知识点 |
react 函数式组件 & react其他一些总结 |
Vue基础超详细 |
前端JS也可以连点成线(Vue中运用 AntVG6) |
Vue事件处理的基本使用 |
Vue后台项目的记录 (一) |
前后端分离vue跨域,devServer配置proxy代理 |
TypeScript |
初识vuex |
vue项目安装包指令收集 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/23 9:41:53- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |