1、动静路由

import Vue from 'vue'import VueRouter from 'vue-router'import Index from '../views/Index.vue'Vue.use(VueRouter)const routes = [  {    path: '/',    name: 'Index',    component: Index  },  {    path: '/detail/:id',    name: 'Detail',    // 开启 props,会把 URL 中的参数传递给组件    // 在组件中通过 props 来接管 URL 参数    props: true,    // route level code-splitting    // this generates a separate chunk (about.[hash].js) for this route    // which is lazy-loaded when the route is visited.    component: () => import(/* webpackChunkName: "detail" */ '../views/Detail.vue')  }]const router = new VueRouter({  routes})export default router<template>  <div>    <!-- 形式1: 通过以后路由规定,获取数据 -->    通过以后路由规定获取:{{ $route.params.id }}        <!-- 形式2:路由规定中开启 props 传参 -->    通过开启 props 获取:{{ id }}  </div></template>

2、嵌套路由

// 加载组件import Layout from '@/components/Layout.vue'import Index from '@/views/Index.vue'import Login from '@/views/Login.vue'Vue.use(VueRouter)const routes = [  {    name: 'login',    path: '/login',    component: Login  },  // 嵌套路由  {    path: '/',    component: Layout,    children: [      {        name: 'index',        path: '',        component: Index      },      {        name: 'detail',        path: 'detail/:id',        props: true,        component: () => import('@/views/Detail.vue')      }    ]  }]

3、编程式导航

4、history

const path = require('path')// 导入解决 history 模式的模块const history = require('connect-history-api-fallback')// 导入 expressconst express = require('express')const app = express()// 注册解决 history 模式的中间件,不应用这个则会导致网站404app.use(history())// 解决动态资源的中间件,网站根目录 ../webapp.use(express.static(path.join(__dirname, '../web')))// 开启服务器,端口是 3000app.listen(3000, () => {  console.log('服务器开启,端口:3000')})

通过history.pushState()办法扭转地址栏

监听postate

依据以后路由寻找对应组件

5、VUE Router外围原理实现

console.dir(Vue)let _Vue = nullclass VueRouter {    static install(Vue){        //1 判断以后插件是否被装置        if(VueRouter.install.installed){            return;        }        VueRouter.install.installed = true        //2 把Vue的构造函数记录在全局        _Vue = Vue        //3 把创立Vue的实例传入的router对象注入到Vue实例        // _Vue.prototype.$router = this.$options.router        _Vue.mixin({            beforeCreate(){                if(this.$options.router){                    _Vue.prototype.$router = this.$options.router                                    }                           }        })    }    constructor(options){        this.options = options        this.routeMap = {}        // observable        this.data = _Vue.observable({            current:"/"        })        this.init()    }    init(){        this.createRouteMap()        this.initComponent(_Vue)        this.initEvent()    }    createRouteMap(){        //遍历所有的路由规定 吧路由规定解析成键值对的模式存储到routeMap中        this.options.routes.forEach(route => {            this.routeMap[route.path] = route.component        });    }    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:{                clickhander(e){                    history.pushState({},"",this.to)                    this.$router.data.current=this.to                    e.preventDefault()                }            }            // template:"<a :href='to'><slot></slot><>"        })        const self = this        Vue.component("router-view",{            render(h){                // self.data.current                const cm=self.routeMap[self.data.current]                return h(cm)            }        })            }    initEvent(){        //        window.addEventListener("popstate",()=>{            this.data.current = window.location.pathname        })    }}

6、数据驱动

数据模型仅仅是JS对象,当咱们批改数据时,视图灰烬更新

双向绑定

数据与视图双向扭转
v-model

defineProperty:vue2.0的响应式根底

    // 数据劫持:当拜访或者设置 vm 中的成员的时候,做一些干涉操作    Object.defineProperty(vm, 'msg', {      // 可枚举(可遍历)      enumerable: true,      // 可配置(能够应用 delete 删除,能够通过 defineProperty 从新定义)      configurable: true,      // 当获取值的时候执行      get () {        console.log('get: ', data.msg)        return data.msg      },      // 当设置值的时候执行      set (newValue) {        console.log('set: ', newValue)        if (newValue === data.msg) {          return        }        data.msg = newValue        // 数据更改,更新 DOM 的值        document.querySelector('#app').textContent = data.msg      }    })
function proxyData(data) {      // 遍历 data 对象的所有属性      Object.keys(data).forEach(key => {        // 把 data 中的属性,转换成 vm 的 setter/setter        Object.defineProperty(vm, key, {          enumerable: true,          configurable: true,          get () {            console.log('get: ', key, data[key])            return data[key]          },          set (newValue) {            console.log('set: ', key, newValue)            if (newValue === data[key]) {              return            }            data[key] = newValue            // 数据更改,更新 DOM 的值            document.querySelector('#app').textContent = data[key]          }        })      })    }

Proxy对象:vue3.0响应式的根底

    // 模仿 Vue 实例    let vm = new Proxy(data, {      // 执行代理行为的函数      // 当拜访 vm 的成员会执行      get (target, key) {        console.log('get, key: ', key, target[key])        return target[key]      },      // 当设置 vm 的成员会执行      set (target, key, newValue) {        console.log('set, key: ', key, newValue)        if (target[key] === newValue) {          return        }        target[key] = newValue        document.querySelector('#app').textContent = target[key]      }    })

7、公布订阅模式和观察者模式

公布订阅模式

// 事件触发器

class EventEmitter {  constructor () {    // { 'click': [fn1, fn2], 'change': [fn] }    this.subs = Object.create(null)  }  // 注册事件  $on (eventType, handler) {    this.subs[eventType] = this.subs[eventType] || []    this.subs[eventType].push(handler)  }  // 触发事件  $emit (eventType) {    if (this.subs[eventType]) {      this.subs[eventType].forEach(handler => {        handler()      })    }  }}

观察者模式

    // 发布者-指标    class Dep {      constructor () {        // 记录所有的订阅者        this.subs = []      }      // 增加订阅者      addSub (sub) {        if (sub && sub.update) {          this.subs.push(sub)        }      }      // 公布告诉      notify () {        this.subs.forEach(sub => {          sub.update()        })      }    }    // 订阅者-观察者    class Watcher {      update () {        console.log('update')      }    }

8、VUE响应式原理

class Vue {  constructor (options) {    // 1. 通过属性保留选项的数据    this.$options = options || {}    this.$data = options.data || {}    this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el    // 2. 把data中的成员转换成getter和setter,注入到vue实例中    this._proxyData(this.$data)    // 3. 调用observer对象,监听数据的变动    new Observer(this.$data)    // 4. 调用compiler对象,解析指令和差值表达式    new Compiler(this)  }  _proxyData (data) {    // 遍历data中的所有属性    Object.keys(data).forEach(key => {      // 把data的属性注入到vue实例中      Object.defineProperty(this, key, {        enumerable: true,        configurable: true,        get () {          return data[key]        },        set (newValue) {          if (newValue === data[key]) {            return          }          data[key] = newValue        }      })    })  }}

Observer

负责把data的属性变成响应式数据
如果属性是对象,则把对象也转换成响应式
数据变动发送告诉

class Observer {  constructor (data) {    this.walk(data)  }  walk (data) {    // 1. 判断data是否是对象    if (!data || typeof data !== 'object') {      return    }    // 2. 遍历data对象的所有属性    Object.keys(data).forEach(key => {      this.defineReactive(data, key, data[key])    })  }  defineReactive (obj, key, val) {    let that = this    // 负责收集依赖,并发送告诉    let dep = new Dep()    // 如果val是对象,把val外部的属性转换成响应式数据    this.walk(val)    Object.defineProperty(obj, key, {      enumerable: true,      configurable: true,      get () {        // 收集依赖        Dep.target && dep.addSub(Dep.target)        return val        //不能应用obj[KEY],因为会产生子递归,反复援用      },      set (newValue) {        if (newValue === val) {          return        }        val = newValue        // 如果newValue是对象,把newValue外部的属性转换成响应式数据,而且因为set是一个function,调用时this指向会扭转        that.walk(newValue)        // 发送告诉        dep.notify()      }    })  }}

Compiler

负责编译模板,解析指令和差值表达式
负责首次渲染
负责数据扭转时驱动视图扭转

class Compiler {  constructor (vm) {    this.el = vm.$el    this.vm = vm    this.compile(this.el)  }  // 编译模板,解决文本节点和元素节点  compile (el) {    let childNodes = el.childNodes    Array.from(childNodes).forEach(node => {      // 解决文本节点      if (this.isTextNode(node)) {        this.compileText(node)      } else if (this.isElementNode(node)) {        // 解决元素节点        this.compileElement(node)      }      // 判断node节点,是否有子节点,如果有子节点,要递归调用compile      if (node.childNodes && node.childNodes.length) {        this.compile(node)      }    })  }  // 编译元素节点,解决指令  compileElement (node) {    // console.log(node.attributes)    // 遍历所有的属性节点    Array.from(node.attributes).forEach(attr => {      // 判断是否是指令      let attrName = attr.name      if (this.isDirective(attrName)) {        // v-text --> text        attrName = attrName.substr(2)        let key = attr.value        this.update(node, key, attrName)      }    })  }  update (node, key, attrName) {    let updateFn = this[attrName + 'Updater']    updateFn && updateFn.call(this, node, this.vm[key], key)  }  // 解决 v-text 指令  textUpdater (node, value, key) {    node.textContent = value    new Watcher(this.vm, key, (newValue) => {      node.textContent = newValue    })  }  // v-model  modelUpdater (node, value, key) {    node.value = value    new Watcher(this.vm, key, (newValue) => {      node.value = newValue    })    // 双向绑定    node.addEventListener('input', () => {      this.vm[key] = node.value    })  }  // 编译文本节点,解决差值表达式  compileText (node) {    // console.dir(node)    // {{  msg }}    let reg = /\{\{(.+?)\}\}/    let value = node.textContent    if (reg.test(value)) {      let key = RegExp.$1.trim()      node.textContent = value.replace(reg, this.vm[key])      // 创立watcher对象,当数据扭转更新视图      new Watcher(this.vm, key, (newValue) => {        node.textContent = newValue      })    }  }  // 判断元素属性是否是指令  isDirective (attrName) {    return attrName.startsWith('v-')  }  // 判断节点是否是文本节点  isTextNode (node) {    return node.nodeType === 3  }  // 判断节点是否是元素节点  isElementNode (node) {    return node.nodeType === 1  }}

Watcher

class Watcher {  constructor (vm, key, cb) {    this.vm = vm    // data中的属性名称    this.key = key    // 回调函数负责更新视图    this.cb = cb    // 把watcher对象记录到Dep类的动态属性target    Dep.target = this    // 触发get办法,在get办法中会调用addSub    this.oldValue = vm[key]    Dep.target = null  }  // 当数据发生变化的时候更新视图  update () {    let newValue = this.vm[this.key]    if (this.oldValue === newValue) {      return    }    this.cb(newValue)  }}

9、调试

10、虚构DOM

保护视图和状态的关系
简单视图状况下晋升渲染性能
跨平台
浏览器
ssr
原生利用
小程序

11、snabbdom

根本应用

繁多元素,只蕴含文本

import { init } from 'snabbdom/build/package/init'import { h } from 'snabbdom/build/package/h'const patch = init([])// 第一个参数:标签+选择器// 第二个参数:如果是字符串就是标签中的文本内容let vnode = h('div#container.cls',{hook: {init (vnode) {console.log(vnode.elm)},create (emptyNode, vnode) {console.log(vnode.elm)}}}, 'Hello World')let app = document.querySelector('#app')// 第一个参数:旧的 VNode,能够是 DOM 元素// 第二个参数:新的 VNode// 返回新的 VNodelet oldVnode = patch(app, vnode)vnode = h('div#container.xxx', 'Hello Snabbdom')patch(oldVnode, vnode)父子元素import { init } from 'snabbdom/build/package/init'import { h } from 'snabbdom/build/package/h'const patch = init([])let vnode = h('div#container', [h('h1', 'Hello Snabbdom'),h('p', '这是一个p')])let app = document.querySelector('#app')let oldVnode = patch(app, vnode)setTimeout(() => {// vnode = h('div#container', [// h('h1', 'Hello World'),// h('p', 'Hello P')// ])// patch(oldVnode, vnode)// 革除div中的内容patch(oldVnode, h('!'))}, 2000);

模块

模块的作用是搞定DOM的款式、属性
能够扩大
是应用钩子的机制实现的

import { init } from 'snabbdom/build/package/init'import { h } from 'snabbdom/build/package/h'// 1. 导入模块import { styleModule } from 'snabbdom/build/package/modules/style'import { eventListenersModule } from 'snabbdom/build/package/modules/eventlisteners'// 2. 注册模块const patch = init([styleModule,eventListenersModule])// 3. 应用h() 函数的第二个参数传入模块中应用的数据(对象)let vnode = h('div', [h('h1', { style: { backgroundColor: 'red' } }, 'Hello World'),h('p', { on: { click: eventHandler } }, 'Hello P')])function eventHandler () {console.log('别点我,疼')}let app = document.querySelector('#app')patch(app, vnode)

12、源码剖析

h函数

函数重载:参数个数或类型不同的函数