IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: 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
模板编译
渲染过程
前端路由

目录

1 Vue 与MVVM

1 如如何理解MVVM模型

1?vue响应式如何实现

2? 监听data变化的核心API

3 如何深度监听·data变化?

4 如何监听 数组变化?

2? diff 算法

1 虚拟DOM 与? ?diff算法?

2 深入diff算法源码?- 生成Vnode

3? 深入diff算法源码?- patch函数?

4?深入diff算法源码?- patchVnode函数

5??深入diff算法源码?- updateChildren函数??

6 diff算法总结

?3?组件化

注册组件的基本步骤

全局组件和局部组件

为什么组件data必须是函数

4? 模板编译

5??渲染过程

6?前端路由

? ? ? ? ? ? ? ? ? ?vue-router实现原理:


1 Vue 与MVVM

1 如如何理解MVVM模型

数据驱动视图

  • 在”很早以前“就有了组件化(如:asp、jap、php)

  • node 也有类似组件化

  • 但传统的组件化,只是静态渲染,更新还要依赖操作 DOM,这也是 jQuery 流行的原因

  • Vue、React 在这基础上做了一个微创新,即数据驱动视图

  • 数据驱动视图的出现,使得我们更关注数据(业务逻辑),不用再去关心DOM的增删改查

  • Vue 是 MVVM,React 是 setState

  • M -> Model V -> View VM -> ViewModel

 

左到右:DOM事件被vm模型监听到之后,可以去修改Model中的数据

右到左:Model中的数据一旦修改就立刻更新view,重新渲染

如此一来视图就不用我们自己手动去改了

1?vue响应式如何实现

只要在 Vue 实例中声明过的数据,这个数据就是响应式的。

什么是响应式,即,数据发生改变的时候,视图会重新渲染,匹配更新为最新的值。
在具体实现上,vue用到了几个核心部件

image-20210226163936839

描述:

? 一开始渲染组件要运行render函数,把render函数交给watcher执行的

? watcher的执行过程中会运行render函数 ,render函数执行中会用到一些数据,

? 这些数据又会用到getter(),这个getter是把原始对象通过observer把每一个属性变成getter和setter,

? 因此在render函数中会用到一些数据,这些数据会触发它的getter执行,而在触发getter的时候会有依赖收集,

? 就会记录watcher用到了这些数据,

? 有一天数据发生变化了,因为在之前数据已经被记录了,所以会通知watcher把render函数重新运行一遍,

? watcher不会自己执行,它会把自己交给调度器,调度器会把watcher添加到队列,如果有重复就不添加。然后把执行

? 这个队列的操作加到nextTick里面,这里面是异步的,
?

响应式原理流程.png


1Observer?

在组件生命周期中,这件事发生在beforeCreate之后,created之前。Observer把对象的每个属性通过Object.defineProperty转换为带有gettersetter的属性。

Observer的目标,就是要让一个对象,它属性的读取、赋值,内部数组的变化都要能够被vue感知到。

vue提供了$set$delete两个实例方法,让开发者通过这两个实例方法对已有响应式对象添加或删除属性。

数组,vue会更改它的隐式原型,之所以这样做,是因为vue需要监听那些可能改变数组内容的方法?

?

?

2?Dep实例??Dependency依赖

Vue会为响应式对象中的每个属性、对象本身、数组本身创建一个Dep实例,每个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 通过?this.$nextTick?暴露给开发者

也就是说,当响应式数据变化时,render函数的执行是异步的,并且在微队列中

? 深入浅出 Vue 响应式原理源码剖析_IT沐华的博客-CSDN博客_vue响应式源码解析先看张图,了解一下大体流程和要做的事初始化在 new Vue 初始化的时候,会对我们组件的数据 props 和 data 进行初始化,由于本文主要就是介绍响应式,所以其他的不做过多说明来,看一下源码源码地址:src/core/instance/init.js - 15行export function initMixin (Vue: Class<Component>) { // 在原型上添加 _init 方法 Vue.prototype._init = function (opthttps://blog.csdn.net/a151681931/article/details/120686534?ops_request_misc=&request_id=&biz_id=102&utm_term=vue%E5%93%8D%E5%BA%94%E5%BC%8F%20%E6%BA%90%E7%A0%81&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-2-120686534.142%5Ev47%5Enew_blog_pos_by_title,201%5Ev3%5Econtrol_2&spm=1018.2226.3001.4187

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)

  • 第一,遍历tree1;第二,遍历 tree2,第三排序

  • 1000 个节点,要计算1亿(1000 ^ 3)次,算法不可用

