赞
踩
**
hash模式和history模式
hash模式
*1.hash是url存在 # 的一种方式,是vueRouter默认模式
*2.当 # 后面的url地址发生变化会触发hashChange事件,通过监听来更新页面
*3.只可修改hash部分
*4.hash模式会创建hashHistory对象,hashHistory有两个方法push()和replace()
history模式
*1.history模式url无 # 号,就是普通的url形式
*2.history模式发生跳转通过history.pushState()和history.replaceState()方法改变浏览器地址
*3.当浏览器前进后退或调用back(), go(), forward()等方法时,会触发popstate事件,通过监听popstate事件来获取路由地址,从而更新页面
*4.需要和服务器配合使用,否则会出现404的情况
history模式在node服务中配置
```
const path = require('path')
// 导入处理history模式的模块
const history = require('connect-history-api-fallback')
// 导入express
const express = require('express');
const app = express();
// 处理history
app.use(history());
app.use(express.static(path.join(__dirname, 'dist')));
const port = process.env.PORT || 8080;
app.listen(port);
```
history模式在nginx服务中配置
在nginx.conf文件中找到
server {
listen 80
.
.
.
loaction / {
root html
index index.html index.htm
try_files $uri $uri/ index.html // 新增($uri当前请求的路径)
}
}
*Vue Router原理
hash模式
1.url # 后面内容作为地址
2.监听hashChange事件
3.根据当前的地址找到对应的组件重新渲染
hsitory模式
1.通过history.pushState()方法改变地址栏
2.监听popstate事件
3.根据当前的地址找到对应的组件重新渲染
let _vue = null
export default class VueRouter {
// vue参数是。vue的构造函数
static install(vue) {
// 1.判断插件是否已经安装
if (VueRouter.install.installed) return
VueRouter.install.installed = true
// 2.把vue构造函数设置为全局变量
_vue = vue
// 3.把创建的router对象注入到vue所有的实例上
_vue.prototype.$router = this.$options.router
this.init()
}
constructor(options) {
this.options = options // 传入的options
this.routeMap = {} // 解析出来的route
// this.data是一个响应式。通过observable创建响应式
this.data = _vue.observable({
current: '/', // 当前路由地址(根据当前路由加载对应的组件)
})
}
init() {
this.createRouteMap()
this.initComponent(_vue)
this.initEvent()
}
// 遍历所有的路有规则 解析成键值对 存放在routeMap中
createRouteMap() {
this.$options.routes.forEach(route => {
this.routeMap[route.path] = route.component
})
}
// 创建 router-link 组件
initComponent(vue) {
vue.component('router-link', {
props: {
to: String // 类型为字符串
},
render(h) {
return h('a', {
attrs: {
href: this.to
},
on: { // 添加事件阻止默认事件
click: this.clickHander
}
}, [this.$slots.default])
methods: {
// 阻止默认事件 通过pushState 改变当前地址。将当前地址同步
clickHander(e) {
// pushState() data title 路径
history.pushState({}, '', this.to)
this.$router.data.current = this.to
e.preventDefault()
}
}
}
// template: `<a :href='to'><slot></slot></a>` // *运行会报错 因为cli创建的vue是运行时版本的(运行效率高)。所以要开始编译器,通过在vue.config.js 设置runtimeCompiler来开启编译器
})
const self = this
vue.component('router-view', {
const component = self.routeMap[self.data.current]
render(h) {
return h(component)
}
})
}
// 创建popState 事件来解决当浏览器回退,前进 组件没有更新问题
initEvent() {
window.addEventListener('popState', () => {
this.data.current = window.location.pathname
})
}
}
**
vue2通过Object.defineProperty来实现响应式
// 模拟 vue 中的data
let data = {
msg: 'hello',
count: 10
}
// 模拟 vue 实例
let vm = {}
Object.keys(data).forEach(key => {
// 数据劫持。当访问和设置vm中的成员时,做一些干预操作
Object.defineProperty(vm, key, {
// 可枚举(可遍历)
enumerable: true,
// 可配置(可以使用 delete 删除,可以通过 defineProperty 重新定义当前属性)
configurable: true,
// 当获取值的时候执行
get() {
return data.msg
},
// 当设置值的时候执行
set(newVal) {
if (newVal === data.msg) return
data.msg = newVal
document.querySelector('#app').textContent = data.msg
}
})
})
vm.msg = 'hell workd'
console.log(vm.msg) // ====> hell workd
vue3通过Proxy来实现响应式
// 模拟 vue 中的data
let data = {
msg: 'hello',
count: 10
}
// 模拟 vue 实例
// new Proxy 监听的是对象而非属性
let vm = new Proxy(data, { // 执行代理行为的函数
// 当访问vm成员的时候执行
get(target, key) {
console.log(`获取 ${key}`)
return target[key]
},
set(target, key, value) {
if (target[key] === value) {
return
}
target[key] = value
console.log(`设置 ${key}=${value}`)
}
})
虚拟DOM是一个普通的javaScript对象用来描述真实DOM,创建虚拟DOM的开销比普通DOM开销小很多。
为什么要是使用虚拟Dom
1.MVVM框架 解决视图和状态同步问题
2.普通模版(jquery等)可以简化视图操作,没办法跟踪状态
3.虚拟DOM跟踪状态变化
虚拟DOM的作用
1.维护视图和状态的关系(可以保存视图的状态)
2.复杂情况下提高渲染性能(首屏加载或第一次加载DOM时并没有提高渲染性能)
3.跨平台(因为虚拟DOM是普通的js对象可以做任何处理)
vue2是基于snabbdom库来实现的
import { init } from '/node_modules/snabbdom/build/package/init';
import { h } from '/node_modules/snabbdom/build/package/h';
// 1.导入模块
import { styleModule } from '/node_modules/snabbdom/build/package/modules/style'
import { eventListenersModule } from '/node_modules/snabbdom/build/package/modules/eventlisteners';
// 2.注册模块
// init 初始化所引用的一些模块,返回patch函数,并且可以定义以哪种方式来转换虚拟DOM(默认是浏览器下的DOM对象)
const patch = init([
styleModule,
eventListenersModule
])
// 3.使用h() 函数创建vnode, 第二个参数传入模块中使用数据(是一个对象)
// h() 用到了函数重载(函数名字相同,参数不同)
// 1. 参数个数或参数类型不同的函数
// 2. js中没有重载概念
// 1. ts中有重载,不过重载的实现还是通过代码来调整参数
// vnode = {
// sel: '', // 选择器和类名
// data: '', // 模块中的数据
// childern: '', // 子节点
// elm: '', // vnode转化的真实DOM元素
// text: '', // 文本内容。和chindern互斥只有一个有值
// key: '', // 唯一标识,有利于对比重用DOM, 并且避免渲染错误(如果不设置key会判断sel属性进行重用DOM,假如子节点有checked选中,会重用dom导致显示错误)
// }
let vnode = h('div', [
h('h1', {
style: {
backgroundColor: 'red',
}
}, 'Hello world'),
h('p', {
on: {
click: eventHandler
}
}, 'hell p')
])
function eventHandler () {
console.log('click p')
}
// 4.使用patch()函数的第一个参数传入要操作的元素,第二个参数传入要操作的元素
// patch()
// 1.把新新节点变化内容渲染到真实DOM,返回新节点作为下次的旧节点
// 2.对比新旧vnode 是否是相同节点(判断节点的key和sel相同)
// 3.如果不是相同节点,删除之前的内容,重新渲染
// 4.如果相同节点对比节点来更新(diff算法)
// diff
// 1. 首先判断新旧节点是否相同,如果相同直接返回,如果新节点的data属性有值,遍历data属性更新节点
// 2. 判断新节点是否有childern属性,如果存在,再去对比新旧节点的子节点
// 四种情况
// 2-1. 从旧开始节点和新开始节点开始做比较
// 2-2. 从旧结束节点和新结束节点开始做比较
// 2-3. 从旧开始节点和新结束节点开始做比较
// 2-4. 从旧结束节点和新开始节点开始做比较
// 先对比旧开始节点和新开始节点,如果是相同节点(会重用旧节点的DOM从而优化性能),会将下一个节点作为开始节点,如果不是相同节点会通过旧的结束节点和新的结束节点做比较,如果不相同然后从旧开始节点和新结束节点做比较,如果是相同节点,会把旧元素移动到最后,然后对比下一组节点,如果不是相同节点,从旧节点的结束节点和新节点的开始节点做比较,如果是相同节点,把当前元素移动到最前面,如果以上情况都不满足,从旧节点中依次查找新节点的开始节点,如果找到把当前元素移动到最前面,如果没找到,创建新的DOM元素,插入到最前面位置,如果旧节点的开始节点大于旧节点的结束节点,创建新的节点插入到旧节点,然后从开始对比,对比结束后发现新节点中有剩余的节点,创建新的DOM放在旧节点DOM的后面,如果新节点的开始节点大于旧结束节点,对比新旧节点,对比完成后发现旧节点有剩余的节点,删除旧节点上的节点和DOM
// 内部实现
// (1) 判断vnode中sel属性 => 解析出标签和选择器 => 创建元素
// (2) 判断vnode中是否有childern属性 => 如果存在遍历子节点然后递归调用createElm()
// (3) 判断vnode中text属性 => 如果有值,创建文本节点 => 挂载到vnode的elm属性上
// (4) 判断是否有hook(钩子函数) => 执行hook
// (5) 把创建的DOM插入到DOM元素上
// (6) 删除旧的节点
let app = document.getElementById('app')
let oldVnode = patch(app, vnode)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。