参考课程
尚硅谷Vue2.0+Vue3.0全套教程丨vuejs从入门到精通_哔哩哔哩_bilibili
1、概念简介
特点:
-
渐进式构建数据页面的JS框架
-
组件化 -
.vue=.html+.css+.js -
声明式
-
虚拟DOM+Diff算法
2、第一个vue
-
在 HTML 中准备一个容器
<div id="roo"></div>- id/class 都可以
-
在 JS 中 new 一个 vue 实例,内部只传一个参数,即为配置对象 options,基本的参数如下:
-
el:绑定一个容器- 原生DOM/./# 都可以绑
-
data:保存 vue 实例中变化的数据,这些数据在<template>中使用方法如下:- HTML 中标签体用插值语法 {{}} 取值;
- 标签属性用指令语法 v-bind/v-model 等取值;
- 内部必须写 JS 表达式;
-
-
Vue 实例和容器一一对应
3、配置对象 options
el
el: '',
-
第一种写法:
el:'#root' -
第二种写法:
vue.$mount('#root')
data
data() {
return {
// content
}
}
-
对象式写法:数据很多,采用对象的形式;
-
函数式写法:后续使用组件,必须用函数的形式,返回对象
-
组件里定义的变量,如果不用函数式写法会污染全局变量,这是闭包原理
一个组件的
data选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝
-
methods
methods: {
<方法名>(参数) {
// content
}
}
computed
计算属性也是属性,里面的属性是对象的形式/函数
computed: {
<计算属性>: {
// 初次读取时调用
// 对应数据发生变化时调用
get(){ // content },
// 涉及到该属性的修改时调用,并且要更新依赖的相关属性,几乎不太用
set(){}
}
}
原理:底层用的就是 Object.defineproperty
定义:属性原本不存在,需要通过已有属性计算得来
优势:能够缓存,复用计算结果,效率高,调试方便
// 简写-默认为 getter,此时只考虑读取,不考虑修改
// fullName:function() {}
computed: {
<计算属性>() {
// content
}
}
watch
watch: {
data/computed 中的属性:{
// 配置对象
immediate: false/true,
deep: false/true,
handler(){ // content },
}
}
-
Immediate - 页面初始化时立即执行一次
- 默认值是 false,在第一次进入页面时,如果没有发生数据变化,watch并不会立即监听
-
handler(新值, 旧值)
- 值在更改时被调用,不传参时可以调用 event
-
Deep - 深度监视
- 默认值是 false,不监测对象内部数据的改变
// 简写-当只配置handler,而不需要其他配置项的时候
// isHot:{ handler(){} }
watch: {
<监视属性>(newVal, oldVal) {
// content
}
}
Watch 和 computed 的区别:
- Computed 能完成的功能,watch 都能完成
- Watch 能完成的功能,computed 不一定能完成,例如异步操作
两个重要的原则:
- 所有被 vue 管理的函数,最好写成普通函数,这样 this 指向才是 vm 或者组件实例对象
- 所有不被 vue 管理的函数(如定时器的回调函数,ajax的回调函数等),最好写成箭头函数,这样 this 指向才是 vm 或者组件实例对象
!当两者都能实现功能时,优先使用 computed
Filters
filters: {
<过滤器函数>() {
// 内容
}
}
管道 | 左侧的 time 作为参数传入右侧的过滤器 timeFormater
然后其返回值直接替代整个 {{}} 内部内容,在页面中显示
作用域
-
局部过滤器
-
new Vue{filters:{}}
-
-
全局过滤器
-
Vue.filter('<过滤器函数>', function() {})
-
Directives
// html - 使用自定义指令 v-big
<div v-big="n"></div>
// js - 配置自定义指令 big
new Vue({
directives:{
big(element, binding){
// content
}
}
})
自定义指令可以收到两个参数:
-
Element : 该指令所在的标签(html 元素)
- element.innerText
- element.value
-
Binding:将 html 标签与 自定义指令绑定
- binding.value
Directives 里的自定义指令什么时候会被调用?
- 指令与元素成功绑定时(初始
- 指令所在的模板被重新解析时
进阶写法
// 特定的函数在特定的时间点调用
directives: {
fbind: {
// 指令与元素成功绑定时(初始
bind(element, binding){}
// 指令所在元素被插入页面时
inserted(element, binding){}
// 指令所在的模板被重新解析时
update(element, binding){}
}
}
作用域
-
局部过滤器
-
new Vue{directives:{}}
-
-
全局过滤器
-
// 复杂写法,自定义指令的内容是配置对象 Vue.directive('<自定义指令>', {}) // 简单写法,自定义指令的内容是函数 Vue.directive('<自定义指令>', function(element, binding){})
-
重点
- 自定义指令里的 this 是 window
Template
相当于把 html 里 template 的内容放在 vue 实例里进行配置
!注意内容物只能有一个根节点
// 第一种方式 没有 :x="n" 了
<div id="root" :x="n">
</div>
new Vue({
el:'#root',
template:`
<div>
<h1>{{msg}}</h1>
<h2>kk</h2>
</div>
`
})
// 第二种方式 :x="1"
<div id="root" :x="n">
<h1>{{msg}}</h1>
<h2>kk</h2>
</div>
new Vue({
el:'#root',
})
两种方式的差别在于
- 第一种方式中 div 被 template 完全替代
- 第二种方式中 div 作为根节点被渲染解析,:x="1"
Components
在 vue.extend 中需要注册组件时使用
4、基础语法 - 指令
语法:在F12页面中调试,需要用DOM去操作
V-bind
<h1 :id="msg">hello</h1>
- HTML 标签中绑定标签属性(区别于 CSS 属性)
- 属性值在 JS 中 vue 实例的 data 中
两种使用方法
- 标签<>里头,用 :
- 标签外头,用 {{}}
V-model
<input v-model="请输入">
- 表格数据(用 vue 插件调试)
- 默认与 表单 -> 输入标签 -> value值 进行双向绑定
- v-model:value 可简写为 v-model
事件修饰符
-
.number
- 原生的 type=number ,可以控制“只输入数字”,但是无法控制 value 值的数据类型
- Vue 提供的 v-model.number,不能控制输入框的输入,但是在收集数据时,会自动做类型转换,控制该 value 值的数据类型
-
// 正确的使用方法 <input type="number" v-model.number="age">
-
.lazy
- v-model.lazy,能够在失去焦点的瞬间收集数据,而不是实时收集
-
.trim
- v-model.trim,能够在收集数据时,将输入框内的前后空格自动剪除
V-on
<button @click="function()"></button>
<button @click="function"></button>
- HTML 标签中绑定事件,比如 click 事件
DOM 里的 event 对象类似于 function 里的 arguments
-
函数后头的 () 加不加都行
- 用插值语法 {{}} 去调用函数,约等于把函数返回值插入模板
- 不管函数需不需要传参,都得带 ()。否则会输出整个函数体,而不是执行函数
-
<span>{{function()}}</span>
事件修饰符
事件修饰符能连着写,如 @click.stop.prevent="function",有先后顺序
键盘事件
- @keyup 事件
- @keydown 事件
<input @keyup.enter="function">
<input @keydown.delete="function">
- 自定义按键别名,注意用 kebab-case 方式命名
vue.config.keyCodes.自定义键名 = 键码
按键修饰符也能连着写,如 @keyup.ctrl.y="function"
V-show
让 DOM 元素不显示的方式有:
display:none
visibility:hidden
opacity:0
- V-show 的底层实现就是调整 display 的取值,即使不显示,结构也是在的
- template 标签上不能用 v-show 控制显示与否
V-if
V-if 用于条件渲染时,不需要显示的标签会将其结构从dom树上抹除
V-show 和 v-if
如果标签需要切换的频率较高,建议使用 v-show
- 节点存在,只是动态控制是否显示
如果标签需要切换的频率不高,可以使用 v-if
- v-if 需要在 dom 树中进行增删
标签只能配合v-if 不能配合v-show使用
v-else-if
- 几个 if if if 并列
效率低,每个 if 都需要进行条件判断
- 用 if else-if else-if 并列
效率高,作为一组判断
- 特殊情况-1
当 v-if 与 v-else-if 的判断条件写得相同时,会跳过 v-else-if 判断条件的执行
第二条的 n===1 会被跳过不执行
- 特殊情况-2
当一组 v-if v-else-if 没有一个判断条件为 true 时,会直接返回最后一条 v-else-if / v-else 的内容
最后一条判断的 n===4 没有任何作用
V-for
1、遍历
// 语法
<li v-for="item in/of arr" :key="item.id"></li>
// 遍历数组
// 第一个值是 item 数据
// 第二个值是 index 索引
<li v-for="(item, index) in arr"></li>
// 遍历对象
// 第一个参数是 value
// 第二个参数是 key
<li v-for="(value, key) of car" :key="key"></li>
// 遍历字符串
// 第一个参数是 字符 char
// 第二个参数是 对应的索引 index
<li v-for="(char, index) of str" :key="index"></li>
2、key值
- 可以绑定 item 对象中的 id 字段
- 可以绑定 index 值
- 也可以不绑定
场景:从数组头部插入数据
// HTML
<button @click.once="add">添加一个老刘</button>
<ul>
<li v-for="(p, index) of persons" :key="index">
{{p.name}}-{{p.age}}
<input type="text" value="">
</li>
</ul>
// JS
const vm = new Vue({
el: '#root',
data() {
return {
persons:[
{ id:'1', name:'一', age:11 },
{ id:'2', name:'二', age:22 },
{ id:'3', name:'三', age:33 },
]
}
},
methods: {
add() {
const p = { id:'4', name:'四', age:44 }
this.persons.unshift(p)
}
}
})
问题:
- 效率问题
- 数据显示问题
3、Diff 对比算法
4、key 的内部原理
- 虚拟 DOM 中 key 的作用:
Key 是虚拟 DOM 对象的标识,当数据发生变化时,vue会根据【新数据】生成【新的虚拟DOM】
随后 vue 进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:
-
对比规则
-
旧虚拟 DOM 中找到了与新虚拟 DOM 相同的 key:
- 若虚拟 DOM 中内容没变,直接使用之前的真实 DOM
- 若虚拟 DOM 中内容变了,则生成新的真实 DOM,随后替换掉页面中之前的真实 DOM
-
旧虚拟 DOM 中未找到与新虚拟 DOM 相同的 key
- 创建新的真实 DOM,随后渲染到页面
-
-
用 index 作为 key 可能会引发的问题
- 若对数据进行:逆序添加、逆序删除等破坏顺序操作:会产生没有必要的真实 DOM 更新(界面效果没问题,但效率低)
- 如果结构中还包含输入类的 DOM:会产生错误 DOM 更新(界面有问题)
-
开发中如何选择 key
- 最好使用每条数据的唯一标识作为 key
- 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用 index 作为 key 是没有问题的
5、列表过滤
用 JS 中的 filter 和 indexOf 方法
内置指令
v-text
- 作用:向其所在的节点中渲染文本内容
- 与插值语法的区别:v-text 会替换掉节点中的内容,{{}}不会
V-html
-
作用:向指定节点中渲染包含 html 结构的内容
-
与插值语法的区别:
- v-html 会替换掉节点中的内容,{{}}不会
- v-html 可以识别 html 结构
-
严重注意:v-html 有安全性问题!!!
- 在网站上动态渲染任意 HTML 是非常危险的,容易导致 XSS 攻击
- 一定要在可信的内容上使用 v-html,永远不要用在用户提交的内容上
V-cloak
- 该指令没有值,直接用
- 本质:是一个特殊属性,Vue 实例创建完毕并接管容器后,会删掉 v-cloak 属性
- 作用:使用 css 配合 v-cloak 解决网速慢时页面展示出 {{}} 的问题
// css
[cloak]{
display: none;
}
// html
<div v-cloak>{{msg}}</div>
网络加载时,v-cloak 属性存在
当网络加载完毕,vue实例挂载到容器上后,v-cloak 属性被去掉了
V-once
- 特点:v-once 所在节点在初次动态渲染后,就视为静态内容了
- 以后数据的改变不会引起 v-once 所在结构的更新,可以用于优化性能
V-pre
- 作用:跳过所在节点的编译过程
- 可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译
自定义指令
- 使用 vue 中的配置项 directives
- 涉及自定义指令名很长时,需要 小写+杠
// html
<div v-big-number></div>
//js 复杂写法
new Vue({
directives:{
'big-number': function(element, binding){
// content
}
}
})
//js 简写
new Vue({
directives:{
'big-number'(element, binding){
// content
}
}
})
5、进阶内容
vue对象
vue原型
MVVM模型
-
model 中 data 里所有的属性,最后都会通过数据代理,出现在 vm 实例中
-
本质上说:模板 view 里可以调用所有 vm 身上的所有属性及其原型上所有的属性
Object.defineProperty
- 数据属性
- [[Configurable]]:删除
- [[Enumerable]]:枚举
- [[Writable]]:修改
- [[Value]]:值
- 访问器属性
-
[[Configurable]]:删除
-
[[Enumerable]]:枚举
-
[[Get]]:读 -
[[Set]]:写
数据代理
通过一个对象代理对另一个对象中属性的操作 读/写
本质:通过 Object.defineProperty 中的 getter 和 setter 来实现
Vue 中的 data 数据做了数据代理,放到 vm 实例中的 _data 里
Vue 中的 methods 方法没有用数据代理,直接赋给了 vm 实例中
给 methods 加数据代理没有任何意义,方法只是用于调用,而不是被更改的
简而言之:数据代理就是 vm实例上有属性了,但是属性值不给你,是动态更新的
数据劫持
数据代理图片中,_data里有数据劫持,也就是说,Vm 在拿到 data 中的数据时,做了两部分工作:
-
加工 data
- 目的:vue 的响应式(当页面改变时,数据也发生改变)
- 方式:为每个数据加一个 getter 和 setter
-
vm._data = data
举例:
*name 改变时,触发 setter 的调用,重新解析模板,生成新的虚拟 DOM ,新旧 DOM 对比,更新页面
具体做法:
这是简单的 data 数据劫持逻辑,vue 比我们考虑的更多:
- 既可以通过 vm._data.属性 操作数据,也可以通过 vm.属性 操作数据,因为有数据代理
- 当 data 中存在对象多层嵌套的数据时, vue 可以为每层数据进行数据劫持,加了递归
数据监视更新的原理
-
vue 如何监测对象里数据改变:
-
为每个数据添加 getter 和 setter
- 更改对象内容:可以直接用=修改
- 添加新的响应式属性:可以用vue.set
-
-
vue 如何监测数组里数据改变:
-
Vue 并不会为数组里的每个数据添加 getter/setter
-
也就是说,当通过索引值改变数组内容时,不会触发页面的响应式更新
- 更改对象内容:不可以用=修改,需要用7种改变数组的方法修改
- 添加新的响应式值:得用7种改变数组的方法修改
-
数据监测
对象更新时的问题
两种 data 中对象更新的方法:
- 用.的方式取数组对象中的属性,用=赋值
- 直接用=对数组中的对象进行赋值
无法正常显示的原因:
后续通过=为vm._data中的数据添加属性和属性值时,vue不会为这种属性配备 getter/setter,因此该属性不是响应式数据,读取时不调用 setter,自然不会引起模板解析,也就不会显示到页面上了
解决办法: vue.set
// 第一种:调用 vue 上的 set 方法
vue.set(<对象>,<属性>,<属性值>)
vue.set(vm._data.student,'sex','男') // 例子
vue.set(vm.student,'sex','男') // 例子,用了数据代理
// 第二种:调用 vm 实例上的 $set 方法
vm.$set(<对象>,<属性>,<属性值>)
this.$set() // 在 vm 实例中
局限性
*vue.set 只能给 data 里的某个对象增加新的属性
官方文档
数组更新时的问题
- 当通过索引值改变数组内容时,不会触发页面的响应式更新
解决办法1:调用对应的数组方法
-
Vue 能够进行响应式监测的数组方法有7个:
- Push
- Pop
- Shift
- Unshift
- Splice
- Sort
- Reverse
*这些数组方法都是能够影响原数组的方法
在调用这些方法时,实际上调用的不是 array 原型对象上的方法了,而是 vue 包装过的方法
解决办法2:vue.set
vue.set(<数组>,<索引值>,<更改后的值>)
Vue 监测数据的原理
-
Vue 会监测 data 中所有层次的数据
-
如何监测对象中的数据?
-
通过 setter 实现监视,且要在 new vue 时就传入要监测的数据
-
对象中后追加的属性,vue 默认不做响应式处理
-
如需给后添加的属性做响应式,请使用如下 API:
- vue.set() 或 vm.$set()
-
-
-
如何监测数组中的数据?
-
通过包裹数组更新元素的方法实现,本质就是做了两件事:
- 调用原生对应的方法对数组进行更新
- 重新解析模板,进而更新页面
-
-
在 vue 修改数组中某个元素,一定要用到如下方法:
- 使用 api:push/pop/shift/unshift/splice/sort/reverse
- vue.set() 或 vm.$set()
-
特别注意:vue.set() 和 vm.$set() 不能给 vm 或 vm的根数据对象 添加属性
生命周期
如果在 methods 里面开定时器,并且在内部改变 data 中的值,那么会导致定时器个数成指数倍增长的情况。
Methods 里面只写回调函数
生命周期里的 this 都是 vue 实例
created
- 数据监测和数据代理都开了
- 可以访问 data 和 methods
- 但页面中还没有显示解析好的内容,vue 没有解析模板,没有生成虚拟 dom
mounted
- 虚拟 dom 转为真实 dom 插入页面了
- 页面中呈现已经编译好的 dom
- 在此时可以 开启定时器、订阅消息、绑定自定义事件等初始化操作
Updated
- 数据是新的,页面也是新的,完成 model->view 的更新
- 涉及新旧 DOM 元素对比
beforeDestroyed
- 所有配置项都处于可用状态,但是所有对数据的修改不会触发 更新/模板解析 了,所以不建议使用
- 在此时可以 关闭定时器、取消订阅、解绑自定义事件等收尾操作
6、业务需要
绑定样式
css
//
:class="xxx"
v-bind:class="xxx"
- 不确定是否使用样式时,动态切换样式,vue 绑定 boolean 数据实现
- 适用于:样式类名不确定,需要动态指定
<div :class="mood" @click="changeMood"></div>
// methods
changeMood() {
const arr = ['happy', 'sad', 'normal']
this.mood = arr[Math.floor(Math.random() * 3)]
}
- 不确定用几个样式时,动态切换样式的个数,vue 绑定数组实现
- 适用于要绑定的样式个数不确定,名字也不确定
<div :class="arr"></div>
// data
arr:['at1', 'at2', 'at3']
- 样式个数和类名确定,但不确定到底用不用时,vue绑定对象实现
<div :class="classObj"></div>
// data
classObj: {
at1: false,
at2: true
}
- 在标签内写js表达式时,带引号与不带引号意义不同
- 带引号:表示引号内是值
- 不带引号:表示该参数是一个变量,需要找其对应的值
<div :class="['at1', 'at2', 'at3']"></div>
// data
<div :class="[at1, at2, at3]"></div>
style
- 当需要绑定行内样式时,用对象的形式
- 1.css 里的 font-size 对应 js 里的 fontSize
<div :style="{fontSize: fsize+'px'}"></div>
// data
fsize: 40
- 2.对象的形式
<div :style="styleObj"></div>
// data
styleObj: {
fontSize: '40px'
}
- 3.数据的写法
<div :style="[styleObj, styleObj2]"></div>
// data
styleObj: {
fontSize: '40px',
color: 'red'
}
styleObj2: {
backgroundColor: 'orange'
}
过滤器
显示格式化后的时间
- 推荐开源库 moment.js / day.js
代码折叠
使用 region 包裹需要折叠的代码,这样无论发生什么,该段代码都可以正常折叠
#region
...
#endregion
0、注意
- 一旦 data 里数据改了,vue 会重新解析模板,和该数据相关的模板、方法都会更新
- 对象里的key是字符串,但是 new vue 实例的时候,经常把 key 简写,还经常把函数简写
- Vue 在配置对象里写的所有东西都可以在 vm.$options 里获取得到
- Form 里的标签取值,可以用 v-bind/v-model 绑在 <> 标签里
- Form 外的标签取值,标签属性仍然可以用 v-bind/v-model 绑在<>标签里,非标签属性则需要用 {{}} 取值
- 读取 data 中某个对象不存在的属性时,返回 undefine,vue 不会显示 undefine,因此最终页面上为空