优化时间复杂度到?O(n)

  • 只比较同一层级,不跨级比较

  • tag 不相同,则直接删掉重建,不再深度比较

  • tag 和key,两者都相同,则认为是相同节点,继续深度比较

2 深入diff算法源码?- 生成Vnode

diff算法源码,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 函数:?

function path(oldVnode, vnode) {
  ...
  if(!isVnode(oldVnode)) {
    // 第一个参数不是 vNode 而是 DOM 元素,就创建一个空的 vNode,关联到这个 DOM 元素
    oldVnode = emtyNodeAt(oldVonde);
  }
  
  // vnode 相同(sel 和 key 都相同)
  if(sameVnode(oldVnode, vnode)) {
    // vnode 对比
    patchVnode(oldVnode, vnode, insertedBnodeQueue);
  
  // vnode 不相同
  } else {
    // 删除 oldVnode / 重建 vnode 当前层级
    ...
    createElm(vode, insertedBnodeQueue);
    
    if(parent !== null) {
      ...
      removeVnodes(parent, [oldVonde], 0, 0)
    }
  }
}

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操作次数,提高性能
原理:
○ 只比较同级,不跨域比较;
○ 如果tag不相同,直接删除重建,不再深度比较
○ 如果tag和key都相同,默认是一样的节点,继续深度比较
● 流程

首先是生成vnode,它是通过h函数返回一个vnode结构,vnode它主要接收tag标签、有一些属性、还有一些子元素,它返回一个对象:
● 里面有一个element,就是vnode对应的DOM元素,比如像patch用新的vnode代替旧的vnode做更新的时候,vnode肯定要对应的一个DOM元素的,不然它不知道往哪里更新;
● 里面的key可以理解为是v-for里面用到的key,但v-for用到的key只是在循环体中用,但实际上所有的组件都可以有key,只是我们在v-for循环的时候key是必须要有的,然后是patch,它主要用来比较新旧DOM,在比较新旧节点的时候,只会在同级比较,不会跨级比较

patch参数有两种情况,一个参数是一个elementDOM元素,第二个是vnode,也有可能两个都是vnode,作用是第一个是直接渲染到一个空的DOM元素中,第二个是更新已有的内容
● 首先会处理下第一个参数如果不是vnode,那么会创建一个空的vnode,关联到这个DOM元素
● 然后判断是否是相同的vnode(通过判断两个vnode的key和select都相同)
● 最后判断是不相同的两个vnode,就直接删掉重建,就不再对比了

在patch函数中调用了patchVnode函数,接收两个参数,一个旧的vnode,一个新的vnode,获取两个的children,然后就是要进行新旧children的一些对比,主要是几种情况:
● 两者都有children的时候,我们要通过updateChilren进行children之间的对比;
● 如果是新的children有值,旧的children没有值,我们要通过addVnode进行添加
● 如果是新的children没有值,旧的children有值,那我们要通过removeVnodes给移除掉

组件化

模板编译

渲染过程

前端路由

?3?组件化

可以从以下几点进行阐述:

组件化是 Vue.js 中的重要思想,它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用。任何的应用都会被抽象成一颗组件树:

在这里插入图片描述

定义
组件是可复用的 Vue 实例,准确讲它们是VueComponent的实例,继承自Vue。

优点
组件化可以增加代码的复用性、可维护性和可测试性。

使用场景
什么时候使用组件?以下分类可作为参考:

通用组件:实现最基本的功能,具有通用性、复用性,例如按钮组件、输入框组件、布局组件等。
业务组件:它们完成具体业务,具有一定的复用性,例如登录组件、轮播图组件。
页面组件:组织应用各部分独立内容,需要时在不同页面组件间切换,例如列表页、详情页组件


如何使用组件
定义:Vue.component(),components选项,sfc
分类:有状态组件,functional,abstract
通信:props,e m i t ( ) / emit()/emit()/on(),provide/inject,c h i l d r e n / children/children/parent/r o o t / root/root/attrs/$listeners
内容分发:<slot>,<template>,v-slot
使用及优化:is,keep-alive,异步组件

