1.简介
构建页面的渐进式JavaScript框架,在数据发生改变的同时,页面也会发送相应的变化,不需要开发者去手动的修改dom
2.入门
导入vue.js文件
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
创建容器和创建vue实例
<!--创建容器 使用模板语法-->
<div id="app">
<h1>
{{message}} <!--插值语法-->
</h1>
</div>
<!--创建vue实例-->
<script>
new Vue({
el:"#app",
/*用于指定当前vue实例所服务的容器,
值通常为css选择器字符串,容器只能与实例只能一一对应*/
data:{ /*用于存储数据,供el所指定的容器去使用*/
message:"hello vue"
/*页面中使用到message的地方,
会随着data中的message变化而变化*/
}
})
</script>
data与el的两种写法
<script>
Vue.config.productionTip = false
const vue = new Vue({
//el: "#app", //实例与容器绑定的第一种写法 new Vue实例的时候绑定容器
//data: {}, //对象式
data:function () { //函数式
return{
}
},
methods: {}
})
vue.$mount('#app') //实例与容器绑定的第二中写法 创建完容器和实例之后在进行绑定
</script>
由vue管理的函数不要写成箭头函数,一旦写了箭头函数,this就不再代表vue实例对象
MVVM模型
M:Mvodel data中的数据
V:View 模板代码
VM:ViewModel Vue实例对象
- data中的所有属性,最后都会出现在Vue实例上
- Vue实例上的所有属性及Vue原型的所有属性,在Vue模板中都可以直接使用
Object.defineProperty方法
defineProperty(定义属性)是JavaScript中一个用于定义新属性或修改现有属性的方法。该方法通常用于修改对象的属性特性,比如可写性(writable)、可枚举性(enumerable)和可配置性(configurable),默认都是false,还有get和set方法
<script>
let number = 0;
let person = {
name:'张三',
sex:'',
}
//将person的age属性和number进行绑定
Object.defineProperty(person,'age',{
/* value:19, //设置参数值
enumerable:true, //控制属性是否可以枚举,默认false
writable:true, //控制属性是否可被修改,默认false
configurable:true, //控制属性是否可被删除,默认false*/
get(){
return number;
},
set(value){
number = value;
}
})
</script>
Vue中的数据代理
- Vue中的数据代理 通过Vue实例对象来代理data对象中属性的操作
- Vue中数据代理的好处 更加方便的操作data中的数据
- 基本原理
通过Object.defineProperty()把data对象中的所有属性添加到Vue实例上,为每个添加到Vue实例上的属性都添加一个getter/setter,在getter/setter内部去操作data中对应的属性
<!--创建容器-->
<div id="app">
<h1>{{person.name}}</h1>
<h1>{{person.age}}</h1>
</div>
<!--创建vue实例-->
<script>
Vue.config.productionTip = false
var vm = new Vue({
el: "#app",
data: {
//将这里的data对象赋值给vm对象里的_data对象 并不是直接赋值,做了一定的改变
//将_data里的person对象与vm.person进行绑定 即数据代理
person:{
name:"张三",
age:18,
}
},
methods: {}
})
</script>
Vue事件
- 使用v-on:xxx或@xxx绑定事件,其中xxx是事件名
- 事件的回调需要配置到methods对象中,最终会在Vue实例中
- methods中配置的函数,不要使用箭头函数,否则this就不是Vue实例
- methods中配置的函数,都是被vue所管理的函数,this指向的是vm或者组件实例对象
- @click = "demo"和@click="demo($event)"效果一样,但后者可以传参
事件修饰符
- prevent:阻止默认事件执行
- stop:阻止事件冒泡
- once:事件只触发一次
- capture:使用事件的捕获模式
- self:只有event.target是当前操作的元素时才触发事件
- passive:事件默认行为立即执行,无需等待事件回调方法执行完毕
修饰符可以连续写:@click.prevent.stop = “test”
<!-- 准备好一个容器-->
<div id="app">
<!-- 阻止默认事件(常用) -->
<a href="http://www.baidu.com" @click.prevent="showInfo">点我提示信息</a>
<!-- 阻止事件冒泡(常用) -->
<div class="demo1" @click="showInfo">
<button @click.stop="showInfo">点我提示信息</button>
<!-- 修饰符可以连续写 -->
<!-- <a href="http://www.atguigu.com" @click.prevent.stop="showInfo">点我提示信息</a> -->
</div>
<!-- 事件只触发一次(常用) -->
<button @click.once="showInfo">点我提示信息</button>
</div>
<script>
Vue.config.productionTip = false
new Vue({
el: "#app",
data: {},
methods: {
showInfo:function () {
alert("你好")
}
}
})
</script>
键盘事件
- vue常用的按键别名
- 回车 enter
- 删除 delete捕获"删除"和"退格"按键
- 推出 esc
- 空格 space
- 换行 tab 特殊 必须配合keydown使用
- 上 up
- 下 down
- 左 left
- 右 right
- vue未提供别名的按键,可以使用按键的原始的key值去绑定,要转为多单词小写短横线写法
- 系统修饰键 ctrl alt shift win
- 配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才会被触发,指定ctrl+y使用 @keyup.ctrl.y
- 配合keydown使用:正常触发事件
- 也可以使用keyCode去指定具体的按键
- Vue.config.keyCodes.自定义键名 = 键码,可以去自定义按键别名\
计算属性
computed计算属性
- 定义:要使用的属性不存在,需要通过已经存在的属性计算得到
- 原理:底层使用了Object.defineproperty()方法提供的setter和getter
- get函数执行事件
- 初次读取时执行一次
- 当依赖的数据发生改变时会执行
- 内部有缓存机制,效率高
- 备注:
- 计算属性最终会出现在vue实例上,直接读取即可
- 如果计算属性要被修改,那么必须写对应的set函数去响应修改,且set中要引起计算时依赖的数据发生改变
- 如果计算属性不考虑更改,可以考虑简写形式
<script type="text/javascript">
const vm = new Vue({
el:'#root',
data:{
firstName:'张',
lastName:'三',
},
computed:{
//完整写法
/* fullName:{
get(){
console.log('get被调用了')
return this.firstName + '-' + this.lastName
},
set(value){
console.log('set',value)
const arr = value.split('-')
this.firstName = arr[0]
this.lastName = arr[1]
}
} */
//简写
fullName(){
console.log('get被调用了')
return this.firstName + '-' + this.lastName
}
}
})
</script>
侦听属性
watch监视属性
- 当被监视的属性发生变化时,回调函数自动调用,进行相关操作
- 监视的属性必须存在,才能进行监视,既可以监视data,也可以监视计算属性
- 配置监视属性immediate:false,改为true,则初始化时调用一次handler(newValue,oldValue)
- 监视的两种写法:
- new Vue时传入watch配置
- 通过vm.$watch监视
<script>
watch:{
isHot:{ //当isHot发生变化时调用该handler
immediate:true, //初始化时让handler调用一下
handler(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue)
}
}
}
</script>
深度侦听
- vue中的watch默认不监视对象内部值的改变
- 在watch中配置deep:true可以监视对象内部值的变化
<script type="text/javascript">
//numbers:{a:1,b:1,c:{d:{e:100}}}
watch:{
isHot:{
handler(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue)
}
},
//监视多级结构中所有属性的变化
numbers:{
deep:true,
handler(){
console.log('numbers改变了')
}
}
}
</script>
计算属性vs侦听属性
computed和watch之间的区别:
- computed能完成的功能,watch都可以完成
- watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作
- 所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象
- 所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数,这样this的指向才是vm 或 组件实例对象
<script>
new Vue({
el:'#root',
data:{
firstName:'张',
lastName:'三',
fullName:'张-三'
},
watch:{
firstName(val){
setTimeout(()=>{
console.log(this)
this.fullName = val + '-' + this.lastName
},1000);
},
lastName(val){
this.fullName = this.firstName + '-' + val
}
}
})
</script>
<script>
new Vue({
el:'#root',
data:{
firstName:'张',
lastName:'三',
},
computed:{
//完整写法
/* fullName:{
get(){
console.log('get被调用了')
return this.firstName + '-' + this.lastName
},
set(value){
console.log('set',value)
const arr = value.split('-')
this.firstName = arr[0]
this.lastName = arr[1]
}
} */
//简写
fullName(){
console.log('get被调用了')
return this.firstName + '-' + this.lastName
}
}
})
</script>
绑定样式
<head>
<meta charset="UTF-8" />
<title>绑定样式</title>
<style>
.basic{
width: 400px;
height: 100px;
border: 1px solid black;
}
.happy{
border: 4px solid red;;
background-color: rgba(255, 255, 0, 0.644);
background: linear-gradient(30deg,yellow,pink,orange,yellow);
}
.sad{
border: 4px dashed rgb(2, 197, 2);
background-color: gray;
}
.normal{
background-color: skyblue;
}
.class1{
background-color: yellowgreen;
}
.class2{
font-size: 30px;
text-shadow:2px 2px 10px red;
}
.class3{
border-radius: 20px;
}
</style>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器-->
<div id="root">
<!-- 绑定class样式--字符串写法,适用于:样式的类名不确定,需要动态指定 -->
<div class="basic" :class="mood" @click="changeMood">{{name}}</div> <br/><br/>
<!-- 绑定class样式--数组写法,适用于:要绑定的样式个数不确定、名字也不确定 -->
<div class="basic" :class="classArr">{{name}}</div> <br/><br/>
<!-- 绑定class样式--对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 -->
<div class="basic" :class="classObj">{{name}}</div> <br/><br/>
<!-- 绑定style样式--对象写法 -->
<div class="basic" :style="styleObj">{{name}}</div> <br/><br/>
<!-- 绑定style样式--数组写法 -->
<div class="basic" :style="styleArr">{{name}}</div>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
const vm = new Vue({
el:'#root',
data:{
name:'样式',
mood:'normal',
classArr:['class1','class2','class3'],
classObj:{
class1:false,
class2:false,
},
styleObj:{
fontSize: '40px',
color:'red',
},
styleObj2:{
backgroundColor:'orange'
},
styleArr:[
{
fontSize: '40px',
color:'blue',
},
{
backgroundColor:'gray'
}
]
},
methods: {
changeMood(){
const arr = ['happy','sad','normal']
const index = Math.floor(Math.random()*3)
this.mood = arr[index]
}
},
})
</script>
绑定样式
- class样式 写法:class=“xxx” xxx可以是字符串、对象、数组
- 字符串写法适用于:类名不确定,要动态获取
- 对象写法适用于:要绑定多个样式,个数不确定,名字也不确定
- 数组写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用
- style样式
- :style="{fontSize: xxx}"其中xxx是动态值
- :style="[a,b]"其中a、b是样式对象
条件渲染
- v-if
- 写法:
- v-if=“表达式”
- v-else-if=“表达式”
- v-else=“表达式”
- 适用于:切换频率较低的场景
- 特点:不展示的DOM元素直接被移除
- 注意:v-if可以和:v-else-if、v-else一起使用,但要求结构不能被“打断”
- 写法:
- v-show
- 写法:v-show=“表达式”
- 适用于:切换频率较高的场景
- 特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉
- 使用v-if的时,元素可能无法获取到,而使用v-show一定可以获取到
<body>
<!-- 准备好一个容器-->
<div id="root">
<h2>当前的n值是:{{n}}</h2>
<button @click="n++">点我n+1</button>
<!-- 使用v-show做条件渲染 -->
<!-- <h2 v-show="false">欢迎来到{{name}}</h2> -->
<!-- <h2 v-show="1 === 1">欢迎来到{{name}}</h2> -->
<!-- 使用v-if做条件渲染 -->
<!-- <h2 v-if="false">欢迎来到{{name}}</h2> -->
<!-- <h2 v-if="1 === 1">欢迎来到{{name}}</h2> -->
<!-- v-else和v-else-if -->
<!-- <div v-if="n === 1">Angular</div>
<div v-else-if="n === 2">React</div>
<div v-else-if="n === 3">Vue</div>
<div v-else>哈哈</div> -->
<!-- v-if与template的配合使用 -->
<template v-if="n === 1">
<h2>你好</h2>
<h2>北京</h2>
</template>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
const vm = new Vue({
el:'#root',
data:{
name:'vue',
n:0
}
})
</script>
列表渲染
基本列表
v-for指令:
- 用于展示列表数据
- 语法:v-for=“(item, index) in xxx” :key=“yyy”
- 可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
key的作用与原理
- 虚拟DOM中key的作用:
- key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】
- 对比规则:
- 旧虚拟DOM中找到了与新虚拟DOM相同的key:
- 若虚拟DOM中内容没变, 直接使用之前的真实DOM
- 若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM
- 旧虚拟DOM中未找到与新虚拟DOM相同的key
- 创建新的真实DOM,随后渲染到到页面
- 旧虚拟DOM中找到了与新虚拟DOM相同的key:
- 用index作为key可能会引发的问题
- 若对数据进行:逆序添加、逆序删除等破坏顺序操作会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低
- 如果结构中还包含输入类的DOM会产生错误DOM更新 ==> 界面有问题
- 最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值
<div id="root">
<!-- 遍历数组 -->
<h2>人员列表(遍历数组)</h2>
<button @click.once="add">添加一个老刘</button>
<ul>
<li v-for="(p,index) of persons" :key="index">
{{p.name}}-{{p.age}}
<input type="text">
</li>
</ul>
</div>
<script type="text/javascript">
Vue.config.productionTip = false
new Vue({
el:'#root',
data:{
persons:[
{id:'001',name:'张三',age:18},
{id:'002',name:'李四',age:19},
{id:'003',name:'王五',age:20}
]
},
methods: {
add(){
const p = {id:'004',name:'老刘',age:40}
this.persons.unshift(p)
}
},
})
</script>
列表过滤
对列表中的元素进行过滤,使用filter方法
<script>
new Vue({
el:'#root',
data:{
keyWord:'',
persons:[
{id:'001',name:'马冬梅',age:19,sex:'女'},
{id:'002',name:'周冬雨',age:20,sex:'女'},
{id:'003',name:'周杰伦',age:21,sex:'男'},
{id:'004',name:'温兆伦',age:22,sex:'男'}
]
},
computed:{
filPerons(){
return this.persons.filter((p)=>{
return p.name.indexOf(this.keyWord) !== -1
})
}
}
})
</script>
数据监视
原理:
- vue会监视data中所有层次的数据
- 通过setter实现监视,且要在new Vue()时就要传入要监测的数据
- vue实例对象创建后,加入属性,vue默认不做响应式处理
- 如需对追加的数据做响应式,使用如下api
- Vue.set(target,propertyName/index,value)
- vm.$set(target,propertyName/index,value)
- 在Vue修改数组中的某个元素一定要使用如下方法
push(),pop(),unshift(),shift(),splice(),sort(),reverse()这几个方法被Vue重写了
- Vue.set()和vm.$set()不能给Vue实例的根数据对象(data)中添加属性
收集表单数据
- 若:,则v-model收集的是value值,用户输入的就是value值
- 若:,则v-model收集的是value值,且要给标签配置value
- 若:
- 没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)
- 配置input的value属性:
- v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)
- v-model的初始值是数组,那么收集的的就是value组成的数组
- v-model的三个修饰符:
- lazy:失去焦点再收集数据
- number:输入字符串转为有效的数字
- trim:输入首尾空格过滤
过滤器
- 定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)
- 语法:
- 注册过滤器:Vue.filter(name,callback) 或 new Vue{filters:{}}
- 使用过滤器:{{ xxx | 过滤器名}} 或 v-bind:属性 = “xxx | 过滤器名”
- 备注:
- 过滤器也可以接收额外参数、多个过滤器也可以串联
- 并没有改变原本的数据, 是产生新的对应的数据
内置指令
- v-bind: 单向绑定解析表达式, 可简写为 :xxx
- v-model: 双向数据绑定
- v-for: 遍历数组/对象/字符串
- v-on: 绑定事件监听, 可简写为@
- v-if: 条件渲染(动态控制节点是否存存在)
- v-else: 条件渲染(动态控制节点是否存存在)
- v-show: 条件渲染 (动态控制节点是否展示)
- v-texzt: 向其所在的节点中渲染文本内容
- v-html: 向指定节点中渲染包含html结构的内容(v-html有安全性问题)
- v-cloak: 本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性
- v-once: 所在节点在初次动态渲染后,就视为静态内容
- v-pre: 跳过其所在节点的编译过程
自定义指令
- 定义语法:
- 局部指令:new Vue({directives:{指令名:配置对象}})
- 全局指令:Vue.directive(指令名,配置对象)
- 配置对象中常用的3个回调:
- bind:指令与元素成功绑定时调用
- inserted:指令所在元素被插入页面时调用
- update:指令所在模板结构被重新解析时调用
- 指令定义时不加v-,但使用时要加v-,指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名
生命周期
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
new Vue({
el:'#root',
data:{
n:1
},
methods: {
add(){
console.log('add')
this.n++
},
bye(){
console.log('bye')
this.$destroy()
}
},
watch:{
n(){
console.log('n变了')
}
},
beforeCreate() {console.log('beforeCreate')}, //vue实例初始化之前(未数据检测和数据代理)
created() {console.log('created')}, //vue实例初始化之后
beforeMount() {console.log('beforeMount')}, //真实dom挂载前
mounted() {console.log('mounted')}, //真实dom挂载后
beforeUpdate() {console.log('beforeUpdate')}, //真实dom更新前
updated() {console.log('updated')}, //真实dom更新后
beforeDestroy() {console.log('beforeDestroy')}, //实例销毁前
destroyed() {console.log('destroyed')}, //实例销毁后
})
</script>
常用的生命周期钩子
- mounted: 发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】
- beforeDestroy: 清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】
销毁Vue实例
- 销毁后借助Vue开发者工具看不到任何信息
- 销毁后自定义事件会失效,但原生DOM事件依然有效
- 一般不会在beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了
Vue组件化编程
组件化思想:用独立可复用的小组件来构建大型应用
通过组件化开发,可以提高代码的重用性、可维护性和可读性,降低项目的复杂度,提升开发效率和团队协作能力
使用组件的三大步骤:
- 定义组件
使用Vue.extend({options})创建,不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器,data必须写成函数式,避免组件复用时,数据存在引用关系
- 注册组件
- 局部注册:new Vue的时候传入components选项
- 全局注册:Vue.component(‘组件名’,组件)
- 使用组件
<组件名></组件名>
<div id="root">
<!-- 编写组件标签,使用组件 -->
<school></school>
</div>
<script>
//创建school组件
const school = Vue.extend({
template:`
<div class="demo">
<h2>学校名称:{{schoolName}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="showName">点我提示学校名</button>
</div>
`,
// el:'#root', //组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器。
data(){
return {
schoolName:'尚硅谷',
address:'北京昌平'
}
},
methods: {
showName(){
alert(this.schoolName)
}
},
}),
//全局注册组件
Vue.component('school',school)
//创建vm
new Vue({
el:'#root',
data:{
msg:'你好啊!'
},
//注册组件(局部注册)
components:{
school
}
})
</script>
VueComponent
- school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的
- 我们只需要写或,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行的:new VueComponent(options)
- 每次调用Vue.extend,返回的都是一个全新的VueComponent
- 关于this指向:
- 组件配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【VueComponent实例对象】
- new Vue(options)配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【Vue实例对象】
内置关系
在js中,我们创建的每一个实例都有一个prototype属性,这个属性就是一个指针,指向一个包含通过调用该构造函数所创建的对象具有一样的属性和方法的对象(原型对象),最终每个对象的prototype都会指向Object的原型对象。在Vue中,vue的实例对象指向Vue的原型对象,获得vue实例对象获得Vue原型对象上的属性和方法,在创建组件时,组件的实例对象指向VueComponent的原型对象,要想实现组件的效果,则组件中必须包含Vue原型对象的方法和属性,那么VueComponent的_proto_就必须指向Vue的原型对象