数据双向绑定

①  明确: 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>
02-10 16:25