uploadhub的技术博客 uploadhub的技术博客
首页
  • 学习笔记

    • 《HTML5和CSS3篇》
    • 《JavaScript基础篇》
    • 《JavaScript高级篇》
    • 《Ajax篇》
    • 《JavaScript模块化篇》
    • 《Node.js篇》
    • 《MongoDB篇》
    • 《Promise篇》
    • 《Git篇》
  • 《Vue2+Vue3篇》
  • 《React篇》
  • 一面-基础
  • 二三面-进阶
关于我
  • 分类
  • 标签
  • 归档

uploadhub

首页
  • 学习笔记

    • 《HTML5和CSS3篇》
    • 《JavaScript基础篇》
    • 《JavaScript高级篇》
    • 《Ajax篇》
    • 《JavaScript模块化篇》
    • 《Node.js篇》
    • 《MongoDB篇》
    • 《Promise篇》
    • 《Git篇》
  • 《Vue2+Vue3篇》
  • 《React篇》
  • 一面-基础
  • 二三面-进阶
关于我
  • 分类
  • 标签
  • 归档
  • Vue相关

    • 1.Vue基本使用
    • 2.Vue原理
      • 1.Vue 原理设计知识点
      • 2.MVVM & 组件化
      • 3.响应式 & Object.defineProperty
        • 3.1 Object.defineProperty 基本用法
        • 3.2 如何深度监听data变化
        • 监听 data 变化 - 简版
        • 3.3 监听 data 变化 - 深度监听
        • 3.4 Object.defineProperty 缺点
        • 3.5 vue如何监听数组变化
      • 4.虚拟 DOM & diff 算法
        • 4.1 用 JS 模拟 DOM结构
        • 4.2 snabbdom example
        • VNode 本质上来说就是一个普通的JavaScript对象
        • vdom 总结
        • 4.3 diff算法
        • 4.4 diff 算法时间复杂度
        • 4.5 深入diff算法源码-生成vnode
      • 5.模板编译compiler
        • 5.1 模板编译前置知识点-with语法
        • 5.2 vue模板被编译成什么
        • 5.3 vue组件可用render代替template
        • 5.4 总结
      • 6.渲染 Render
        • 6.1 组件渲染 and 更新的过程
        • 6.2 vue组件是如何渲染和更新的
        • 6.3 vue组件是异步渲染的
      • 7.路由 Router
        • 7.1 如何用JS实现hash路由
        • 7.2 如何用JS实现H5 history路由
    • 3.Vue面试真题演练
    • 4.Vue3使用
  • React相关

  • Webpack和Babel

  • 项目设计
  • 项目流程
  • 二三面-进阶
  • Vue相关
uploadhub
2022-05-28
目录

2.Vue原理

# 1.Vue 原理设计知识点

  1. 组件化
  2. 响应式
  3. vdom 和 diff
  4. 模板编译
  5. 渲染过程
  6. 前端路由

# 2.MVVM & 组件化

如何理解MVVM?

组件化基础

  1. “很久以前” 就有组件化
  2. 数据驱动视图(MVVM,setState)

“很久以前” 的组件化

  1. asp、jsp、php 已经有组件化了
  2. nodejs 中也有类似的组件化

数据驱动视图

  1. 传统组件,只是静态渲染,更新还要依赖于操作 DOM
  2. 数据驱动视图 - Vue MVVM
  3. 数据驱动视图 - React setState

Vue MVVM

<template>
    <div id="app">
        <p @click="changeName"></p>
        <ul>
            <li v-for="(item, index) in list" :key="index"></li>
        </ul>
        <button @click="addItem">添加一项</button>
    </div>
</template>

