vue2学习文档

Vue2学习文档

英文官网:

  • Vue2: https://vuejs.org/
  • Vue3:https://vuejs.org/

中文官网

  • Vue2: https://v2.cn.vuejs.org/
  • Vue3: https://cn.vuejs.org/

入门

前言

用来构建用户界面的渐进式的js库

与其它前端JS框架的关联

  • 借鉴Angular的模板和数据绑定技术
  • 借鉴React的组件化和虚拟DOM技术
特点
  • 声明式

    无需亲自操作DOM,利用模板描述界面

    只要更新data数据,Vue就会自动更新DOM

  • 组件化

    将功能界面拆分成多组件组合使用,提高代码复用率,更好维护

  • 虚拟DOM+DIff算法

    实现最小化DOM更新

MVVM

MVVM(Model-View-ViewModel)是一种软件架构设计模式

Vue是MVVM模式的一个实现库

MVVM支持双向绑定,当M层数据进行修改时,VM层会监测到变化,并且通知V层进行相应的修改,反之修改V层则会通知M层数据进行修改
vue2学习文档_第1张图片

MVVM的组成
  • M: Model 模型

    • 数据模型,主要负责业务数据相关
    • 包含n个可变数据的data对象
    • 作用:给View提供响应式数据
  • V: View 视图

    • 模板页面,负责视图相关
    • 作用:读取data数据进行动态显示
  • VM: ViewModel 视图模型

    • V与M沟通的桥梁,负责监听M或者V的修改,是实现MVVM双向绑定的要点

    • ViewModel是Vue.js的核心,是Vue的实例对象,简称vm对象

    • 作用:model到view和view到model的双向数据绑定

Diff算法❗
概述❗
  • 什么是diff算法

    diff算法就是比较新旧虚拟DOM树,寻找差异的算法。在源码中是通过patch函数来完成的,所以也称为patch算法

  • diff算法比较思路:深度优先,同级比较

    • 深度优先即针对一个元素一路深挖下去,广度优先的反面

    • 同级比较只进行同级比较,不进行父子级等比较

vue2学习文档_第2张图片

执行过程❗
1.基本执行过程
  1. 当组件内部的响应式数据发生更新的时候,就会执行vue内部的updateComponent函数,在函数内部先执行_render方法生成新的虚拟DOM,把虚拟DOM传递给_update方法,并调用_update方法
  2. _update函数中,首先定义一个变量保存旧的虚拟DOM(在vm._vnode_属性上),然后再把_update接受的新的虚拟DOM vNode放在vm的_vnode_属性上,此时在_update函数中就有了新旧虚拟DOM,最后使用patch方法对新旧虚拟DOM进行比较
function Vue(){
   
    const updateComponent = ()=>{
   
        this._update(this._render())
    }
}
function _update(vNode){
   
    //将旧虚拟DOM存下来,以便新虚拟DOM赋值vm._vnode
    const oldVnode = vm._vnode;
    vm._vnode = vNode;
    //使用patch函数进行新旧虚拟DOM比较
    patch(oldVnode, vNode)
}
2.patch比较过程
  1. patch函数首先使用sameVnode方法专门比较两个节点是否相同(相同:两个虚拟DOM节点的标签类型相同[input还要比较type类型],并且key的值也要相同,如果没有写key,则key的值是undefined)

  2. 如果sameVnode比较两个节点相同,则直接进入更新流程

    更新流程:

    1. 把旧节点的真实DOM拿到新节点位置复用
    2. 对比新旧节点的属性是否相同,如果不同则更新
    3. 开始比较子节点
  3. 如果sameVnode比较两个不相同,则直接遍历新的节点创建新的元素,并直接删除旧的元素

3.比较子元素

vue2学习文档_第3张图片

  1. vue使用四个指针分别指向 新旧子节点列表 的 首尾节点
  2. 首先比较新旧树的头指针,判断是否相同,如果相同则进入更新流程…(即比较两个的子节点,深度优先原则)
  3. 继续比较两个树的头指针,如果不相同,则比较新旧树的尾指针,如果相同则进入更新流程…
  4. 如果头指针比较和尾指针比较都不相同,则比较头尾指针是否相同,如果相同则进入更新流程…
  5. 如果上边的规则都不相等,则会以新树的头指针为基础,循环旧的虚拟DOM节点,如果存在相等则直接拿过进入更新流程,如果找不到则直接拿当前的新的虚拟DOM节点创建真实DOM
  6. 当新树的头指针超过尾指针的时候,比较结束,如果旧树中存在剩余节点,则删除这个剩余节点
