当前位置:   article > 正文

Vue原理深度剖析

vue原理

一、Vue基础语法

Vue.js基础结构

使用el方式:
在这里插入图片描述
使用render函数方式:
在这里插入图片描述

Vue生命周期在这里插入图片描述

Vue.js语法和概念

插值表达式
插值表达式相当于一个占位符,只会替换掉其中的占位置的内容。
v-text只能显示Vue对象传递过来的数据,会替换掉节点里已有的内容。
插值表达式和v-text不能够解析html标签,v-html能够解析html标签。

结论:

  • 如果只是单独展示Vue对象里的数据,建议使用“v-text”指令。
  • 如果要同时展示用户前台数据,那么就需要用插值表达式,但是不要忘记和“v-cloak”属性一起使用(同时需要设置样式[v-cloak]{display:none;})。
  • 如果Vue对象传递过来的数据含有HTML标签,则使用v-html

指令
VUE提供的14个指令:

  • v-model :双向数据绑定,表单元素常用 input select radio checkbox textarea 等,v-model有三个修饰符,例如input元素 v-model.trim去掉输入值的前后空格和v-model.number,将输入的字符串转换为number,v-model.lazy 输入的数据不再实时更新,而是数据失去焦点的时候再更新输入的数据
  • v-show: 元素的显示和隐藏,频繁操作元素的显示和隐藏,就用v-show ,原理是操作的dom 的css样式 display的值是true还是false
  • v-if:元素的显示和隐藏,原理是,是否创建元素的dom,例如表格中某条数据是否显示编辑,删除按钮,由后台传的数据解决的,这种不频繁操作的情况可用v-if,v-if 可以加入template标签中判断 v-show 不可以
  • v-else : 和v-if 搭配使用
  • v-else-if :条件满足v-if ?不满足判断v-else-if 如果还不满足直接走v-else 这个的使用方式和我们的js 中的 if ,else if() ,else 是类似的使用方式
  • v-bind: 绑定 v-bind:class v-bind:style v-bind:attribute v-bind可以省略成: 最后写成 :class, :style, :attribute
  • v-on :绑定常用事件 下面的常用事件去掉on 改为@click:点击某个对象时触发@clickondblclick:双击某个对象时触发@dblclickonmouseover:鼠标移入某个元素时触发@mouseoveronmouseout:鼠标移出某个元素时触发@mouseoutonmouseenter:鼠标进入某个元素时触发@onmouseenter
  • v-for:项目中常用循环数组的指令。
  • v-html :将字符串html 转换为结构显示,项目中基本不这种方式去处理,涉及到安全性问题
  • v-text:防止为了{ {}} 闪烁问题 项目不常用
  • v-once: 指令指的是元素仅仅绑定一次,只是渲染一次
  • v-cloak:指的是cloak 等元素编译结束以后才会显示dom
  • v-pre :跳过当前元素及子元素的编译过程,先进行编译,项目中基本没有用过
  • v-slot:插槽

自定义指令

计算属性和侦听器

  • 当模板中有太多逻辑需要处理时,推荐使用计算属性。计算属性的结果会被缓存,下次再访问该计算属性时,会从缓存中获取相应的结果,提高性能。
  • 如果我们需要监听数据的变化,做一些比较复杂的操作,例如异步操作或者开销比较大的操作,此时我们可以使用侦听器。

Class和Style绑定
当绑定样式时,我们可以使用Class和Style。它们分别可以绑定数组或者对象,实际开发中,我们推荐使用Class绑定,可以实现样式复用。

条件渲染/列表渲染

  • v-if :控制元素显示/隐藏。条件为false时,是不会输出相应的元素。
  • v-show:控制元素显示/隐藏。元素会渲染到页面,通过样式控制其隐藏。
  • v-for:列表渲染。Vue推荐我们给循环项都设置一个key,用来跟踪每个节点的身份,让每一项都能最大程度地被重用,从而提高性能。

表单输入绑定
当我们使用v-model绑定表单元素时,它负责去监听用户的输入事件,以及更新数据,即双向绑定。

组件
组件是可复用的Vue实例,一个组件封装了html,css,js,它可以实现页面上的一个功能区域,可以无限次的被重用。

插槽
插槽一般用于在自定义组件中挖坑,使用这个组件时去填坑。这样可以使组件更灵活。

插件
如vuex、vue-router都是插件。也可以自己开发插件。

混入mixin
如果多个组件都有相同的选项,就可以使用mixin方式,把相同的选项进行合并,让代码重用。是让组件重用的一种方式。

深入响应式原理
Vue 最独特的特性之一,是其非侵入性的响应式系统。数据模型仅仅是普通的 JavaScript 对象。而当你修改它们时,视图会进行更新。