<script>
export default {
    name: 'app',
    data() {
        return {
            name: 'vue',
            list: ['a', 'b', 'c']
        }
    },
    methods: {
    	changeName() {
            this.name = 'youyi'
        },
        addItem() {
            this.list.push(`${Date.now()}`)
        }
    }
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
  1. M 就是数据,data里面的数据
  2. V 是视图, template 模板渲染的
  3. VM 包括 视图里定义的 click 事件,以及 methods 中的方法

# 3.响应式 & Object.defineProperty

Vue 响应式

  1. 组件 data 的数据一旦变化,立刻触发视图的更新
  2. 实现数据驱动视图的第一步
  3. 考察 Vue 原理的第一题
  4. 核心 API - Object.defineProperty
  5. 如何实现响应式,代码演示
  6. Object.defineProperty 的一些缺点 (Vue 3.0 启用 Proxy)

Proxy 有兼容性问题

  1. Proxy 兼容性不好,且无法 polyfill
  2. Vue 2.x 还会存在一段时间,所以都得学

# 3.1 Object.defineProperty 基本用法

const data = {}
const name = 'zhangsan'
Object.defineProperty(data, 'name', {
    get: function() {
        console.log('get')
        return name
    },
    set: function(newVal) {
        console.log('set')
        name = newVal
    }
})

// 测试
console.log(data.name) // get zhangsan
data.name = 'lisi' // set
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

Object.defineProperty 实现响应式

  1. 监听对象,监听数组
  2. 复杂对象,深度监听
  3. 几个缺点

# 3.2 如何深度监听data变化

# 监听 data 变化 - 简版

// 触发更新视图
function updateView() {
    console.log('视图更新')
}

// 重新定义属性,监听起来
function defineReactive(target, key, value) {

    // 核心 API
    Object.defineProperty(target, key, {
        get() {
            return value
        },
        set(newValue) {
            if (newValue !== value) {

                // 设置新值
                // 注意,value 一直在闭包中,此处设置完之后,再 get 时也是会获取最新的值
                value = newValue

                // 触发更新视图
                updateView()
            }
        }
    })
}

// 监听对象属性
function observer(target) {
    if (typeof target !== 'object' || target === null) {
        // 不是对象或数组
        return target
    }

    // 重新定义各个属性(for in 也可以遍历数组)
    for (let key in target) {
        defineReactive(target, key, target[key])
    }
}

// 准备数据
const data = {
    name: 'zhangsan',
    age: 20,
}

// 监听数据
observer(data)

// 测试----------------------------
data.name = 'lisi'
data.age = 21
console.log('age', data.age)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

# 3.3 监听 data 变化 - 深度监听

递归

// 触发更新视图
function updateView() {
    console.log('视图更新')
}

// 重新定义属性,监听起来
function defineReactive(target, key, value) {
    // 深度监听 !!!!!!!!!!!!!!!!!!
    observer(value)
    // 核心 API
    Object.defineProperty(target, key, {
        get() {
            return value
        },
        set(newValue) {
            if (newValue !== value) {
                // 深度监听  !!!!!!!!!!!!!!!!!!
                observer(newValue)
                // 设置新值
                // 注意,value 一直在闭包中,此处设置完之后,再 get 时也是会获取最新的值
                value = newValue

                // 触发更新视图
                updateView()
            }
        }
    })
}

// 监听对象属性
function observer(target) {
    if (typeof target !== 'object' || target === null) {
        // 不是对象或数组
        return target
    }

    // 重新定义各个属性(for in 也可以遍历数组)
    for (let key in target) {
        defineReactive(target, key, target[key])
    }
}

// 准备数据
const data = {
    name: 'zhangsan',
    age: 20,
    info: {
        address: '北京' // 需要深度监听
    },
}

// 监听数据
observer(data)

// 测试
data.info.address = '上海' // 深度监听
data.age = {num: 22}  // set 的时候也需要深度监听 newVal
data.age.num = 24

// data.x = '100' // 新增属性,监听不到 —— 所以有 Vue.$set
// delete data.name // 删除属性,监听不到 —— 所以有 Vue.$delete
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

# 3.4 Object.defineProperty 缺点

  1. 深度监听,需要递归到底,一次性计算量大
  2. 无法监听新增属性/删除属性 (Vue.set Vue.delete)
  3. 无法原生监听数组,需要特殊处理

# 3.5 vue如何监听数组变化

重新定义数组原型

// 触发更新视图
function updateView() {
    console.log('视图更新')
}

// 重新定义数组原型
const oldArrayProperty = Array.prototype
// 创建新对象,原型指向 oldArrayProperty ,再扩展新的方法不会影响原型
const arrProto = Object.create(oldArrayProperty);
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => {
    arrProto[methodName] = function () {
        updateView() // 触发视图更新
        oldArrayProperty[methodName].call(this, ...arguments)
        // Array.prototype.push.call(this, ...arguments)  ,同上
    }
})

