1. 双向绑定原理vue利用Object.definedProperty 这个方法遍历data中所有的属性,给每一个属性增 setter方法,和getter方法,当数据发生变化时,会触发setter方法,当获取数据时,会触发getter方法; Object.definedPropertyIE以下是不兼容的;vue 是不兼容IE8 以下;

双向数据绑定实现步骤:

  • 实现一个监听器observer:对数据数据对象进行遍历,包括子属性对象的属性,利用Object.defineProperty()对属性都加上setter和getter。这样的话,给这个对象的某个值赋值,会触发setter,就可以监听到数据变化
  • 实现一个解析器compile:解析vue模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每一个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据变动,收到通知,调用更新函数进行数据更新
  • 实现一个订阅者watcher:watcher订阅者是observer和compile之间通信的桥梁,主要的任务是订阅observer中的属性值变化的消息,当收到属性值变化的消息时,触发解析器compile中对应的更新函数
  • 是实现一个订阅器dep:订阅器采用发布-订阅模式,用来收集订阅者watcher,对监听器observer和订阅者watcher进行统一管理

注意:

用get和set的数据才是响应式数据,才可以在更新的数据后,vue帮我们去渲染视图

  1. get :
  2. $attrs:f()
  3. $listeners:f()
  4. obj:f()
  5. set
  6. $attrs:f()
  7. $listeners:f()
  8. obj:f()
  9. _proto_:Object
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8" />
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  6. <title>Document</title>
  7. </head>
  8. <body>
  9. <div id="app">
  10. <div class="baseInfo">
  11. <h3>姓名:{{obj.name}}</h3>
  12. <p>年龄:{{obj.age}}</p>
  13. <p></p>
  14. </div>
  15. </div>
  16. <script src="../vue/node_modules/vue/dist/vue.js"></script>
  17. <script>
  18. //对对象的劫持
  19. //forceUpdate 强制通知视图重新渲染
  20. let vm = new Vue({
  21. el: "#app",
  22. data: {
  23. //data中设置的响应式是数组,可以挂载在实例上vm.obj
  24. obj: {
  25. name: "zhufnge",
  26. age: 10,
  27. sex: 0,
  28. score:{
  29. en:12
  30. }
  31. },
  32. },
  33. });
  34. </script>
  35. <script>
  36. //观察者:把数据劫持,对对象进行深层次处理
  37. function observer(obj){
  38. if(obj && typeof obj === "object"){
  39. for (let key in obj){
  40. if(!obj.hasOwnProperty(key)) break;
  41. defineReactive(obj,key,obj[key])
  42. }
  43. }
  44. }
  45. //数据劫持
  46. function defineReactive(obj,attr,value){
  47. observer(value)
  48. Object.definProperty(obj,attr,{
  49. get(){
  50. return value
  51. },
  52. set(newValue){
  53. observer(newValue)
  54. if (newValue===value) return;
  55. value=newValue
  56. }
  57. })
  58. }
  59. //基于$set处理数据,也会进行数据劫持
  60. function $set(obj,attr,value){
  61. }
  62. </script>
  63. </body>
  64. </html>

v-model

首先可以把响应数据绑定在文本框中,并且可以监听文本框内的改变,内容改变后会修改响应数据,相应数据一更改,视图还会重新渲染

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8" />
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  6. <title>Document</title>
  7. </head>
  8. <body>
  9. <div id="app">
  10. <!--
  11. v-model:首先可以把响应数据绑定在文本框中,并且可以监听文本框内的改变,内容改变后会修改响应数据,相应数据一更改,视图还会重新渲染
  12. -->
  13. <input type="text" v-model="text" />
  14. <p v-text="text"></p>
  15. </div>
  16. <script src="../vue/node_modules/vue/dist/vue.js"></script>
  17. <script>
  18. let vm = new Vue({
  19. data: {
  20. text: "你好",
  21. },
  22. });
  23. //等价于设置的el
  24. vm.$mount("#app");
  25. </script>
  26. <script>
  27. let data = {
  28. text: "年后",
  29. };
  30. let temp = {
  31. ...data,
  32. };
  33. Object.defineProperty(data, "text", {
  34. get() {},
  35. set(newValue) {
  36. temp.text = newValue; //这里不能用data.text=newValue 会陷入死循环
  37. render()
  38. },
  39. });
  40. //根据数据去渲染视图
  41. function render() {
  42. inpBox.value = temp.text;
  43. conBox.innerHTML = temp.text;
  44. }
  45. render();
  46. //视图更新控制数据的更新
  47. inpBox.addEventListener("input", function () {
  48. let val = inpBox.value;
  49. data.text = val;
  50. });
  51. </script>
  52. </body>
  53. </html>