key的作用❗
  • 在新旧虚拟DOM对比更新的时候,默认的diff算法是"就地复用"原则
  • “就地复用”:多个子节点比较的时候,如果没有添加key属性,则key属性都是undefined,所以每一个新旧DOM的key都是相同的,所以就会简单的按照节点的顺序依次比较(如果新旧节点的顺序发生变化,vue仍然都是创建新节点删除旧节点)
  • 我们可以给每一个节点添加一个key属性,方便Vue跟踪每一个元素的身份,从而在diff算法计算的时候可以按照key确定比较节点
  • key的作用:高效的更新渲染虚拟DOM
  • 不要使用遍历出来的index作为key(index不是稳定性),key的要求是 唯一性!!! 稳定性!!!
简短版Diff算法流程
  1. 当组件创建和更新的时候,vue会执行内部的update函数,该函数使用render函数生成的虚拟DOM树,将新旧DOM树调用patch方法进行比较找到差异,并更新真实DOM
  2. 对比的过程被称作diff,vue内部使用patch函数完成对比过程
  3. 在对比的时候,vue采用深度优先,同级比较的方式
  4. 在比较的时候,通过虚拟DOM的key和tag来判断是否相同
    1. 第一:先对根节点的新旧虚拟DOM进行对比,如果相同则将旧节点复用,然后更新数据,递归比较子节点,如果不同则直接根据新的虚拟DOM递归创建新的节点,并且删除旧的DOM
    2. 第二:比较子节点,首先vue给新旧虚拟DOM都使用了两个指针,分别指向头尾,然后不断向中间靠拢进行对比(头头,尾尾,头尾,尾头,乱序),提高对比性能,在比较的过程中如果发现相同则进入更新流程,否则新增和移除dom
    3. 按照上边的两点一直递归,直到整个DOM对比完成
Vue扩展插件
  • vue-cli: vue脚手架
  • vue-router: 路由
  • vuex: 状态管理
  • vue-lazyload: 图片懒加载
  • vee-validate: 表单校验
  • vant-ui: 基于vue的UI组件库(移动端)
  • element-ui: 基于vue的UI组件库(PC端)
插件配置
谷歌插件
  • Vue.js devtools.crx
vsCode插件
  • Vetur

    vue-helper

基本使用

使用
<head>
  
  <script src="../js/vue.js">script>
head>
<body>
  
  <div id="app">{
  {msg}}div>

  <script>
    /* 3. 创建vue实例对象 */
    new Vue({
     
      // 页面根元素
      el: '#app',
      // 定义响应式数据
      data: {
     
        msg: 'Hello Vue!'
      },
      // 要解析显示的模板页面, 模板页面中可以读取data数据
      // template会将页面挂载的根元素替换掉
      template: `
        

{ { msg }}

`
})
script> body>
插值语法