在这里插入图片描述

注册组件的基本步骤

在这里插入图片描述

?Vue.extend()这种写法在Vue2.x以后基本就很少见了,会直接使用语法糖,但内部还是这个原理。

全局组件和局部组件

当我们通过调用?Vue.component()?注册组件时,组件的注册是全局的,这意味着该组件可以在任意 Vue 实例下使用;如果我们注册的组件是挂载在某个实例中, 那么就是一个局部组件

在这里插入图片描述

?
组件的本质
vue中的组件经历如下过程
组件配置 => VueComponent实例 => render() => Virtual DOM=> DOM
所以组件的本质是产生虚拟DOM

为什么组件data必须是函数

组件是一个单独功能模块的封装,这个模块有属于自己的HTML模板。组件对象也有一个 data 属性,只是这个 data属性必须是一个函数,而且这个函数返回一个对象,对象内部保存着数据。

首先,如果不是一个函数,Vue直接就会报错;其次,Vue?让每个组件对象都返回一个新的对象,因为如果是同一个对象的,组件在多次使用后会相互影响?

4? 模板编译

vue模板=》render函数=》vnode=》进行patch和diff

对于Vue来说,我们所认为的“HTML”其实都是字符串。

Vue会根据其规定的模板语法规则,将其解析成AST语法树;

然后会对这个大对象进行一些初步处理,比如标记没有动态绑定值的节点;

最后,会把这个大对象编译成render函数,并将它绑定在组件的实例上。

这样,我们所认为的“HTML”就变成了JavaScript代码,可以基于JavaScript模块规则进行导入导出,在需要渲染组件的地方,就调用render函数,根据组件当前的状态生成虚拟dom,然后就可以根据这个虚拟dom去更新视图了。?

Vue的模板编译就是将“HTML”模板编译成render函数的过程。这个过程大致可以分成三个阶段:

  • 解析阶段:将“HTML”模板解析成AST语法树;
  • 优化阶段:从AST语法树中找出静态子树并进行标记(被标记的静态子树在虚拟dom比对时会被忽略,从而提高虚拟dom比对的性能);
  • 代码生成阶段:通过AST生成代码字符串,并最终生成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??渲染过程

初次渲染过程?

  1. 解析模板为render函数,开发环境中,vue-loader会做这件事

  2. 触发响应式,利用getter和setter监听data属性

  3. 首次执行render函数(会触发getter),生成vnode,patch(elem, vnode)

更新过程

  1. 修改data,触发setter

  2. 重新执行render函数,生成newVnode

  3. patch(vnode,newVnode)

流程图:

  1. 组件被 complier 编译成 Render 函数

  2. 执行 Render函数 生成 vDom

  3. 在 Render 函数中,触发 Data 属性的 getter,并被收集起来(Collect as Dependency)

  4. 观察(Watcher) 这些被收集的依赖

  5. 修改 Data 属性,setter 去通知(Notify)Watcher

  6. 若 Watcher 中存在修改的 Data 属性,则触发(Trigger)re-render

  7. 重新执行 Render 函数

 

异步渲染

  1. 汇总data修改,一个事件循环中多次修改会汇总一次性更新试图

  2. 减少渲染次数,提高性能

  3. nexttick在渲染完成后执行

6?前端路由

?后端路由:
早期传统MVC网站路由都是服务端主导,前端通过不同URL请求后端,后端框架有专门的路由模块用来匹配URL地址,然后根据不同地址和参数调用相关处理程序并返回html页面给前端。

前端路由:
后来前后端分离,react/vue等框架流行,路由由前端主导。还是由前端改变url,但要做到不发生真实的网页跳转,即不向服务器请求网页。然后改由前端监听路由变化,并截获路由进行匹配以显示不同的前端组件,组件再通过ajax获取视图所需json数据。

前端路由分两种:hash模式 和 history模式。
Vue Router sh

hash模式?实现原理

(1) 通过a标签、window.location改变hash。

hash是URL中#及后面的那部分,改变hash会记入历史栈,不会发起页面请求。

(2) 通过hashchange事件监听hash变化,触发页面改变。

a标签跳转、window.location跳转,浏览器前进后退引起的hash变化都可以触发chashchange 事件。
?

location.hash 获取浏览器的hash值

hashchange 监听浏览器的hash值变化

