双向绑定原理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.objobj: {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: "你好",},});//等价于设置的elvm.$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);},// 计算属性的settercheckAll: {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>
