js 脏检测

基础知识

<!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>
      <input type="text" ng-bind="name" />
      <button type="button" ng-click="increment">increment</button>
      <div ng-bind="name"></div>
    </div>
    <script>
      class Watcher {
        constructor(name, last, exp, listener) {
          this.name = name; // 数据变量名
          this.last = last; // 数据变量旧值
          this.newVal = exp; // 返回数据变量新值的函数
          this.listener = listener || function () {}; // 监听回调函数,变量“脏”时触发
          this.listener(this.last, this.newVal());
        }
      }

      class Scope {
        constructor() {
          // 观察者数组
          this.watchers = [];
        }

        // 添加数据观察者
        watch(name, exp, listener) {
          this.watchers.push(new Watcher(name, "", exp, listener));
        }

        // 对监视器的新旧值进行对比
        // 当新旧值不同时,调用listener函数进行相应操作
        // 并将旧值更新为新值。它将不断重复这一过程,直到所有数据变量的新旧值相等:
        digest() {
          let dirty = true;
          while (dirty) {
            dirty = false;
            this.watchers.forEach(watcher => {
              let newVal = watcher.newVal();
              var oldVal = watcher.last;
              if (newVal !== oldVal) {
                dirty = true;
                watcher.listener(oldVal, newVal);
                watcher.last = newVal;
              }
            });
          }
        }
      }

      class App extends Scope {
        name = "Ajanuw";

        constructor() {
          super();
        }

        increment() {
          this.name += "+";
        }
      }

      const app = new App();
      run(app);
      function run(app) {
        document // 绑定依赖观察者
          .querySelectorAll("[ng-bind]")
          .forEach(it => {
            const nodeName = it.nodeName.toLowerCase();
            const bindKey = it.getAttribute("ng-bind");
            if (bindKey in app) {
              app.watch(
                bindKey,
                () => app[bindKey],
                (oldVal, newVal) => {
                  if (nodeName === "input") {
                    it.value = newVal;
                  } else {
                    it.textContent = newVal;
                  }
                }
              );
            }
          });

        // 绑定事件
        document.querySelectorAll("[ng-click]").forEach(it => {
          const bindKey = it.getAttribute("ng-click");
          it.addEventListener("click", e => {
            if (app[bindKey] && typeof app[bindKey] === "function") {
              app[bindKey]();
              app.digest();
            }
          });
        });

        // 双向绑定
        document.querySelectorAll("input[ng-bind]").forEach(it => {
          const bindKey = it.getAttribute("ng-bind");
          it.addEventListener("input", e => {
            app[bindKey] = it.value;
            app.digest();
          });
        });
      }
    </script>
  </body>
</html>

监听object和array的修改

