vue

router

  • new Vue配置router , 会给vue实例对象创建两个对象属性,$router $route $router路由对象可以调用路由相关方法,$route存储当前路由相关数据

  • 使用步骤
    1 Vue.use(router)注册路由插件
    2 创建路由配置
    3 vue实例中注册router对象

  • 动态路由可以在路由配置中配置props:true 在组件中通过prop属性声明访问对应数据

  • hash模式 基于锚点 锚点 变化触发onhashchange事件 改变的#后面的值

  • history模式 基于h5中的history.pushState() 改变地址栏地址 并把地址记录到访问历史中(不会向服务器发送请求)history.popstate()(前进后退按钮触发,获取通过js前进后退history.go(n) / history.forward() )监听浏览器历史的变化 history.replaceState() ie10后才支持 部署服务器刷新浏览器会出现404,原因是会去服务器请求对应资源找不到,需要服务器配置对应地址,找不到重定向到index.html

  • node服务器配置history

  • 刷新页面浏览器向服务器发送对应地址请求,服务器接受请求找不到对应资源,返回index.html ,浏览器根据请求地址(如/about)加载再去加载对应路由进行显示

const path = require('path')
// 导入处理 history 模式的模块
const history = require('connect-history-api-fallback')
// 导入 express
const express = require('express')

const app = express()
// 注册处理 history 模式的中间件
app.use(history())
// 处理静态资源的中间件,网站根目录 ../web
app.use(express.static(path.join(__dirname, '../web')))

// 开启服务器,端口是 3000
app.listen(3000, () => {
  console.log('服务器开启,端口:3000')
})

nginx配置history

  • nginx目录运行 start nginx
  • 重启 nginx.exe -s reload
    -nginx config配置
   location   / {
          root   html;
          index index.html index.htm;  
          try_files $uri $uri/  /index.html;   //尝试匹配当前路径资源 找不到返回index.html  返回html客户端根据index去匹配对应路由地址进行显示
        }

自定义vueRouter

  • Vue.use接受函数或者对象 ,传入函数会调用该函数,传入对象,会调用对象的install方法
  • VueRouter 是一个类 有个静态方法install
let _Vue = ''
export default class VueRouter {
    static install(Vue) { //第一个参数为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 = {} // key对应路由路劲 value对应组件
        this.data = _Vue.observable({
            current: '/'
        })
        this.init()
    }

    init() {
        this.createRouteMap(this.options)
        this.initEvent()
        this.createComponent()
    }

    initEvent() {
        //监听前进后退
        window.addEventListener('popstate', () => {
            this.data.current = window.location.pathname
        })
    }

    createRouteMap(options) {
        options.routes.forEach(item => {
            this.routeMap[item.path] = item.component
        })
    }

    createComponent() {
        const self = this
        _Vue.component('router-link', {
            render(h) {
                return h('a', {
                    attrs: {
                        href: this.to
                    },
                    on: {
                        click: this.handleClick
                    }
                }, [this.$slots.default])
            },
            props: {
                to: String
            },
            methods: {
                handleClick(e) {
                    // 改变地址栏参数
                    history.pushState({}, '', this.to)
                    // 切换对应组件
                    this.$router.data.current = this.to
                    e.preventDefault()
                }
            }
        })
        _Vue.component('router-view', {
            render(h) {
                const currentComponent = self.routeMap[self.data.current]
                if (!currentComponent) {
                    return h(self.routeMap['*'])
                } else {
                    return h(currentComponent)
                }
            }
        })
    }
}


vue响应式原理
1.vue2.0 Object.defineProperty给对象绑定get set数据劫持,读取对象属性,触发get,设置对象属性触发set

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>defineProperty 多个成员</title>
</head>
<body>
  <div id="app">
    hello
  </div>
  <script>
    // 模拟 Vue 中的 data 选项
    let data = {
      msg: 'hello',
      count: 10
    }

    // 模拟 Vue 的实例
    let vm = {}

    proxyData(data)

    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]
          }
        })
      })
    }

    // 测试
    vm.msg = 'Hello World'
    console.log(vm.msg)
  </script>