二、Vue Route原理分析与实现

Vue Router使用步骤在这里插入图片描述在这里插入图片描述

在这里插入图片描述

动态路由在这里插入图片描述在这里插入图片描述

嵌套路由

当多个路由组件都有相同的内容,我们可以把这个相同的内容提取到一个公共的组件当中。
在这里插入图片描述
在这里插入图片描述

编程式导航

常用方法:
$router.push()

  • 参数为跳转路由地址
this.$router.push('/')
  • 1
  • 参数为对象
this.$router.push({
    name: 'Detail', params: {
    id: 1 } })
  • 1
  • 2
  • 3

push方法会记录本次历史。

$router.replace()

this.$router.replace('/login')
  • 1

注意:和push方法有些类似,都可以跳转到指定的路径,参数形式也是一样的。但是,replace方法不会记录本次历史,它会把我们当前的历史改变为我们指定的路径。

$router.go()
go是跳转到历史中的某一次,负数为后退。

this.$router.go(-2)
  • 1

Hash和History模式区别

这两种模式都是客户端路由的实现方式,即当路径发生变化时,不会像服务器发送请求,是由JS监视路径的变化,然后根据不同的地址渲染不同的内容。如果需要服务端内容,会发送Ajax请求去获取。
表现形式的区别

  • Hash模式在这里插入图片描述
    URL中#后面的内容作为路径地址;监听hashchange事件;根据当前路由地址找到对应组件重新渲染。

  • History模式
    在这里插入图片描述
    History模式是一个正常的url。要用好History模式,还需要服务端配置支持。
    通过调用history.pushState()改变地址栏;监听popstate事件;根据当前路由地址找到对应组件重新渲染。

原理的区别

  • Hash模式
    Hash模式是基于瞄点,以及onhashchange事件。
    Vue Router 默认使用的是 hash 模式,使用 hash 来模拟一个完整的 URL,通过
    onhashchange 监听路径的变化
  • History模式
    基于HTML5中的History API
history.pushState() //IE10以后才支持
history.replaceState()
history.go()
  • 1
  • 2
  • 3

History模式的使用

  • 开启 History 模式:
const router = new VueRouter({
   
// mode: 'hash',
mode: 'history',
routes
})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • History需要服务器的支持
  • 单页应用中,服务端不存在 http://www.testurl.com/login 这样的地址会返回找不到该页面
  • 在服务端应该除了静态资源外都返回单页应用的 index.html
    在这里插入图片描述
    History模式-Node.js服务器配置:
    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')
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

