M V VM模型
M模型(model): 对应data
V视图(view): 模板
VM视图模型(ViewModel): Vue实例对象
<div id="root">
<h1>学校名{{name}}</h1>
<h1>学校地址{{address}}</h1>
</div>
new Vue({
el: '#root',
data: {
name: '山西大学',
address: '山西'
}
})
说白了就是把一堆数据和一堆dom解构 通过 viewmodel来连接
Vue中this指向
vue管理的函数必须使用普通函数 这样this的指向才是vm或组件实例对象 vue管理的函数中使用的定时器,ajax的回调汉书等window帮忙调用的 都写为箭头函数这样才
watch: {
firstNfame(newValue, oldValue) {
setTimeout(() => {
this.fullName = newValue + this.lastName
}, 1000);
},
lastName(newValue, oldValue) {
setTimeout(() => {
this.fullName = this.firstName + newValue
}, 1000);
}
}
Object.defineProperty
基本用法
三个参数(给那个对象添加属性 , 属性叫什么 , {配置项} )
Object.defineProperty('给那个对象添加属性' , '添加的属性叫什么' , {
value:18
})
-----------------------------------------------------------
比如 我给person加一个age = 18
let person = {
name: "张三",
sex: "男"
}
Object.defineProperty(person, 'age', {
value: 18
})
console.log(person)
打印的时候你会发现person的age项是淡色的 说明他不会被枚举
如果要枚举 则增加一个配置项 增加的值也不能改也不能删除
Object.defineProperty(person, 'age', {
value: 18,
enumerable: true,
writable: true,
configurable:true
})
接下来引出高级的
let number = 18
let person = {
name: "张三",
sex: "男",
age:number
}
这样写的时候 number变 person里的age不会跟着变
拿的不是同一个地址
但是我想number变person里的age也变,person的age变number也变,怎么办呢?
先引入一个新的配置项 还是之前的person
Object.defineProperty(person, 'age', {
get:function(){
return 'hello'
}
})
logperson后你会发现 age:(...) 意思为必须得点击(年龄多少我不知道 我得问问getter)
而且还会多一个属性 get age : f() 为age服务
所以直接这样就可以和number绑定
Object.defineProperty(person, 'age', {
get(){
return number
}
})
还一个set也类似
let number = 18
let person = {
name: "张三",
sex: "男",
}
Object.defineProperty(person, 'age', {
get() {
console.log('getter');
return number
},
set(value) {
console.log('setter');
number = value
}
})
数据代理
基本
数据代理:通过一个对象代理对另一个对象中属性的操作(读/写)
let obj1 = {
x:100
}
let obj2 = {
y:200
}
Object.defineProperty(obj2, 'x', {
get() {
return obj1.x
},
set(value) {
obj1.x = value
}
})
这样可以通过操作obj2里面的x来修改obj1里面的x
Vue中的数据代理
const vm = new Vue({
el: '#root',
data: {
name: '山西大学',
address: '山西'
}
})
log vm 会发现vm上面有name和address还有对应的setget
Vue其实把name就和data.name做了代理
执行第一步
Vue把你代码里写的data里的数据存在vm._data里 但是如果这样,你在模板里就要_data.name这样展示,方法里取值就要this._data = xxx,很麻烦。
所以把_data里的值添加到vm本身,并且使用gettersetter映射,谁改vm身上的值就去_data里改值
1. Vue中的数据代理
通过vm对象来代理data对象中属性的操作(读/写)
2. Vue数据代理的好处
更加方便的操作data中的数据
3. 基本原理
通过Object.defineProperty()把data对象中的所有属性添加到vm身上
为每一个添加到vm上的属性,都指定一个getter/setter
在getter/setter内部操作data中对应的属性
在vm._data中,还做了数据劫持来实现响应式
番外
console.log(vm._data);
console.log(vm.$data);
console.log(vm.$options.data());
都是一样的。
vue2
el和data的两种写法
el
new Vue({
el:'root'
})
或者
const vm = new Vue({
})
vm.$mount('root')
data
new Vue({
data:{
name:'你好'
}
})
或者
new Vue({
data:function(){
return {
}
}
})
简写
new Vue({
data(){
return {
}
}
})
数据绑定v-bind和v-model
v-bind单项绑定
数据只能从data流向页面
v-model双向绑定
数据可以在data和页面互流
一般应用在表单元素上 有value属性的标签
事件处理
事件函数不能用箭头函数 否则会被指向window
事件放在methods里也会被放到vm身上,但是不会做数据代理 事件函数也可以放在data里但是会被做数据代理,但是事件不会变 所以没必要做代理
@click='btn' 和 @click='btn($event)' 一样,但是后者可以传参
@click='btn($event,a)' @click='btn(a,$event)' 这里的$event没有前后限制
事件修饰符
https://cn.vuejs.org/v2/guide/events.html#%E4%BA%8B%E4%BB%B6%E4%BF%AE%E9%A5%B0%E7%AC%A6
.prevent:阻止默认事件
.stop:阻止事件冒泡
.once:事件只触发一次
.capture:使用事件的捕获模式,捕获阶段就会执行
.passive:事件的默认行为立即执行,无需等待事件回调执行完毕,比如滚动条优先滚动在执行,但是不是每个都需要 scroll就不需要但是wheel就需要,一般移动端用的比较多
.self只有event.target是当前操作的元素时才触发事件
比如div包button 点击btn冒泡到div 但是两者事件参数的e.target其实都是button
<form v-on:submit.prevent="onSubmit"></form>
<a v-on:click.stop.prevent="doThat"></a>
<form v-on:submit.prevent></form>
键盘事件
语法糖
@keyup.enter="handle"
回车 enter
删除和退格 delete
推出 esc
空格 space
换行 tab(特殊,必须配合keydown)
上 up
下 down
left
right
获取按键名字 event.key
获取按键编码 event.keyCode
除了语法糖
也可以直接@keyup.按键名='handle'(推荐)
也可以直接@keyup.按键码='handle'(不推荐,某一天会弃用)而且不同电脑某些按键编码不同
Vue.config.keyCode.xxx = 13
切换大小写这种多个单词的Caps Lock使用按键名时
必须写成@keyup.caps-lock='handle'
特殊的几个按键
tab是特殊的 keyup是按键抬起 但是tab有个功能按下的时候切走焦点 所以keyup监听不到,所以不用keyup
ctrl alt shift meta(win键)
配合key使用:按下修饰符键的同时,再按下其他键,随后释放其他键,事件才触发
配合keydown使用:正常触发事件
计算属性computed
vue管data里的数据就叫属性 计算属性是通过计算已经有的属性的来 计算属性也会被挂载到vm上 案例
需求是:两个input框
输入姓和名 显示全名
firstName和lastName显示fullName
展示全名的代码这样写
1. 使用插值表达式{{firstName1+lastName1}}
2. 使用函数methods实现 只要姓或者名的值改变 vue会重新阅读模板代码 每次都会重新调用
{{fullName()}}
methods:{
fullName(){
return this.firstName2 + this.lastName2
}
}
3. 使用计算属性(简写)
{{fullName}}
fullName(){
return this.firstName2 + this.lastName2
}
computed和methods的区别
computed有缓存 当view层两次使用计算属性时第二次调用缓存
methods没有缓存 veiw层发现使用的值时函数返回值就会调用
完整写法
原理就是借助了Object.defineproperty方法提供给的get来计算 vue做了缓存
想自己加改的话就用set去响应式修改,且set中要引起计算时依赖的数据发生变化
这里的getset不可以写为箭头函数
computed:{
fullNameComputed:{
get(){
return this.firstName3 + this.lastName3
}
set(value){
const arr = value.split('-')
this.firstName3 = arr[0]
this.lastName3 = arr[1]
}
}
}
如果不改的话就直接简写就好
侦听器watch
可以监听data也可以监听computed
比如data中有个isHot
watch:{
isHot(newValue,oldValue){
}
},
watch:{
isHot:{
immediate:true,
handler(newValue,oldValue){
}
}
},
watch:{
'obj.a'(newValue,oldValue){
}
}
watch:{
'obj':{
deep:true,
handler(){
}
}
}
也可以使用另一种写法
vm.$watch('isHot',function(newValue,oldValue){
})
vm.$watch('isHot',{
})
计算属性与侦听器
先说结论
- computed能完成的,watch都能完成
- watch能完成的,computed不一定都能完成,例如:watch可以进行异步操作
- 可以这样试着理解 computed只是一个值。watch就是监听然后执行一个函数
需求 需要输入姓和名来展示全名
先看一下用计算属性和侦听器的写法区别
姓<input v-model='firstName' type="text"><br/>
名<input v-model='lastName' type="text"><br/>
全名<h3>{{fullName}}</h3>
const vm = new Vue({
el: '#root',
data: {
firstName:'',
lastName:'',
},
computed: {
fullName(){
return this.firstName + this.lastName
}
},
})
看似没什么 但是如果需求变成输入姓名后 1秒后再展示全名
computed: {
fullName(){
setTimeout(() => {
return this.firstName + this.lastName
}, 1000);
}
}
watch: {
firstName(newValue, oldValue) {
setTimeout(() => {
this.fullName = newValue + this.lastName
}, 1000);
},
lastName(newValue, oldValue) {
setTimeout(() => {
this.fullName = this.firstName + newValue
}, 1000);
}
}
class与style绑定
class绑定
字符串写法适用于:样式的类名不确定,需要动态指定 默认的就直接写 变的绑定
<div class="basic" :class="mood" @click="changeMood">{{name}}</div>
data里定义mood:'类名' 再通过事件改
数组写法适用于:要绑定的样式个数不确定,名字也不确定
<div class="basic" :class="arr">{{name}}</div>
data里定义arr:['类名','类名','类名']
对象写法适用于:样式个数确定,名字确定,但要动态决定用不用
<div class="basic" :class="classObj">{{name}}</div>
data里定义
classObj:{
类名:false,
类名:true
}
style绑定
写对象形式1
<div class="basic" :style='{ fontSize:size + "px" }'>{{name}}</div>
data中size:40
写对象形式2
<div class="basic" :style='styleObj'>{{name}}</div>
data中定义
styleObj:{
fontSize:'40px'
}
还可以写成数组 包含两个style对象
<div class="basic" :style='["styleObj1","styleObj2"]'>{{name}}</div>
或者
<div class="basic" :style='styleArr'>{{name}}</div>
data中定义
styleArr:[
{fontSize:'40px'},
{backgroundColor:'red'}
]
条件语句v-if,v-show,v-for
v-if,v-show
v-show 不动dom
v-if 动dom
v-else-if
v-else
如果使用if,if-else,else的组合 标签必须挨着
v-show可以拿到节点,v-if不一定能拿到
template可以配合v-if不可以配合show
v-for
v-for
遍历数组
v-for="(item,i) in arr"
v-for="item,index in arr"
v-for="(item,index) in arr"
v-for="(item,index) of arr"
遍历数组
v-for='(value,key) in obj'
遍历字符串
v-for='(char,index) in str'
遍历指定次数
v-for='(number,index) in 5'
模糊搜索 列表过滤 列表排序
首先使用侦听器
<body>
<div id="root">
<h2>列表</h2>
<input v-model='keyWord' placeholder="搜索" type="text">
<ul>
<li v-for="(item) in persons">
{{item.name}}-{{item.age}}-{{item.sex}}
</li>
</ul>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
const vm = new Vue({
el: '#root',
_persons: [
{ id: '001', name: '马冬梅', age: 18, sex: '女' },
{ id: '002', name: '周冬雨', age: 19, sex: '女' },
{ id: '003', name: '周杰伦', age: 20, sex: '男' },
{ id: '004', name: '温兆伦', age: 23, sex: '男' },
],
data: {
keyWord: '',
persons: undefined,
},
watch: {
keyWord: {
immediate:true,
handler(newV) {
let newPersons = this.$options._persons.filter((cur, index, arr) => {
return cur.name.includes(newV) == true
})
this.persons = newPersons
}
}
}
})
</script>
使用计算属性
<body>
<div id="root">
<h2>列表</h2>
<input v-model='keyWord' placeholder="搜索" type="text">
<ul>
<li v-for="(item) in persons">
{{item.name}}-{{item.age}}-{{item.sex}}
</li>
</ul>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
const vm = new Vue({
el: '#root',
_persons: [
{ id: '001', name: '马冬梅', age: 18, sex: '女' },
{ id: '002', name: '周冬雨', age: 19, sex: '女' },
{ id: '003', name: '周杰伦', age: 20, sex: '男' },
{ id: '004', name: '温兆伦', age: 23, sex: '男' },
],
data: {
keyWord: '',
},
computed: {
persons(){
return this.$options._persons.filter((cur,i,arr)=>{
return cur.name.includes(this.keyWord) == true
})
}
},
})
</script>
列表排序
<body>
<div id="root">
<h2>列表</h2>
<input v-model='keyWord' placeholder="搜索" type="text">
<button @click='sortType = 2'>列表升序</button>
<button @click='sortType = 1'>列表降序</button>
<button @click='sortType = 0'>原顺序</button>
<ul>
<li v-for="(item) in persons" :key='item.id'>
{{item.name}}-{{item.age}}-{{item.sex}}
</li>
</ul>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
const vm = new Vue({
el: '#root',
data: {
keyWord: '',
sortType: 0,
fullPersons: [
{ id: '001', name: '马冬梅', age: 18, sex: '女' },
{ id: '002', name: '周冬雨', age: 19, sex: '女' },
{ id: '003', name: '周杰伦', age: 20, sex: '男' },
{ id: '004', name: '温兆伦', age: 23, sex: '男' },
],
},
computed: {
persons2() {
let arr = this.fullPersons.filter((cur, i, arr) => {
return cur.name.includes(this.keyWord) == true
})
if (this.sortType == 0) return arr
return arr.sort((a, b) => {
return this.sortType == 1 ? b.age - a.age : a.age - b.age
})
},
persons() {
let arr = this.fullPersons.filter((cur, i, arr) => {
return cur.name.includes(this.keyWord) == true
})
if (this.sortType) {
return arr.sort((a, b) => {
return this.sortType == 1 ? b.age - a.age : a.age - b.age
})
}
return arr
}
}
})
</script>
vue更新时的问题 $set
直接替换数组中的数据vue检测不到
const vm = new Vue({
el: '#root',
data: {
persons: [
{ id: '001', name: '马冬梅', age: 18, sex: '女' },
{ id: '002', name: '周冬雨', age: 19, sex: '女' },
],
},
methods: {
updateMei() {
this.persons[0] = { id: '001', name: '马老师', age: 50, sex: '男' }
}
},
})
Vue底层如何实现监控对象中数据数据变化
let data = {
name: '你好',
age: 18
}
function Observer(obj) {
const keys = Object.keys(obj)
keys.forEach((k) => {
Object.defineProperty(this, k, {
get() {
return obj[k]
},
set(val) {
console.log(`${k}被改了,我要去解析模板,生成虚拟dom,去比较`);
obj[k] = val
}
})
})
}
const obs = new Observer(data)
console.log(obs);
let vm = {}
vm._data = data = obs
console.log(vm);
Object.defineProperty(vm, 'name', {
get() {
return vm._data.name
},
set(val) {
vm._data.name = val
}
})
function Observer(obj)这里 当构造函数穿进去的参数就会在参数内部复制一份了
所以vm._data = data = obs 尽管改变了data 但是构造函数里的那一份和这个不是一份了
比如
let a = 1
function man(num){
num++
}
man(a)
console.log(a);
你会发现打印的还是a
这样自己写你会发现 并不能检测对象中的对象,如果data中有对象里面有值的话你打开会发现里面的值没有做getter和setter,因为const keys = Object.keys(obj)你就取了一层 但是vue中做了递归 有几层做几层 数组中对象也做了 都会生成getset vue检测对象中改变就是靠set 但是vue虽然都做了 但是也没有那么完善
比如
data:{
students:{
name:'张三',
}
}
在浏览器里vm._data.students里的name是被做了getset的
这时候你vm._data.students.age = 18 你就会发现添加是添加了 但是没有对应的set和get vue也不会代理到自身 当然也不会刷新view层
这时候使用set语句 set(往哪添加或修改,key,val)
Vue.set(vm._data.student,'sex','男')或者Vue.set(vm.student,'sex','男')
vm.$set(vm.student,'sex','女')就会刷新view层
vue身上是set vm身上是$set
set不允许给vm或者data根添加数据 只能给data中的某个对象添加
Vue底层如何实现监控数组中数据数据变化
const vm = new Vue({
el: '#root',
data: {
student:{
friends:[
{name:'jerry',age:35},
{name:'tony',age:18},
],
hobby:['抽烟','喝酒','烫头']
}
}
})
如果直接vm._data.student.hobby[0] = 'xxx'
或者脚手架里this.student.hobby[0]
vue是检测不到的
vue把修改原数组的方法都重写了
unshift 前添加
push 后
shift 前删除
pop 后删除
splice 截取删除添加
sort 排序
reverse 反转
使用这些方法修改会引起view层刷新
也可以使用filter,map等数组生成新的数组在替换掉原数组(注意是替换)
接用官网的话:你可能认为这将导致 Vue 丢弃现有 DOM 并重新渲染整个列表。幸运的是,事实并非如此。Vue 为了使得 DOM 元素得到最大范围的重用而实现了一些智能的启发式方法,所以用一个含有相同元素的数组去替换原来的数组是非常高效的操作。
也可以使用vm.set(要修改谁里面的值,索引值,值)
或Vue.set()
过滤器+日期案例dayjs(轻量级的moment.js)
dayjs和momentjs可以取bootcdn搜索使用
<div id="root">
时间戳:{{time}} <br />
computed解析后: {{fmtTime}} <br />
methods解析后:{{getFmtTime()}} <br />
过滤器实现:{{time | timeForMater}} <br/><br/>
过滤器传参:{{time | timeForMater2('YYYY年MM月DD日')}} <br/>
两个过滤:{{time | timeForMater2('YYYY年MM月DD日') | timeSlice | substr}}.
<h2 :x='time | substr'>你好</h2>这样dom里x就等于16
</div>
<script type="text/javascript">
Vue.config.productionTip = false
//全局过滤器
Vue.filter('substr',function(val){
return val.toString().substring(0,2)
})
const vm = new Vue({
el: '#root',
data: {
time: 1630257635397
},
computed: {
fmtTime() { //使用计算属性
return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss')
}
},
methods: {
getFmtTime() { //使用方法
return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss')
}
},
filters: { //过滤器本质就是个函数
timeForMater(time){
console.log(this); //过滤器内部this指向window,所以不能this.time
return dayjs(time).format('YYYY年MM月DD日 HH:mm:ss')
},
timeForMater2(time,type){ //第一个参数还是time,第二个是传过来的
return dayjs(time).format(type)
},
timeSlice(time){
return time.slice(0,4)
}
}
})
<script/>
日期函数 小写h为12小时制
Date.prototype.format = function (fmt) {
var o = {
"M+": this.getMonth() + 1,
"d+": this.getDate(),
"h+": this.getHours() % 12 == 0 ? 12 : this.getHours() % 12,
"H+": this.getHours(),
"m+": this.getMinutes(),
"s+": this.getSeconds(),
"q+": Math.floor((this.getMonth() + 3) / 3),
"S": this.getMilliseconds()
};
if (/(y+)/.test(fmt))
fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
for (var k in o)
if (new RegExp("(" + k + ")").test(fmt))
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
return fmt;
}
vue中收集表单数据
v-model修饰符
v-model.lazy.trim.number
labal的配合使用
<form>
账号:<input type="text"> //这样写点击账号不会获取input框的焦点
<label for="username">账号:</label> //这样通过label的for绑定input的id
<input id="username" type="text">
</form>
代码
账号:<input type="text"><br/>
密码:<input type="password"><br/><br/>
性别:
男<input type="radio" name="sex"> //使用name属性告诉他们是一组的
女<input type="radio" name="sex"><br/><br/>
爱好:
学习<input type="checkbox" name="hobby"> //使用name属性告诉他们是一组的
打游戏<input type="checkbox" name="hobby">
吃饭<input type="checkbox" name="hobby"><br/><br/>
所属校区:
<select>
<option>请选择校区</option>
<option value="bj">北京校区</option>
<option value="sh">上海校区</option>
<option value="sz">深圳校区</option>
</select><br/><br/>
其他信息:
<textarea>
</textarea><br/><br/>
<input type="checkbox"> 阅读并接受<a href="">《用户协议》</a>
<button>提交</button>
使用vue收集的代码
<form>
账号:<input v-model.number='username' type="text" autocomplete><br/>
密码:<input v-model='password' type="password" autocomplete><br/><br/>
性别:
男<input type="radio" value='男' v-model='sex' name="sex">
女<input type="radio" value='女' v-model='sex' name="sex"><br/><br/>
爱好:
学习<input type="checkbox" v-model='hobby' value="study" name="hobby">
打游戏<input type="checkbox" v-model='hobby' value="play" name="hobby">
吃饭<input type="checkbox" v-model='hobby' value="eat" name="hobby"><br/><br/>
所属校区:
<select v-model='select'>
<option disabled value=''>请选择校区</option>
<option>北京校区</option>
<option>上海校区</option>
<option>深圳校区</option>
</select><br/><br/>
其他信息:
<textarea v-model='textarea'>
</textarea><br/><br/>
<input type="checkbox" v-model='agree'> 阅读并接受<a href="">《用户协议》</a><br/><br/>
<button @click.prevent='submit'>提交</button v-model='hobby'>
</form>
const vm = new Vue({
el: '#root',
data: {
username:'',
password:'',
sex:'',
hobby:[],
select:'',
textarea:'',
agree:false,//用户协议
},
methods: {
submit(){
console.log(JSON.stringify(this._data));
}
},
})
内置指令
<div v-text='name'></div>和插值语法差不多但是会替换div里面的东西,所以div里有任何东西就会被替换,并且只渲染文本内容
<div v-html='name'></div>支持解析html解构,但是最好不要使用 容易受xss攻击
v-cloak 当页面的vue.js文件加载完毕后 v-cloak自动删除 css可以写:[c-cloak]{...}
<div v-onec>{{name}}</div> v-onec所在的节点在初次动态渲染后,就视为静态内容了,以后数据改变不会引起更新,可以优化性能
v-pre 跳过其节点的编译过程,优化性能,建议没有使用指令语法和插值语法的节点使用
自定义指令
自定义指令 可以自己编写类似v-show等指令 使用方式
在vue中配置一个directives对象,比如配置v-big
const vm = new Vue({
directives:{
big(element,binding){
}
}
})
案例 需求1:定义一个v-big指令,和v-text类似,但会把绑定的数值放大10倍 需求2:定义一个v-fbind指令和v-bind类似,但是让其绑定的input元素默认获取焦点
<h2>当前n值为<span v-text='n'></span></h2><br/>
<h2>v-big后的n值为<span v-big='n'></span></h2>
const vm = new Vue({
data: {
n:'1'
},
directives:{
big(element,binding){
element.innerText = binding.value * 10
}
}
})
|