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

 更新时间:2023年04月20日 11:47:32   作者:你这个年龄怎么睡得着的  
这篇文章主要为大家介绍了如何使用Object.defineProperty实现简易的vue功能示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

vue 双向绑定的原理

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

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

  • observe:遍历 data 中的属性,使用 Object.definePropertyget/set 方法- 对其进行数据劫持;
  • dep:每个属性拥有自己的消息订阅器 dep,用于存放所有订阅了该属性的观察者对象;
  • 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 => {
      /*
       *当value值为基本数据类型时,this.key数据变化,对用的$data不会变化
       *只有当value值为对象或者数组类型时,数据变化会同步
       **/
      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的资料请关注脚本之家其它相关文章!

相关文章

  • vue实现购物车加减

    vue实现购物车加减

    这篇文章主要为大家详细介绍了vue实现购物车加减,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-05-05
  • 在Vue3中生成动态的word文档的示例代码

    在Vue3中生成动态的word文档的示例代码

    这篇文章主要介绍了如何在 Vue 3 中生成动态的 Word 文档,在开发过程中遇到一个需求,动态生成一个word报表,当时考虑了是前端做还是后端做的问题,最后发现两个解决需求的方法都大差不差,但考虑到前端少发一个请求,就此使用的前端来解决,需要的朋友可以参考下
    2024-09-09
  • Vuex中mutations和actions的区别及说明

    Vuex中mutations和actions的区别及说明

    这篇文章主要介绍了Vuex中mutations和actions的区别及说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-12-12
  • Vue Router 返回后记住滚动条位置的实现方法

    Vue Router 返回后记住滚动条位置的实现方法

    使用 Vue router 创建 SPA(Single Page App),往往有这种需求:首页是列表页,点击列表项进入详情页,在详情页点击返回首页后,希望看到的是,首页不刷新,并且滚动条停留在之前的位置,这篇文章主要介绍了Vue Router 返回后记住滚动条位置的实现方法,需要的朋友可以参考下
    2023-09-09
  • vue项目纯前端实现的模板打印功能示例代码

    vue项目纯前端实现的模板打印功能示例代码

    在Vue项目中,通过使用vue-print-nb插件,可以实现页面的打印功能,这篇文章主要介绍了vue项目纯前端实现的模板打印功能的相关资料,需要的朋友可以参考下
    2024-10-10
  • vue单页面SEO优化的实现

    vue单页面SEO优化的实现

    本文主要介绍了vue单页面SEO优化的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-06-06
  • vue中methods、mounted等的使用方法解析

    vue中methods、mounted等的使用方法解析

    这篇文章主要介绍了vue中methods、mounted等的使用方法解析,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-06-06
  • Vue3中Element Plus Table(表格)点击获取对应id方式

    Vue3中Element Plus Table(表格)点击获取对应id方式

    这篇文章主要介绍了Vue3中Element Plus Table(表格)点击获取对应id方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-10-10
  • vue进行post和get请求实例讲解

    vue进行post和get请求实例讲解

    这篇文章主要介绍了vue进行post和get请求实例讲解,违章围绕post和get请求的相关资料展开详细内容,具有一的的参考价值,需要的小伙伴可以参考一下
    2022-03-03
  • 动画详解Vue3的Composition Api

    动画详解Vue3的Composition Api

    为让大家更好的理解Vue3的Composition Api本文采用了详细的动画演绎,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-07-07

最新评论