• Vue


    JS - 如何实现一个类似 vue 的双向绑定 Github JS 实现代码

    先来看一张图:

    这张图我做个简要的描述:

    首先创建一个实例对象,分别触发了 compile  解析指令 和 observer 监听器,

    compile 解析指令则循环递归 解析 类似 v-model 这样的指令,初始化 data 绑定数据,同时每个节点创建一个订阅者 watcher ,

    observer 监听器 则利用了 Object.defineProperty()  方法的描述属性里边的 set,get方法,来监听数据变化,

    get 方法是在创建实例对象,生成dom节点的时候都会触发,固:在compile 解析编译的时候,依次给每一个节点添加了一个订阅者到主题对象 Dep

    set 方法则是数据发生改变了,通知Dep订阅器里的所有wachter,然后找到对应订阅者 wachter 触发对应 update 更新视图

    简单的说明就是这样了。

    双向绑定原理

    vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的。

    具体点儿

    Vue双向数据绑定的原理就是利用了 Object.defineProperty() 这个方法重新定义了对象获取属性值(get)和设置属性值(set)的操作来实现的
     
    再具体点儿
     
    好吧,总结下来,分为以下四个步骤
     
    1.实现一个解析器Compile,可以扫描和解析每个节点的相关指令(v-model,v-on等指令),如果节点存在v-model,v-on等指令,则解析器Compile初始化这类节点的模板数据,使之可以显示在视图上,同时初始化相应的订阅者(Watcher)。

    2.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。

    3.实现一个订阅者Watcher,每一个Watcher都绑定一个 update,watcher 可以收到属性的变化通知并执行相应的 update ,从而更新视图。

    4.实现MVVM,双向绑定

    以下实践里边的几个方法我就不做介绍了,感兴趣可查询

    Object.defineProperty() 
    
    createDocumentFragment()
    
    Object.keys()

    话不多说:直接上代码:实现一个解析器Compile

     /*
      第一步
      1,创建文档碎片,劫持所有dom节点,重绘dom节点
      2,重绘dom节点,初始化文档碎片绑定数据 实现文档编译 compile
      */
      function getDocumentFragment(node, vm) {
        var flag = document.createDocumentFragment();
        var child;
        while (child = node.firstChild) {
          /*
           while (child = node.firstChild)
           相当于 
           child = node.firstChild
           while (child)
           */
          compile(child, vm);
          flag.appendChild(child);
        }
        node.appendChild(flag);
      }
      function compile(node, vm) {
        /*
       nodeType 返回数字,表示当前节点类型    
       1 Element    代表元素    Element, Text, 
       2    Attr    代表属性    Text, EntityReference
       3    Text    代表元素或属性中的文本内容。
       . . . 更多请查看文档
       */
        if (node.nodeType === 1) {
          // 获取当前元素的attr属性
          var attr = node.attributes;
          for (let i = 0; i < attr.length; i++) {
            // nodeName 是attr属性 key 即名称 , 匹配自定义 v-m
            if (attr[i].nodeName === 'v-m') {
              // 获取当前值 即 v-m = "test" 里边的 test 
              let name = attr[i].nodeValue;
              // 当前节点输入事件
              node.addEventListener('keyup', function (e) {
                vm[name] = e.target.value;
              });
              // 页面元素写值  vm.data[name] 即 vm.data['test'] 即 MVVM
              node.value = vm.data[name];
              //最后移除标签中的 v-m 属性
              node.removeAttribute('v-m');
              // 为每一个节点创建一个 watcher  
              new Watcher(vm, node, name, "input");
            }
          }
          /*
          继续递归调用 文档编译 实现 视图更新 ;
          */
          if (child = node.firstChild) {
            /*
            if (child = node.firstChild)
            相当于 
            child = node.firstChild
            id(child)
            */
            compile(child, vm);
          }
        }
        if (node.nodeType === 3) {
          let reg = /{{(.*)}}/;
          if (reg.test(node.nodeValue)) {
            let name = RegExp.$1.trim();
            node.nodeValue = vm.data[name];
            // 为每一个节点创建一个 watcher  
            new Watcher(vm, node, name, "text");
          }
        }
      }

    实现一个监听器Observer

     /* 
      第二步
      实现一个数据监听
      1,获取当前实例对象的  data 属性 key  
         observer(当前实例对象 data ,当前实例对象)
      2,使用 Object.defineProperty 方法 实现监听 
      */
      function observe(data, vm) {
        Object.keys(data).forEach(function (key) {
          defineReactive(vm, key, data[key]);
        });
      }
      function defineReactive(vm, key, val) {
        /*
        Object.defineProperty
        obj
        要在其上定义属性的对象。
        prop
        要定义或修改的属性的名称。
        descriptor
        将被定义或修改的属性描述符。 描述符有很多,就包括我们要市用 set , get 方法
        */
        var dep = new Dep();
        Object.defineProperty(vm, key, {
          get: function () {
            /* 
            if (Dep.target) dep.addSub(Dep.target);
            看到这段代码不要差异,生成每一个 dom节点,都会走 get 方法
            这里为每一个节点 添加一个订阅者 到主题对象 Dep
            */
            if (Dep.target) dep.addSub(Dep.target);
            console.log(val)
            return val;
          },
          set: function (newValue) {
            if (newValue === val) return;
            val = newValue;
            console.log(val + "=>" + newValue)
            // 通知所有订阅者
            dep.notify();
          }
        });
      }

    实现一个订阅者Watcher

    /*
      第三步
      
      1,实现一个 watcher 观察者/订阅者
        订阅者原型上挂在两个方法 分别是
        update 渲染视图
    
      2,定义一个消息订阅器
        很简单,维护一个数组,用来收集订阅者
        消息订阅器原型挂载两个方法 分别是  
        addSub 添加一个订阅者   
        notify 数据变动 通知 这个订阅者的 update 方法
      */
      function Watcher(vm, node, name, nodeType) {
        Dep.target = this;
        this.vm = vm;
        this.node = node;
        this.name = name;
        this.nodeType = nodeType;
        this.update();
        console.log(Dep.target)
        Dep.target = null;
      }
      Watcher.prototype = {
        update: function () {
          /*
          this.node 指向当前修改的 dom 元素
          this.vm 指向当前 dom 的实例对象
          根据 nodeType 类型 赋值渲染页面
          */
          if (this.nodeType === 'text') {
            this.node.nodeValue = this.vm[this.name]
          }
          if (this.nodeType === 'input') {
            this.node.value = this.vm[this.name]
          }
        }
      }
      function Dep() {
        this.subs = [];
      }
      Dep.prototype = {
        addSub: function (sub) {
          this.subs.push(sub);
        },
        notify: function () {
          this.subs.forEach(function (sub) {
            sub.update();
          });
        }
      }

    实现类似Vue的MVVM

    /*
      创建一个构造函数,并生成实例化对象 vm
      */
      function Vue(o) {
        this.id = o.el;
        this.data = o.data;
        observe(this.data, this);
        getDocumentFragment(document.getElementById(this.id), this);
      }
      var vm = new Vue({
        el: 'app',
        data: {
          msg: 'HiSen',
          test: 'Hello,MVVM'
        }
      });

      也许看到最后大家也没有看出个所以然,曾几何时的我跟你们一样,看来看去,就是这么几段代码;建议:拿下我的源码,自己跑一跑,看一看,是骡子是马拉出来溜溜。

  • 相关阅读:
    学习smali
    android XML解析器全解案例
    android text中显示HTML语言
    Viewpager图片自动轮播,网络图片加载,图片自动刷新
    Android TextView内容过长加省略号,点击显示全部内容
    Android 反编译 代码注入之HelloWorld
    APK软件反编译 去广告
    【HACK】破解APK并注入自己的代码
    apk反编译生成程序的源代码和图片、XML配置、语言资源等文件
    LA 3905 Meteor
  • 原文地址:https://www.cnblogs.com/hai-cheng/p/8718106.html
Copyright © 2020-2023  润新知