文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

详解如何使用Object.defineProperty实现简易的vue功能

2023-05-16 20:36

关注

vue 双向绑定的原理

实现 vue 的双向绑定,v-textv-modelv-on 方法

Vue 响应系统,其核心有三点:observe、watcher、dep

class MinVue {
  constructor(options) {
    this.$data = options.data;
    this.$methods = options.methods;
    this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : el;
    this.bindData(this.$data);
    new Observer(this.$data);
    new Compile(this);
  }
  bindData(data) {
    Object.keys(data).forEach(key => {
      
      Object.defineProperty(this, key, {
        enumerable: true,
        configurable: true,
        get: () => {
          return data[key];
        },
        set: newValue => {
          data[key] = newValue;
        },
      });
    });
  }
}
class Observer {
  constructor(data) {
    this.work(data);
  }
  work(data) {
    if (Object.prototype.toString.call(data) === '[object Object]') {
      Object.keys(data).forEach(key => {
        this.defineReactive(data, key, data[key]);
      });
    }
  }
  defineReactive(data, key, value) {
    const observer = this;
    const dep = new Dep();
    // 当value为对象时,递归调用
    this.work(value);
    
    Object.defineProperty(data, key, {
      enumerable: true,
      configurable: true,
      get: () => {
        if (Dep.target) {
          dep.add(Dep.target);
        }
        return value;
      },
      set: newValue => {
        value = newValue;
        // 赋新值后,新值有可能为对象,重新绑定get set方法
        observer.work(newValue);
        dep.notify();
      },
    });
  }
}
class Dep {
  constructor() {
    this.watcher = new Set();
  }
  add(watcher) {
    if (watcher && watcher.update) this.watcher.add(watcher);
  }
  notify() {
    this.watcher.forEach(watch => watch.update());
  }
}
class Watcher {
  constructor(vm, key, cb) {
    Dep.target = this;
    this.vm = vm;
    this.key = key;
    // 会触发Observer定义的getter方法,收集Dep.target
    this._old = vm.$data[key];
    this.cb = cb;
    Dep.target = null;
  }
  update() {
    const newValue = this.vm.$data[this.key];
    this.cb(newValue);
    this._old = newValue;
  }
}
class Compile {
  constructor(vm) {
    this.vm = vm;
    this.methods = vm.$methods;
    this.compile(vm.$el);
  }
  compile(el) {
    const childNodes = el.childNodes;
    Array.from(childNodes).forEach(node => {
      if (this.isTextNode(node)) {
        this.compileTextNode(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;
  }
  compileTextNode(node) {
    // .+?正则懒惰匹配
    const reg = /\{\{(.+?)\}\}/g;
    const text = node.textContent;
    if (reg.test(text)) {
      let key = RegExp.$1.trim();
      node.textContent = text.replace(reg, this.vm[key]);
      new Watcher(this.vm, key, newValue => {
        node.textContent = newValue;
      });
    }
  }
  compileElement(node) {
    const attrs = node.attributes;
    if (attrs.length) {
      Array.from(attrs).forEach(attr => {
        if (this.isDirective(attr.name)) {
          // 根据v-来截取一下后缀属性名
          let attrName = attr.name.indexOf(':') > -1 ? attr.name.substr(5) : attr.name.substr(2);
          let key = attr.value;
          this.update(node, attrName, key, this.vm[key]);
        }
      });
    }
  }
  isDirective(dir) {
    return dir.startsWith('v-');
  }
  update(node, attrName, key, value) {
    if (attrName === 'text') {
      node.textContent = value;
      new Watcher(this.vm, key, newValue => {
        node.textContent = newValue;
      });
    } else if (attrName === 'model') {
      node.value = value;
      new Watcher(this.vm, key, newValue => {
        node.value = newValue;
      });
      node.addEventListener('input', e => {
        this.vm[key] = node.value;
      });
    } else if (attrName === 'click') {
      node.addEventListener(attrName, this.methods[key].bind(this.vm));
    }
  }
}

测试 MinVue

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <h3>{{ msg }}</h3>
      <p>{{ count }}</p>
      <h1>v-text</h1>
      <p v-text="msg"></p>
      <input type="text" v-model="count" />
      <button type="button" v-on:click="increase">add+</button>
      <button type="button" v-on:click="changeMessage">change message!</button>
      <button type="button" v-on:click="recoverMessage">recoverMessage!</button>
    </div>
  </body>
  <script src="./js/min-vue.js"></script>
  <script>
    new MinVue({
      el: '#app',
      data: {
        msg: 'hello,mini vue.js',
        count: 666,
      },
      methods: {
        increase() {
          this.count++;
        },
        changeMessage() {
          this.msg = 'hello,eveningwater!';
        },
        recoverMessage() {
          console.log(this);
          this.msg = 'hello,mini vue.js';
        },
      },
    });
  </script>
</html>

以上就是详解如何使用Object.defineProperty实现简易的vue功能的详细内容,更多关于Object.defineProperty vue的资料请关注编程网其它相关文章!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-前端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