前端笔记--Vue

Vue基本使用

指令

  • {{ msg }} 、v-text(会覆盖子文本)
  • v-html 会有xss风险,会覆盖子组件
  • 设置动态属性 v-bind: (简写:)
    <button v-bind:disabled="isButtonDisabled">Button</button>
  • 表达式
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
  • v-on:(简写@)监听DOM事件
  • 修饰符 . 用于指出一个指令应该以特殊方式绑定
<form v-on:submit.prevent="onSubmit">...</form>
<input @keyup.enter="onCheck" />
  • v-if
  • v-show
  • v-if和v-show的区别
    1.v-if是通过控制dom节点的存在与否来控制元素的显隐;v-show是通过设置DOM元素的display样式,block为显示,none为隐藏;
    2.性能消耗:v-if有更高的切换消耗;v-show有更高的初始渲染消耗
    3.使用场景:如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。
  • v-for
    v-for和v-if不能一起使用
  • v-model

计算属性和监听属性

  • computed
    对于任何复杂逻辑,你都应当使用计算属性。computed有缓存,data不变不会重新计算。
  • watch
    当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。
    1.当需要监听引用类型时,普通的watch方法无法监听到对象内部属性的改变,此时就需要deep属性对对象进行深度监听。
    2.watch监听引用类型,拿不到oldVal,

组件通讯

父子组件传值

任何组件的通讯,包括父子,兄弟,跨级

组件通讯
通过自定义事件实现兄弟组件通讯,在beforeDestroy时及时进行销毁,否则可能造成内存泄漏
1.需要创建一个空白vue实例作为兄弟间的桥梁(event.js)

import Vue from 'vue'
export default new Vue()

2.要传送数据的组件使用$emit触发接收数据组件的自定义事件并把数据传过去

import event from '../utils/event.js' 
export default {
  data () {
    return {
      msg: 'A组件数据'
    }
  },
  methods: {
    send: function () {
      event.$emit('receive', this.msg) // 使用 $emit 自定义事件把数据带过去
    }
  }
}

3.接收数据的组件使用$on监听自定义事件接收数据

import event from '../utils/event.js' // vue的空白实例(兄弟间的桥梁)
export default {
  data () {
    return {
      msg: ''
    }
  },
  mounted () {
    event.$on('receive', (val) => { // 监听事件receive,回调函数要使用箭头函数;
      console.log(val)
      this.msg = val
    })
  },
  beforeDestroy () {
    event.$off('receive')
  },
}

Vue生命周期

1.created和mounted区别
created完成了vue实例的初始化,页面还没开始渲染,mounted完成了页面的渲染
2.父子组件生命周期调用顺序

p created
s created
s mounted
p mounted
p beforeUdate
s beforeUdate
s updated
p updated
p beforeDestroy
s beforeDestroy
s destroyed
p destroyed

Vue高级特性

手动实现一个v-model

父组件

<template>
  <div id="app">
    <p>{{name}}</p>
    <HelloWorld v-model="name"/>
  </div>
</template>

<script>
import HelloWorld from './components/HelloWorld'

export default {
  name: 'App',
  components: {
    HelloWorld
  },
    data(){
      return {
          name: 'vue'
      }
    }
}
</script>

子组件

<template>
  <div>
    <input type="text" :value = "val" @input="$emit('change',$event.target.value)">
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  model: {
      prop: 'val',
      event: 'change'
  },
  props: {
    val: {
        type: String,
        default(){
            return ''
        }
    }
  },
}
</script>

补充:

  1. <input type="text" v-model="name"> 实则等于 <input type="text" :value = "name" @input = "name=$event.target.value"> ,也就是说v-model就是绑定的了一个名为value的prop和一个input事件
    2.model属性,允许一个自定义组件在使用v-model时定制prop和event。避免与一些表单类型的value产生冲突。

$nextTick

定义:在下次DOM更新循环结束之后执行延时回调。在修改数据后立即使用该方法,获取更新后的DOM。

  • Vue是异步渲染的
  • data改变过后,DOM不会立即渲染
  • $nextTick会在DOM渲染完成后被触发,以获取最新的DOM节点

什么时候需要用$nextTick

1.在created钩子函数中进行的DOM操作一定要放在Vue.nextTick()回调函数中
2.在改变数据后,基于新DOM进行的一系列js操作都需要放到Vue.nextTick()回调函数中

Vue.nextTick(callback)使用原理

Vue是异步渲染DOM的,一旦观察到数据变化,Vue就会开启一个队列,然后把在同一个事件循环(event loop)中观察到数据变化的watcher推送进这个队列。如果这个watcher被触发多次,只会被推送到队列一次。这种缓冲行为可以有效地去掉重复数据造成的不必要的计算和DOM操作。而在下一个事件循环时,Vue会清空队列,并进行必要的DOM更新。
为了在数据变化之后等待 Vue 完成渲染 DOM ,可以在数据变化之后立即使用 Vue.nextTick(callback) ,这样回调函数在 DOM 渲染完成后就会调用。