{ { js表达式 }}

  • 作用:动态显示表达式的值
  • 内部查找的是vue实例对象的属性或方法
  • 支持的数据类型:
    • 都支持(包括基本类型和对象类型
    • 在显示时,会自动将数据转换为字符串值显示
    • true / false => 显示对应名称字符串
    • undefined / null => 不显示
  • ⚠vue的插值语法只在vue实例上找数据

React中的JSX表达式

标签体文本和标签属性值上使用

{表达式}

作用: 动态显示表达式的值

Vue配置项

el-挂载

el: 用来指定模板的根元素

  • 选择器字符串
  • 元素对象
  • 本质调用$mount
挂载vue对象
  1. 自动挂载——el: 'selector'
  2. 手动挂载——vue.$mount('selector')

挂载后,vue内部才会进行模板的解析/编译生成虚拟DOM,进而生成真实DOM显示

// 创建vue对象
const vm = new Vue({
   
  // 1.自动挂载  => 内部会调用$mount进行挂载
  // el: '#root',
  data: {
   
    content: 'abc'
  }
})
// 2.手动挂载
vm.$mount('#root')

vm的函数必须在挂载之前定义

const vm = new Vue({
    
data: {
    
content: 'abc'
}
})
vm.fn = ()=> 'fn函数'
vm.$mount('#root');//挂载

data-数据

data: 用来指定可变的数据,模板可以直接读取,对应react的state

  • 包含n个数据的对象
  • 返回数据对象的函数
响应式更新data数据

⚠vue3中所有数据类型直接修改都有响应式,无需注意以下

基本类型

直接赋值

vue能监听到数据发生的变化,并作出修改

对象类型❗

Vue内部会对data中所有层次属性都通过defineProperty添加getter/setter实现对data数据的深度劫持(监视)

但defineProperty不能监视添加和删除属性,只能监视读取和修改属性

  • 修改对象已有属性 => 响应式 => 界面自动更新

  • 添加新属性/删除属性 => 不是响应式 => 界面不会自动更新

修改已有属性

直接修改,为响应式

this.obj.yyy = value

添加新属性
  1. Vue.set( target, propertyName, value )
  2. vm.$set( target, propertyName, value )
删除属性
  1. Vue.delete( target, propertyName )
  2. vm.$delete( target, propertyName )
数组类型❗

Vue实现数组数据的响应式,不是通过defineProperty给元素添加getter/setter

而是对数组更新元素的7个方法进行包装:

  • push/pop
  • unshift/shift
  • splice
  • sort
  • reverse

先调用原生方法对数组进行更新,再去更新DOM

对于其他未实现响应式的数组方法,可以用新数组替换旧数组

template-模板

template: 用来生成真实DOM的模板字符串

  • 由: html + vue语法(插值语法 + 指令语法)组成的
  • 模板最终会替换掉页面中的空容器元素
  • 缺点: 编码没有提示, 代码没有高亮, 不利于编写和阅读
  • 解决: 可以不写此配置, 将模板直接写在html标签中

methods-方法

methods:包含n个方法的对象

  • methods的所有方法都会被自动添加到vm上
  • 方法的this都被绑定指向vm

⚠methods中的方法不要用箭头函数

实现methods
function Vue(options){
   
	this._data = options.data;
    this._initData();
    this._initMethods(options.methods)
}
Vue.prototype = {
   
    _initData(){
   },
    _initMethods(methods){
   
        Object.keys(methods).forEach(key => {
   
            this[key] = methods[key].bind(this)
        })
    }
}

computed-计算属性

计算属性监视内部使用的属性的变化,一旦发生变化,计算属性就要重新计算

  • 计算属性调用情况
    1. 初始显示
    2. 依赖数据发生变化

⚠computed属性在使用时同data属性,无需带上小括号调用,{ { fullName }}

什么时候使用计算属性

  • 如果一个数据仅需直接展示,直接展示
  • 如果一个数据要进行处理之后才能展示,可以考虑使用计算属性
  • 如果模板页面中表达式较长,可以使用计算属性完成
计算属性两种写法
只读-value
computed: {
   
  fullName () {
   
  	// setTimeout(() => 计算的结果)
  	// 进行特定的计算
  	return 同步计算的结果  => 专门用于显示
  }
}
可读可写-get & set
computed: {
   
  fullName () {
   
	get () {
   
		return 计算后的结果
	},
	// 监视计算属改变, 当fullName被指定了新的值时调用, 
	set (value) {
   
		// 可以在其中更新其它数据/发请求/保存数据
	}
  }
}
computed VS. methods
computed methods
调用情况 1.初始显示
2.依赖数据发生变化
1.data中的数据发生变化
(只要data中的数据发生变化,vue需要重新解析模板,就会重新调用methods)
多次读取 一次
计算属性有缓存,多次读取只计算一次(只在相关响应式依赖发生改变时才会重新求值)
methods多次读取需要执行多次
watch vs. computed
watch computed
是否可异步 可以执行异步操作
监视数据变化后,做一些特定工作, 比如:更新其它数据/保存数据/发请求提交数据
只能同步返回结果,不能异步返回计算结果

分别适用场景

  • method: 事件的回调或者我们封装一些功能函数(更新数据/提示界面/发请求, 一般不用来返回一个要显示的数据)
  • computed: 当要显示的数据是由已有n个数据来共同确定(需要有计算过程)的
  • watch: 用于当某个数据发生改变时就需要做一些相应工作(比如: 更新其它数据/发请求/保存数据)

watch-侦听器

被监听的属性可以是属性(data)也可以是计算属性(computed)

使用

⚠监听属性首先需要保证有该属性(data)或计算属性(computed)

  1. watch配置写法

    watch: {
         
      firstName: {
         
        handler (newVal, oldVal) {
         
          console.log('watch2 firstName', newVal, oldVal)
          // 更新fullName4
          setTimeout(() => {
         
            this.fullName4 = newVal + '-' + this.lastName
          }, 1000);
        }
      }
    }
    
  2. $watch方法

    vm.$watch('lastName', function (value) {
         
      this.fullName4 = this.firstName + '-' + value
    })
    
  3. watch配置简写

    watch: {
         
      firstName(newVal, oldVal){
         
        console.log(oldVal)
      }
    }
    
监听配置
说明
立即监听 immediate: true 初始化时就执行一次监听
深度监听 deep: true watch默认不监测对象内部值的改变(监视一层)
配置deep:true可以监测对象内部值改变(监视多层)

要使用监听配置,必须用一般写法1

{
    
	handler (value, oldValue) {
    } // 监视的回调
	deep: true, // 深度监视
	immediate: true, // 初始化执行一次,  后面数据时会再次执行
}

⚠**immediate & mounted**

如果某个功能既需要watch监听执行, 又需要初始化(mounted)时执行, 直接给watch设置immediate立即监听

监听对象属性

有两种写法:

  1. ["xx.xx"](){}
  2. "xx.xx"(){}
watch:{
   "person.name"(){
   }}
data(){
   
    return{
   
        person:{
   
            name:"asa",
            age:22
        }
    }
}
监听多个属性

watch本身不能同时监听多个属性,只能利用computed实现

computed可以同时监听多个属性的变化,并重新计算值

  1. 让computed返回一个对象,对象包含所有监听的属性,一旦监听的属性发生改变,则computed就会重新计算得到新的属性值
  2. watch深度监听第1步的computed,只要computed内部有任何变化,watch都可以监听到
computed:{
   
    nameAge(){
   
        return{
   
            name:this.name,
            age:this.age
        }
    }
}
watch:{
   nameAge(){
   }}

Vue指令

vue指令:vue中自定义的一些标签属性,都以v-开头

  • 作用:对所在标签进行特定操作

    • 绑定动态属性值
    • 绑定事件监听
    • 显示/隐藏
    • 复制产生多个

v-bind 强制绑定:

基本使用

v-bind可以给给任意标签属性,指定动态的属性值

  • 内置属性: value / src / class / style
  • 自定义属性: xxx
语法
v-bind:属性名="表达式"

//可简写为
:属性名="表达式"
实例
<body>
  <div id="test">
    <img :src="imgSrc" yyy="xxx" />
  </div>

  <script src="../js/vue.js"></script>
  <script>
    const vm = new Vue({
   
      el: "#test",
      data: {
   
        imgSrc: "https://v2.cn.vuejs.org/images/logo.svg",
        xxx: "123",
      },
    });
    setTimeout(() => {
   
      vm.imgSrc = "https://sponsors.vuejs.org/images/aircode.png";
    }, 1000);
  </script>
</body>
style和class动态绑定
style动态绑定
<p :style="{fontSize: '20px', 'backgroun-color': 'pink' color: val}">str</p>
  • v-bind:style 是一个包含样式属性的对象
  • 样式命名方式
    1. 驼峰式 (camelCase)
    2. 破折号方式 (kebab-case)
  • 样式值的单位不能省略

React的style写法

<p style={ { fontSize: 20, 'background-color': 'pink'}}>style动态绑定p>
class动态绑定

:class="xxx" ,xxx可以是字符串、对象、数组

hasA、hasB: boolean,
myClass: 'classA',
myClass2: ['classA', 'classB']
  1. 类名确定,且只有一个,但不确定有没有 => 三元表达式写法

    <p :class="hasA ? 'classA' : ''">情况1</p>
    

    类名确定,且有多个,但不确定有没有 => 对象写法

    <p :class="{classA: hasA, classB: hasB}">情况2</p>
    
    **类名只有一个,类名不确定时 => 字符串写法**```js
    

    情况3

    **类名有多个,类名不确定时 => 数组写法** ​
    `
    ``js <p :class="myClass2">情况4</p> **有固定/静态类名,也有动态类名 => 可分开写,会自动合并它们**```js <p class="classC" :class="myClass">aaaa</p>

React的class写法

  • 在react中,静态类名和动态类名不能分开,需写在一起
<p class={
    hasA ? 'classA' : ''}>情况1</p>
<p class={
    hasA ? 'classC classA' : 'classC'}>情况1</p>
批量绑定多个

v-bind="包含多个属性的对象"

<div id="test">
  <p v-bind="obj">aaaaaaap>
div>

<script src="../js/vue.js">script>
<script>
  const vm = new Vue({
     
    el: '#test',
    data: {
     
      obj: {
     
        a: 1,
        b: 2
      }
    }
  })
script>

v-model 数据双向绑定

  • 数据单向绑定(v-bind):data => 页面,数据只能从data流向页面
  • 数据双向绑定:data <=> 页面
    • 页面能读取data数据进行动态显示,一旦更新数据页面会自动更新
    • 当用户改变页面输入时,输入的数据会自动保存到data中
基本使用
<div id="test">
  <input type="text" v-model:value="msg" />
  <p>{
  {msg}}p>
div>
<script src="../js/vue.js">script>
<script>
  const vm = new Vue({
      el: "#test", data: {
      msg: 123 } });
script>
v-model本质

监听用户的输入事件,从而更新数据

  • 动态value data => view的绑定
  • input事件监听 view => data的绑定

也就是双向数据绑定:data数据 和 模板页面

  • 页面能读取data数据进行动态显示,一旦更新数据页面会自动更新 data => view 绑定
  • 当用户改变页面输入时,输入的数据会自动保存到data中 view => data 绑定
收集表单数据

对于checkbox、radio、option,需要设置value值,选中后会自动将绑定数据更新为选中项的value值

radio
<span>性别: </span>
<input type="radio" id="female" value="女" v-model="gender">
<label for="female"></label>
<input type="radio" id="male" value="男" v-model="gender">
<label for="male"></label><br>
checkbox
<span>爱好: </span>
<input type="checkbox" id="basket" value="basket" v-model="hobbies">
<label for="basket">篮球</label>
<input type="checkbox" id="foot" value="foot" v-model="hobbies">
<label for="foot">足球</label>
select & option
<select v-model="city">
  <option value="">未选择</option>
  <option value="芬兰">芬兰</option>
</select><br>
重置表单

将value值设置为初始值即可

<input type="button" value="重置" @click="reset">
reset () {
   
  this.user = {
   
    username: '',
    gender: '女',
    hobbies: ['foot'],
    city: '',
  }
修饰符

v-model.xxx="事件函数"

说明
.lazy 转为change事件进行同步,失焦时触发同步
(v-model默认为input事件,输入即触发同步)
.number 将输入值转为数值类型
.trim 过滤输入值的首尾空白字符
v-model父子组件绑定双向通信

v-on 事件绑定@

基本使用

一般写法:v-on:事件类型="事件函数"

简写:@:事件类型="事件函数"

  • 事件回调函数需要写在methods中
  • 事件函数中的this指向的是vue的实例对象vm
事件对象及传参
  1. 事件函数默认接收一个event对象

    <button @click="fn">默认传递event</button>
    
    fn(e) {
          ...code }
    
  2. 传递自定义参数

    <button @click="fn2('abc')">传递自定义参数</button>
    
    fn2(msg) {
         }
    
  3. 传递自定义参数和event对象

    <button @click="fn3('abc', $event)">传递自定义参数+event</button>
    
    fn3(msg, e) {
         }
    

原理

vue在解析模板会自动在事件函数外层包裹一个函数

($event) => {
     事件函数 }
修饰符
事件修饰符

在@事件名后, 加.xxx来对事件监听进行进一步处理

@click.stop="fn"

说明
.stop 停止事件传播
.self 事件只有绑定事件的元素能触发,点击子元素不能触发
.prevent 阻止事件默认行为
.once 只执行一次
.capture 默认绑定的事件都是在冒泡阶段执行,添加capture修饰符可以设置捕获阶段执行
按键修饰符

@按钮事件后, 加.xxx来事件监听进行进一步处理

@keyup.enter="fn"

说明
.enter 监视enter键
.keyCode 监视keyCode对应的按钮,不推荐使用(enter键: 13

条件渲染

v-if

条件性地渲染内容

这块内容只会在表达式为true时才渲染

v-if="表达式1"
v-else-if="表达式2"
v-else
  1. v-else-if 和 v-else 注意点

    • v-else-if必须用在紧跟在带有v-if的元素后面的元素上,中间不能有其他元素

    • v-else必须用在紧跟在带有v-if或v-else-if的元素后面的元素上,中间不能有其他元素

  2. 可在