- vue是什么
是一套用于构建用户界面(将数据变成界面)的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
- 特点
1,采用组件化模式(HTML,CSS JS),提高代码复用率让代码更好维护
2,声明式编码(命令式编码,一步一个指令,一步也少不了),让编码人员无需直接操作DOM,提高开发效率
3,使用虚拟DOM+优秀的Diff算法,尽量复用DOM节点 - 写一个例子
<!-- 准备一个容器 -->
<!-- {{}}只是分隔符 -->
<div id="root">
<h1>插值语法</h1>
<h3>你好,{{name}}</h3>
<h1>指令语法</h1>
<a v-bind:href="school.url.toUpperCase()" x="hello">点我去{{school.name}}</a> //v-bind可以简写成:,里面的内容也要js表达式
</div>
<script>
Vue.config.productionTip=false //阻止启动时产生生产提示
//创建Vue实例
new Vue({
el:'#root' , //指定当前Vue实例为root容器服务,这个值通常使用css选择器字符串
data:{ //存储数据数据供el指定的容器使用
name:'Vue',
school:{
name:'baidu',
url:'http://www.baidu.com'
}
}
})
</script>
el和data的两种写法:
1,el:(1)在new Vue时候配置;(2)先创建vue实例,给实例起名字,再通过实例.$mount(‘#root’)指定el的值
2,data:(1)对象式{};(2),函数式data(){return } 或者 data:function(){ return }
,但是不要写箭头函数,一旦写了箭头函数,this就不再是Vue的实例。组件必须要用函数式
vue模板语法有2大类:
1,插值语法:功能是用于解析标签体内容,写法{{}},可以直接读取到data中所有属性
2,指令语法:功能是用于解析标签,其中包括标签属性,标签体内容,绑定事件;
tips:
1,容器和实例之间是一对一
2,真实开发中只有一个vue实例,并且只会配合着组件一起使用
3,{{}}中要写JS表达式(JS表达式:一个表达式只会产生一个值,可以放在任何一个需要值的地方,比如a,a+b;JS代码语句:if(){};for(){};),JS表达式可以自动读取到data中的所有属性。
4,data发生改变,页面中用到该数据的地方就会自动更新
- 数据绑定
vue有两种数据绑定的方式
1,单向绑定,数据只能从data流向页面,v-bind简写为:
2,双向绑定:页面和data互流,但是双向绑定只能应用在表单类元素上(input,select),v-model:value可以简写为v-model,因为本身就是收集value值的
<div id="root">
单向数据绑定:<input type="text" :value="name"><br>
双向数据绑定:<input type="text" v-model="name">
</div>
<script>
new Vue({
el:'#root',
data:{
name:'csdn'
}
})
</script>
- MVVM模型
Vue虽然没有完全遵循 MVVM 模型,但是 Vue 的设计也受到了它的启发。因此在文档中经常会使用 vm (ViewModel 的缩写) 这个变量名表示 Vue 实例。
M:model,对应data中的数据
V:View,模板(DOM 页面)
VM:ViewModel,Vue实例对象(包括DOM Listeners,Data Bindings)
tips:data中所有属性最后都出现在vm身上;vm身上所有属性以及vue原型上所有属性,在vue模板中可以直接使用
- 数据代理
数据代理:通过一个对象代理对另一个对象中属性的操作(读,写。。)
//obj2读/写obj1中的x
let obj1={x:100}
let obj2={y:300}
Object.defineProperty(obj2,'x'{
get(){
return obj1.x
},
set(value){
obj1.x=value
}
}
vue中的数据代理是通过vm对象来处理data对象中的属性的操作
好处:更加方便的操作data中的数据
基本原理:通过Object.defineProperty()把data对象中所有属性添加到vm上;为每一个添加到vm上的属性都指定一个getter/setter(set函数);在getter/setter内部操作(读写)data中对应的属性
<div id="root">
<h2>地址名称:{{name}}</h2>
</div>
var vm=new Vue({
el:'#root',
data:{
name:'csdn' //使用Object.defineProperty()使{{name}}和data里的name发生数据代理
}
}
- 事件的处理
事件的基本使用:
1,使用v-on:xxx或@xxx绑定事件,其中xxx是事件名
2,事件的回调需要配置在method对象中,最终会在vm上
3,method配置的函数不要用箭头函数,否则this会是window;配置的函数都是被vue管理的函数,this指向vm或组件实例对象
4,@click="demo"和@click=“demo($event)"效果一致,但后者可传参
<div id="root">
<button @click="show1">点我1</button>
<button @click="show2($event,32)">点我2</button>
</div>
<script>
var vm=new Vue({
el:'#root',
data:{
name:'csdn'
},
methods:{
show1(evnet){
//console.log(event.target.innerText)
alert(1232245)
},
show2(event,number){
console.log(event,number)
alert("你好")
}
}
})
</script>
- 事件修饰符
<style>
* {
margin-top: 20px;
}
.demo1{
height: 55px;
background-color: aquamarine;
}
.box1{
padding: 5px;
background-color: blue;
}
.box2{
padding: 5px;
background-color: rgb(108, 108, 220);
}
.list{
width: 200px;
height: 20px;
background-color: pink;
overflow: auto;
}
li{
height: 100px;
}
</style>
</head>
<body>
<div id="root">
<h2>welcome {{name}}</h2>
<!-- 阻止默认事件发生,即a在点击之后不会跳转 -->
<a href="https://editor.csdn.net" @click.prevent="show">jump</a>
<!-- 阻止冒泡 在method中写就是event.stopPropagation -->
<div class="demo1" @click="show">
<button @click.stop="show">jump</button>
</div>
<!-- 事件触发一次 -->
<button @click.once="show">jump</button>
<!-- 使用事件的捕获模式 -->
<div class="box1" @click.capture="showmsg">
div1
<div class="box2" @click="showmsg">
div2
</div>
</div>
<!-- 只有event.target是当前操作的元素时触发事件,某种程度上是阻止冒泡 -->
<div class="demo1" @click.self="show">
<button @click="show">jump</button>
</div>
<!-- 事件默认行为立刻执行,无需等待事件回调执行完 -->
<!-- 鼠标滚轮滚动是wheel 滚动条滚动是scroll -->
<ul @whell.passive="demo" class="list">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
</div>
<script>
new Vue({
el:'#root',
data:{
name:CSDN
},
methods:{
show(event){
// event.preventDefault() //取消默认行为
// alert("你好")
console.log(event.target)
},
showmsg(msg){
console.log(msg)
},
demo(){
for(let i=0;i<1000;i++){
console.log(1234)
}
console.log("tired")
}
}
})
</script>
</body>
- 键盘事件
Vue按键的别名:
回车----enter;删除----delete(捕获删除或退格键);退出----esc;空格----space;上----up;下----down;左----left;右----right;换行—tab(要keydown)
(Vue没有提供的别名的按键,可以使用按键原始的key值去绑定,但要注意转为kebab-case(两个词中间用短横杠)
系统修饰符(ctrl,shift,alt,meta):要配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键事件才被触发@keyup.ctrl.y
;配合keydown使用:正常触发事件。
(可以用keycode去指定具体键(不推荐)Vue.config.keyCodes.自定义键名=键码;可以去定制按键别名
)
<div id="root">
<h2>{{name}}</h2>
<input type="text" placeholder="按下回车输入" @keyup.enter="show">
</div>
<script>
Vue.config.keyCodes.huiche=13//定义了键为回车
new Vue({
el:'#root',
data:{
name:'csdn'
},
method:{
show(e){
console.log(e.key)//按键名
}
}
})
</script>
- 计算属性
定义:要用的属性不存在,要通过已有的属性计算得到
原理:底层借助了Object.defineproperty方法提供的getter和setter
getter执行:初次读取会执行一次;当依赖的数据发生改变会被再次调用
优点:和method相比内部有缓存机制(复用),效率更高,调试方便
tips:计算属性最终会出现在vm上,直接读取使用即可;如果计算属性被修改,要写setter去响应修改,且set中要引起计算时依赖的数据发生变化
<body>
<!-- <div id="root">
姓:<input type="text" v-model="name1"><br>
名:<input type="text" v-model="name2"> -->
<!-- 可以用{{name1.slice(0,3)}}-{{name2}}但是不推荐 -->
<!-- 全名:<span>{{fullName()}}</span>
</div> -->
<!-- <script>
Vue.config.productionTip=false
new Vue({
el:'#root',
data:{
name1:'cs',
name2:'DN'
},
methods:{
fullName(){
return this.name1+'-'+this.nam2
}
}
})
</script> -->
<!-- 简单有用的 -->
<div id="root">
姓:<input type="text" v-model="name1"><br>
名:<input type="text" v-model="name2">
测试:<input type="text" v-model="x"><br>
全名:<span>{{全名}}</span><br>
</div>
<script>
Vue.config.productionTip=false
let a=1
var vm=new Vue({
el:'#root',
data:{
name1:'cs',
name2:'DN'
},
methods:{
demo(){
}
},
computed:{
fullName:{
get(){
return this.name1+'-'+name2//this是vm
},
set(value){
const arr=value.split('-')
this.name1=arr[0]
this.name2=arr[1]
}
/*
当只考虑读取不考虑修改时将上面fullname简写,
直接写成函数形式来代替getter,但在引用的时候不能加()
fullName(){
console.log("getter")
return this.name1+'-'+this.name2
}
*/
}
}
})
</script>
- 监视属性
当监视属性变化时,回调函数自动调用,进行相关操作;监视属性必须存在(比如isHot不能随便被不存在的abc所代替),才能进行监视。(但不会报错,如果不存在);监视有两种写法,见下面代码
<div id="root">
<h2>今天天气很{{info}}</h2>
<button @click="change">切换</button>
<!-- 绑定事件时@xxx=‘yyy’,yyy可以写一些简单的语句 -->
<!-- <button @click="isHot=!isHot">切换</button> -->
</div>
<script>
Vue.config.productionTip = false
var vm = new Vue({
el: '#root',
data: {
isHot: true
},
computed: { //使用计算属性
info() {
return this.isHot ? 'hot' : 'cold' //isHot是true就‘hot’,不是就‘cold’
}
},
methods: { //事件处理
change() {
this.isHot = !this.isHot //取反
}
},
watch: { //监视属性,监视改动,不仅可以监视data中的值,也可以监视info
isHot: {
immediate: true,//初始化时使handler调用一下
//handler在isHot发生改变时被调用
handler(newValue, oldValue) {
console.log("isHot 被修改了", newValue, oldValue)
}
}
// //isHot简写条件,只有handler
// isHot(newValue, oldValue){
// console.log("isHot 被修改了", newValue, oldValue)
// }
}
})
// //另一种监视方法
// vm.$watch('isHot', {
// handler(newValue, oldValue) {
// console.log("isHot 被修改了", newValue, oldValue)
// }
// })
//简写,不能写成箭头函数
// vm.$watch('isHot',function(newValue, oldValue){
// console.log("isHot 被修改了", newValue, oldValue)
// })
</script>
深度监视
1,Vue中的监视属性watch默认不可以监测对象内部值的改变(一层);
2,配置deep:true可以监测到对象内部的值的改变(多层)
注意:Vue自身可以监测到对象内部值的改变,但是Vue提供的watch默认不可以;使用watch时根据数据的具体结构,决定是否采用深度监视
<div id="root">
<h2>今天天气很{{info}}</h2>
<button @click="change">切换</button>
<!-- 绑定事件时@xxx=‘yyy’,yyy可以写一些简单的语句 -->
<!-- <button @click="isHot=!isHot">切换</button> -->
<br>
<h3>a的值是{{numbers.a}}</h3>
<button @click="numbers.a++">a++</button>
<h3>b的值是{{numbers.b}}</h3>
<button @click="numbers.b++">b++</button>
</div>
<script>
Vue.config.productionTip = false
var vm = new Vue({
el: '#root',
data: {
isHot: true,
numbers:{
a:1,
b:1
}
},
computed: { //使用计算属性
info() {
return this.isHot ? 'hot' : 'cold' //isHot是true就‘hot’,不是就‘cold’
}
},
methods: { //事件处理
change() {
this.isHot = !this.isHot //取反
}
},
watch: { //监视属性,监视改动,不仅可以监视data中的值,也可以监视info
isHot: { //原来是‘isHot’
// immediate: true,//初始化时使handler调用一下
//handler在isHot发生改变时被调用
// 'numbers.a':{ //监视某个属性
// handler() {
// console.log("a被修改了")
// }
// },
//监视多级结构中所有属性的变化
},
numbers:{ //监视number
deep:true,//改为true会使监视注意到number中的a,b
handler() {
console.log("numbers被修改了")
}
},
},
})
// //另一种监视方法
// vm.$watch('isHot', {
// handler(newValue, oldValue) {
// console.log("isHot 被修改了", newValue, oldValue)
// }
// })
</script>
计算属性和监视属性的区别: 1,计算属性能完成的功能,监视属性都能完成 2,监视属性能完成的功能,计算属性不一定能完成,例如,watch可以异步操作 (所有被Vue管理的函数最好都写成普通函数,this指向才会是vm或组件实例操作; 因此所有不被Vue管理的函数,比如定时器的回调函数,ajax的回调函数等,都写成箭头函数,this才会指向vm或组件实例对象
- 绑定样式
绑定class
方法1:字符串写法,适用于样式类名不确定,需要动态指定(举例点击切换三个不同颜色的盒子:在div里写上:class='color',@click='change'
在vue的data里面写上color:'redclass'
,在methods里面写上相应的点击函数
方法2:数组写法,使用要绑定的样式个数不确定,名字也不确定,可以随时在页面vue扩展工具上修改增加样式(举例一个盒子上有好几种不同的样式叠加,可以根据需要随时更换盒子上的样式:<div class='basic' :class='arr'>
在data中添加arr:['a','b','c']
的样式数组
方法3:对象写法,适用于要绑定的样式个数确定,名字也确定,但是要动态的决定要不要用其中的样式(举例方法2的例子:<div class='basic' :class='obj'>
data中obj:{a:false,b:false}
style样式绑定
和class样式绑定差不多,只不过class换成style,但是style里面要改变的东西必须是存在的
总结
1,class样式
写法: :class=‘xxx’,xxx可以是对象数组字符串 xxx的详细信息请看上方《绑定class》这一小节
2,style样式
写法: :style:“{fontsize:xxx}”其中xxx是动态值。 :style=“[a,b]"其中a,b是样式对象
- 渲染
条件渲染
1,v-if
写法:v-if=‘表达式’;v-else-if=‘’;v-else=‘’
适用于切换频率比较低的场景,因为它将不展示的DOM元素直接移除(注意:这三个不能断,要和相对应的div或其他的一起使用
2,v-show
v-show=‘表达式’ 适用于切换频率较高的场景,它不会将不使用的DOM元素删除,而是隐藏起来
举例:将一直点击按钮,点三下,某个div隐藏:<div v-show='n==3'>
(使用v-if元素可能无法获取,如果要将li使用v-if实现,但代码冗余,就使用template 将li列表包裹,这样也不会破坏原有结构)
列表渲染
v-for指令
用于展示列表数据,语法是v-for=“(item,index) in xxx” :key=“yyy”;可遍历的对象包括数组,对象,字符串,指定次数,后两个不常用
<!-- 示例代码数组 -->
<div id="root">
<h2>persons</h2>
<ul>
<li v-for="(p,index) in persons" :key="index">
{{p.name}}-{{p.age}}
</li>
</ul></div>
<script>
new Vue({
el:'#root',
data:{
persons:[
{id:'001',name:'张三',age:21}, //使用数组,是因为顺序不可调换,添加id
{id:'002',name:'lucy',age:34}, //是唯一标识性,:key也是因为唯一标识性
{id:'002',name:'deve',age:23}
]
},
})
</script>
key的作用和原理
1,虚拟DOM中key的作用
key是虚拟DOM对象的标识,当数据发生变化时,vue会根据新数据生成新的虚拟DOM;随后Vue会进行新的虚拟DOM和旧的虚拟DOM的差异比较
2,比较规则
(1),旧虚拟DOM中找到与新虚拟DOM相同的key:若虚拟DOM内容没变,则直接复用;若变了,则生成新的真实DOM,随后替换掉之前的真实DOM
(2),旧虚拟DOM中未找到与新虚拟DOM相同的key,就创建新的真实的DOM,随后渲染到页面上。
3,用index作为key引发的问题
对数据进行逆序增加删除等破坏顺序的操作会产生没有必要的真实DOM更新,效率低下;如果结构中还包含输入类的DOM,会产生错误的DOM更新,界面会出错
4,开发中如何选择key
最好使用每条数据的唯一标识作为key,比如id,手机号,身份证号等唯一值,如果不存在逆序的破坏性操作,仅用于渲染列表用于展示,可以使用index
列表过滤
筛选出符合条件的‘新的’列表
<div id="root">
<h2>persons</h2>
<input type="text" placeholder="请输入名字" v-model="keywords">
<ul>
<li v-for="(p,index) in filPersons" :key="id">
{{p.name}}-{{p.age}}
</li>
</ul></div>
<script>
//用监视属性进行列表过滤
// new Vue({
// el:'#root',
// data:{
// keywords:'',
// persons:[
// {id:'001',name:'马冬梅',age:21},
// {id:'002',name:'周冬雨',age:34},
// {id:'003',name:'周杰伦',age:23},
// {id:'004',name:'温兆伦',age:23}
// ],
// filPersons:[]
// },
// watch:{
// keywords:{
// immediate:true,
// handler(val){
// this.filPersons=this.persons.filter((p)=>{
// return p.name.indexOf(val)!=-1
// })
// }
// }
// }
// }
// )
//计算属性
new Vue({
el:'#root',
data:{
keywords:'',
persons:[
{id:'001',name:'马冬梅',age:21},
{id:'002',name:'周冬雨',age:34},
{id:'003',name:'周杰伦',age:23},
{id:'004',name:'温兆伦',age:23}
]
},
computed:{
filPersons(){
return this.persons.filter((p)=>{
return p.name.indexOf(this.keywords)!=-1
})
}
}
}
)
</script>
列表排序
<div id="root">
<h2>persons</h2>
<input type="text" placeholder="请输入名字" v-model="keywords">
<button @click="sorttype=2">年龄升序</button>
<button @click="sorttype=1">年龄降序</button>
<button @click="sorttype=0">原顺序</button>
<ul>
<li v-for="(p,index) in filPersons" :key="id">
{{p.name}}-{{p.age}}
</li>
</ul></div>
<script>
//计算属性
new Vue({
el:'#root',
data:{
keywords:'',
sorttype:0,//0是原顺序,1是降序,2升序
persons:[
{id:'001',name:'马冬梅',age:21},
{id:'002',name:'周冬雨',age:34},
{id:'003',name:'周杰伦',age:23},
{id:'004',name:'温兆伦',age:29}
]
},
computed:{
filPersons(){
const arr=this.persons.filter((p)=>{
return p.name.indexOf(this.keywords)!=-1
})
//判断是否需要排序
if(this.sorttype){
arr.sort((p1,p2)=>{
return this.sorttype===1?p2.age-p1.age:p1.age-p2.age//p2-p1降序
})
}
return arr
}
}
}
)
</script>
- vm的数据监视
1,vue会监视data中的所有层次的数据
2,如何检测对象中的数据
<script>
//vue如何监测对象
//这个基本的模拟vue的监测属性,与真正vue的相比还差在:
//控制台搜索只能vm._data.name,不能将_data忽略,另外只能监视对象的第一层
let data={
name:'shangguigu',
address:'csdn'
}
//创建一个监视的实例对象,用于监视data中属性的变化
const obs=new Observer(data)
console.log(obs)
//准备一个vm实例对象
let vm={}
vm._data=data=obs //_data数据代理
function Observer(obj){
//汇总对象中所有的属性形成一个数组
const keys=Object.keys(obj)
//遍历
keys.forEach((k)=>{
Object.defineProperty(this,k,{ //使用this不会改变原数据,从而死循环
//使用setter和getter是因为监视必须,使用定时器什么的监视会引起死循环
get(){
return obj[k]
},
set(val){
console.log('${k}被改了,接下来是解析模板,生成虚拟DOM.....')
}
})
})
}
</script>
通过setter实现监视,且要在new Vue时就要传入要监视的数据:对象后追加的属性,vue默认不做响应式处理;如果要响应,使用set()
<div id="root">
<h1>学校信息</h1>
<h2>学校名称{{name}}</h2>
<h2>学校地址{{address}}</h2><hr>
<h1>学生信息</h1>
<button @click="addSex">添加性别属性,默认为男</button>
<h2>姓名{{student.name}}</h2>
<h2 v-if="student.sex">性别{{student.sex}}</h2>
<h2>盆友们</h2>
<ul>
<li v-for="(f,index) in student.friends" :key="index">
{{f.name}}-{{f.age}}
</li>
</ul>
<hr>
</div>
<script>
const vm=new Vue({
el:'#root',
data:{
name:'BILIBILI',
address:'csdn',
student:{
name:'tom',
age:{
rAge:23,
sAge:18
},
friends:[
{name:'jerry',age:32},
{name:'tony',age:42}
]
}
},
methods: {
addSex(){
//用于后添加,使用其他方法可能不会有setter和getter,
//局限:只能添加到data中的某个对象中,不可以直接在vm或data中添加
// Vue.set(this.student,'sex','男')
this.$set(this.student,'sex','女') //
}
},
})
</script>
3,监测数组中的数据
通过包裹数组更新元素的方法实现:调用原生对应方法对数组进行更新;更新解析模板,进而更新页面
4,vue中修改数组中某个元素:使用一下API(push,pop等);Vue.set()或vm.$set() (注意这两个不能给vm或vm的跟数据对象_data添加属性)
<div id="root">
<h2>爱好</h2>
<button @click="click">点击增加爱好</button>
<ul>
<li v-for="(h,index) in hobby" :key="index">
{{h}}
</li>
</ul>
</div>
<script>
const vm=new Vue({
el:'#root',
data:{
hobby:['学习','跑步','游泳']
},
methods: {
click(){
//不可以通过数组的索引值赋值
//在数组中改变要用push,splice,pop等,这里没有用到原型,vue将数组的变更方法进行包裹
//也可以用set,详情看vue官网
vm.hobby.splice(0,1,'打台球')
}
},
})
</script>
tips:在要修改元素时用到setter的过程叫数据劫持