zoukankan      html  css  js  c++  java
  • 09-仿双向数据绑定

    数据双向绑定

    ①  明确: vue 双向绑定时通过 js Object.defineProperty 来实现的

    ②  特征:每个 属性都有 get 和 set 方法

    一  、 Object.defineProperty

      ① vue 是通过 js 的 Object.defineProperty 来实现数据劫持的,当数据读写时分别触发 get/set 方法

      ② 语法

    Object.defineProperty (带监听的对象,带监听的属性,{     get(){},     set(){} })

      ③ 仿数据绑定(简单版)

    <input type="text" name="" id="inputObj">  
    
    <script>
     let dataObj ={     
        msg:'cgw' 
    } 
    
    // 1.拦截数据
     let tmpData;
     Object.defineProperty(dataObj,'msg',{
         get(){
             return tmpData;     
        },     
        set(newData){
             tmpData = newData;         
          // 2.将数据同步到视图         
          let inputObj = document.querySelector('#inputObj');
             inputObj.value = newData;
         } }) 
    // 3.将数据同步到模型 
    let inputObj = document.querySelector('#inputObj'); 
    inputObj.onkeyup = () => {
         dataObj.msg = inputObj.value; 
    } 
    </script>

      ④ 面试题

        *1- 双向绑定原理是什么

        答:通过 js 的 Object.defineProperty 实现数据的劫持

    二 、vue 双向绑定原理(完整版)

      ① 核心原理

     

    ② 代码(完整版)

    <div id="app">
        <input type="text" v-model="username">
        {{username}}
    </div>
    <script>
    function Watcher (vm, node, name, nodeType) {
      this.name = name;
      this.node = node;
      this.vm = vm;
      this.nodeType = nodeType;
      this.update = function() {
        if (this.nodeType == 'text') {
          this.node.nodeValue = this.vm.data[this.name];
        }
        if (this.nodeType == 'input') {
          this.node.value = this.vm.data[this.name];
        }
      }
      Dep.target = this;
    }
     
    function Dep () {
      this.subs = []
      this.addSub = function(sub) {
        this.subs.push(sub);
      },
      this.notify = function() {
        this.subs.forEach(function(sub) {
          sub.update();
        });
      }
    }
     
    //记录所有绑定数据的节点
    // let watchers = [];
     
    function observer(vm)
    {
        Object.keys(vm.data).forEach(function(key){
            let dep = new Dep()
            let value = vm.data[key]
            Object.defineProperty(vm.data, key,{
                get(){
                    // 添加订阅者 watcher 到主题对象 Dep
                    dep.addSub(Dep.target)
                    return value
                },
                set(newValue){
                    if(newValue === value) return
                    value = newValue
                    //通过该主题的所有订阅者更新数据
                    dep.notify();
                }
            })
        })
    }
     
    //编译解析指令
    function compile(node, vm)
    {
        let reg = /{{(.*)}}/g  //正则匹配页面指令
        //元素节点
        if(node.nodeType === 1)
        {
            let attr = node.attributes;
            //解析节点的属性
            for(let i = 0;i < attr.length; i++)
            {
                if(attr[i].nodeName == 'v-model')
                {
                    //-------------------------------
                    node.addEventListener('input',function(e){
                    vm.data[name] = e.target.value;
                    });
                    //-----------------------------
     
                    let name = attr[i].nodeValue     //获取v-model绑定的属性名
                    //记录绑定数据的元素节点
                    // watchers.push({ name: name, node:node, nodeType: 'input', vm:vm })
                    new Watcher(vm, node, name, 'input')
                    node.value = vm.data[name]       //将data中的值赋给该节点
                    node.removeAttribute('v-model'); //解析完毕不要出现vue指令
                }
            }
        }
        //如果节点类型为text
        if(node.nodeType === 3)
        {
            if(reg.test(node.nodeValue))
            {
                let name = RegExp.$1;//获取匹配到的字符串
                name = name.trim();
     
                //记录绑定数据的文本节点
                // watchers.push({ name:name, node:node, nodeType: 'text', vm:vm })
                new Watcher(vm, node, name, 'text');
     
                node.nodeValue = vm.data[name];
            }
        }
    }
     
    function Vue(options)
    {
    //初始化
    let el = options.el
    this.data = options.data
    //监听模型数据变化
    observer(this)
    //将挂载目标劫持 -> 存到节点容器中(数据处理) -> 再放到挂载目标中
    let flag = document.createDocumentFragment()
    let child
    let dom = document.querySelector(el);
    while(child = dom.firstChild){
        compile(child, this)    //挂载目标中的节点挨个过滤(解析指令)
        flag.appendChild(child) //放到DocumentFragment页面渲染就不会显示/劫持
    }
    dom.appendChild(flag)//将DocumentFragment放到挂载目标中
    }
     
    let vm = new Vue({
    el:'#app',
    data:{
        username:'webopenfather',
        age: 5
    }
    })
    </script>
  • 相关阅读:
    BZOJ5473: 仙人掌
    BZOJ5289: [Hnoi2018]排列
    BZOJ5322: [JXOI2018]排序问题
    BZOJ5323:[JXOI2018]游戏
    webstorm引用ESLint进行静态代码检查
    React基础知识
    静态资源优化方案
    Nodejs 饭店
    linux du和df
    Docker知识-1
  • 原文地址:https://www.cnblogs.com/CGWTQ/p/12031337.html
Copyright © 2011-2022 走看看