hash特点:

  1. hash变化会触发网页跳转,就是浏览器的前进、后退

  2. hash变化不会刷新页面,SPA的必备条件

  3. hash不会提交到server端

//这里需要你在html中创建一个id为app的dom用于放置内容
var appNode = document.querySelector("#app");
window.addEventListener("hashchange", () => {
  Router.hash.handler();
});
var Router = {
  //list = 路由列表, 模仿vue-router中的routerlist写法, component做了简化只是一段文字;
  list: [
    { path: "/", name: "index", component: "This is index page" },
    { path: "/hash", name: "hash", component: "This is hash page" },
    { path: "/history", name: "history", component: "This is history page" },
    { path: "*", name: "notFound", component: "404 NOT FOUND" }
  ],
  //输入path:String对应路由列表中的path, 实现渲染功能
  render: function(path) {
    var ele = null;
    //用于存储没有匹配路径时的404内容, 在这里默认路由列表中最后一个元素为404内容
    var naEle = this.list[this.list.length - 1];
    //通过path找出路由列表中对应的路由信息
    this.list.forEach(item => {
      if (item.path === path) ele = item;
    });
    //如果找到了path对应的路由信息, 则返回; 没找到的话, 返回404信息
    ele = ele ? ele : naEle;
    //将路由信息中的component加载进根节点
    appNode.innerHTML = ele.component;
  },
  hash: {
    //渲染
    handler: function() {
      Router.render(this.getState());
    },
    //获取当前hash
    getState: function() {
      var hash = window.location.hash;
      hash = hash ? hash.slice(1) : "/";
      return hash;
    },
    getUrl: function(path) {
      var href = window.location.href;
      var i = href.indexOf("#");
      var base = i >= 0 ? href.slice(0, i) : href;
      return base + "#" + path;
    },
    push: function(path) {
      window.location.hash = path;
    },
    replace: function(path) {
      window.location.replace(this.getUrl(path));
    },
    go: function(n) {
      window.history.go(n);
    }
  }
};
//加载初始页面
Router.render(window.location.hash ? window.location.hash.slice(1) : "/");

history的原理是H5的几个新API
history.pushState(data,title,url):在浏览器中新增一条历史记录;
data会在onpopstate事件触发时作为参数传递过去,title为页面标题,url为页面地址;

history.replaceState(data,title,url):在浏览器中替换当前历史记录;
data会在onpopstate事件触发时作为参数传递过去,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是属于浏览器中的子对象,两种调用方法都是生效的;

var appNode = document.querySelector("#app");
window.addEventListener("popstate", () => {
  Router.history.handler();
});
var Router = {
  list: [
    { path: "/", name: "index", component: "This is index page" },
    { path: "/hash", name: "hash", component: "This is hash page" },
    { path: "/history", name: "history", component: "This is history page" },
    { path: "*", name: "notFound", component: "404 NOT FOUND" }
  ],
  render: function(path) {
    var ele = null;
    var naEle = this.list[this.list.length - 1];
    this.list.forEach(item => {
      if (item.path === path) ele = item;
    });
    ele = ele ? ele : naEle;
    appNode.innerHTML = ele.component;
  },
  history: {
    //渲染
    handler: function() {
      Router.render(this.getState());
    },
    //获取当前path
    getState: function() {
      const path = window.location.pathname;
      return path ? path : '/';
    },
    //pushState相关参数说明
    //状态对象(state object):一个JavaScript对象,与用pushState()方法创建的新历史记录条目关联。无论何时用户导航到新创建的状态,会触发popstate事件,并能在事件中使用该对象。
    //标题(title):一般浏览器会忽略,最好传入null。
    //地址(URL):就是需要新增的历史记录的地址,浏览器不会去直接加载改地址,但后面也可能会去尝试加载该地址。此外需要注意的是,传入的URL与当前URL应该是同源的。
    push: function(path) {
      window.history.pushState(null, null, path)
      this.handler()
    },
    replace: function(path) {
      window.history.replaceState(null, null, path)
      this.handler()
    },
    go: function(n) {
      window.history.go(n);
    }
  }
};
//加载初始页面
Router.render(window.location.pathname);


?

前端路由:原理篇_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:?<router-linkto="路由地址"></router-link>

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项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-09-13 11:05:12  更:2022-09-13 11:06:56 
 
开发: 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-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码