// 重新定义属性,监听起来
function defineReactive(target, key, value) {
    // 深度监听
    observer(value)

    // 核心 API
    Object.defineProperty(target, key, {
        get() {
            return value
        },
        set(newValue) {
            if (newValue !== value) {
                // 深度监听
                observer(newValue)

                // 设置新值
                // 注意,value 一直在闭包中,此处设置完之后,再 get 时也是会获取最新的值
                value = newValue

                // 触发更新视图
                updateView()
            }
        }
    })
}

// 监听对象属性
function observer(target) {
    if (typeof target !== 'object' || target === null) {
        // 不是对象或数组
        return target
    }

    // 污染全局的 Array 原型
    // Array.prototype.push = function () {
    //     updateView()
    //     ...
    // }
    
    // 监听数组
    if (Array.isArray(target)) {
        target.__proto__ = arrProto
    }

    // 重新定义各个属性(for in 也可以遍历数组)
    for (let key in target) {
        defineReactive(target, key, target[key])
    }
}

// 准备数据
const data = {
    name: 'zhangsan',
    age: 20,
    info: {
        address: '北京' // 需要深度监听
    },
    nums: [10, 20, 30]
}

// 监听数据
observer(data)

// 测试
// data.name = 'lisi'
// data.age = 21
// console.log('age', data.age)
// data.x = '100' // 新增属性,监听不到 —— 所以有 Vue.$set
// delete data.name // 删除属性,监听不到 —— 所以有 Vue.$delete
// data.info.address = '上海' // 深度监听
data.nums.push(4) // 监听数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88

# 4.虚拟 DOM & diff 算法

虚拟DOM-面试里的网红

  1. vdom 是实现 vue 和 React 的重要基石
  2. diff 算法是 vdom 中最核心、最关键的部分
  3. vdom 是一个热门话题,也是面试中的热门话题

为何会出现虚拟DOM

  1. DOM 操作非常耗费性能
  2. 以前用 jQuery ,可以自行控制 DOM 操作的时机,手动调整
  3. Vue 和 React 是数据驱动视图,如何有效控制视图

解决方案 - vdom

  1. 有了一定复杂度,想减少计算次数比较难
  2. 能不能把计算,更多的转移为 JS 计算? 因为 JS 执行速度很快
  3. vdom - 用 JS 模拟 DOM 结构,计算出最小的变更,操作 DOM

# 4.1 用 JS 模拟 DOM结构

<div id="div1" class="container">
	<p>vdom</p>
	<ul style="font-size:20px">
		<li>a</li>
	</ul>
</div>
1
2
3
4
5
6

JS 模拟以上 DOM结构

