学习Vue之前要掌握的 JavaScript基础知识?
在MVVM中:
1、 ViewModel 会监听数据源的变化,当数据源变化后会同步更新到View的视图中
2、随后ViewModel会监听View的DOM的变化,当View变化后会同步变化到Model的数据源中
官网传送门(opens new window)
Vue 是动态构建用户界面的渐进式 JavaScript 框架
Vue 借鉴 Angular 的模板和数据绑定技术,React 的组件化和虚拟 DOM 技术
Object.defineProperty()
的使用let person = {
name: 'Vue',
sex: 'none',
}
let number = 19
// 配置一
// 参数:对象、属性名、配置
Object.defineProperty(person, 'age', {
// 属性值
value: 21,
// 属性是否可修改
writable: true,
// 属性是否可枚举(遍历)
enumerable: true,
// 属性是否可删除
configurable: true,
})
Object.keys(person)
// 配置二
// getter、setter 不能和 value、writable 同时指定
Object.defineProperty(person, 'age', {
enumberable: true,
configurable: true,
get() {
console.log('age 属性被读取')
return number
}
set(value) {
console.log('age 属性被修改', value)
number = value
}
})
数据代理:通过一个对象对另一个对象的属性进行操作
let obj = { a: 21 }
let obj2 = { b: 10 }
Object.defineProperty(obj2, 'a', {
get() {
return obj.a
}
set(value) {
obj.a = value
}
})
obj2.a // 21
obj2.a = 1000
obj.a // 1000
Vue 中通过 vm 实例对象代理对 data 对象属性的操作,让我们更方便操作 data 中的数据。
data 中的数据实际上被存在 vm._data
属性上,如果不进行代理,使用起来很不方便。
通过 Object.defineProperty()
给 vm 添加属性,并且指定 getter 和 setter,通过 getter 和 setter 访问和修改 data 对应是属性。
监测数据,即 Vue 是如何监听数据发生变化,从而重新解析模板渲染页面的。Vue 会监测 data 中所有层级的数据。
原理:通过 Object.defineProperty()
为属性添加 getter
、setter
,对属性的读取、修改进行拦截,即数据劫持
存在问题:
解决办法,使用如下
API (opens new window):
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)
// 简单模拟实现对象的数据监测,Vue 更完善
// Vue 通过 vm.name 即可修改
// Vue 实现深层监听
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 把数据放到_data中
console.log(observer.name); // 通过代理后的对象可以直接监听数据 输出 Vue
push()、pop()、shift()、unshift()、splice()、sort()、reverse()
Vue.set()
、vm.$set()
Vue.delete()
、vm.$delete()
<p>用户名:{{ username }}p>
<p>密码{{ password }}p>
<p>{{ flag ? 111 : 000}}p>
data() {
return {
username: 'docsify',
password: 55520,
flag: true
}
}
<input type="text" v-bind:placeholder="desc" />
<img :src="url" alt="这是一张图片" />
<div :id="'hello' + 1">div>
data() {
return {
desc: '请输入用户名',
url: 'www.baidu.com',
name: 'hello'
}
}
v-model
用于表单元素如 input
,textarea
,select
。
<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>
修饰符 | 作用 | 示例 |
---|---|---|
.number | 将用户输入转为数值类型 |
|
.trim | 删除输入的首尾空白字符 |
|
.lazy | 当失去焦点时,才更新数据,类似防抖 |
|
,收集的是 value
值,用户输入的就是 value
值。
,收集的是 value
值,且要给标签配置 value
值。
value
属性,收集的就是 checked
value
属性:
v-model
的初始值是非数组,那么收集的就是 checked
v-model
的初始值是数组,那么收集的的就是 value
组成的数组<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>
data() {
return {
userInfo:{
account:'',
password:'',
age:18,
sex:'female',
hobby:[],
city:'beijing',
other:'',
agree:''
}
}
},
methods: {
demo() {
console.log(JSON.stringify(this.userInfo))
}
}
<p>count的值:{{ count }}p>
<button v-on:click="add">+1button>
<button @click="add">+1button>
data() {
return {
count: 1
}
},
methods: {
add() {
this.count++
}
}
如果事件处理函数没有传参,则默认会传一个时间参数对象 $event
,通过它可以获取触发事件的元素,并进行相关操作。
methods: {
add(e) {
e.target.style.backgroundColor = 'red'
this.count++
}
}
如果事件处理函数传递参数了,则默认的 $event
会被覆盖,需要手动进行传递。
<button @click="add(2, $event)">+1button>
methods: {
add(step, e) {
e.target.style.backgroundColor = 'red'
this.count += step
}
}
事件修饰符 | 说明 |
---|---|
.prevent | 阻止默认行为,如 a 链接跳转、表单提交 |
.stop | 阻止事件冒泡 |
.once | 绑定的事件只触发 1 次 |
.capture | 以捕获模式触发事件处理函数 |
.self | 只有在 event.target 是当前元素自身时触发事件处理函数 |
.passive | 事件的默认行为立即执行,无需等待事件回调执行完毕 |
<a href="www.baidu.com" @click.prevent="fn">阻止链接跳转a>
<div @click.stop="handleClick">阻止事件冒泡div>
如果回调比较耗时,那么会等一段时间才发生滚动。 添加 .passive 后,则先进行滚动再执行回调。
Vue 未提供别名的按键,可以使用按键原始的 key
值去绑定,但注意要转为 kebab-case(短横线命名)
系统修饰键(用法特殊):ctrl、alt、shift、meta(即 win 键)
可使用 keyCode 去指定具体的按键,此法不推荐,因为 keyCode 以后可能废除
Vue.config.keyCodes.自定义键名 = 键码
,可以去定制按键别名
<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>
<p v-if="status === 200">successp>
<p v-else-if="status === 201">xxxp>
<p v-else>yyyp>
<p v-show="status === 404">errorp>
<template v-if="status === 200">
<p>111p>
<p>222p>
<p>333p>
template>
实现原理不同:
v-if
通过创建或删除 DOM 元素来控制元素的显示与隐藏v-show
通过添加或删除元素的 style="display: none"
样式来控制元素的显示与隐藏性能消耗不同:
v-if
切换开销更高,如果运行时条件很少改变,使用 v-if
更好v-show
初始渲染开销更高,如果切换频繁,使用 v-show
更好原来在 patchVnode 过程中,内部会对执行 v-show 指令对应的钩子函数 update,然后它会根据 v-show 指令绑定的值来设置它作用的 DOM 元素的 style.display 的值控制显隐。
因此相比于 v-if 不断删除和创建函数新的 DOM,v-show 仅仅是在更新现有 DOM 的显隐值,所以 v-show 的开销要比 v-if 小的多,当其内部 DOM 结构越复杂,性能的差异就会越大。
但是 v-show 相比于 v-if 的性能优势是在组件的更新阶段,如果仅仅是在初始化阶段,v-if 性能还要高于 v-show,原因是在于它仅仅会渲染一个分支,而 v-show 把两个分支都渲染了,通过 style.display 来控制对应 DOM 的显隐。
在使用 v-show 的时候,所有分支内部的组件都会渲染,对应的生命周期钩子函数都会执行,而使用 v-if 的时候,没有命中的分支内部的组件是不会渲染的,对应的生命周期钩子函数都不会执行。
因此你要搞清楚它们的原理以及差异,才能在不同的场景使用适合的指令。
<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>
data() {
return {
list: [...],
obj: {
name: 'Bruce',
age: 88,
sex: 'unknown'
},
str: 'hello vue'
}
}
key
的作用:
key
的注意事项:
(图为用index做key值时,倒序插入触发的数值紊乱)
v-text
指令会覆盖元素默认值。
<p v-text="username">这段内容会被覆盖p>
data() { return { username: "Bruce" } }
v-html 存在安全问题,容易导致 XSS 攻击
<p v-html="desc">原本内容被覆盖p>
data() {
return {
desc: '红色标题
',
str: '兄弟我找到你想要的资源了,快来!'
}
}
v-cloak
属性v-cloak
可解决网速慢时页面展示 的问题[v-cloak] {
display: none;
}
<h2 v-cloak>{{ username }}h2>
v-once
所在节点初次渲染后就成为静态内容v-once
所在节点内容的更新,可用于优化性能<h2 v-once>初次的内容:{{ content }}h2>
<h2>最新的内容:{{ content }}h2>
<h2 v-pre>Vue 内置指令h2>
<p>用户名:{{ username }}p>
v-bind
属性绑定。vue 2.x
和 vue 1.x
中支持,vue 3.x
废弃了过滤器,官方建议使用计算属性或方法代替过滤器。
<p>{{ message | capitalize }}p>
<div :id="rawId | formatId">div>
// 定义私有过滤器
filters: {
capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1)
}
}
/ 在 main.js 中定义全局过滤器
Vue.filter('capitalize', (str) => {
return str.charAt(0).toUpperCase() + str.slice(1)
})
如果私有过滤器和全局过滤器冲突,按照就近原则调用私有过滤器。
过滤器从左往右调用,前一个过滤器的结果交给下一个过滤器继续处理。
<p>{{ text | capitalize | maxLength }}p>
<p>{{ message | myFilter(arg1, arg2) }}p>
// 第一个参数永远都是管道符前的值
Vue.filter('myFilter', (value, arg1, arg2) => {
...
})
Object.defineProperty()
提供的 getter 和 settermethods
相比,有缓存机制,效率更高<span>{{ fullName }}span> // 不用加()计算属性
// 完整写法
computed: {
fullName: {
get() {
// 计算属性依赖于已有属性得到
return this.firstName + '-' + this.lastName
}
set(value) {
// setter 中要引起依赖数据的变化
const arr = value.split('-')
this.firstName = arr[0]
this.lastName = arr[1]
}
}
}
// 简写形式
// 只有明确计算属性不需要被修改时,才能用简写形式,即没有 setter
computed: {
fullName() {
return this.firstName + this.lastName
}
}
watch
侦听器允许开发者监视数据的变化,针对数据的变化做特定的操作。
侦听器可以监听普通属性和计算属性
// watch 简写形式
export default {
data() {
return {
username: '',
}
},
watch: {
username(newVal, oldVal) {
console.log('新值: ', newVal)
console.log('旧值: ', oldVal)
},
},
}
默认情况下,组件在初次加载完毕后不会调用 watch
侦听器。如果想让 watch
侦听器立即被调用,
需要在监听的属性里创建一个对象,并在里面设置 handler
处理函数,和 immediate
选项
使用 immediate
选项:
watch: {
// 对象形式的侦听器
username: {
// handler 属性是固定写法
handler(newVal, oldVal) {
...
},
immediate: true
}
}
当 watch
侦听的是一个对象,如果对象中的属性值发生了变化,则无法被监听到。此时需要使用 deep
选项进行深度监听:
export default {
data() {
return {
info: { username: 'admin' },
}
},
watch: {
info: {
handler(newVal) {
console.log(newVal)
},
deep: true,
},
},
}
若只想监听对象里单个属性的变化,代码如下:
export default {
data() {
return {
info: { username: 'admin' },
}
},
watch: {
// 记得加引号
'info.username': {
handler(newVal) {
console.log(newVal)
},
},
},
}
通过 Vue 实例的 $watch
监听:
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
样式,可动态操作元素样式。
<div class="basic" :class="mood" @click="changeMood">字符串写法div>
<div class="basic" :class="arr">数组写法div>
<div class="basic" :class="classObj">对象写法div>
export default {
data() {
return {
mood: 'happy',
arr: ['happy', 'sad'],
classObj: {
happy: true, // 为true 的就使用
sad: false, // 为flase 不使用
},
}
},
methods: {
changeMood() {
this.mood = 'sad'
},
},
}
css
属性名既可以用驼峰形式,也能用短横线形式(需要使用引号括起来)。
<div :style="{color: active, fontSize: fsize + 'px', 'background-color': bgcolor}">对象写法div>
<div :style="styleObj">对象写法div>
<div :style="[styleObj, styleObj2]">数组写法(用得少)div>
data() {
return {
active: 'red',
fsize: 30,
bgcolor: 'yellow',
styleObj: {
color: 'active',
'font-size': '20px'
},
styleObj2: {
backgroundColor: 'yellow'
}
}
}
vue 生命周期是指一个组件从创建、运行、销毁的整个过程。每个阶段对应着不同的生命周期钩子。
生命周期钩子也可理解为:Vue 在特定的时刻调用特定的函数。
除了图中 8 个钩子,还有 nextTick
,activated
,deactivated
关于销毁过程:
beforeDestroy
操作数据,因为即便操作数据,也不会再触发更新流程