History模式-nginx服务器配置:

  • 从官网下载 nginx 的压缩包
  • 把压缩包解压到 c 盘根目录,c:\nginx-1.18.0 文件夹
  • 修改 conf\nginx.conf 文件
    location / {
         
    root html;
    index index.html index.htm;
    #新添加内容
    #尝试读取$uri(当前请求的路径),如果读取不到读取$uri/这个文件夹下的首页
    #如果都获取不到返回根目录中的 index.html
    try_files $uri $uri/ /index.html;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
  • 打开命令行,切换到目录c:\nginx-1.18.0
  • nginx 启动、重启和停止
    #
    启动
    start nginx
    # 重启
    nginx -s reload
    # 停止
    nginx -s stop
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

Vue Router模拟实现

Vue Router 的核心代码

//router/index.js
// 注册插件
// Vue.use() 内部调用传入对象的 install 方法
Vue.use(VueRouter)
// 创建路由对象
const router = new VueRouter({
   
routes: [
{
    name: 'home', path: '/', component: homeComponent }
]
})
// 创建 Vue 实例,注册 router 对象
new Vue({
   
router,
render: h => h(App)
}).$mount('#app')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

类图
在这里插入图片描述
实现思路

  • 创建 LVueRouter 插件,静态方法 install
    1. 判断插件是否已经被加载/安装
    2. 把Vue构造函数记录到全局变量
    3. 当 Vue 加载的时候把创建Vue实例时传入的 router 对象注入/挂载到 Vue 实例上(注意:只执行一次)
  • 创建 LVueRouter 类
    1. 初始化,options、routeMap、app(简化操作,创建 Vue 实例作为响应式数据记录当前路
      径)
    2. initRouteMap() 遍历所有路由信息,把组件和路由的映射记录到 routeMap 对象中
    3. 注册 popstate 事件,当路由地址发生变化,重新记录当前的路径
    4. 创建 router-link 和 router-view 组件
    5. 当路径改变的时候通过当前路径在 routerMap 对象中找到对应的组件,渲染 router-view

实现代码(History模式)

let _Vue = null

//创建 VueRouter 插件,即实现 VueRouter 类
export default class VueRouter {
   
    static install(Vue) {
   
        // 1. 判断插件是否已经被加载/安装
        // 如果插件已经安装直接返回
        if (VueRouter.install.installed) {
   
            return
        }
        VueRouter.install.installed = true
        // 2. 把Vue构造函数记录到全局变量
        _Vue = Vue
        // 3. 把创建Vue实例时传入的 router 对象注入/挂载到 Vue 实例上(注意:只执行一次)
        //混入
        _Vue.mixin({
   
            beforeCreate() {
   
                // 插件的 install() 方法中调用 init() 初始化
                if (this.$options.router) {
   
                    _Vue.prototype.$router = this.$options.router
                    // 初始化插件的时候,调用 init
                    this.$options.router.init()
                }
            }
        })
    }

    //构造函数
    constructor(options) {
   
        this.options = options
        // 记录路径和对应的组件
        this.routeMap = {
   }
        this.data = _Vue.observable({
   
            // 当前的默认路径
            current: '/'
        })
    }

    //初始化
    init() {
   
        this.initRouteMap()
        this.initComponents()
        this.initEvent()
    }

    //解析路由规则成键值对形式
    initRouteMap() {
   
        // routes => [{ name: '', path: '', component: }]
        //遍历所有的路由规则,把路由规则解析成键值对的形式,存储到routeMap中
        this.options.routes.forEach(route => {
   
            // 记录路径和组件的映射关系
            this.routeMap[route.path] = route.component
        })
    }

    //创建 router-link 和 router-view 组件
    initComponents() {
   
        _Vue.component('router-link', {
   
            props: {
   
                to: String
            },
            // template: '<a :href="to"><slot></slot></a>'              
            //运行时版本不支持template,需要使用render
            render(h) {
   
                return h('a', {
   
                    attrs: {
   
                        href: this.to
                    },
                    on: {
   
                        click: this.clickHandler
                    }
                }, [this.$slots.default])
            },
            methods: {
   
                clickHandler(e) {
   
                    history.pushState({
   }, '', this.to)
                    this.$router.data.current = this.to
                    e.preventDefault()
                }
            }
        })

        const self = this
        _Vue.component('router-view', {
   
            render(h) {
   
                // 根据当前路径找到对应的组件,注意 this 的问题
                const component = self.routeMap[self.data.current]
                return h(component)
            }
        })
    }

    //注册事件
    initEvent() {
   
        window.addEventListener('popstate', () => {
   
            this.data.current = window.location.pathname
        })
    }
}
  • 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
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125

Vue的构建版本

  • 运行时版:不支持template模板,需要打包的时候提前编译
  • 完整版:包含运行时和编译器,体积比运行时版大10K左右,程序运行的时候把模板转换成render函数

注意:
vue-cli 创建的项目默认使用的是运行时版本的 Vue.js

  • 如果想切换成带编译器版本的 Vue.js,需要修改 vue-cli 配置
  • 项目根目录创建 vue.config.js 文件,添加 runtimeCompiler
module.exports = {
   
	runtimeCompiler: true
}
  • 1
  • 2
  • 3
  • 4

三、模拟 Vue.js 响应式原理

数据驱动

数据响应式、双向绑定、数据驱动。

  • 数据响应式
    数据模型仅仅是普通的 JavaScript 对象,而当我们修改数据时,视图会进行更新,避免了繁
    琐的 DOM 操作,提高开发效率。
  • 双向绑定
    数据改变,视图改变;视图改变,数据也随之改变。
    我们可以使用 v-model 在表单元素上创建双向数据绑定。
  • 数据驱动
    数据驱动是 Vue 最独特的特性之一。
    开发过程中仅需要关注数据本身,不需要关心数据是如何渲染到视图。

响应式的核心原理

Vue 2.x(基于Object.defineProperty)

一个对象中一个属性需要转换 getter/setter ,处理方式:

 // 模拟 Vue 中的 data 选项
    let data = {
   
      msg: 'hello'
    }

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

    // 数据劫持:当访问或者设置 vm 中的成员的时候,做一些干预操作
    Object.defineProperty(vm, 'msg', {
   
      // 可枚举(可遍历)
      enumerable: true,
      // 可配置(可以使用 delete 删除,可以通过 defineProperty 重新定义)
      configurable: true,
      // 当获取值的时候执行
      get () {
   
        console.log('get: ', data.msg)
        return data.msg
      },
      // 当设置值的时候执行
      set (newValue) {
   
        console.
  • 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
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/114708
推荐阅读
相关标签
  

闽ICP备14008679号