文章目录
重要原则
- 原理:普通函数内出现的this指向的是它的调用者,箭头函数中出现的this指向的是外部作用域的this指向
- Vue管理的函数由Vue调用
-
所有被Vue管理的函数(不考虑嵌套),最好写出普通函数,这样this执行Vue实例
-
所有不被Vue管理的函数(不考虑嵌套),最好写出箭头函数,这样this才指向Vue实例
定时器的回调函数,ajax的回调函数
1. 插值语法
- 语法:((xxx})
- 作用:让Vue先计算出xxx的值,再用这个值替换((xxx})
- xxx是
js
表达式,这个表达式中出现的属性或方法必须是JS原生的或者Vue实例身上的 - 一个完整实例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="root">
<h3>{{url}}</h3>
<span>{{person.name}}</span>
<!--hint:
只能直接使用data对象的下一级属性,
就是你可以直接写person,但是不能直接写name,
只能借助person来访问name
-->
</div>
</body>
<script>
new Vue({
el: '#app',
data: {
url:"http://www.atguigu.com",
person:{
name:"liu"
}
}
})
</script>
</html>
1. 单向绑定:v-bind指令
-
语法:
v-bind:attribute
=“xxx” ,简写为:attribute
=“xxx”(attribute必须是标签的属性) -
作用:让
Vue
先计算出xxx的值,再把这个值赋给attribute -
单向绑定:即不能通过在页面修改attribute的值来反向修改value的值,但反之可以
-
xxx是
js
表达式,这个表达式中出现的属性或方法必须是JS原生的或者Vue实例身上的 -
完整的案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<a v-bind:href="url">点我去尚硅谷</a>
</div>
</body>
<script>
const vm = new Vue({
el: '#app',
data: {
url:"http://www.atguigu.com",
}
})
</script>
</html>
样式绑定
- 样式与Vue实例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<style>
.one{
width:100px
}
.two{
width:100px
}
.three{
width:100px
}
</style>
<script>
const vm = new Vue({
el: '#app',
data: {
styleOne:"one"
styleArr:['one','two','three'],
classStyle:{
one:false,
//不应用样式one
two:true
//应用样式two
}
styleObj:{
width:40px;
height:40px;
fontSize:16px
//如果属性是css原生的(用的是短横线命名法),必须改成大驼峰命名
//如:font-size 改成fontSize
}
}
})
</script>
</html>
-
class样式的三种写法
- 字符串式写法:适用样式的类名不确定
<span :class="styleOne">1</span>
- 数组式写法:适用样式的类名和个数不确定
class=“one two three”,即span标签使用这三个样式
<span :class="styleArr">2</span>
- 对象式写法:适用样式的类名和个数确定,但是要动态决定用哪些
<span :class="classStyle">3</span>
-
内联样式的写法
style=“ width:40px;height:40px;font-size=16px”
<span :sytle="styleObj"></span>
2. 双向绑定:v-model指令
- 语法:v-model:value =“xxx”,可以简写为v-model`=“xxx”,因为v-model默认绑定到value属性上
- 作用:让
Vue
先计算出xxx的值,再把这个值赋给value - 双向绑定:能通过在页面修改value的值来反向修改value的值
- note:该指令只能用于表单元素
- xxx是
js
表达式,这个表达式中出现的属性必须是data对象中的,出现的方法必须是JS内置的方法或者Vue实例身上的
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<input type="text " v-model="input">
</div>
</body>
<script>
const vm = new Vue({
el: '#app',
data: {
input:"",
person:{
name:"shen",
age:20
}
}
})
</script>
</html>
收集表单数据
- <input type="text"/>
: v-model收集的是value值,用户输入的就是value
<input type="text" v-model="sex"/>
- :v-model收集的是value值,但是要给标签配置value属性
value 属性对于 是必需的
<input type="radio" name="sex" v-model="sex" value="male"/>
<input type="radio" name="sex" v-model="sex" value="female"/>
-
<input type="checkbox"/>
:- 没有给标签配置value属性,那么收集的就是checked(勾选or 未勾选,是布尔值)
happy初始值为真值,对应的input标签默认被选中
<input type="checkbox" v-model="happy"/> <input type="checkbox" v-model="happy"/> <input type="checkbox" v-model="happy"/> <!--happy:"123"-->
-
给标签配置了value属性:
- v-model的初始值是非数组,那么收集的就是checked(勾选 or未勾选,是布尔值
happy初始值为真值,对应的input标签默认被选中
<input type="checkbox" v-model="happy" value='study'/> <input type="checkbox" v-model="happy" value='study'/> <input type="checkbox" v-model="happy" value='study'/> <!--happy:"123"-->
- v-model的初始值是数组,那么收集的的就是value组成的数组
<input type="checkbox" v-model="happy" value='s'/> <input type="checkbox" v-model="happy" value='t'/> <input type="checkbox" v-model="happy" value='u'/> <!--happy:[]-->
v-model修饰符
- lazy:失去焦点再收集数据
- number:输入字符串转为有效的数字
- trim:输入首尾空格过滤
v-model.lazy= "sex"
3. 事件绑定:v-on指令
- 语法:
v-on:xxx="methodName"
,简写为@xxx="methodName"
- 作用:当触发事件xxx时,执行methodName对应的方法
- methodName:用参数event调用其对应的方法。event是触发的事件对象
- methodName():不传参调用其对应的方法
- methodName(num):用参数num调用对应的方法
- methodName($event,num):用参数event,num调用对应的方法
无论用不用参数调用,对应的方法都可以不写参数
- note:methods对象中的普通函数中出现的this是指向Vue实例,因为普通函数的this指向的是它的调用者,即Vue实例。箭头函数中出现的this指向的是外部函数的this指向,即window对象
这里的结论不一定适应于嵌套方法
- 完整案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<button @click="printOne">1</button>
<button @click="printTwo">2</button>
<button @click="printThere">3</button>
<button @click="printFour(1)">4</button>
</div>
</body>
<script>
new Vue({
el: '#app',
methods: {
printOne(event) {
console.log(event.target.innerText)
//拿到触发事件的标签的内容
},
printTwo() {
console.log(this)
},
printThere: () => {
console.log(this)
},
printFour(num) {
console.log(num)
console.log(num.target.innerText)
}
}
})
</script>
</html>
Vue中的事件修饰符
- prevent:阻止默认事件
- a标签的默认事件是转跳
- form标签的默认事件会重载页面
<!--假设调用printOne函数会弹窗-->
<a href="" @click="printOne">点我转跳</a>
<!--
当你点击时,会先弹窗,然后转跳
-->
<a href="" @click.prevent="printOne">点我转跳</a>
<!--
当你点击时,只会弹窗,弹窗之后不会转跳
-->
- stop:阻止事件冒泡
- 事件冒泡:当一个元素接收到事件时,会把它接收到的事件逐级向上传播给它的祖先元素,一直传到顶层的 window 对象
<!--假设调用printOne函数会弹窗-->
<div @click="printOne">
<button @click="printOne">1</button>
</div>
<!--
当你点击按钮时,先执行按钮身上的点击事件,然后事件会冒泡到div上,触发div身上的点击事件
-->
<div @click="printOne">
<button @click.stop="printOne">1</button>
</div>
<!--
当你点击按钮时,先执行按钮身上的点击事件,但是Vue会阻止事件冒泡到div身上,因此不会在触发div身上的点击事件
-->
- once:事件只触发一次
- capture:使用事件捕获模式,(事件默认是冒泡的)
- 事件捕获:就是最顶层的 window 对象最早接收事件,最低层的具体被操作的元素最后接收事件
<!--假设调用print函数会打印传入的参数-->
<div @click.capture="print(1)">
<button @click="print(2)">1</button>
</div>
<!--结果:打印1-->
- self:只有event.target是当前的元素时才触发事件
<!--假设调用printOne函数会弹窗-->
<div @click.self="printOne">
<button @click="printOne">1</button>
</div>
<!--
当你点击按钮时,先执行按钮身上的点击事件,然后事件会冒泡到div上,Vue会判断event.target是不是这个div,发现不是不触发div身上的点击事件
-->
- passive:事件的默认行为立即执行,无需等待事件回调执行完毕
移动端和平板端用的多
#####按键事件的修饰符
-
按键事件
-
keydown:按下键盘键触发事件
-
keyup:释放键盘键触发事件
-
keypress:按下并抬起同一个按键触发事件
-
-
Vue提供的常用按键的别名
- 回车键:enter
- 删除键和退格键:delete
- 退出键:esc
- 空格键:space
- 换行键:tab(必须搭配keydown事件去使用)
因为tab可以把光标从当前元素身下切走,keyup.tab:当你按下tab键时就已经失去了对元素获取焦点,所有无法触发对应的函数
- 上:up
- 下:down
- 左:left
- 右:right
<input type="text" @keyup.enter="print">
<!--
当释放按下的回车键时调用print函数
-->
<input type="text" @keyup.enter="print">
<!--
当释放按下的回车键时调用print函数
-->
- Vue未提供别名的按键,可以使用按键原始的名字去绑定,但要注意转为短横线命令(单词首字母小写,单词之间用短横线连接)
按键对应的keyCode被web标准弃用了
<input type="text" @keyup.caps—lock="print">
<input type="text" @keyup.13="print">
- 自定义键名
- 通过Vue.config.keyCodes.键名 = 键码来定义按键别名
<script>
Vue.config.keyCodes.huiche = 13
new Vue({
el: '#app',
})
</script>
-
特殊的系统修饰键:ctrl,alt,shift,win
-
配合keyup键使用时:按下修饰键的同时,再按下其他键,随后释放其他键,事件才触发
-
配合keydown键使用时:正常触发
3.条件渲染
- xxx:表示这是个JS表达式,这个表达式中出现的属性或方法必须是JS原生的或者Vue实例身上的
- 作用:控制节点是否展示在页面
1. v-show指令
- 语法:v-show=“xxx”,xxx为false即不展示该元素,反之展示
- 用法:适用于切换频率较高的场景,
v-if
有更高的切换开销 - note:不管初始条件是什么,元素总是会被渲染
- 该指令 不支持
<template>
元素
<span v-show="false">
2. v-if 指令
- 语法:v-if = “xxx”, v-else-if = “xxx”,v-else = “xxx”
- xxx === false:对应的DOM元素直接被移除,反之不移除
- 用法:适用于切换频率较低的场景,
v-show
有更高的初始渲染开销 - note:
v-if
是惰性的,它直到条件第一次变为真时,才会开始渲染条件块
最终的渲染结果将不包含 元素
<!--可以切换template标签下的所有标签-->
<template v-if="ok">
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</template>
- 搭配1
<div v-if="type === 'A'"> A</div>
<div v-else-if="type === 'B'"> B</div>
<div v-else="type === 'C'"> C</div>
- 搭配2
<div v-if="type === 'A'"> A</div>
<div v-else-if="type === 'B'"> B</div>
- 搭配3
<div v-if="type === 'A'"> A</div>
<div v-else="type === 'C'"> C</div>
4. 列表渲染:v-for 指令
- 语法:v-for = “xxx in yyy”
yyy:是可迭代的,按照从前往后的顺序迭代,每次迭代都从yyy拿到一个值给xxx。迭代几次就有几个列表标签
- 作用:基于可迭代的来渲染列表
1. yyy是数组items
- 写法1:item in yyy
- 写法2:(item,index) in yyy
yyy[index] === item,第一个参数item表示它是yyy的元素,第二个参数index表示它在yyy的索引值
<ul id="rendering">
<li v-for="item in items">
{{item.message }}
</li>
<li v-for="(item,index) in items">
{{index}} - {{item.message }}
</li>
</ul>
new Vue({
data() {
return {
items: [{ message: 'Foo' }, { message: 'Bar' }]
}
}
}).mount('#rendering')
2. yyy 是对象obj
- 写法1:value in obj
- 写法2:(value,name) in obj
按
Object.keys()
的结果遍历,遍历一次得到一个obj的属性。第一个参数name代表这个属性的名字,第二个参数value代表这个属性的值
<ul id="rendering">
<li v-for="value in myObject">
{{value }}
</li>
<li v-for="(value,name) in myObject">
{{name}} - {{value}}
</li>
</ul>
new Vue({
data() {
return {
myObject: {
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
}
}
}
}).mount('#rendering')
3.yyy 是字符串str
- 写法1:value in str
- 写法2:(value,index) in str
按顺序遍历,str[index] === value,第一个参数value表示它是str的字符,第二个参数index代表value在yyy的索引值
<ul id="rendering">
<li v-for="value in str">
{{value }}
</li>
<li v-for="(value,index) in str">
{{index}} - {{value}}
</li>
</ul>
new Vue({
data() {
return {
str:"hello"
}
}
}).mount('#rendering')
4. yyy 是整数
- 写法1:n in yyy
- 写法2:(n,idnex) in yyy
index:是索引,从0开始的,但是n的是从
1
开始的
<div id="range" class="demo">
<span v-for="(n,index) in 10">{{n}} - {{index}} </span>
</div>
嵌套
- 对于多层嵌套的
v-for
,作用域的工作方式和函数的作用域很类似。每个v-for
作用域都可以访问到父级作用域:
<li v-for="item in items">
<span v-for="childItem in item.children">
{{ item.message }} {{ childItem }}
</span>
</li>
状态管理—key
- 对于使用v-for指令渲染的列表,Vue会通过key来管理这些列表
- 对于每个列表元素,key都要是唯一的(key不存在于真实DOM上)
跟虚拟DOM,真实DOM,diff算法有关,下面简单讲解带来的问题,后面再详细讨论
- 默认模式:key默认用index绑定
<li v-for="item in items"></li>
等价于后者
<li v-for="item in items" :key="index"></li>
- 自定义模式
<li v-for=" item in items" :key="item.id"></li>
遍历列表时使用index作为key带来的问题
-
Vue动态渲染页面的步骤 (个人的理解)
- Vue首先将数据渲染到模板上面(初始化)
- 生成虚拟DOM (用于复用)
- 将虚拟DOM转为真实DOM
- 当Vue发现数据变化时,会根据新数据生成新的虚拟DOM
- 将新虚拟DOM与旧虚拟DOM按照一定规则进行比较,找出它们之间的区别,并应用这其中的变化到真实的 DOM 上(这就是diff算法)
-
diff算法怎么比较新虚拟DOM和旧虚拟DOM?
- key是虚拟DOM的唯一标识
- diff算法只会在同层级按照一定规则进行比较
- 由于生成的虚拟DOM会构成一个DOM树,diff算法会将这新旧两颗DOM树从第一层开始进行同层比较
- 如果新虚拟DOM的key与旧虚拟DOM的key值相同
- 如果新虚拟DOM的内容与旧虚拟DOM的内容相同,直接使用旧虚拟DOM对应的真实DOM
- 如果不同,则生成新的真实DOM,随后替换掉页面中旧虚拟对应的真实DOM
- 如果新虚拟DOM的key与旧虚拟DOM的key值不同
- 创建新的真实DOM,随后渲染到页面
这里简单讨论了Vue渲染页面的过程以及diff算法比较新旧DOM树的一些细节,可以发现key是在diff算法对比新旧DOM树时发挥作用,但是在某些情况下会带来一些问题
- 旧虚拟DOM
<li key="0">刘<input type = "text"></li>
<li key="1">陈<input type = "text"></li>
<li key="2">成<input type = "text"></li>
<li key="3">程<input type = "text"></li>
- 生成的真实DOM
- 输入数据,但虚拟DOM没变
- 逆序插入数据生成新的虚拟DOM
<li key="0">熊<input type = "text"></li>
<li key="1">刘<input type = "text"></li>
<li key="2">陈<input type = "text"></li>
<li key="3">成<input type = "text"></li>
<li key="4">程<input type = "text"></li>
-
因修改数据而生成真实DOM
-
可以发现此时生成的真实DOM出问题了,这是由于diff算法的对比规则导致的
这里因为虚拟DOM都是同层的,diff算法按顺序比较,首先比较新虚拟DOM和旧虚拟DOM的的第一个节点,发现这两个DOM的key相同,然后开始比较这个节点的内部节点,发现内容节点不同,但是节点input是相同的,于是拿内容节点生成真实的DOM会替换旧虚拟DOM的内容节点对应的真实DOM,新的真实DOM展示在页面是这样的
依次比较,直到新虚拟DOM的第五个节点,此时算法发现这个节点不存在于旧虚拟DOM中,于是将第五个节点转成真实DOM,展示在页面是这样的
总结
- 会破坏原来顺序的就不要用index作为key的值
5.v-text指令和v-html指令
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"> </script>
</head>
<body>
<div id="root">
<a v-text="name">name</a><br/>
<a v-html="name">name</a><br/>
</div>
</body>
<script>
new Vue({
el:'#root',
data:{
name:'<h1>hello</h1>',
}
})
</script>
</html>
- v-text指令:用该指令的值替换所在标签的内容
- v-html指令:用该指令的值替换所在标签的内容,如果该指令的值包含html语法,这部分就按html语法解析
容易导致xss攻击,永远不要在用户提交的内容上使用v-html指令
- 运行结果如图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C1Hoss31-1652154741617)(images/图一.png)]
6.v-once指令和v-pre指令
-
v-once指令的作用:其所在节点在初次动态绚染后就不可更改,用于优化性能
-
v-pre指令的作用:用来跳过没有使用指令语法,插值语法的节点的编译过程,加快编译
-
案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="root">
<h2>n:{{n}}</h2>
<h2 v-once>n:{{n}}</h2>
<button @click="n++">n++</button>
<h2 v-pre>{{name}}</h2>
<h2 v-pre>hello</h2>
</div>
</body>
<script>
new Vue({
el:'#root',
data:{
name:'<h1>hello</h1>',
n:0
}
})
</script>
</html>
7.v-cloak指令(没有值)
- 使用css + v-cloak可以解决网速慢时页面展示出{{xxx}}的问题
本质是一个特殊属性:Vue实例创建对象并接管容器后,会删除v-cloak属性
- 案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
[v-cloak]{
display:none,
}
</style>
</head>
<body>
<div id="root">
<a v-cloak>{{name}}</a>
</div>
</body>
<script>
new Vue({
el:'#root',
data:{
name:'<h1>hello</h1>'
}
})
</script>
</html>
8.自定义指令
局部自定义指令
-
函数式
-
函数接收的可选参数(内部出现的this执行windows)
- element:使用该指令的元素
- bind:绑定的一些相关信息
- vNode:当前的虚拟dom节点
- oldvNode:上一个虚拟dom
-
函数(如print函数)什么时候调用:
- 指令与元素成功绑定时 (此时页面还没加载出来)
绑定之后,解析模板并加载到页面
- 指令所在的模板重新解析时(此时页面已加载出来)
const vm = new Vue({
name:"home",
data:{
n:1
},
directives: {
//定义v-foucus指令
focus(element,bind,vNode,oldvNode){
console.log(element,bind,vNode,oldvNode)
},
//定义v-printMessage指令
//多个单词的写法
`print-message`(){
console.log("hello")
}
}
})
vm.$mount("#demo")
- 示例
<div id="demo">
<span v-print-message>hello</span>
<span v-focus>hello</span>
<span v-focus="n">hello</span>
</div>
- 对象式
- 指令中可写的特殊函数(内部出现的this执行windows)
- bing函数:当指令与元素成功绑定时执行
- inserted函数:指令所在的元素被插入页面时执行
- update函数:指令所在的模板被重新解析时调用
- 这些函数接收的可选参数
- element:使用该指令的元素
- bind:绑定的一些相关信息
- vNode:当前的虚拟dom节点
- oldvNode:上一个虚拟dom
const vm = new Vue({
name:"home",
data:{
n:1
},
directives: {
focus: {
bind(element,bind){
console.log('bind')
},
inserted(element,bind){
console.log('inserted')
},
update(){
console.log('update')
}
}
}
})
vm.$mount("#demo")
全局自定义指令
-
函数接收的参数,调用时机与定义局部指令一致
-
函数式
Vue.directives
(指令名,回调函数)
Vue.directives(“find”,function(element,bind){
})
- 对象式
Vue.directives
(指令名,配置对象)
Vue.directives(“find”,{
bind(element,bind){
console.log('bind')
},
inserted(element,bind){
console.log('inserted')
},
update(){
console.log('update')
}
})