filter过滤器

过滤器:是一种处理数据但是不会改变原数据的数据处理方式,一般用于格式化数据

1. 全局过滤器

Vue.filter(过滤器,callback)

2. 局部过滤器

写在filters中的过滤器均是局部过滤器

3.过滤器语法

使用竖线分隔,把竖线左侧的值传递给竖线右侧的过滤器方法,经过方法处理之后,把处理后的结果展示在视图中的过滤器方法只能在胡子语法{{}}和v-bind中使用,过滤器中的方法并没有挂载在实例上。

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <div id="app">
  9. <ul>
  10. <li v-for="(item, index) in products" :key="index">
  11. 商品{{item.name}}
  12. 价格{{item.price | toDollar}}
  13. 国行{{item.price | toRMB | toFixed(3)}}
  14. <!--| 叫做管道符,把它前面的值传递给过滤器函数的第一个参数;然后数据就会展示成过滤器函数的返回值-->
  15. <!--过滤器可以连续使用,后面的过滤器的参数,是上一个过滤器的处理结果,数据会展示成最后一个过滤器的结果-->
  16. <!--过滤器可以传参,参数是传给第二个形参的,第一个参数是管道符前面的值-->
  17. </li>
  18. </ul>
  19. </div>
  20. <script src="vue.js"></script>
  21. <script>
  22. // 过滤器:是一种处理数据但是不会改变原数据的数据处理方式,一般用来格式化数据;
  23. // 全局过滤器:Vue.filter(过滤器, callback)
  24. // Vue.filter('toDollar', (val) => '$' + val);
  25. // Vue.filter('toRMB', (val) => val * 6.853);
  26. // Vue.filter('toFixed', val => '¥' + val.toFixed(2));
  27. let vm = new Vue({
  28. el: '#app',
  29. data: {
  30. products: [
  31. {
  32. name: '苹果',
  33. price: 1230
  34. },
  35. {
  36. name: '香蕉',
  37. price: 1000
  38. },{
  39. name:'樱桃'
  40. price:2999
  41. ]
  42. },
  43. filters: { // 写在filters里面的过滤器是局部过滤器
  44. // toDollar: function (val) {},
  45. toDollar (val) {
  46. //=>val:需要过滤的数据 return返回的是过滤后的结果
  47. return'$' + val
  48. },
  49. toRMB(val) {
  50. return val * 6.8534
  51. },
  52. toFixed(val, num = 2) {
  53. return '¥' + val.toFixed(num);
  54. }
  55. }
  56. })
  57. </script>
  58. </body>
  59. </html>
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8"/>
  5. </head>
  6. <body>
  7. <div id="app">
  8. <input type="text" v-model='text'>
  9. <br>
  10. <!-- <span v-text='text.replace(/\b[a-zA-Z]+\b/g,item=>{
  11. return item.charAt(0).toUpperCase()+item.substring(1);
  12. })'></span> -->
  13. <!-- <span v-text='toUP(text)'></span> -->
  14. <span>{{text|toUP|filterB}}</span>
  15. <!-- <img :src="pic|picHandle" alt=""> -->
  16. </div>
  17. <!-- IMPORT JS -->
  18. <script src="./node_modules/vue/dist/vue.js"></script>
  19. <script>
  20. let vm = new Vue({
  21. el: '#app',
  22. data: {
  23. //=>响应式数据:DATA中准备的要在视图中渲染的数据(MODEL)
  24. text: ''
  25. },
  26. methods: {
  27. //=>都会挂载到实例上(不能和DATA中的属性名冲突):这里的制定的方法是普通方法,可以在视图中调取使用,也可以在其它方法中调取使用
  28. toUP(value) {
  29. return value.replace(/\b[a-zA-Z]+\b/g, item => {
  30. return item.charAt(0).toUpperCase() + item.substring(1);
  31. });
  32. }
  33. },
  34. filters: {
  35. //=>设置过滤器:把需要在视图中渲染的数据进行二次或者多次的处理
  36. toUP(value) {
  37. //=>value:需要过滤的数据 return返回的是过滤后的结果
  38. return value.replace(/\b[a-zA-Z]+\b/g, item => {
  39. return item.charAt(0).toUpperCase() + item.substring(1);
  40. });
  41. },
  42. filterB(value) {
  43. return value.split('').reverse().join('');
  44. },
  45. picHandle(value){
  46. return value.length===0?'http://www.zhufengpeixun.cn/static/1.png':value;
  47. }
  48. }
  49. });
  50. </script>
  51. </body>
  52. </html>

