组件化 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用到了几个核心部件:

描述:
? 一开始渲染组件要运行render函数,把render函数交给watcher执行的
? watcher的执行过程中会运行render函数 ,render函数执行中会用到一些数据,
? 这些数据又会用到getter(),这个getter是把原始对象通过observer把每一个属性变成getter和setter,
? 因此在render函数中会用到一些数据,这些数据会触发它的getter执行,而在触发getter的时候会有依赖收集,
? 就会记录watcher用到了这些数据,
? 有一天数据发生变化了,因为在之前数据已经被记录了,所以会通知watcher把render函数重新运行一遍,
? watcher不会自己执行,它会把自己交给调度器,调度器会把watcher添加到队列,如果有重复就不添加。然后把执行
? 这个队列的操作加到nextTick里面,这里面是异步的, ?

1Observer?
在组件生命周期中,这件事发生在beforeCreate之后,created之前。Observer把对象的每个属性通过Object.defineProperty转换为带有getter和setter的属性。
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 (opt https://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)
优化时间复杂度到?O(n)


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??渲染过程
初次渲染过程?
-
解析模板为render函数,开发环境中,vue-loader会做这件事 -
触发响应式,利用getter和setter监听data属性 -
首次执行render函数(会触发getter),生成vnode,patch(elem, vnode)
更新过程
-
修改data,触发setter -
重新执行render函数,生成newVnode -
patch(vnode,newVnode)
流程图:
-
组件被 complier 编译成 Render 函数 -
执行 Render函数 生成 vDom -
在 Render 函数中,触发 Data 属性的 getter,并被收集起来(Collect as Dependency) -
观察(Watcher) 这些被收集的依赖 -
修改 Data 属性,setter 去通知(Notify)Watcher -
若 Watcher 中存在修改的 Data 属性,则触发(Trigger)re-render -
重新执行 Render 函数

异步渲染
-
汇总data修改,一个事件循环中多次修改会汇总一次性更新试图 -
减少渲染次数,提高性能 -
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特点:
-
hash变化会触发网页跳转,就是浏览器的前进、后退 -
hash变化不会刷新页面,SPA的必备条件 -
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面向个体消费者)
能选择简单的,就别用复杂的,要考虑成本和收益
|