双向绑定原理vue利用Object.definedProperty 这个方法遍历data中所有的属性,给每一个属性增 加 setter方法,和getter方法,当数据发生变化时,会触发setter方法,当获取数据时,会触发getter方法; Object.definedProperty在IE以下是不兼容的;vue 是不兼容IE8 以下;
双向数据绑定实现步骤:
- 实现一个监听器observer:对数据数据对象进行遍历,包括子属性对象的属性,利用Object.defineProperty()对属性都加上setter和getter。这样的话,给这个对象的某个值赋值,会触发setter,就可以监听到数据变化
- 实现一个解析器compile:解析vue模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每一个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据变动,收到通知,调用更新函数进行数据更新
- 实现一个订阅者watcher:watcher订阅者是observer和compile之间通信的桥梁,主要的任务是订阅observer中的属性值变化的消息,当收到属性值变化的消息时,触发解析器compile中对应的更新函数
- 是实现一个订阅器dep:订阅器采用发布-订阅模式,用来收集订阅者watcher,对监听器observer和订阅者watcher进行统一管理
注意:
用get和set的数据才是响应式数据,才可以在更新的数据后,vue帮我们去渲染视图
get :
$attrs:f()
$listeners:f()
obj:f()
set
$attrs:f()
$listeners:f()
obj:f()
_proto_:Object
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app">
<div class="baseInfo">
<h3>姓名:{{obj.name}}</h3>
<p>年龄:{{obj.age}}</p>
<p></p>
</div>
</div>
<script src="../vue/node_modules/vue/dist/vue.js"></script>
<script>
//对对象的劫持
//forceUpdate 强制通知视图重新渲染
let vm = new Vue({
el: "#app",
data: {
//data中设置的响应式是数组,可以挂载在实例上vm.obj
obj: {
name: "zhufnge",
age: 10,
sex: 0,
score:{
en:12
}
},
},
});
</script>
<script>
//观察者:把数据劫持,对对象进行深层次处理
function observer(obj){
if(obj && typeof obj === "object"){
for (let key in obj){
if(!obj.hasOwnProperty(key)) break;
defineReactive(obj,key,obj[key])
}
}
}
//数据劫持
function defineReactive(obj,attr,value){
observer(value)
Object.definProperty(obj,attr,{
get(){
return value
},
set(newValue){
observer(newValue)
if (newValue===value) return;
value=newValue
}
})
}
//基于$set处理数据,也会进行数据劫持
function $set(obj,attr,value){
}
</script>
</body>
</html>
v-model
首先可以把响应数据绑定在文本框中,并且可以监听文本框内的改变,内容改变后会修改响应数据,相应数据一更改,视图还会重新渲染
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app">
<!--
v-model:首先可以把响应数据绑定在文本框中,并且可以监听文本框内的改变,内容改变后会修改响应数据,相应数据一更改,视图还会重新渲染
-->
<input type="text" v-model="text" />
<p v-text="text"></p>
</div>
<script src="../vue/node_modules/vue/dist/vue.js"></script>
<script>
let vm = new Vue({
data: {
text: "你好",
},
});
//等价于设置的el
vm.$mount("#app");
</script>
<script>
let data = {
text: "年后",
};
let temp = {
...data,
};
Object.defineProperty(data, "text", {
get() {},
set(newValue) {
temp.text = newValue; //这里不能用data.text=newValue 会陷入死循环
render()
},
});
//根据数据去渲染视图
function render() {
inpBox.value = temp.text;
conBox.innerHTML = temp.text;
}
render();
//视图更新控制数据的更新
inpBox.addEventListener("input", function () {
let val = inpBox.value;
data.text = val;
});
</script>
</body>
</html>
filter过滤器
过滤器:是一种处理数据但是不会改变原数据的数据处理方式,一般用于格式化数据
1. 全局过滤器
Vue.filter(过滤器,callback)
2. 局部过滤器
写在filters中的过滤器均是局部过滤器
3.过滤器语法
使用竖线分隔,把竖线左侧的值传递给竖线右侧的过滤器方法,经过方法处理之后,把处理后的结果展示在视图中的过滤器方法只能在胡子语法{{}}和v-bind中使用,过滤器中的方法并没有挂载在实例上。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<ul>
<li v-for="(item, index) in products" :key="index">
商品{{item.name}}
价格{{item.price | toDollar}}
国行{{item.price | toRMB | toFixed(3)}}
<!--| 叫做管道符,把它前面的值传递给过滤器函数的第一个参数;然后数据就会展示成过滤器函数的返回值-->
<!--过滤器可以连续使用,后面的过滤器的参数,是上一个过滤器的处理结果,数据会展示成最后一个过滤器的结果-->
<!--过滤器可以传参,参数是传给第二个形参的,第一个参数是管道符前面的值-->
</li>
</ul>
</div>
<script src="vue.js"></script>
<script>
// 过滤器:是一种处理数据但是不会改变原数据的数据处理方式,一般用来格式化数据;
// 全局过滤器:Vue.filter(过滤器, callback)
// Vue.filter('toDollar', (val) => '$' + val);
// Vue.filter('toRMB', (val) => val * 6.853);
// Vue.filter('toFixed', val => '¥' + val.toFixed(2));
let vm = new Vue({
el: '#app',
data: {
products: [
{
name: '苹果',
price: 1230
},
{
name: '香蕉',
price: 1000
},{
name:'樱桃',
price:2999
]
},
filters: { // 写在filters里面的过滤器是局部过滤器
// toDollar: function (val) {},
toDollar (val) {
//=>val:需要过滤的数据 return返回的是过滤后的结果
return'$' + val
},
toRMB(val) {
return val * 6.8534
},
toFixed(val, num = 2) {
return '¥' + val.toFixed(num);
}
}
})
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
</head>
<body>
<div id="app">
<input type="text" v-model='text'>
<br>
<!-- <span v-text='text.replace(/\b[a-zA-Z]+\b/g,item=>{
return item.charAt(0).toUpperCase()+item.substring(1);
})'></span> -->
<!-- <span v-text='toUP(text)'></span> -->
<span>{{text|toUP|filterB}}</span>
<!-- <img :src="pic|picHandle" alt=""> -->
</div>
<!-- IMPORT JS -->
<script src="./node_modules/vue/dist/vue.js"></script>
<script>
let vm = new Vue({
el: '#app',
data: {
//=>响应式数据:DATA中准备的要在视图中渲染的数据(MODEL)
text: ''
},
methods: {
//=>都会挂载到实例上(不能和DATA中的属性名冲突):这里的制定的方法是普通方法,可以在视图中调取使用,也可以在其它方法中调取使用
toUP(value) {
return value.replace(/\b[a-zA-Z]+\b/g, item => {
return item.charAt(0).toUpperCase() + item.substring(1);
});
}
},
filters: {
//=>设置过滤器:把需要在视图中渲染的数据进行二次或者多次的处理
toUP(value) {
//=>value:需要过滤的数据 return返回的是过滤后的结果
return value.replace(/\b[a-zA-Z]+\b/g, item => {
return item.charAt(0).toUpperCase() + item.substring(1);
});
},
filterB(value) {
return value.split('').reverse().join('');
},
picHandle(value){
return value.length===0?'http://www.zhufengpeixun.cn/static/1.png':value;
}
}
});
</script>
</body>
</html>
computed计算属性
计算属性:处理某一个或者某些属性复杂展示逻辑,不会改变原数据;目的是不在模板中写太多逻辑。 computed里面的属性最终会被vm取代,这些属性都会在vm身上有一份。
使用computed属性的情况
- 数据显示
- 需要显示的数据依赖其他数据 ,通过其他数据计算出来的
-
computed注意点
- computed里面的属性会被vm所代理;
2. computed里面的属性和data/methods/filters/都不能重名;
3. computed的计算属性可以是一个函数还可以是一个对象;对象中有get和set方法,取值的时候执行get,设置的时候执行set;而函数形式的计算属性,只有get的情况,只能获取不能设置,如果设置会报错;
4. 如果一个值需要依赖其他属性计算而来,这个时候最好用计算属性;
- computed里面的属性会被vm所代理;
计算属性不能写在异步处理程序:ajax,定时器,promise的then
- computed的getter不支持异步获取数据
computed
- 计算属性不是一个方法,而是属性,因此在视图调用的时候不能加括号
- 计算属性对应的值,会被挂载在当前实例上,挂载的内容是函数的返回值(getter函数的处理结果),
- 计算属性会有对应的缓存,当计算属性依赖的值不发生改变时,视图刷新,它会使用之前的数据结果进行渲染,不会再执行函数(依赖:在函数里用到了那个变量,就是依赖了那个变量)
- 计算属性中必须关联一个响应式的数据,否则getter函数(getter函数:获取这个属性值就会触发get函数执行;setter函数:给属性设置的时候会触发set函数,value是给这个属性设置的值)只执行一次
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="bootstrap.css">
</head>
<body>
<div id="app">
<div class="container">
<div class="row">
<table class="table table-bordered">
<tr>
<td>
全选:<input type="checkbox"
v-model="checkAll">
</td>
<td>商品</td>
<td>数量</td>
<td>单价</td>
<td>小计</td>
</tr>
<tr v-for="(product, index) in carts" :key="index">
<td>
<input type="checkbox"
v-model="product.isSelected"
@change="changeOne">
</td>
<td>
{{product.name}}
</td>
<td>
<input type="number" v-model="product.count" min="1">
</td>
<td>
{{product.price}}
</td>
<td>
{{product.count * product.price | toRMB}}
</td>
</tr>
<tr>
<td colspan="5">
总价:{{total | toRMB}}
</td>
</tr>
</table>
<input type="text" v-model="total">
</div>
</div>
</div>
<script src="vue.js"></script>
<script>
// npm install 依赖包@版本号 指定版本号安装;如果不指定,就会按照最新的装;
let vm = new Vue({
el: '#app',
data: {
carts: [
{
isSelected: true,
count: 3,
price: 57.86,
name: '车厘子'
},
{
isSelected: true,
count: 1,
price: 6666,
name: 'iPhoneX'
}
]
},
filters: {
toRMB(val) {
return '¥' + val.toFixed(2)
}
},
methods: {
changeAll() {
},
changeOne() {
}
},
//真实项目中:一般用一个计算属性和某些响应式数据进行关联,响应式数据发生改变,计算属性的GETTER函数会重新执行,否则使用的是上一次计算出来的缓存结果
computed: {
// computed里面的属性最终也会被vm代理,这些属性都会在vm身上也有一份;
total: function () { // 计算属性的getter形式,这样声明的total只能读,不能写;这个属性不能修改,修改它会报错;
//计算属性中必须要关联一个响应式数据,否则getter函数只执行一次
// 首先把打钩的商品筛选出来
let selected = this.carts.filter(i => i.isSelected);
return selected.reduce((prev, next) => {
// next是数组项,现在是对象
return prev + next.count * next.price;
}, 0);
},
// 计算属性的setter
checkAll: {
get() {
//
// 当获取checkAll的时候就会执行get方法,并且取到的值是get方法的返回值
return this.carts.every(item => item.isSelected);
},
set(val) {
// 当修改checkAll属性的时候,会触发set方法,并且val会接收到checkAll的新值;
// console.log(val); // val就是修改checkAll的时候传过来的值
this.carts.forEach(item => item.isSelected = val)
}
}
}
});
</script>
</body>
</html>
watch侦听器
watch侦听器属性:
- 当监听一些属性的改变,他改变时需要做某些事,此时就可以使用侦听器属性;
- 写在watch属性中的对象的属性是会被监控的,当被监控的属性值发生变化时,就会触发对应的函数。
- 侦听器属性可以使用异步
- watch监听响应式数据的改变(watch中监听的响应式数据必须在data中初始化)和computed中的setter类似,区别是computed是自己单独设置的计算属性(不能和data中的冲突),而watch只能监听data中的属性
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<input type="text" v-model="text" @input="fn"> <br>
{{msg}}
</div>
<script src="vue.js"></script>
<script>
// 侦听器属性:watch
// 当我们需要监听一个属性的改变,当他改变的时候我们要做某些事,此时我们就需要使用侦听器属性;
let vm = new Vue({
el: '#app',
data: {
text: '',
msg: ''
},
watch: {
// 属性名:被监控的属性名,例如text;属性值是一个函数
text(newVal, oldVal) {
// console.log(newVal, oldVal);
// newVal是被监控属性的新值
// oldVal 是被监控的属性的旧值
/*if (newVal.length > 5) {
this.msg = '太长了';
} else if (newVal.length < 3) {
this.msg = '太短了';
} else {
this.msg = '';
}*/
// 侦听器属性可以使用异步;
setTimeout(() => {
if (newVal.length > 5) {
this.msg = '太长了';
} else if (newVal.length < 3) {
this.msg = '太短了';
} else {
this.msg = '';
}
}, 0)
}
// 能用表单的事件就用事件或者使用计算属性;这两种都不行的时候再用watch;
},
methods: {
fn() {
console.log(this.text);
}
}
})
</script>
</body>
</html>
watch和computed区别
computed
- 页面加载时就求值,当依赖的数据发生改变时会重新求值
- 不支持异步
- computed可以依赖多个属性,被依赖的属性有一个发生变化,就会重新求值,等同于监控多个属性
watch
- 页面加载时,watch不会执行,只有被监控的数据发生变化时才会执行对应的函数
- 支持异步
- 一个函数对应一个被监控的属性,只有当这个属性的值发生变化时才会执行这个函数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<input type="text" v-model="firstName"> <br>
<input type="text" v-model="lastName"> <br>
<p>{{fullName}}</p>
</div>
<script src="vue.js"></script>
<script>
let vm = new Vue({
el: '#app',
data: {
firstName: 'm',
lastName: 'l',
fullName: ''
},
/*computed: {
fullName() {
return this.lastName + this.firstName;
}
}*/
watch: {
firstName(newVal, oldVal) {
this.fullName = newVal + this.lastName;
},
lastName(newVal, oldVal) {
this.fullName = this.firstName + newVal;
}
}
})
</script>
</body>
</html>