最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Vue2 响应式原理简单实现

    正文概述 掘金(HJM515)   2020-12-14   451

    Vue2 中,响应式实现的核心就是 ES5 的 Object.defineProperty(obj, prop, descriptor)。通过 Object.defineProperty() 劫持 data 和 props 各个属性的 getter 和 setter , getter 做依赖收集, setter 派发更新。整体来说是一个 数据劫持 + 发布-订阅者模式。

    • vue 初始化阶段( beforeCreate 之后 create 之前),遍历 data/props,调用 Object.defineProperty 给每个属性加上 getter、setter。
    • 每个组件、每个 computed 都会实例化一个 watcher (当然也包括每个自定义 watcher ),订阅渲染/计算所用到的所用 data/props/computed
    • 一旦数据发生变化, setter 被调用,会通知渲染 watcher 重新计算、更新组件。

    响应式原理简单实现,设计五个类:

    • Vue:处理 options 传入的参数,把 data 中的成员转成 getter/setter
    • Observer:递归处理 data 所有属性,劫持数据 get/set 添加依赖/更新视图
    • Dep:添加 watcher ,数据变化通知所有 watcher
    • Watcher:实例化时往 dep 中添加自己,数据变化 dep 通知 watcher 更新视图
    • Compiler:解析指令/插值表达式,遇到模板依赖数据,添加 watcher

    Vue

    功能:

    • 记录传入的选项,设置 data,data, data,el
    • 把 data 中的属性注入到 Vue 实例,转换成 getter/setter
    • 调用 observer 监听 data 中所有属性的变化
    • 调用 compiler 解析指令 / 插值表达式

    类属性方法:

    • $options
    • $el
    • $data
    • _proxyData()
    class Vue {
      constructor(options) {
        this.$options = options || {};
        this.$data = options.data || {};
        this.$el =
          typeof options.el === "string"
            ? document.querySelector(options.el)
            : options.el;
        this._proxyData(this.$data);
        this.initMethods(options.methods || {});
        new Observer(this.$data);
        new Compiler(this);
      }
    
      _proxyData(data) {
        Object.keys(data).forEach((key) => {
          Object.defineProperty(this, key, {
            configurable: true,
            enumerable: true,
            get() {
              return data[key];
            },
            set(newValue) {
              if (newValue === data[key]) return;
              console.log("set -> newValue", newValue);
              data[key] = newValue;
            },
          });
        });
      }
    
      initMethods(methods) {
        Object.keys(methods).forEach((key) => {
          this[key] = methods[key];
        });
      }
    }
    

    Observer

    功能:

    • 数据劫持
      • 负责把 data 中的成员转换成 getter/setter
      • 负责把多层属性转换成 getter/setter
      • 如果给属性赋值为新对象,把新对象的成员设置为 getter/setter
    • 添加 Dep 和 Watcher 的依赖关系
    • 数据变化发送通知

    类属性方法:

    • walk(data) 遍历 data
    • defineReactive(data, key, value) 响应式处理
    class Observer {
      constructor(data) {
        this.walk(data);
      }
    
      walk(data) {
        if (typeof data !== "object" || data === null) {
          return;
        }
        Object.keys(data).forEach((key) => {
          this.defineReactive(data, key, data[key]);
        });
      }
    
      defineReactive(obj, key, val) {
        let self = this;
        const dep = new Dep();
        this.walk(val);
        Object.defineProperty(obj, key, {
          enumerable: true,
          configurable: true,
          get() {
            Dep.target && dep.addSub(Dep.target);
            return val;
          },
          set(newValue) {
            if (newValue === val) return;
            val = newValue;
            self.walk(newValue);
            dep.notify();
          },
        });
      }
    }
    

    Dep

    功能:

    • 收集依赖,添加观察者 (watcher)
    • 通知所有观察者

    类属性方法:

    • subs 存放所有观察者
    • addSub() 添加观察者
    • notify() 事件发生时,调用所有观察者的 update 方法
    class Dep {
      constructor() {
        this.subs = [];
      }
    
      addSub(sub) {
        if (sub && sub.update) {
          this.subs.push(sub);
        }
      }
    
      notify() {
        this.subs.forEach((sub) => {
          sub.update();
        });
      }
    }
    

    Watcher

    功能:

    • 当数据变化触发依赖, dep 通知所有的 Watcher 实例更新视图
    • 自身实例化的时候往 dep 对象中添加自己

    类属性方法:

    • vm vue 实例
    • key data 中的属性名称
    • cb
    • oldValue
    • update()
    class Watcher {
      constructor(vm, key, cb) {
        this.vm = vm;
        this.key = key;
        this.cb = cb;
        Dep.target = this;
        this.oldValue = vm[key];
        Dep.target = null;
      }
      update() {
        const newValue = this.vm[this.key];
        if (newValue === this.oldValue) {
          return;
        }
        this.cb(newValue);
      }
    }
    

    Compiler

    功能:

    • 负责编译模板,解析指令 / 插值表达式
    • 负责页面的首次渲染
    • 当数据变化后重新渲染视图

    类属性方法:

    • el
    • vm
    • compile(el) 编译原生的指令
    • compileElement(node)
    • compileText(node) 编译差值表达式
    • isDirective(attrName) 判断是否是以 v - 开头的指令
    • isTextNode(node)
    • isElementNode(node)
    class Compiler {
      constructor(vm) {
        this.vm = vm;
        this.el = vm.$el;
        this.compile(this.el);
      }
      compile(el) {
        let childNodes = el.childNodes;
        Array.from(childNodes).forEach((node) => {
          if (this.isTextNode(node)) {
            this.compileText(node);
          } else if (this.isElementNode(node)) {
            this.compileElement(node);
          }
          if (node.childNodes && node.childNodes.length) {
            this.compile(node);
          }
        });
      }
      isTextNode(node) {
        return node.nodeType === 3;
      }
      isElementNode(node) {
        return node.nodeType === 1;
      }
      compileText(node) {
        let reg = /\{\{(.+)\}\}/;
        let value = node.textContent;
        if (reg.test(value)) {
          let key = RegExp.$1.trim();
          console.log("compileText -> key", key);
          node.textContent = value.replace(reg, this.vm[key]);
    
          new Watcher(this.vm, key, (newValue) => {
            node.textContent = newValue;
          });
        }
      }
      compileElement(node) {
        Array.from(node.attributes).forEach((attr) => {
          let attrName = attr.name;
          if (this.isDirective(attrName)) {
            attrName = attrName.substr(2);
            if (attrName.includes("on:")) {
              const tmp = attrName.split(":");
              const name = tmp[1];
              this.onHandler(node, attr.value, name);
            } else {
              this.update(node, attr.value, attrName);
            }
          }
        });
      }
      isDirective(attrName) {
        return attrName.startsWith("v-");
      }
      update(node, key, attrName) {
        let fn = this[attrName + "Updater"];
        fn && fn.call(this, node, this.vm[key], key);
      }
      textUpdater(node, value, key) {
        node.textContent = value;
        new Watcher(this.vm, key, (newValue) => {
          node.textContent = newValue;
        });
      }
      modelUpdater(node, value, key) {
        node.value = value;
        new Watcher(this.vm, key, (newValue) => {
          node.value = newValue;
        });
        node.addEventListener("input", () => {
          this.vm[key] = node.value;
        });
      }
      htmlUpdater(node, value, key) {
        node.innerHTML = value;
        new Watcher(this.vm, key, (newValue) => {
          node.innerHTML = newValue;
        });
      }
      onHandler(node, value, name) {
        let modifier = "";
        if (name.includes(".")) {
          const tmp = name.split(".");
          name = tmp[0];
          modifier = tmp[1].trim();
        }
    
        // 动态时间处理:v-on:[event]="doThis"
        if (name.startsWith("[")) {
          name = name.slice(1, -1);
          name = this.vm[name];
        }
    
        let third_params = false;
        if (modifier === "capture") {
          third_params = true;
        } else if (modifier === "passive") {
          third_params = { passive: true };
        }
    
        const cb = (e) => {
          if (modifier === "stop") {
            e.stopPropagation();
          }
          if (modifier === "prevent") {
            e.preventDefault();
          }
          let methodName = value;
          let args = [e];
          // 处理内联语句 传递额外参数
          if (value.endsWith(")")) {
            const tmp = value.split("(");
            methodName = tmp[0];
            args = tmp[1]
              .slice(0, -1)
              .split(",")
              .map((item) => {
                item = item.trim();
                console.log("onHandler -> item", item, typeof item);
                if (item === "$event") {
                  return e;
                }
                if (item.startsWith('"') || item.startsWith("'")) {
                  console.log("onHandler -> item", item);
                  return item.slice(1, -1);
                }
                return this.vm[item];
              });
          }
          this.vm[methodName](...args);
          if (modifier === "once") {
            node.removeEventListener(name, cb, third_params);
          }
        };
        node.addEventListener(name, cb, third_params);
      }
    }
    

    码云地址


    起源地下载网 » Vue2 响应式原理简单实现

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元