slot

具名插槽

slot元素有一个特殊的 attribute:name。一个不带 name 的 slot 出口会带有隐含的名字“default”。任何没有被包裹在带有 v-slot 的 template中的内容都会被视为默认插槽的内容。
注意:v-slot只能添加在template上

作用域插槽

让插槽内容能够访问子组件中的数据
绑定在 元素上的 attribute 被称为插槽 prop。现在在父级作用域中,我们可以使用带值的 v-slot 来定义我们提供的插槽 prop 的名字

动态组件

需要根据数据动态渲染的场景,即组件不确定

异步组件

只在需要的时候才从服务器加载,提高性能

keep-alive

缓存组件,频繁切换,不需要重复渲染,提高性能

mixin

多个组件有相同的逻辑,抽离出来
mixin的问题:
1.变量来源不明确,不利于阅读
2.多mixin可能会造成命名冲突
3.mixin和组件可能出现多对多关系,复杂度较高

vuex

专为Vue.js开发的状态管理模式

基本使用


vuex核心概念

1.state单一状态树
2.mutations
同步操作方法
vuex的store状态的唯一更新方式:mutation

  • mutation传递参数

    多个参数以对象的形式传递

    mutation另一种提交风格

    mutation响应式规则

    使用vue.set()和vue.delete()
    3.actions

异步操作方法
4.getters
类似于单个组件中的计算属性,当state需要进行一系列变化再展示时使用

5.modules
当应用变得复杂时,store对象可能变得相当臃肿,可以将store进行模块划分,而每个模块也有自己的state,mutations,actions,getters等
uploading-image-269448.png

vuex推荐的项目结构

vue-router

什么是前端渲染,什么是后端渲染

后端渲染:服务器直接生产渲染好对应的HTML页面,返回给客户端进行展示
前端渲染:浏览器中显示的页面中的大部分内容都是由前端编写的js代码在浏览器中执行,最终渲染出来的

后端路由阶段

后端处理URL和页面之间的映射关系,有利于SEO优化
后端路由的缺点:
1.整个页面的模块是由后端人员来编写和维护的
2.前端人员如果要开发页面,需要使用php和java语言来编写代码
3.HTML代码和数据以及对应的逻辑混在一起,编写和维护都很麻烦

前后端分离阶段

后端只负责提供数据,不负责任何界面的内容;后端只提供API返回数据,前端通过ajax获取数据,并且通过js将数据渲染到页面上

优点:
1.前后端责任清晰,后端专注于数据,前端专注于交互和可视化上
2.开发移动端,后端也不需要进行任何的处理,依然使用之前的一套API即可

单页面富应用阶段

在前后端分离阶段的基础上加了一层前端路由
整个网页只有一个html页面

前端路由

由前端管理url和组件之间的映射关系

路由模式

hash模式(默认),如http://abc.com/#/user/10


JS实现hash路由
核心:hashchange事件

H5 history模式,如http://abc.com/user/10

1.history方法

  • history.pushState(data,title,url) 栈结构(入栈操作)
  • history.replaceState(data,title,url) 不能返回
  • history.go() 控制压栈出栈 history.go(-1) == history.back() , history.go(1) == history.forward()
  • history.back()出栈
  • history.forward()压榨
    注意:后者需要server端支持
    2.JS实现h5 history
    核心:history.pushState popstate事件

动态路由

:id 结合 $route.params.id 使用

懒加载

按需加载,提高性能(跟异步组件一样)

导航守卫

监听路由的跳转
全局前置钩子beforeEach()
必须手动调一下next()

全局后置钩子afterEach((to,from))

Vue原理

如何理解MVVM

Vue响应式

核心API - Object.defineProperty

监听对象(深度监听)

//更新视图
function updateView(){
  console.log("视图更新")
}

//监听数据
function observer(target) {
  if (!target || typeof target !== "object") return 
  for (let key in target){
    defineReactive(target,key,target[key])
  }
}

//核心函数
function defineReactive(target,key,value) {
  observer(value)
  Object.defineProperty(target,key,{
    get() {
      console.log("get:",value)
      return value
    },
    set(v) {
      if (v !== value){
        observer(v)
        value = v
        console.log('set:',v)
        //更新视图
        updateView()
      }
    }
  })
}

监听数组

//更新视图
function updateView(){
  console.log("视图更新")
}

//重新定义数组原型
const oldArrayPrototype = Array.prototype
const arrProto = Object.create(oldArrayPrototype);
['push','pop','shift','unshift','splice'].forEach(methodName => {
  arrProto[methodName] = function () {
    updateView()
    oldArrayPrototype[methodName].call(this,...arguments)
  }
})