<!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>
      <input type="text" ng-bind="name" />
      <button type="button" ng-click="increment">increment</button>
      <div ng-bind="name"></div>
      <hr />
      <div ng-bind="obj"></div>
      <button ng-click="changeValue">改变object的值</button>
    </div>
    <script>
      function equal(obj, other) {
        const objectTag = "[object Object]";
        const arrayTag = "[object Array]";
        const _tostring = value => Object.prototype.toString.call(value);
        const emptyp = value => JSON.stringify(value).length === 2;
        function Equal(obj, other) {
          let objTag = _tostring(obj);
          let otherTag = _tostring(other);

          // 非集合,使用===判断
          if (
            objTag !== objectTag &&
            objTag !== arrayTag &&
            otherTag !== objectTag &&
            otherTag !== arrayTag
          ) {
            return obj === other;
          }

          // 集合类型不一样
          if (objTag !== otherTag) return false;

          // 集合元素数量不一样
          if (
            Object.getOwnPropertyNames(obj).length !==
            Object.getOwnPropertyNames(other).length
          )
            return false;

          // 类型一样的空集合,永远相等。
          if (emptyp(obj) && emptyp(other)) return true;

          let rsult = false;
          for (const k in obj) {
            if (k in other) {
              const obj_value = obj[k];
              const other_value = other[k];
              rsult = Equal(obj_value, other_value);
            } else {
              return false;
            }
          }
          return rsult;
        }

        return Equal(obj, other);
      }
      function copytree(tree, all = true) {
        const objectTag = "[object Object]";
        const arrayTag = "[object Array]";

        const _tostring = value => Object.prototype.toString.call(value);
        // 记录所有的对象
        const map = new WeakMap();
        function copyTree(tree, all = true) {
          const treeTag = _tostring(tree);
          const res =
            treeTag === objectTag ? {} : treeTag === arrayTag ? [] : tree;

          if (treeTag !== objectTag && treeTag !== arrayTag) return res;

          // 判断是否有此对象
          if (map.has(tree)) {
            // 直接返回
            return tree;
          } else {
            map.set(tree, true);
          }

          const t = all ? Object.getOwnPropertyNames(tree) : tree;

          if (all) {
            for (const i in t) {
              const k = t[i];
              res[k] = copyTree(tree[k], all);
            }
          } else {
            for (const k in t) {
              res[k] = copyTree(tree[k], all);
            }
          }

          return res;
        }

        return copyTree(tree, all);
      }
      function evalFun(bindKey, data) {
        try {
          const r = Function(`with(this){ return ${bindKey} }`).apply(
            data,
            arguments
          );
          return r === "" ? undefined : r;
        } catch (error) {}
      }
      function setData(key, newValue, context) {
        return Function(`return function(d) {
        with(this){
          ${key} = d;
        }
      }`)().call(context, newValue);
      }

      class Watcher {
        constructor(last, exp, listener, valueEq) {
          this.last = last; // 数据变量旧值
          this.newVal = exp; // 返回数据变量新值的函数
          this.listener = listener || function () {}; // 监听回调函数,变量“脏”时触发
          this.valueEq = valueEq;
          this.listener(this.last, this.newVal());
        }
      }

      class Scope {
        constructor() {
          // 观察者数组
          this.watchers = [];
        }

        // 添加数据观察者
        // valueEq检查值,而不是引用
        watch(v, exp, listener, valueEq = false) {
          this.watchers.push(
            new Watcher(valueEq ? copytree(v) : v, exp, listener, valueEq)
          );
        }

        // 对监视器的新旧值进行对比
        // 当新旧值不同时,调用listener函数进行相应操作
        // 并将旧值更新为新值。它将不断重复这一过程,直到所有数据变量的新旧值相等:
        digest() {
          let dirty = true;
          // while (dirty) {
          //   dirty = false;
          this.watchers.forEach(watcher => {
            const newVal = watcher.newVal();
            const oldVal = watcher.last;
            if (!this.valueEqual(newVal, oldVal, watcher.valueEq)) {
              dirty = true;
              watcher.listener(oldVal, newVal);
              watcher.last = watcher.valueEq ? copytree(newVal) : newVal;
            }
          });
          // }
        }

        valueEqual(newValue, oldValue, valueEq) {
          if (this.valueEq) {
            return equal(newValue, oldValue);
          } else {
            return newValue === oldValue;
          }
        }
      }

      class App extends Scope {
        name = "Ajanuw";
        obj = {
          value: "hello world"
        };

        constructor() {
          super();
        }

        increment() {
          this.name += "+";
        }

        changeValue() {
          this.obj.value = "hello ajanuw";
        }
      }

      const app = new App();
      run(app);
      function run(app) {
        document // 绑定依赖观察者
          .querySelectorAll("[ng-bind]")
          .forEach(it => {
            const nodeName = it.nodeName.toLowerCase();
            const bindKey = it.getAttribute("ng-bind");
            const v = evalFun(bindKey, app);
            if (v) {
              app.watch(
                v,
                () => evalFun(bindKey, app),
                (oldVal, newVal) => {
                  if (nodeName === "input") {
                    it.value = newVal;
                  } else {
                    if (typeof newVal === "object") {
                      it.textContent = JSON.stringify(newVal);
                    } else {
                      it.textContent = newVal;
                    }
                  }
                },
                typeof v === "object" && v !== null
              );
            }
          });

        // 绑定事件
        document.querySelectorAll("[ng-click]").forEach(it => {
          const bindKey = it.getAttribute("ng-click");
          const fn = evalFun(bindKey, app);
          if (fn && typeof fn === "function") {
            it.addEventListener("click", e => {
              fn.call(app);
              app.digest();
            });
          }
        });

        // 双向绑定
        document.querySelectorAll("input[ng-bind]").forEach(it => {
          const bindKey = it.getAttribute("ng-bind");
          it.addEventListener("input", e => {
            setData(bindKey, it.value, app);
            app.digest();
          });
        });
      }
    </script>
  </body>
</html>
posted @ 2020-03-31 20:56  Ajanuw  阅读(437)  评论(0编辑  收藏  举报