• 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>
    
  • 相关阅读:
    浅入深出Vue:数据绑定
    浅入深出Vue:数据渲染
    浅入深出Vue:环境搭建
    浅入深出Vue:工具准备之PostMan安装配置及Mock服务配置
    浅入深出Vue:工具准备之WebStorm安装配置
    【重点突破】—— react使用http-proxy-middleware反向代理跨域
    【重点突破】—— 工作中git分支的基本使用
    【营销小程序】—— webview嵌套web端项目(原生开发支付功能)
    【营销App】—— react/ant design mobile项目爬坑
    【重点突破】—— UniApp微信小程序开发教程学习Three
  • 原文地址:https://www.cnblogs.com/ajanuw/p/12608256.html
Copyright © 2020-2023  润新知