{
    tag: 'div',
    props: {
        className: 'container',
        id: 'div1'
    },
    children: [
        {
            tag: 'p',
            children: 'vdom'

        },
        {
            tag: 'ul',
            props: { style: 'font-size: 20px'},
            children: [
                {
                    tag: 'li',
                    children: 'a'
                },
                // ...
            ]

        }
    ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

通过 snabbdom 学习 vdom

  1. 简洁强大的 vdom 库易学易用
  2. Vue 参考它实现 vdom 和 diff
  3. https://github.com/snabbdom/snabbdom
  4. Vue 3.0 重写了 vdom 的代码,优化了性能
  5. 但是 vdom 的基本理念不变,面试考点也不变
  6. React vdom 具体实现和 Vue 也不同,但不妨碍统一学习

# 4.2 snabbdom example

  1. h 函数生成 vnode
  2. vnode 数据结构
  3. patch 函数用法,初次渲染;dom再次更新

部分源码示例

import { init } from 'snabbdom/init'
import { classModule } from 'snabbdom/modules/class'
import { propsModule } from 'snabbdom/modules/props'
import { styleModule } from 'snabbdom/modules/style'
import { eventListenersModule } from 'snabbdom/modules/eventlisteners'
import { h } from 'snabbdom/h' // helper function for creating vnodes

var patch = init([ // Init patch function with chosen modules
  classModule, // makes it easy to toggle classes
  propsModule, // for setting properties on DOM elements
  styleModule, // handles styling on elements with support for animations
  eventListenersModule, // attaches event listeners
])

var container = document.getElementById('container')

var vnode = h('div#container.two.classes', { on: { click: someFn } }, [
  h('span', { style: { fontWeight: 'bold' } }, 'This is bold'),
  ' and this is just normal text',
  h('a', { props: { href: '/foo' } }, 'I\'ll take you places!')
])
// Patch into empty DOM element – this modifies the DOM as a side effect
patch(container, vnode)

var newVnode = h('div#container.two.classes', { on: { click: anotherEventHandler } }, [
  h('span', { style: { fontWeight: 'normal', fontStyle: 'italic' } }, 'This is now italic type'),
  ' and this is still just normal text',
  h('a', { props: { href: '/bar' } }, 'I\'ll take you places!')
])
// Second `patch` invocation
patch(vnode, newVnode) // Snabbdom efficiently updates the old view to the new state
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# VNode 本质上来说就是一个普通的JavaScript对象

export function h (sel: any, b?: any, c?: any): VNode {
  var data: VNodeData = {}, children: any, text: any, i: number;
  if (c !== undefined) {
    if (b !== null) { data = b; }
    if (is.array(c)) {
      children = c;
    } else if (is.primitive(c)) {
      text = c;
    } else if (c && c.sel) {
      children = [c];
    }
  } else if (b !== undefined && b !== null) {
    if (is.array(b)) {
      children = b;
    } else if (is.primitive(b)) {
      text = b;
    } else if (b && b.sel) {
      children = [b];
    } else { data = b; }
  }
  if (children !== undefined) {
    for (i = 0; i < children.length; ++i) {
      if (is.primitive(children[i])) children[i] = vnode(undefined, undefined, undefined, children[i], undefined);
    }
  }
  if (
    sel[0] === 's' && sel[1] === 'v' && sel[2] === 'g' &&
    (sel.length === 3 || sel[3] === '.' || sel[3] === '#')
  ) {
    addNS(data, children, sel);
  }

  // 返回 vnode
  return vnode(sel, data, children, text, undefined);
};

export function vnode (
  sel: string | undefined,
  data: any | undefined,
  children: Array<VNode | string> | undefined,
  text: string | undefined,
  elm: Element | Text | undefined): VNode {
  let key = data === undefined ? undefined : data.key;
  return { sel, data, children, text, elm, key };
}

export interface VNode {
  sel: string | undefined;
  data: VNodeData | undefined;
  children: Array<VNode | string> | undefined;
  elm: Node | undefined;
  text: string | undefined;
  key: Key | undefined;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

# vdom 总结

  1. 用 JS 模拟 DOM 结构(vnode)
  2. 新旧 vdom 对比,得出最小的更新范围,最后更新 DOM
  3. 数据驱动视图的模式下,有效控制 DOM 操作

# 4.3 diff算法

diff算法重要性

  1. diff 算法是 vdom 中最核心、最关键的部分
  2. diff 算法能在日常使用 vue React 中体现出来(如 key)
  3. diff 算法是前端热门话题,面试“宠儿”

diff算法概述

  1. diff 即对比,是一个广泛的概念,如 linux diff 命令、git diff 等
  2. 两个 js 对象也可以做 diff,https://github.com/cujojs/jiff
  3. 两棵树做 diff,如这里的 vdom diff

# 4.4 diff 算法时间复杂度

树 diff 的时间复杂度 O(n^3)

  1. 第一,遍历 tree1 ; 第二,遍历 tree2
  2. 第三,排序
  3. 1000 个节点,要计算 1亿次,算法不可用

优化时间复杂度 O(n)

  1. 只比较同一层级,不跨级比较
  2. tag 不相同,则直接删除重建,不再深度比较
  3. tag 和 key ,两者都相同,则认为是相同,不再深度比较

# 4.5 深入diff算法源码-生成vnode

  1. snabbdom - 源码解读
  2. vdom 核心概念很重要: h、vnode、patch、diff、key 等。
  3. vdom 存在的价值更加重要:数据驱动视图、控制 dom 操作

# 5.模板编译compiler

# 5.1 模板编译前置知识点-with语法

  1. 模板是 vue 开发中最常用的部分,即与使用相关的原理
  2. 他不是 html ,有指令、插值、js 表达式,到底是什么?
  3. 面试不会直接问,但是会通过“组件渲染和更新过程考察”

模板编译

  1. 前置知识: JS 的 with 语法
  2. vue template compiler 将模板编译成 Render 函数
  3. 执行 Render 函数生成 vnode

with 语法

const obj = {a: 100, b: 200}

console.log(obj.a)
console.log(obj.b)
console.log(obj.c)  // undefined
// 使用 with, 能改变 {} 内自由变量的查找方式
// 将{} 内自由变量,当做 obj 的属性来查找
with() {
    console.log(a)
    console.log(b)
    console.log(c) // 会报错!!!
}
1
2
3
4
5
6
7
8
9
10
11
12
  1. 改变 {} 内自由变量的查找规则,当做 obj 属性来查找
  2. 如果找不到匹配的 obj 属性,就会报错
  3. with 要慎用,它打破了作用域规则,易读性变差

# 5.2 vue模板被编译成什么

模板编译

  1. 模板不是 HTML,有指令、插值、js表达式,能实现循环、判断
  2. HTML 是标签语言,只有 JS 才能实现判断、循环(图灵完备的)
  3. 因此,模板一定是转换为某种 JS 代码,即模板编译
const compiler = require('vue-template-compiler')

// 插值
const template = `<p></p>`
with(this){return createElement('p',[createTextVNode(toString(message))])}
// h -> vnode  !!!!!!!!!!!!!!!!!
// createElement -> vnode  !!!!!!!!!!!!!!!!!

// 表达式
const template = `<p></p>`
with(this){return _c('p',[_v(_s(flag ? message : 'no message found'))])}

// 属性和动态属性
const template = `
    <div id="div1" class="container">
        <img :src="imgUrl"/>
    </div>
`
with(this){return _c('div',
     {staticClass:"container",attrs:{"id":"div1"}},
     [
         _c('img',{attrs:{"src":imgUrl}})])}

// 条件
const template = `
    <div>
        <p v-if="flag === 'a'">A</p>
        <p v-else>B</p>
    </div>
`
with(this){return _c('div',[(flag === 'a')?_c('p',[_v("A")]):_c('p',[_v("B")])])}

// 循环
const template = `
    <ul>
        <li v-for="item in list" :key="item.id"></li>
    </ul>
`
with(this){return _c('ul',_l((list),function(item){return _c('li',{key:item.id},[_v(_s(item.title))])}),0)}

// 事件
const template = `
    <button @click="clickHandler">submit</button>
`
with(this){return _c('button',{on:{"click":clickHandler}},[_v("submit")])}

// v-model
const template = `<input type="text" v-model="name">`
// 主要看 input 事件
with(this){return _c('input',{directives:[{name:"model",rawName:"v-model",value:(name),expression:"name"}],attrs:{"type":"text"},domProps:{"value":(name)},on:{"input":function($event){if($event.target.composing)return;name=$event.target.value}}})}

// render 函数
// 返回 vnode
// patch

// 编译
const res = compiler.compile(template)
console.log(res.render)

// ---------------分割线--------------

// // 从 vue 源码中找到缩写函数的含义
// function installRenderHelpers (target) {
//     target._o = markOnce;
//     target._n = toNumber;
//     target._s = toString;
//     target._l = renderList;
//     target._t = renderSlot;
//     target._q = looseEqual;
//     target._i = looseIndexOf;
//     target._m = renderStatic;
//     target._f = resolveFilter;
//     target._k = checkKeyCodes;
//     target._b = bindObjectProps;
//     target._v = createTextVNode;
//     target._e = createEmptyVNode;
//     target._u = resolveScopedSlots;
//     target._g = bindObjectListeners;
//     target._d = bindDynamicKeys;
//     target._p = prependModifier;
// }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
  1. 模板编译为 render 函数,执行 render 函数返回 vnode
  2. 基于 vnode 再执行 patch 和 diff
  3. 使用 webpack vue-loader,会在开发环境下编译模板(重要)

# 5.3 vue组件可用render代替template

  1. 在有些复杂情况中,不能用 template,可以考虑用 Render
  2. React 一直都用 Render(没有模板),和这里一样

# 5.4 总结

  1. with 语法
  2. 模板到 Render 函数,再到 vnode,再到渲染和更新
  3. vue 组件可以用 Render 代替 template

# 6.渲染 Render

# 6.1 组件渲染 and 更新的过程

❶ 响应式:监听 data 属性,getter setter (包括数组) ❷ 模板编译:模板到 render 函数,再到 vnode ❸ vdom: patch(elem, vnode) 和 patch(vnode, newVnode)

① 初次渲染 ② 更新过程 ③ 异步渲染

# 6.2 vue组件是如何渲染和更新的

初次渲染过程

  1. 解析模板为 render 函数(或在开发环境已完成,vue-loader)
  2. 触发响应式,监听 data 属性 getter setter
  3. 执行 render 函数,生成 vnode,patch(elem, vnode)

执行 Render 函数会触发 getter

<p></p>

<script>
export default {
    data() {
        return {
            message: 'hello', // 会触发 get
            city: '深圳' // 不会触发get,因为模板没有用到,和视图没有关系
        }
    }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12

更新过程

  1. 修改 data,触发 setter (此前在 getter 中已被监听)
  2. 重新执行 render 函数,生成 newVnode
  3. patch(vnode, newVnode) 【diff 算法上场】

# 6.3 vue组件是异步渲染的

异步渲染

  1. 回顾 $nextTick
  2. 汇总 data 的修改,一次性更新视图
  3. 减少 DOM 操作次数,提高性能

# 7.路由 Router

# 7.1 如何用JS实现hash路由

前端路由原理

  1. 稍微复杂一点的 SPA, 都需要路由
  2. vue-router 也是 vue 全家桶的标配之一
  3. 属于“和日常使用相关联的原理”, 面试常考
  4. 回顾 vue-router 的路由模式:hash、H5 history

hash 的特点

❶ hash 变化会触发网页跳转,即浏览器的前进、后退 ❷ hash 变化不会刷新页面, SPA 必需的特点 ❸ hash 永远不会提交到 server 端(前端自生自灭)

hash 变化包括:

  1. js 修改 url
  2. 手动修改 url 的 hash
  3. 浏览器前进、后退

hash 路由 demo

<body>
    <p>hash test</p>
    <button id="btn1">修改 hash</button>

    <script>
        // hash 变化,包括:
        // a. JS 修改 url
        // b. 手动修改 url 的 hash
        // c. 浏览器前进、后退
        window.onhashchange = (event) => {
            console.log('old url', event.oldURL)
            console.log('new url', event.newURL)

            console.log('hash:', location.hash)
        }

        // 页面初次加载,获取 hash
        document.addEventListener('DOMContentLoaded', () => {
            console.log('hash:', location.hash)
        })

        // JS 修改 url
        document.getElementById('btn1').addEventListener('click', () => {
            location.href = '#/user'
        })
    </script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 7.2 如何用JS实现H5 history路由

H5 history

  1. 用 url 规范的路由,但跳转时不刷新页面
  2. history.pushState
  3. window.onpopstate

正常页面浏览

https://github.com/xxx 刷新页面 https://github.com/xxx/yyy 刷新页面

改造成 H5 history 模式

https://github.com/xxx 刷新页面 https://github.com/xxx/yyy 前端跳转,不刷新页面

H5 history 路由 demo

<body>
    <p>history API test</p>
    <button id="btn1">修改 url</button>

    <script>
        // 页面初次加载,获取 path
        document.addEventListener('DOMContentLoaded', () => {
            console.log('load', location.pathname)
        })

        // 打开一个新的路由
        // 【注意】用 pushState 方式,浏览器不会刷新页面
        document.getElementById('btn1').addEventListener('click', () => {
            const state = { name: 'page1' }
            console.log('切换路由到', 'page1')
            history.pushState(state, '', 'page1') // 重要!!
        })

        // 监听浏览器前进、后退
        window.onpopstate = (event) => { // 重要!!
            console.log('onpopstate', event.state, location.pathname)
        }

        // 需要 server 端配合,可参考
        // https://router.vuejs.org/zh/guide/essentials/history-mode.html#%E5%90%8E%E7%AB%AF%E9%85%8D%E7%BD%AE%E4%BE%8B%E5%AD%90
    </script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

总结

  1. hash - window.onhashchange
  2. H5 history - history.pushState 和 window.onpopsate
  3. H5 history 需要后端支持

两者选择

  1. to B (后台管理系统) 的系统推荐用 hash,简单易用,对 url 规范不敏感
  2. to C 的系统,可以考虑选择 H5 history ,但需要服务端支持【SEO、搜索引擎优化需要 H5 history】
  3. 能选择简单的,就别用复杂的,要考虑成本和收益
#面试#Vue
1.Vue基本使用
3.Vue面试真题演练

← 1.Vue基本使用 3.Vue面试真题演练→

最近更新
01
HTTP协议及缓存机制
05-28
02
开发环境
05-28
03
JS基础知识(一)-变量类型和计算
05-28
更多文章>
Theme by Vdoing | Copyright © 2021-2023 uploadhub | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式