gitee: https://gitee.com/hey-u/vue-core
    createDocumentFragment ()方法,是用来创建一个虚拟的节点对象,或者说,是用来创建文档碎片节点。
    image.png
    image.png

    1. <!DOCTYPE html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
    6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
    7. <title>Document</title>
    8. </head>
    9. <body>
    10. <div id="app">
    11. <input type="text" v-model="school.name">
    12. <div>{{school.name}}</div>
    13. <div>{{school.age}}</div>
    14. {{getNewName}}
    15. <ul>
    16. <li>1</li>
    17. <li>2</li>
    18. </ul>
    19. <button v-on:click="change">更新</button>
    20. <div v-html="msg"></div>
    21. </div>
    22. <!-- <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script> -->
    23. <script src="./MVVM.js"></script>
    24. <script>
    25. let vm = new Vue({
    26. el: '#app',
    27. data: {
    28. school: {
    29. name: "社会",
    30. age: 18
    31. },
    32. name: "xiao",
    33. msg: '<h1>hello world</h1>'
    34. },
    35. computed: {
    36. getNewName() {
    37. return this.school.name + 'xixi'
    38. }
    39. },
    40. methods: {
    41. change() {
    42. this.school.name = "test"
    43. }
    44. }
    45. })
    46. </script>
    47. </body>
    48. </html>
    // 基类 调度
    
    /*
    存放watcher
    实现把Watcher放到每一个需要监听的属性上,当指定数据变化时触发对应的Watcher进行更新,
    而不是监听所有,牵一发动全身(很耗性能)
    */
    class Dep {
      constructor() {
        this.subs = [] // 存放所有的watcher
      }
      // 订阅 (添加观察者)
      addSub(watcher) {
        this.subs.push(watcher)
      }
      // 发布
      notify() {
        this.subs.forEach(watcher => watcher.update())
      }
    }
    
    // 观察者 (发布订阅) -> 里面存放被观察者
    // vm.$watch(vm, 'school', (newV) => {})
    class Watcher {
      constructor(vm, expr, cb) {
        this.vm = vm
        this.expr = expr
        this.cb = cb
        // 默认存放老值
        this.oldV = this.get()
      }
      get() {
        Dep.target = this; // 把 Watcher 挂到 Dep 上
        // 取值,把观察者和数据关联起来
        let val = CompileUtil.getVal(this.vm, this.expr)
        Dep.target = null; // 用完即释放, 监听器之间解耦,不然任何值(不相关的属性)取值都会触发Watcher
        return val
      }
      // 更新操作 (数据更新后,调用观察者的update方法)
      update() {
        let newV = CompileUtil.getVal(this.vm, this.expr)
        if (newV != this.oldV) {
          this.cb(newV)
        }
      }
    }
    
    // 实现数据劫持
    class Observer {
      constructor(data) {
        this.observer(data)
      }
      observer(data) {
        // 如果是对象才观察
        if (data && typeof data === 'object') {
          for (const key in data) {
            this.defineReactive(data, key, data[key])
          }
        }
      }
      defineReactive(obj, key, value) {
        this.observer(value)
        // 给每个属性增加 发布订阅 功能
        let dep = new Dep()
        Object.defineProperty(obj, key, {
          get() {
            // 创建 watcher 时会取到对应的内容, 并且把 watcher 放到全局上
            Dep.target && dep.addSub(Dep.target)
            return value
          },
          set: (newV) => {
            if (newV != value) {
              this.observer(newV)
              value = newV
              dep.notify()
            }
          }
        })
      }
    }
    
    class Compiler {
      constructor(el, vm) {
        this.vm = vm
        this.el = this.isElementNode(el) ? el : document.querySelector(el)
        // 1、把当前节点的元素获取到,放到内存中
        let fragment = this.node2fragment(this.el)
        // 2、把节点中的内容进行替换
        // 3、用数据编译模板
        this.compile(fragment)
        // 4、把内容塞到页面中
        this.el.appendChild(fragment);
      }
    
      // 判断节点是否带有指令属性
      isDirective(attrName) {
        return attrName.startsWith('v-')
      }
    
      // 编译元素的 v-xxx
      compileElement(node) {
        let attrs = node.attributes;
        [...attrs].forEach(attr => {
          let { name, value: expr } = attr
          // v-model v-html v-text v-for v-if v-show v-on:click ...
          if (this.isDirective(name)) {
            let [, directive] = name.split('-')
            let [directiveName, eventName] = directive.split(':')
            // 调用不同的指令处理 (节点,表达式,当前实例对象)
            CompileUtil[directiveName](node, expr, this.vm, eventName)
          }
        })
      }
    
      // 编译文本的  {{a}} {{b}}
      compileText(node) {
        let content = node.textContent
        if (/\{\{(.+?)\}\}/.test(content)) {
          CompileUtil['text'](node, content, this.vm)
        }
      }
    
      // 核心的编译方法(编译内存dom节点)
      compile(node) {
        let childNodes = node.childNodes;
        [...childNodes].forEach(child => {
          if (this.isElementNode(child)) {
            this.compileElement(child)
            this.compile(child)
          } else {
            this.compileText(child)
          }
        })
      }
    
      // 把节点移动到内存中
      node2fragment(node) {
        let fragment = document.createDocumentFragment()
        let firstChild
        while (firstChild = node.firstChild) {
          fragment.appendChild(firstChild)
        }
        return fragment
      }
      isElementNode(node) {
        return node.nodeType === 1
      }
    }
    
    // 编译工具(策略模式处理)
    CompileUtil = {
      // 通过 expr 表达式 获取实例上的属性值
      getVal(vm, expr) {
        return expr.split('.').reduce((data, curKey) => {
          return data[curKey]
        }, vm.$data)
      },
      setVal(vm, expr, val) {
        expr.split('.').reduce((data, curKey, index, arr) => {
          if (arr.length - 1 === index) {
            // 给属性设置新值
            data[curKey] = val
          }
          return data[curKey]
        }, vm.$data)
      },
      model(node, expr, vm) {
        let fn = this.updater['modelUpdater']
        // 添加观察者 (数据更新后,会触发回调,更新视图)
        new Watcher(vm, expr, (newV) => {
          fn(node, newV)
        })
        // 监听事件 -> 双向绑定
        node.addEventListener('input', (e) => {
          let val = e.target.value
          this.setVal(vm, expr, val)
        })
        let value = this.getVal(vm, expr)
        fn(node, value)
      },
      html(node, expr, vm) { // v-html="msg"
        let fn = this.updater['htmlUpdater']
        // 添加观察者 (数据更新后,会触发回调,更新视图)
        new Watcher(vm, expr, (newV) => {
          fn(node, newV)
        })
        let value = this.getVal(vm, expr)
        fn(node, value)
      },
      on(node, expr, vm, eventName) { // v-on:click="change"
        node.addEventListener(eventName, (e) => {
          vm[expr].call(vm, e)
        })
      },
      text(node, expr, vm) { // {{a}} {{b}}
        let fn = this.updater['textUpdater']
        let content = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
          new Watcher(vm, args[1], (newV) => {
            this.getContentValue(vm, expr)
            fn(node, newV)
          })
          return this.getVal(vm, args[1])
        })
        fn(node, content)
      },
      getContentValue(vm, expr) {
        return expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
          return this.getVal(vm, args[1])
        })
      },
      // 提取更新方法 (数据插入到节点中)
      updater: {
        modelUpdater(node, value) {
          node.value = value
        },
        htmlUpdater(node, value) { // xss攻击
          node.innerHTML = value
        },
        textUpdater(node, value) {
          node.textContent = value
        }
      }
    }
    
    class Vue {
      constructor(options) {
        this.$el = options.el
        this.$data = options.data
        let computed = options.computed
        let methods = options.methods
        if (this.$el) {
          // 1、数据劫持(属性转化)
          new Observer(this.$data)
    
          // 2、处理 computed (有依赖关系)
          // {{getNewName}} reduce vm.$data.getNewName
          for (const key in computed) {
            Object.defineProperty(this.$data, key, {
              get: () => {
                return computed[key].call(this)
              },
            })
          }
          // 代理 methods
          for (const key in methods) {
            Object.defineProperty(this, key, {
              get: () => {
                return methods[key]
              },
            })
          }
          // 3、数据代理,属性穿透,this.$data.xxx -> this.xxx
          this.proxyVm(this.$data)
          // 4、编译器
          new Compiler(this.$el, this)
        }
      }
      proxyVm(data) {
        // 遍历所有属性做代理
        for (const key in data) {
          // this[key] 返回 data[key]
          Object.defineProperty(this, key, {
            get() {
              return data[key]
            },
            set(newV) {
              data[key] = newV
            }
          })
        }
      }
    }