简介

在前端的三大框架中都有数据响应式的影子,那它到底是怎样去实现(Vue 里面是 E5 语法 object.defindProperty)的呢?
先来看看 Vue 里面是如何使用双向绑定的

  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. {{LGD}}<br>
  10. {{OG}}<br>
  11. <input type="text" v-model="mydata"><br>
  12. {{mydata}}
  13. </div>
  14. <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  15. <script>
  16. let app = new Vue({
  17. el: '#app',
  18. data: {
  19. LGD: '老干爹是不可战胜的',
  20. OG: '内部数据',
  21. mydata: '双向绑定'
  22. },
  23. });
  24. </script>
  25. </body>
  26. </html>

我们使用了 Vue 中提供的文本插值(后续会详细介绍)的方法来提供视图,
输入框中输入数据,就可以实时更新我们的视图
一起来尝试写出一个双向绑定吧~

一个小功能

数据改变=>视图实时改变
视图改变=>数据实时改变

Proxy代理与数据劫持

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <div class="box"></div>
  9. <script>
  10. let box = document.querySelector('.box')
  11. let data = {
  12. name: 'dingdang',
  13. age: 18
  14. }
  15. box.innerHTML = data.age
  16. data.age = 19
  17. console.log(data);
  18. </script>
  19. </body>
  20. </html>

直接开撸~咦,看到了没?上述代码确实可以做到:
数据改变=>视图改变
视图改变=>数据改变
不是实时改变的,因为我们监听到双方任何一方的变化。。。
怎么办呢?
ES6 语法 Proxy 可以解决

Proxy代理

MDN : Proxy 对象用于定义基本操作的自定义行为。
看不懂是吧,我也看不懂~~
代理倒是知道,什么?你不知道?
emm,中介好吧!代理=中介
他的方法有很多,只列出要用的吧

Handler.set()

拦截对象设置属性操作。

  1. let p = new Proxy(target, {
  2. set(target, property, newValue, receiver) {
  3. }
  4. });
  5. target 目标对象
  6. property 属性
  7. newValue 要设置的值
  8. receiver Proxy或者继承Proxy的对象(不常用)

Handler.get()

拦截对象读取属性操作

  1. let p = new Proxy(target, {
  2. get(target, property, receiver) {
  3. }
  4. });
  5. target 目标对象
  6. property 属性
  7. receiver Proxy或者继承Proxy的对象(不常用)

Handler…

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <div class="box"></div>
  9. <script>
  10. let box = document.querySelector('.box')
  11. let data = {
  12. name: 'dingdang',
  13. age: 18
  14. }
  15. /* box.innerHTML = data.age
  16. data.age = 19
  17. console.log(data);*/
  18. let p = new Proxy(data, {
  19. set(target, property, newValue) {
  20. console.log('set...', target, property, newValue)
  21. },
  22. get(target, property,) {
  23. console.log('get...', target, property,)
  24. }
  25. })
  26. </script>
  27. </body>
  28. </html>

嘿,此时控制台什么都没打印!WTF?!
没有打印才是正确的,因为不管是 set 还是 get 都已经被我们拦截。所以什么都不会打印
最后加上对 p 的使用,就可以打印咯

  1. p.age = 19
  2. console.log(p.name)

image.png
我们要监控变化时,只需要监控对象 p 就可以啦。
等等,为什么 p.name 是 undefined 呢?
原因同上解释,被拦截了。那怎么才能打印出来呢?
答案:Reflect,只要把 arguments 里面的东西返回就可以啦

Reflect反射

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <div class="box"></div>
  9. <script>
  10. let box = document.querySelector('.box')
  11. let data = {
  12. name: 'dingdang',
  13. age: 18
  14. }
  15. /* box.innerHTML = data.age
  16. data.age = 19
  17. console.log(data);*/
  18. let p = new Proxy(data, {
  19. set(target, property, newValue) {
  20. console.log('set...', target, property, newValue)
  21. return Reflect.set(...arguments)
  22. },
  23. get(target, property,) {
  24. console.log('get...', target, property,)
  25. return Reflect.get(...arguments)
  26. }
  27. })
  28. p.age = 19
  29. console.log(p.name)
  30. </script>
  31. </body>
  32. </html>

呼呼,写完了,实现了对数据的代理
下一个~

Vue 数据响应式实现

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. <script src="./vm.js"></script>
  7. </head>
  8. <body>
  9. <div id="app">
  10. {{name}}
  11. <div>{{age}}</div>
  12. </div>
  13. <script>
  14. let vm = new vue({
  15. el: '#app',
  16. data: {
  17. name: 'dingdang',
  18. age: 18
  19. }
  20. })
  21. </script>
  22. </body>
  23. </html>
  1. class vue {
  2. //数据响应式
  3. constructor(option) {
  4. this.option = option
  5. this._data = this.option.data
  6. this.el = document.querySelector(this.option.el)
  7. this.compileNode(this.el)
  8. }
  9. //工具函数
  10. //正则匹配&修改视图
  11. compileNode(el) {
  12. let child = el.childNodes;
  13. [...child].forEach(node => {
  14. if (node.nodeType === 3) {
  15. let text = node.textContent;
  16. let reg = /\{\{\s*([^\s\{\}]+)\s*\}\}/g;
  17. if (reg.test(text)) {
  18. let $1 = RegExp.$1;
  19. this._data[$1] && (node.textContent = text.replace(reg, this._data[$1]));
  20. }
  21. } else if (node.nodeType === 1) {
  22. console.log('元素节点');
  23. this.compileNode(node);
  24. }
  25. })
  26. }
  27. }

三个步骤

  1. 获取数据
  2. 在 HTML 里面匹配{{ }}双大括号模板
  3. 修改替换数据

现在我们不仅可以对数据进行代理,还实现了数据的响应式,就差最后一个双向绑定啦
其实只要把他们结合起来就可以了

Vue 双向绑定实现

监控数据

通过自定义事件通知

修改视图