//监听数据
function observer(target) {
  if (!target || typeof target !== "object") return
  //判断是否是数组
  if (Array.isArray(target)){
    target.__proto__ = arrProto
  }
  for (let key in target){
    defineReactive(target,key,target[key])
  }
}

//核心函数
function defineReactive(target,key,value) {
  observer(value)
  Object.defineProperty(target,key,{
    get() {
      console.log("get:",value)
      return value
    },
    set(v) {
      if (v !== value){
        observer(v)
        value = v
        console.log('set:',v)
        //更新视图
        updateView()
      }
    }
  })
}

Object.defineProperty的缺点
1.深度监听,需要递归到底,一次性计算量大
2.无法监听新增属性/删除属性(所以需要使用Vue.set/Vue.delete)
3.无法监听原生数组,需要特殊处理
Vue3.0启用Proxy,但Proxy兼容性不好,且无法polyfill

虚拟DOM

用JS模拟DOM结构,计算出最小的变更,再操作DOM
因为DOM操作是非常耗时的,而Javascript的运算速度快,因此,把大量的DOM操作搬运到Javascript中,使用diff算法来计算出真正需要更新的节点,最大限度地减少DOM操作,从而显著提高性能。

JS模拟DOM结构

diff算法

diff算法是vdom中最核心,最关键的部分
1.只比较同一层级,不跨级比较
2.tag不相同,则直接删掉重建,不再深度比较
3.tag和key两者都相同,则认为是相同节点,不再深度比较
h函数:根据真实的DOM节点生成vnode
patch函数:
patch(container,vnode)初次渲染的时候,将vnode渲染成真正的DOM然后插入到容器里面。
patch(vnode,newVnode)再次渲染的时候,将新的vnode和旧的vnode进行对比,找出之间差异并更新到真正的DOM树上。

v-for为什么要加key

使用key来给每个节点做一个唯一标识,Diff算法就可以正确的识别此节点,找到正确的位置区插入新的节点。为了高效的更新虚拟DOM。

模板编译

with语法

  • 改变{}内自由变量的查找方式,将{}内的自由变量当做obj的属性来查找
  • 如果找不到匹配的obj属性就会报错
  • with要慎用,因为它打破了作用域规则,易读性变差

vue模板编译

  • 模板编译为render函数,执行render函数返回vnode
  • 基于vnode再执行patch和diff
  • 使用webpack vue-loader,会在开发环境下编译模板(提高性能)
  • vue组件可以用render代替template

vue组件渲染/更新过程

初次渲染过程

1.解析模板为render函数(或在开发环境已完成,webpack,vue-loader)
2.触发响应式,监听data属性getter setter
3.执行render函数,生成vnode
4.将vnode渲染成真正的dom,patch到容器里面
(只监听模板上需要用的data属性)

更新过程

1.修改data,触发setter
2.重新执行render函数,生成newVnode
3.将新的vnode和旧的vnode进行对比,找出之间差异并更新到真正的DOM树上

异步渲染

  • 汇总data的修改,一次性更新视图
  • 减少DOM操作次数,提高性能
  • $nextTick

面试题

vue组件如何通讯

  • 父子组件 props 和 this.$emit
  • 自定义事件 event.$on event.$emit event.$off
  • vuex

组件渲染和更新的过程

双向数据绑定v-model的实现原理

对MVVM的理解

computed有什么特点

缓存,data不变不会重新计算,提高性能

为何组件data必须是一个函数

Object是引用数据类型,如果不用function 返回,每个组件实例的data 都指向内存的同一个地址,当一个组件实例的数据改变了,其他组件实例也会跟着改变;
而函数存在作用域,当data是一个函数时,每个组件实例都拥有自己的作用域,每个组件实例相互独立,不会造成相互影响

ajax请求应该放在哪个生命周期(?)

mounted

何时使用异步组件

  • 加载大组件
  • 路由异步加载

何时需要使用keep-alive

  • 缓存组件,不需要重复渲染
  • 如多个静态tab页的切换
  • 提高性能

何时需要使用beforeDestroy

  • 解绑自定义事件event.$off
  • 清除定时器
  • 解绑自定义的DOM事件,如window scroll等

描述响应式原理

1.监听data变化
2.组件渲染和更新的流程

简述diff算法过程

vue常见性能优化方式

1.合理使用v-show和v-if
2.合理使用computed
3.v-for时加key,以及避免和v-if使用(v-for优先级更高,每次v-for都要重新计算一遍v-if)
4.自定义事件,DOM事件及时销毁
5.合理使用异步组件
6.合理使用keep-alive
7.data层级不要太深
8.使用vue-loader在开发环境做模板编译(预编译)

vue3

posted @ 2020-08-17 17:46  炸呼呼er  阅读(178)  评论(0编辑  收藏  举报