computed计算属性

计算属性:处理某一个或者某些属性复杂展示逻辑,不会改变原数据;目的是不在模板中写太多逻辑。 computed里面的属性最终会被vm取代,这些属性都会在vm身上有一份。

使用computed属性的情况

  1. 数据显示
  2. 需要显示的数据依赖其他数据 ,通过其他数据计算出来的
  3. 当他依赖的数据发生变化时,会重新计算;

    computed注意点

    1. computed里面的属性会被vm所代理;
      2. computed里面的属性和data/methods/filters/都不能重名;
      3. computed的计算属性可以是一个函数还可以是一个对象;对象中有get和set方法,取值的时候执行get,设置的时候执行set;而函数形式的计算属性,只有get的情况,只能获取不能设置,如果设置会报错;
      4. 如果一个值需要依赖其他属性计算而来,这个时候最好用计算属性;
  4. 计算属性不能写在异步处理程序:ajax,定时器,promise的then

  5. computed的getter不支持异步获取数据

    computed

    • 计算属性不是一个方法,而是属性,因此在视图调用的时候不能加括号
    • 计算属性对应的值,会被挂载在当前实例上,挂载的内容是函数的返回值(getter函数的处理结果),
    • 计算属性会有对应的缓存,当计算属性依赖的值不发生改变时,视图刷新,它会使用之前的数据结果进行渲染,不会再执行函数(依赖:在函数里用到了那个变量,就是依赖了那个变量)
    • 计算属性中必须关联一个响应式的数据,否则getter函数(getter函数:获取这个属性值就会触发get函数执行;setter函数:给属性设置的时候会触发set函数,value是给这个属性设置的值)只执行一次
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. <link rel="stylesheet" href="bootstrap.css">
  7. </head>
  8. <body>
  9. <div id="app">
  10. <div class="container">
  11. <div class="row">
  12. <table class="table table-bordered">
  13. <tr>
  14. <td>
  15. 全选:<input type="checkbox"
  16. v-model="checkAll">
  17. </td>
  18. <td>商品</td>
  19. <td>数量</td>
  20. <td>单价</td>
  21. <td>小计</td>
  22. </tr>
  23. <tr v-for="(product, index) in carts" :key="index">
  24. <td>
  25. <input type="checkbox"
  26. v-model="product.isSelected"
  27. @change="changeOne">
  28. </td>
  29. <td>
  30. {{product.name}}
  31. </td>
  32. <td>
  33. <input type="number" v-model="product.count" min="1">
  34. </td>
  35. <td>
  36. {{product.price}}
  37. </td>
  38. <td>
  39. {{product.count * product.price | toRMB}}
  40. </td>
  41. </tr>
  42. <tr>
  43. <td colspan="5">
  44. 总价:{{total | toRMB}}
  45. </td>
  46. </tr>
  47. </table>
  48. <input type="text" v-model="total">
  49. </div>
  50. </div>
  51. </div>
  52. <script src="vue.js"></script>
  53. <script>
  54. // npm install 依赖包@版本号 指定版本号安装;如果不指定,就会按照最新的装;
  55. let vm = new Vue({
  56. el: '#app',
  57. data: {
  58. carts: [
  59. {
  60. isSelected: true,
  61. count: 3,
  62. price: 57.86,
  63. name: '车厘子'
  64. },
  65. {
  66. isSelected: true,
  67. count: 1,
  68. price: 6666,
  69. name: 'iPhoneX'
  70. }
  71. ]
  72. },
  73. filters: {
  74. toRMB(val) {
  75. return '¥' + val.toFixed(2)
  76. }
  77. },
  78. methods: {
  79. changeAll() {
  80. },
  81. changeOne() {
  82. }
  83. },
  84. //真实项目中:一般用一个计算属性和某些响应式数据进行关联,响应式数据发生改变,计算属性的GETTER函数会重新执行,否则使用的是上一次计算出来的缓存结果
  85. computed: {
  86. // computed里面的属性最终也会被vm代理,这些属性都会在vm身上也有一份;
  87. total: function () { // 计算属性的getter形式,这样声明的total只能读,不能写;这个属性不能修改,修改它会报错;
  88. //计算属性中必须要关联一个响应式数据,否则getter函数只执行一次
  89. // 首先把打钩的商品筛选出来
  90. let selected = this.carts.filter(i => i.isSelected);
  91. return selected.reduce((prev, next) => {
  92. // next是数组项,现在是对象
  93. return prev + next.count * next.price;
  94. }, 0);
  95. },
  96. // 计算属性的setter
  97. checkAll: {
  98. get() {
  99. //
  100. // 当获取checkAll的时候就会执行get方法,并且取到的值是get方法的返回值
  101. return this.carts.every(item => item.isSelected);
  102. },
  103. set(val) {
  104. // 当修改checkAll属性的时候,会触发set方法,并且val会接收到checkAll的新值;
  105. // console.log(val); // val就是修改checkAll的时候传过来的值
  106. this.carts.forEach(item => item.isSelected = val)
  107. }
  108. }
  109. }
  110. });
  111. </script>
  112. </body>
  113. </html>

