组件化 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转换为带有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 nodefunction 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.selreturn 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!;// 旧 childrenconst oldCh = oldVnode.children;// 新 childrenconst ch = vnode.children;...// children 和 text 一般是不能共存,text 为 undefined 说明有 childrenif(isUndef(vnode.text)) {if(isDef(oldCh) && isDef(ch)) { // 都有 children// 若 children 不相等,则更新if(oldCh !== ch) updateChildren(...)} else if(isDef(ch)) { // 只 vnode 有 children// 若 oldVnode 有 text,清空 text 并添加 childrenif(isDef(oldVnode.text)) api.setTextContent(elm, '');addVnodes(...)} else if(isDef(oldCh)) { // 只 oldVnode 有children// vnode 没有 text 也没有 children,则删除removeVnodes(...);} else if(isDef(oldVnode.text)) { // 都没有,且 oldVnode有text// 清空 textapi.setTextContent(elm, '');}// 执行到 else 表示 vnode.text !== undefined(vnode.children 无值)// 并且和 oldVnode.text 不相等} else if(oldVnode.text !== vnode.text) {// 如果oldVnode.children有值,则移除if(isDef(oldCh)) {removeVnodes(...);}// 设置 vnode.textapi.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());},//获取当前hashgetState: 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());},//获取当前pathgetState: 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.4187https://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.4187https://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面向个体消费者)

能选择简单的,就别用复杂的,要考虑成本和收益

6 Vue 原理(SY)相关推荐

  1. element 往node里面增加属性值_【Vue原理】Compile - 源码版 之 Parse 属性解析

    写文章不容易,点个赞呗兄弟 专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧 研究基于 Vue版本 [2.5.17] 如果你觉得排版难 ...

  2. 初始化触发点击事件_【Vue原理】Event - 源码版 之 自定义事件

    专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧 研究基于 Vue版本[2.5.17] Vue 的自定义事件很简单,就是使用 观察者模 ...

  3. vue 启动时卡死_十分钟浅入Vue 原理

    vue原理 引用 众所周知vue是一个MVVM 渐进式框架,MVVM是vue的设计模式,在vue框架中数据会自动驱动视图. 1.MVVM设计模式 ​ 解释 View是视图,就是DOM:对应视图也就是H ...

  4. vue 数值 拼接字符串_【Vue原理】Compile - 白话版

    写文章不容易,点个赞呗兄弟 专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧 研究基于 Vue版本 [2.5.17] 如果你觉得排版难 ...

  5. v-model双向绑定原理_【Vue原理】VModel 白话版

    ↑点击上方 "神仙朱" 一起研究Vue源码吧 专注 Vue 源码分享,为了方便大家理解,分为了白话版和 源码版,白话版让大家可以轻松理解工作原理,源码版让大家更清楚内部操作和 Vu ...

  6. data的值 如何初始化vue_【Vue 原理】Vue 是如何代理 data、methods 和 props 的?

    前言 Vue 有一个非常有趣的功能,就是我们所有传进去的 data .methods 或者 props,都会挂载到 Vue 实例上, 我们可以通过 this.xxx 的简单做法来进行访问.那么,这到底 ...

  7. 2021-08-06随记(vertical-align, 顶线、底线、中线、基线,vue原理理解)

    vertical-align 父元素是inline或者是table-cel,如果是inline-block或者block则必须要设置line-height(和高度一样)子元素为inline或者inli ...

  8. 【Vue原理】Diff - 源码版 之 Diff 流程

    写文章不容易,点个赞呗兄弟 专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧 研究基于 Vue版本 [2.5.17] 如果你觉得排版难 ...

  9. Vue 原理解析(五)之 虚拟Dom 到真实Dom的转换过程

    上一篇 vue 原理解析(四): 虚拟Dom 是怎么生成的 再有一颗树形结构的Javascript对象后, 我们需要做的就是讲这棵树跟真实Dom树形成映射关系.我们先回顾之前的mountComponn ...

  10. 掌握这些Vue原理,就能月薪30K?

    点击上方蓝色字关注我们~ 不会vue的前端工作者不是合格的web工程师!在2019年接近尾声的时候,尤雨溪宣布 Vue 3.0的源码开放了,想要在2020年跳槽的程序员们,现在可以着手准备了.去BAT ...

最新文章

  1. 魅族员工哀叹把青春献给了公司,当年如果选择小米,人生会不一样
  2. JS滚轮事件(mousewheel/DOMMouseScroll)了解
  3. jittor 和pytorch的生成网络对比之aae
  4. html5页面热力图几十万数据,基于百度地图的数据可视化,包括大量数据的标绘以及热力图的插入...
  5. VBScript学习笔记 - 数组
  6. Golang笔记——go使用Redis
  7. C#坏习惯:通过不好的例子学习如何制作好的代码——第3部分
  8. 三次样条插值matlab,Matlab关于三次样条插值
  9. 中国大学50强排行:北大清华复旦位列三甲
  10. java中输出日历_Java:输入年份和月份打印出相应的日历表
  11. Java实现局域网流量监控
  12. 在SQL Sever中使用form membership认证
  13. 只有360浏览器能打开别的都打不开
  14. php pcre 什么用,PHP—PCRE正则表达式性能 - pcre
  15. 设置chrome浏览器访问http服务时使用麦克风
  16. 学习笔记|视觉语言导航任务
  17. 这6部韩国电影你都看过吗?
  18. AutoCAD 2019,cad设计绘图必备哦
  19. WPF实现Aero毛玻璃效果
  20. Openstack(T版)组件部署

热门文章

  1. campaign 缩写_Campaign Monitor的画布:简化电子邮件设计
  2. 转:细数国内市场智能语音开放平台有哪些?
  3. 电路基本原理和加法器的实现---计算机组成原理学习心得04
  4. 第7课 微信小程序实现图片搜索器案例:
  5. [笔记]OpenCV+FFmpeg+Qt实现视频编辑器之OpenCV视频lO接口
  6. 基于web的在线视频编辑的设计
  7. no implicit conversion of nil into String
  8. Android.网络连接状态(联网,2g,3g,wifi等)
  9. sqlDbx连接oracle64位
  10. ucharts折线图出现断点时画小圆圈(当线的width很大时,小圆圈会很大)