</body>
</html>
  1. vue3.0 Proxy ie不支持
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Proxy</title>
</head>
<body>
  <div id="app">
    hello
  </div>
  <script>
    // 模拟 Vue 中的 data 选项
    let data = {
      msg: 'hello',
      count: 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]
      }
    })

    // 测试
    vm.msg = 'Hello World'
    console.log(vm.msg)
  </script>
</body>
</html>

发布订阅模式

  • $on 注册事件 $emit发布事件
<!DOCTYPE html>
<html lang="cn">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>发布订阅模式</title>
</head>
<body>
  <script>
    // 事件触发器
    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()
          })
        }
      }
    }

    // 测试
    let em = new EventEmitter()
    em.$on('click', () => {
      console.log('click1')
    })
    em.$on('click', () => {
      console.log('click2')
    })

    em.$emit('click')
  </script>
</body>
</html>

观察者模式

  • 发布者Dep记录所有订阅者watcher所有更新操作,存储在数组中,当需要更新的时候调用notify遍历数组,调用update方法更新
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>观察者模式</title>
</head>
<body>
  <script>
    // 发布者-目标
    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')
      }
    }

    // 测试
    let dep = new Dep()    //发布者
    let watcher = new Watcher()  //订阅者

    dep.addSub(watcher)

    dep.notify()
  </script>
</body>
</html>

虚拟DOM

  • snabbdom
  • cnpm i snabbdom -D
  • snabbdom 核心库不能处理属性样式,需要使用对应模块
// import { h } from 'snabbdom'
import { init } from 'snabbdom/build/package/init'
import { thunk } from 'snabbdom/build/package/thunk'
import { h } from 'snabbdom/build/package/h'
let patch = init([])   //返回值对比两个vnode的差异更新到真实DOM
//第一个参数标签加选择器
//第二个参数为标签中的内容
let vnode = h('div#container.cls', 'hello world')
let app = document.getElementById('app')
//第一个参数:可以是DOM,内部会把DOM转换为vnode
//第二个参数vnode
//返回值vnode
// let oldValue = patch(app, vnode)
vnode = h('div','new Value')
vnode = h('div',[
    h('h1','标题'),
    h('h2','二级标题')
])
let oldValue = patch(app, vnode)
setTimeout(function(){
    vnode = h('div',[
        h('h1','标题111'),
        h('h2','二级标题')
    ])
    patch(oldValue,vnode)
},2000)

模块

  • attributes 设置DOM属性 处理布尔类型
  • props 设置DOM属性 不处理布尔类型
  • class 切换样式
  • dataset 设置自定义属性
  • eventlisters 注册移除事件
  • style设置行内样式 支持动画
使用
// import { h } from 'snabbdom'
import { init } from 'snabbdom/build/package/init'
import { h } from 'snabbdom/build/package/h'
import { styleModule } from "snabbdom/build/package/modules/style"
import { eventListenersModule } from "snabbdom/build/package/modules/eventlisteners"
let patch = init([styleModule, eventListenersModule])
let vnode = h('div', {
    style:
        { backgroundColor: '#aaa' },
    on:{
        click:function(){
             alert('click')
        }
    }
},[
    h('h1','hello'),
    h('h2','二级标题')
])
let app = document.getElementById('app')
patch(app,vnode)

snabbdom 源码

核心

  • 使用h函数创建js对象(vnode)描述真实DOM
  • init()设置模块,返回patch函数
  • patch函数比较新旧两个Vnode
  • 把变化的内容弄更新到真实DOM上

vscode 调试

ctrl+鼠标左键 跳转函数定义 alt+键盘方向左回退

h函数 - 创建vnode
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。