watch侦听器

watch侦听器属性:

  • 当监听一些属性的改变,他改变时需要做某些事,此时就可以使用侦听器属性;
  • 写在watch属性中的对象的属性是会被监控的,当被监控的属性值发生变化时,就会触发对应的函数。
  • 侦听器属性可以使用异步
  • watch监听响应式数据的改变(watch中监听的响应式数据必须在data中初始化)和computed中的setter类似,区别是computed是自己单独设置的计算属性(不能和data中的冲突),而watch只能监听data中的属性
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <div id="app">
  9. <input type="text" v-model="text" @input="fn"> <br>
  10. {{msg}}
  11. </div>
  12. <script src="vue.js"></script>
  13. <script>
  14. // 侦听器属性:watch
  15. // 当我们需要监听一个属性的改变,当他改变的时候我们要做某些事,此时我们就需要使用侦听器属性;
  16. let vm = new Vue({
  17. el: '#app',
  18. data: {
  19. text: '',
  20. msg: ''
  21. },
  22. watch: {
  23. // 属性名:被监控的属性名,例如text;属性值是一个函数
  24. text(newVal, oldVal) {
  25. // console.log(newVal, oldVal);
  26. // newVal是被监控属性的新值
  27. // oldVal 是被监控的属性的旧值
  28. /*if (newVal.length > 5) {
  29. this.msg = '太长了';
  30. } else if (newVal.length < 3) {
  31. this.msg = '太短了';
  32. } else {
  33. this.msg = '';
  34. }*/
  35. // 侦听器属性可以使用异步;
  36. setTimeout(() => {
  37. if (newVal.length > 5) {
  38. this.msg = '太长了';
  39. } else if (newVal.length < 3) {
  40. this.msg = '太短了';
  41. } else {
  42. this.msg = '';
  43. }
  44. }, 0)
  45. }
  46. // 能用表单的事件就用事件或者使用计算属性;这两种都不行的时候再用watch;
  47. },
  48. methods: {
  49. fn() {
  50. console.log(this.text);
  51. }
  52. }
  53. })
  54. </script>
  55. </body>
  56. </html>

watch和computed区别

computed

  • 页面加载时就求值,当依赖的数据发生改变时会重新求值
  • 不支持异步
  • computed可以依赖多个属性,被依赖的属性有一个发生变化,就会重新求值,等同于监控多个属性

watch

  • 页面加载时,watch不会执行,只有被监控的数据发生变化时才会执行对应的函数
  • 支持异步
  • 一个函数对应一个被监控的属性,只有当这个属性的值发生变化时才会执行这个函数
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <div id="app">
  9. <input type="text" v-model="firstName"> <br>
  10. <input type="text" v-model="lastName"> <br>
  11. <p>{{fullName}}</p>
  12. </div>
  13. <script src="vue.js"></script>
  14. <script>
  15. let vm = new Vue({
  16. el: '#app',
  17. data: {
  18. firstName: 'm',
  19. lastName: 'l',
  20. fullName: ''
  21. },
  22. /*computed: {
  23. fullName() {
  24. return this.lastName + this.firstName;
  25. }
  26. }*/
  27. watch: {
  28. firstName(newVal, oldVal) {
  29. this.fullName = newVal + this.lastName;
  30. },
  31. lastName(newVal, oldVal) {
  32. this.fullName = this.firstName + newVal;
  33. }
  34. }
  35. })
  36. </script>
  37. </body>
  38. </html>