vue2 响应-ast-render 函数

47 阅读2分钟
<!DOCTYPE html>
<html>

<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="content">
      <div class="box" data-id="34" @click="test12" v-if="show">{{msg}}</div>
      <div>{{age}}</div>
      <div>{{_msg}}</div>
    </div>
  </div>

  <div onclick="handlerT()">
    点击
  </div>

  <script type="text/javascript">
    
    (function (w) {
      class Observer {
        constructor(data) {
          setConstantProperty(data, '__ob__', this);
          this.dep = new Dep()
          if (isArray(data)) {
            data.__proto__ = newArrMethods;
            this.observeArr(data);
          } else {
            this.walk(data);
          }
        }

        walk(data) {
          const keys = Object.keys(data);

          keys.map((key) => {
            defineReactive(data, key, data[key]);
          });
        }

        observeArr(data) {
          data.map((item) => {
            observe(item);
          });
        }
      }

      function setConstantProperty(data, key, value) {
        Object.defineProperty(data, key, {
          enumerable: false,
          configurable: false,
          value
        });
      }

      function defineReactive(data, key, value) {
        let childOb = observe(value);//递归 监听对象
        let dep = new Dep()
        Object.defineProperty(data, key, {
          get() {
            dep.depend()
            return value;
          },
          set(newValue) {
            if (value === newValue) return;
            value = newValue;
            dep.notify()
          }
        })
      }


      function observe(data) {
        if (!isObject(data) || data.__ob__) {
          return data;
        }
        return new Observer(data);
      }

      class Dep {
        constructor() {
          this.subs = []
        }
        addSub(watcher) {
          this.subs.push(watcher)
        }
        depend() {
          if (Dep.target) {
            Dep.target.addDep(this)
          }
        }
        notify() {
          this.subs.forEach((watcher) => {//依次执行回调函数 
            watcher.update()
          })
        }

      }

      let watcherId = 0
      let watcherQueue = []
      let targetStack = []

      class Watcher {
        constructor(vm, exp, cb, options = {}, render = false) {
          this.dirty = this.lazy = !!options.lazy
          this.vm = vm;
          this.exp = exp;
          this.cb = cb;
          this.id = ++watcherId
          this.deps = [];
          if (!this.lazy) {
            this.value = this.get()
            render && this.update()
          }
        }
        addDep(dep) {
          //dep实例有可能被收集过,如果收集过,则直接返回
          if (this.deps.indexOf(dep) !== -1) {
            return
          }
          
          this.deps.push(dep)
          dep.addSub(this)
        }
        get() {
          targetStack.push(this)
         // console.log(this, 'get');
          Dep.target = this
          if (typeof this.exp === 'function') {
            this.value = this.exp.call(this.vm)
            //console.log(this.value, "watcher")
          }else if(typeof this.exp === 'object'){
            this.value = this.vm.noop
          }else {
            this.value = this.vm[this.exp]
          }

         
          targetStack.pop()
          //console.log(targetStack, 'get');
          if (targetStack.length > 0) {
            //将栈顶的watcher拿出来放到“舞台”
            Dep.target = targetStack[targetStack.length - 1]
          } else {
            Dep.target = null
          }
        }
        update() {
          if (this.lazy) {
            this.dirty = true
          } else {
            this.run()
          }
        }
        run() {
          if (watcherQueue.indexOf(this.id) !== -1) { //已经存在于队列中
            return
          }
          watcherQueue.push(this.id)

          Promise.resolve().then(() => {
            this.get()
            this.cb.call(this.vm)
            let index = watcherQueue.indexOf(this.id)
            watcherQueue.splice(index, 1)
          })
        }
      }
      //======工具====================

      function isObject(value) {
        return typeof value === 'object' && value !== null;
      }

      function isArray(value) {
        return Array.isArray(value);
      }
      function query(el) {
        return document.querySelector(el)
      }

      //======mina====================
      function jui(el, options) {
        this.el = el;
        this.$options = options;
        this._init();
      }
      init$(jui)
      init2$(jui)

      initReader(jui);

      function init$(jui) {
        jui.prototype._init = function () {
          this.initState();
          this.initComputed();
          this.initWatch();
          this.mount();
        }
      }


      function init2$(jui) {
        jui.prototype.initState = function () {
          var vm = this;
          let data = vm.$options.data;
          vm._data = data = typeof data === 'function' ? data.call(vm) : data;
          data['noop'] = "l.0";
          for (let key in data) {
            proxy(vm, '_data', key);
          }
          observe(data)

        }
        jui.prototype.initWatch = function () {
          let watch = this.$options.watch;

          if (watch) {
            let keys = Object.keys(watch);
            for (let i = 0; i < keys.length; i++) {
              new Watcher(this, keys[i], watch[keys[i]],{}, true)
            }
          }

        }
        jui.prototype.initComputed = function () {

          let computed = this.$options.computed;
          if (!computed) return;

          let keys = Object.keys(computed)
          for (let i = 0; i < keys.length; i++) {

            const watcher = new Watcher(this, computed[keys[i]], function () { }, {
              lazy: true
            })


            // console.log(keys[i]);

            Object.defineProperty(this, keys[i], {
              enumerable: true,
              configurable: true,
              get: function computedGetter() {
                //1号watcher lazy watcher 计算属性
                // watcher.dirty = true
                if (watcher.dirty) {
                  watcher.get()
                  watcher.dirty = false
                }
                //person已经收集了 1号watcher,此时1号watcher也记录了person的dep
                //watcher.get()
                //watcher.exp.call(watcher.vm)
                
                if (Dep.target) {
                  //console.log(watcher.deps)
                  //1号watcher收集到的dep,把这些dep一个个拿出来通知他们收集,现在仍然在台上的2号watcher
                  for (let j = 0; j < watcher.deps.length; j++) {
                    watcher.deps[j].depend()
                  }
                }
                //console.log(watcher.value, "computed");
                return watcher.value
              },
              set: function computedSetter() {
                console.warn('不要给计算属性赋值')
              }
            })

          }
        }

        function proxy(vm, target, key) {
          Object.defineProperty(vm, key, {
            get() {
              return vm[target][key];
            },
            set(newValue) {
              if (vm[target][key] === newValue) return;
              vm[target]["noop"] = newValue + Date.parse(new Date());
              vm[target][key] = newValue;
            }
          })
        }
      }

      function initReader(jui) {
        jui.prototype.mount = function () {
          // var reder = compileToRenderFunction(el.outerHTML);
          var vm = this;
          new Watcher(vm, {}, function (value) {
            updateHtml(vm);
          }, {}, true)
        }

        let _template = "";
        function updateHtml(vm) {
          const reg_double_bracket = /\{\{(.*?)\}\}/g;//双括号{{}}
          if (!_template) _template = query(vm.el).innerHTML;
          query(vm.el).innerHTML = replaceVar(_template, vm, reg_double_bracket);
        }

        function replaceVar(html, data, reg) {
          return html.replace(reg, (node, key) => {
            const obj = {};
            key = key.trim();
            return obj[key] = data[key];
          });
        }

        function compileToRenderFunction(template) {
          var ast = parse(template)
          // console.log(ast);
        }
        function parse(html) {
          // id="app" id='app' id=app
          const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;
          //标签名  <my-header></my-header>
          const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`;
          // <my:header></my:header>
          const qnameCapture = `((?:${ncname}\\:)?${ncname})`;
          // <div
          const startTagOpen = new RegExp(`^<${qnameCapture}`);
          // > />
          const startTagClose = /^\s*(\/?)>/;
          // </div>
          const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`);

          let text,
            root,
            currentParent,
            stack = [];
          let i = 1;

          while (html) {
            let textEnd = html.indexOf('<');
            //console.log(textEnd);
            if (textEnd === 0) {
              const startTagMatch = parseStartTag();
              if (i == 2) {
                console.log('startTagMat', startTagMatch);
              }

              if (startTagMatch) {
                start(startTagMatch.tagName, startTagMatch.attrs);
                continue;
              }

              const endTagMatch = html.match(endTag);
              // console.log("endTagMatch", endTagMatch);
              if (endTagMatch) {
                advance(endTagMatch[0].length);
                end(endTagMatch[1]);
                continue;
              }

            }

            if (textEnd > 0) {
              text = html.substring(0, textEnd);
            }

            if (text) {
              advance(text.length);
              chars(text);
            }
            i++
          }

          function start(tagName, attrs) {
            const element = createASTElement(tagName, attrs);

            if (!root) {
              root = element;
            }

            currentParent = element;
            stack.push(element);
          }

          function end(tagName) {
            // span
            // 
            console.log(stack);
            const element = stack.pop();
            // div
            currentParent = stack[stack.length - 1];
            if (currentParent) {
              // span => parent => div
              element.parent = currentParent;
              // div => children => push => span
              currentParent.children.push(element);
            }
          }

          function parseStartTag() {
            const start = html.match(startTagOpen);
            let end,
              attr;
            if (start) {
              const match = {
                tagName: start[1],
                attrs: []
              }
              advance(start[0].length);

              while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {
                match.attrs.push({
                  name: attr[1],
                  value: attr[3] || attr[4] || attr[5]
                });
                advance(attr[0].length);
              }
              if (end) {
                advance(end[0].length);
                return match;
              }

            }
          }

          function advance(n) {
            html = html.substring(n);
          }

          function createASTElement(tagName, attrs) {
            return {
              tag: tagName,
              type: 1,
              children: [],
              attrs,
              parent
            }
          }

          function chars(text) {
            text = text.trim();

            if (text.length > 0) {
              currentParent.children.push({
                type: 3,
                text
              })
            }
          }
          return root;
        }

      }


      w.Jui = jui;

    })(window)



    var jui = new Jui("#app", {
      computed: {
        _msg: function () {
          return this.msg + this.age;
        }
      },
      watch: {
        _msg: function () {
          console.log("watch!")
        },
      },
      data: function () {
        return {
          msg: 3,
          name: "Tom",
          age: 18
        }
      }
    });


    let i = 1
    function handlerT() {
      jui.msg = ++i;
      jui.age = ++i;
     // console.log(jui)
    }

    //console.log("OK", jui)



  </script>


</body>

</html>