官网传送门
Vue
Vue 是动态构建用户界面的渐进式 JavaScript 框架
Vue 借鉴 Angular 的模板和数据绑定技术,React 的组件化和虚拟 DOM 技术
数据代理
Object.defineProperty()
的使用
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
| let person = { name: 'Vue', sex: 'none', } let number = 19
Object.defineProperty(person, 'age', { value: 21, writable: true, enumerable: true, configurable: true, })
Object.keys(person)
Object.defineProperty(person, 'age', { enumberable: true, configurable: true,
get() { console.log('age 属性被读取') return number }
set(value) { console.log('age 属性被修改', value) number = value } })
|
何为数据代理
数据代理:通过一个对象对另一个对象的属性进行操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| let obj = { a: 21 } let obj2 = { b: 10 }
Object.defineProperty(obj2, 'a', { get() { return obj.a } set(value) { obj.a = value } })
obj2.a obj2.a = 1000 obj.a
|
Vue 中的数据代理
Vue 中通过 vm 实例对象代理对 data 对象属性的操作,让我们更方便操作 data 中的数据。
data 中的数据实际上被存在 vm._data
属性上,如果不进行代理,使用起来很不方便。
通过 Object.defineProperty()
给 vm 添加属性,并且指定 getter 和 setter,通过 getter 和 setter 访问和修改 data 对应是属性。
Vue 监测数据的原理
:::info 监测数据
监测数据,即 Vue 是如何监听数据发生变化,从而重新解析模板渲染页面的。Vue 会监测 data 中所有层级的数据。
:::
Vue 监测对象数据
- 原理:通过
Object.defineProperty()
为属性添加 getter
、setter
,对属性的读取、修改进行拦截,即数据劫持
- 存在问题:
- 对象新增加的属性,默认不做响应式处理
- 对象删除属性,也不是响应式的
- 解决办法,使用如下 API :
Vue.set(target, propertyName/index, value)
vm.$set(target, propertyName/index, value)
Vue.delete(target, propertyName/index)
vm.$delete(target, propertyName/index)
Vue.set()
和 vm.$set()
不能给 vm 或 vm 的根数据对象添加属性(即 data)
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
|
let person = { name: 'Vue', age: 99 }
function Observer(obj) { const keys = Object.keys(obj)
keys.forEach(key => { Object.defineProperty(this, key, { get() { return obj[key] } set(value) { console.log('数据被修改,重新解析模板...') obj[key] = value } }) }) }
let vm = {} let observer = new Observer(person) vm._data = observer
|
Vue 监测数组
- 原理:通过重写数组的 API 实现拦截:
push()、pop()、shift()、unshift()、splice()、sort()、reverse()
- 7 个 API 之所以是响应式的,是因为 Vue 对这些方法进行了包裹,即二次封装。做了两件事:调用对应的原生方法更新数组 & 重新解析模板更新页面
- 存在问题:
- 解决办法:
- 使用 7 个 API 修改数组
Vue.set()
、vm.$set()
Vue.delete()
、vm.$delete()
思否文章{link=card}
插值语法
1 2 3 4 5
| <p>用户名:{{ username }}</p> <p>密码{{ password }}</p>
<p>{{ flag ? 111 : 000}}</p>
|
1 2 3 4 5 6 7
| data() { return { username: 'docsify', password: 55520, flag: true } }
|
属性绑定指令 v-bind
1 2 3 4 5 6 7
| <input type="text" v-bind:placeholder="desc" />
<img :src="url" alt="这是一张图片" />
<div :id="'hello' + 1"></div>
|
1 2 3 4 5 6 7
| data() { return { desc: '请输入用户名', url: 'www.baidu.com', name: 'hello' } }
|
双向绑定指令 v-model
v-model
用于表单元素如 input
,textarea
,select
。
v-model 基础用法
1 2 3 4 5 6 7 8 9 10 11
| <p>{{ username }}</p> <input type="text" v-model:value="username" /> <input type="text" v-model="username" />
<p>{{ province }}</p> <select v-model="province"> <option value="">请选择</option> <option value="1">北京</option> <option value="2">上海</option> <option value="3">广州</option> </select>
|
v-model 指令修饰符
修饰符 |
作用 |
示例 |
.number |
将用户输入转为数值类型 |
<input v-model.number="age" /> |
.trim |
删除输入的首尾空白字符 |
<input v-model.trim="msg"> |
.lazy |
当失去焦点时,才更新数据,类似防抖 |
<input v-model.lazy="msg"> |
v-model 收集表单数据
<input type="text"/>
,收集的是 value
值,用户输入的就是 value
值。
<input type="radio"/>
,收集的是 value
值,且要给标签配置 value
值。
<input type="checkbox"/>
- 没有配置
value
属性,收集的就是 checked
- 配置了
value
属性:
v-model
的初始值是非数组,那么收集的就是 checked
v-model
的初始值是数组,那么收集的的就是 value
组成的数组
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
| <div id="root"> <form @submit.prevent="demo"> 账号:<input type="text" v-model.trim="userInfo.account" />
密码:<input type="password" v-model="userInfo.password" />
年龄:<input type="text" v-model.number="userInfo.age" />
性别: 男<input type="radio" name="sex" v-model="userInfo.sex" value="male" />
女<input type="radio" name="sex" v-model="userInfo.sex" value="female" />
爱好: 学习<input type="checkbox" v-model="userInfo.hobby" value="study" />
打游戏<input type="checkbox" v-model="userInfo.hobby" value="game" />
吃饭<input type="checkbox" v-model="userInfo.hobby" value="eat" />
所属校区 <select v-model="userInfo.city"> <option value="">请选择校区</option> <option value="beijing">北京</option> <option value="shanghai">上海</option> <option value="shenzhen">深圳</option> <option value="wuhan">武汉</option> </select>
其他信息: <textarea v-model.lazy="userInfo.other"></textarea> <input type="checkbox" v-model="userInfo.agree" />阅读接受协议 <button>提交</button> </form> </div>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| data() { return { userInfo:{ account:'', password:'', age:18, sex:'female', hobby:[], city:'beijing', other:'', agree:'' } } }, methods: { demo() { console.log(JSON.stringify(this.userInfo)) } }
|
事件绑定指令 v-on
v-on 基础用法
1 2 3 4 5
| <p>count的值:{{ count }}</p> <button v-on:click="add">+1</button>
<button @click="add">+1</button>
|
1 2 3 4 5 6 7 8 9 10
| data() { return { count: 1 } }, methods: { add() { this.count++ } }
|
事件参数对象
如果事件处理函数没有传参,则默认会传一个时间参数对象 $event
,通过它可以获取触发事件的元素,并进行相关操作。
1 2 3 4 5 6
| methods: { add(e) { e.target.style.backgroundColor = 'red' this.count++ } }
|
如果事件处理函数传递参数了,则默认的 $event
会被覆盖,需要手动进行传递。
1
| <button @click="add(2, $event)">+1</button>
|
1 2 3 4 5 6
| methods: { add(step, e) { e.target.style.backgroundColor = 'red' this.count += step } }
|
事件修饰符
事件修饰符 |
说明 |
.prevent |
阻止默认行为,如 a 链接跳转、表单提交 |
.stop |
阻止事件冒泡 |
.once |
绑定的事件只触发 1 次 |
.capture |
以捕获模式触发事件处理函数 |
.self |
只有在 event.target 是当前元素自身时触发事件处理函数 |
.passive |
事件的默认行为立即执行,无需等待事件回调执行完毕 |
1 2 3 4 5 6 7
| <a href="www.baidu.com" @click.prevent="fn">阻止链接跳转</a>
<div @click.stop="handleClick">阻止事件冒泡</div>
如果回调比较耗时,那么会等一段时间才发生滚动。 添加 .passive 后,则先进行滚动再执行回调。
|
按键修饰符
- Vue 中常用的按键别名:
- 回车 => enter
- 删除 => delete (捕获“删除”和“退格”键)
- 退出 => esc
- 空格 => space
- 换行 => tab (特殊,必须配合 keydown 去使用)
- 上 => up
- 下 => down
- 左 => left
- 右 => right
Vue 未提供别名的按键,可以使用按键原始的 key
值去绑定,但注意要转为 kebab-case(短横线命名)
系统修饰键(用法特殊):ctrl、alt、shift、meta(即 win 键)
- 配合 keyup 使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。
- 配合 keydown 使用:正常触发事件。
可使用 keyCode 去指定具体的按键,此法不推荐,因为 keyCode 以后可能废除
Vue.config.keyCodes.自定义键名 = 键码
,可以去定制按键别名
1 2 3 4 5 6 7 8 9 10
| <input type="text" @keyup.enter="submit" /> <input type="text" @keyup.esc="back" /> <input type="text" @keydown.tab="showInfo" /> <input type="text" @keyup.caps-lock="showInfo" />
<input type="text" @keyup.huiche="showInfo" /> <input type="text" @keyup.13="showInfo" /> <script> Vue.config.keyCodes.huiche = 13 </script>
|
条件渲染指令
基础用法
1 2 3 4 5 6 7 8 9 10 11 12
| <p v-if="status === 200">success</p> <p v-else-if="status === 201">xxx</p> <p v-else>yyy</p>
<p v-show="status === 404">error</p>
<template v-if="status === 200"> <p>111</p> <p>222</p> <p>333</p> </template>
|
v-if 和 v-show 的区别
实现原理不同:
v-if
通过创建或删除 DOM 元素来控制元素的显示与隐藏
v-show
通过添加或删除元素的 style="display: none"
样式来控制元素的显示与隐藏
性能消耗不同:
v-if
切换开销更高,如果运行时条件很少改变,使用 v-if
更好
v-show
初始渲染开销更高,如果切换频繁,使用 v-show
更好
列表渲染指令 v-for
基本用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <ul> <li v-for="(item, index) in list" :key="item.id">{{ item.name }}</li> </ul>
<ul> <li v-for="(value, key) in obj" :key="key">{{ key }} - {{ value }}</li> </ul>
<ul> <li v-for="(char, index) in str" :key="index">{{ index }} - {{ char }}</li> </ul>
<ul> <li v-for="(number, index) in 5" :key="index">{{ index }} - {{ number }}</li> </ul>
|
1 2 3 4 5 6 7 8 9 10 11
| data() { return { list: [...], obj: { name: 'Bruce', age: 88, sex: 'unknown' }, str: 'hello vue' } }
|
key
的作用
key
的作用:
- 当列表的数据变化时,默认情况下,vue 会尽可能的复用已存在的 DOM 元素,从而提升渲染的性能。但这种默认的性能优化策略,会导致有状态的列表无法被正确更新。
- 为了给 vue 一个提示,以便它能跟踪每个节点的身份,从而在保证有状态的列表被正确更新的前提下,提升渲染的性能。此时,需要为每项提供一个唯一的 key 属性。
key
是虚拟 DOM 对象的标识,可提高页面更新渲染的效率。当数据变化时,Vue 会根据新数据生成新的虚拟 DOM,随后进行新旧虚拟 DOM 的差异比较
比较规则
- 旧虚拟 DOM 找到和新虚拟 DOM 相同的 key:
- 若内容没变,直接复用真实 DOM
- 若内容改变,生成新的真实 DOM,替换旧的真实 DOM
- 旧虚拟 DOM 未找到和新虚拟 DOM 相同的 key:创建新的真实 DOM,渲染到页面
key
的注意事项:
- key 的值只能是字符串或数字类型
- key 的值必须具有唯一性(即:key 的值不能重复)
- 建议把数据项 id 属性的值作为 key 的值(因为 id 属性的值具有唯一性)
- 使用 index 的值当作 key 的值没有意义(因为 index 的值不具有唯一性)
- 建议使用 v-for 指令时一定要指定 key 的值(既提升性能、又防止列表状态紊乱)
其它内置指令
v-text
v-text
指令会覆盖元素默认值。
1
| <p v-text="username">这段内容会被覆盖</p>
|
1
| data() { return { username: "Bruce" } }
|
v-html
:::warning 安全问题
v-html 存在安全问题,容易导致 XSS 攻击
:::
1
| <p v-html="desc">原本内容被覆盖</p>
|
1 2 3 4 5 6
| data() { return { desc: '<h1 style="color: red">红色标题</h1>', str: '<a href="http://www.baidu.com?"+document.cookie>兄弟我找到你想要的资源了,快来!</a>' } }
|
v-cloak
- 本质是一个特殊属性,Vue 实例创建完毕并接管容器后,会删除
v-cloak
属性
- 使用 CSS 配合
v-cloak
可解决网速慢时页面展示 的问题
1 2 3
| [v-cloak] { display: none; }
|
1
| <h2 v-cloak>{{ username }}</h2>
|
v-once
v-once
所在节点初次渲染后就成为静态内容
- 即数据变化不会引起
v-once
所在节点内容的更新,可用于优化性能
1 2
| <h2 v-once>初次的内容:{{ content }}</h2> <h2>最新的内容:{{ content }}</h2>
|
v-pre
- 跳过所在节点的编译过程
- 没有使用插值语法等特殊语法的节点,可用其跳过编译过程,加快编译
1 2
| <h2 v-pre>Vue 内置指令</h2> <p>用户名:{{ username }}</p>
|
过滤器
- 过滤器常用于文本的格式化,可用在插值表达式和
v-bind
属性绑定。
- 过滤器只在
vue 2.x
和 vue 1.x
中支持,vue 3.x
废弃了过滤器,官方建议使用计算属性或方法代替过滤器。
基本使用
1 2 3 4
| <p>{{ message | capitalize }}</p>
<div :id="rawId | formatId"></div>
|
1 2 3 4 5 6
| filters: { capitalize(str) { return str.charAt(0).toUpperCase() + str.slice(1) } }
|
1 2 3 4
| Vue.filter('capitalize', (str) => { return str.charAt(0).toUpperCase() + str.slice(1) })
|
如果私有过滤器和全局过滤器冲突,按照就近原则调用私有过滤器。
连续调用多个过滤器
过滤器从左往右调用,前一个过滤器的结果交给下一个过滤器继续处理。
1
| <p>{{ text | capitalize | maxLength }}</p>
|
过滤器传参
1
| <p>{{ message | myFilter(arg1, arg2) }}</p>
|
1 2 3 4
| Vue.filter('myFilter', (value, arg1, arg2) => { ... })
|
computed 计算属性
- 定义:使用的属性不存在,要通过已有属性计算得到
- 原理:底层使用了
Object.defineProperty()
提供的 getter 和 setter
- getter 何时执行:
- 优点:与
methods
相比,有缓存机制,效率更高
- 若计算属性要修改,必须声明 setter 响应修改,且 setter 中要引起依赖的数据发生改变
1
| <span>{{ fullName }}</span>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| computed: { fullName: { get() { return this.firstName + '-' + this.lastName } set(value) { const arr = value.split('-') this.firstName = arr[0] this.lastName = arr[1] } } }
computed: { fullName() { return this.firstName + this.lastName } }
|
watch 侦听器
watch
侦听器允许开发者监视数据的变化,针对数据的变化做特定的操作。
:::tip watch vs computed
侦听器可以监听普通属性和计算属性
computed
能完成的功能,watch
也能
watch
能完成的功能,computed
不一定,如异步操作
:::
- Vue 管理的函数写成普通函数,使其
this
指向 vue 实例对象
- 不被 Vue 管理的函数写成箭头函数(定时器回调、
ajax
回调、Promise
回调),这样其 this
才是 vue 实例对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| export default { data() { return { username: '', } }, watch: { username(newVal, oldVal) { console.log('新值: ', newVal) console.log('旧值: ', oldVal) }, }, }
|
默认情况下,组件在初次加载完毕后不会调用 watch
侦听器。如果想让 watch
侦听器立即被调用,需要使用 immediate
选项:
1 2 3 4 5 6 7 8 9 10
| watch: { username: { handler(newVal, oldVal) { ... }, immediate: true } }
|
当 watch
侦听的是一个对象,如果对象中的属性值发生了变化,则无法被监听到。此时需要使用 deep
选项进行深度监听:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| export default { data() { return { info: { username: 'admin' }, } }, watch: { info: { handler(newVal) { console.log(newVal) }, deep: true, }, }, }
|
若只想监听对象里单个属性的变化,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| export default { data() { return { info: { username: 'admin' }, } }, watch: { 'info.username': { handler(newVal) { console.log(newVal) }, }, }, }
|
通过 Vue 实例的 $watch
监听:
1 2 3 4 5 6 7 8 9 10 11 12 13
| const vm = new Vue({...})
vm.$watch('isHot',{ immediate: true, deep: true, handler(newValue,oldValue) { console.log(newValue, oldValue) } })
vm.$watch('isHot',function(newValue,oldValue) { console.log(newValue, oldValue) })
|
动态绑定 class 和 style
通过动态绑定 class
属性和行内 style
样式,可动态操作元素样式。
动态绑定 class 类名
- 字符串写法:类名不确定,要动态获取
- 对象写法:要绑定多个样式,个数不确定,名字也不确定
- 数组写法:要绑定多个样式,个数确定,名字也确定,但不确定用不用
1 2 3 4 5 6 7 8
| <style> .happy { ...; } .sad { ...; } </style>
|
1 2 3
| <div class="basic" :class="mood" @click="changeMood">字符串写法</div> <div class="basic" :class="arr">数组写法</div> <div class="basic" :class="classObj">对象写法</div>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| export default { data() { return { mood: 'happy', arr: ['happy', 'sad'], classObj: { happy: true, sad: false, }, } }, methods: { changeMood() { this.mood = 'sad' }, }, }
|
动态绑定 style 样式
css
属性名既可以用驼峰形式,也能用短横线形式(需要使用引号括起来)。
1 2 3 4
| <div :style="{color: active, fontSize: fsize + 'px', 'background-color': bgcolor}">对象写法</div> <div :style="styleObj">对象写法</div>
<div :style="[styleObj, styleObj2]">数组写法(用得少)</div>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| data() { return { active: 'red', fsize: 30, bgcolor: 'yellow', styleObj: { color: 'active', 'font-size': '20px' }, styleObj2: { backgroundColor: 'yellow' } } }
|
vue 生命周期
vue 生命周期是指一个组件从创建、运行、销毁的整个过程。每个阶段对应着不同的生命周期钩子。
生命周期钩子也可理解为:Vue 在特定的时刻调用特定的函数。
除了图中 8 个钩子,还有 nextTick
,activated
,deactivated
关于销毁过程:
- 销毁后借助 Vue 开发者工具看不到任何信息。
- 销毁后自定义事件会失效,但原生 DOM 事件依然有效。
- 一般不在
beforeDestroy
操作数据,因为即便操作数据,也不会再触发更新流程