vue2双向绑定的实现

vue2双向绑定的实现

通过四个步骤循序渐进,加深对vue2.0响应式的理解


一、极简双向绑定

html页面

<!-- QAQshfit专用模板 -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>极简双向绑定</title>
<style>
  
</style>
<script src="./index.js"></script>
</head>
<body>
  <div id="app">
    <input type="text" id="a">
    <span id="b"></span>
  </div>
</body>
<script>
  
</script>
</html>

js

var obj = {}

Object.defineProperty(obj, 'data1', {
  //data1发生改变时调用
  set: function (newVal) {
    console.log('set调用')

    document.getElementById('a').value = newVal
    document.getElementById('b').innerHTML = newVal
  },
  get: function () {
    console.log('get调用')
  }
})

document.addEventListener('keyup', function (e) {
  obj.data1=e.target.value
  console.log('按下')
})


/*
	这里只实现了view和model简单的双向绑定,只有一个data,而不是data对象,并且没有对v-model和{{}}进行处理
*/

二、初始化渲染

html

<!-- QAQshfit专用模板 -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>初始化渲染</title>
<style>
  
</style>

</head>
<body>
  <div id="app">
    <input type="text" id="a" v-model="d1">
    {{d1}}
  </div>
</body>
<script>
  
</script>
<script src="./index.js"></script>
</html>

js

/*
  该过程值完成了从model到view的初始化渲染

  具体过程为:
    1.vue实例创建,vue方法执行。
    2.vue将挂载对象的子节点进行劫持,传入vue实例
    3.获取到子节点,并用compile进行处理,传入vue实例
    4.将带有绑定属性的结点,将其值修改为vue中data的值
    5.将处理的结点返回,再进行挂载
*/


// 用一个方法对node结点进行劫持处理
function nodeToFragment(node,vm) {

  /*
    DocumentFragment(文档片段)可以看作节点容器,它可以包含多个子节点,当我们将它插入到DOM中时,只有它的子节点会插入目标节点,所以把它看作一组节点的容器。
  */
  //DocumentFragment处理节点,速度和性能远远优于直接操作DOM。

  var flag = document.createDocumentFragment()
  var child;

  //node.firstChild返回node的第一个子节点
  //下面原理是,如果子节点存在就添加到DocumentFragment当中
  while (child = node.firstChild) {
    //对子节点进行处理
    compile(child,vm)
    flag.append(child); //劫持node的所有子节点
  }

  return flag;
}


//对双向绑定和单向绑定进行处理
function compile(node, vm) {
  //.表示任意字符 *表示任意数量
  var reg = /\{\{(.*)\}\}/


  console.log('结点类型' + node.nodeType)

  //结点类型为元素
  if (node.nodeType === 1) {
    //获取节点属性
    var attr = node.attributes;

    //解析属性
    for (var i = 0; i < attr.length; i++) {
      //如果属性为 v-model
      if (attr[i].nodeName == 'v-model') {
        //获取到v-model的值 即data的属性名
        var name = attr[i].nodeValue

        node.value = vm.data[name]  //data的值赋给node

        node.removeAttribute('v-model')
      }
    }
  }

  //结点类型为text
  if (node.nodeType === 3) {
    console.log(node.nodeValue)
    if (reg.test(node.nodeValue)) {
      var name = RegExp.$1;  //获取匹配的字符串
      name = name.trim()
      
      node.nodeValue = vm.data[name]  //data的值赋给node
    }
  }
}

function Vue({ el, data } = {}) {
  this.data = data
  var id = el

  console.log()
  // 返回一个新的dom结点
  var dom = nodeToFragment(document.getElementById(id), this)

  //将处理完的结点重新挂载到根结点
  document.getElementById(id).appendChild(dom)
}


var vm=new Vue({
  el:'app',
  data:{
    d1:'hello world'
  }
})

在这里插入图片描述

三、响应式数据绑定

html

<!-- QAQshfit专用模板 -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>响应式数据绑定</title>
<style>
  
</style>

</head>
<body>
  <div id="app">
    <input type="text" id="a" v-model="d1">
    {{d1}}
  </div>
</body>
<script>
  
</script>
<script src="./index.js"></script>
</html>

js

/*
  该过程值完成了从model到view的初始化渲染,同时增加了对data中数据的监听,即model可以监听到view的变化了

  具体过程为:
    1.vue实例创建,vue方法执行。
    2.observe遍历data中的数据,传入defineReactive处理
    3.defineReactive中为每个数据绑定set和get方法,来对data中的数据进行监听
    4.vue将挂载对象的子节点进行劫持,传入data
    5.获取到子节点,并用compile进行处理,传入data
    6.将带有绑定属性的结点,将其值修改为vue中data的值
    7.将处理的结点返回,再进行挂载

*/


//作用:给数据绑定set,和get,可以监听到model中数据的变化
//obj:data对象  key:data中的一个数据  val:view的值
function defineReactive(obj,key,val){
  Object.defineProperty(obj, key, {
    get: function () {
      console.log('get调用')
      return val
    },
    //data1发生改变时调用
    set: function (newVal) {
      console.log('set调用')
      
      //如果view的数据发生改变则更新data中的数据
      if(newVal===val) return
      val=newVal


      console.log(val)
    }    
  })
}

//给每一个数据进行绑定处理
function observe(obj){
  Object.keys(obj).forEach(function(key){
    defineReactive(obj,key,obj[key])
  })
}

// 用一个方法对node结点进行劫持处理
function nodeToFragment(node,data) {

  /*
    DocumentFragment(文档片段)可以看作节点容器,它可以包含多个子节点,当我们将它插入到DOM中时,只有它的子节点会插入目标节点,所以把它看作一组节点的容器。
  */
  //DocumentFragment处理节点,速度和性能远远优于直接操作DOM。

  var flag = document.createDocumentFragment()
  var child;

  //node.firstChild返回node的第一个子节点
  //下面原理是,如果子节点存在就添加到DocumentFragment当中
  while (child = node.firstChild) {
    //对子节点进行处理
    compile(child,data)
    flag.append(child); //劫持node的所有子节点
  }

  return flag;
}


//对双向绑定和单向绑定进行处理
function compile(node, data) {
  //.表示任意字符 *表示任意数量
  var reg = /\{\{(.*)\}\}/


  console.log('结点类型' + node.nodeType)

  //结点类型为元素
  if (node.nodeType === 1) {
    //获取节点属性
    var attr = node.attributes;

    //解析属性
    for (var i = 0; i < attr.length; i++) {
      //如果属性为 v-model
      if (attr[i].nodeName == 'v-model') {
        //获取到v-model的值 即data的属性名
        var name = attr[i].nodeValue

        node.addEventListener('input',function(e){
          //给相应的data属性赋值,进而触发该属性的set方法
          data[name]=e.target.value;
        })

        node.value = data[name]  //data的值赋给node

        node.removeAttribute('v-model')
      }
    }
  }

  //结点类型为text
  if (node.nodeType === 3) {
    console.log(node.nodeValue)
    if (reg.test(node.nodeValue)) {
      var name = RegExp.$1;  //获取匹配的字符串
      name = name.trim()
      
      node.nodeValue = data[name]  //data的值赋给node
    }
  }
}

function Vue({ el, data } = {}) {
  this.data = data
  var id = el

  observe(this.data)

  // 返回一个新的dom结点
  var dom = nodeToFragment(document.getElementById(id), this.data)

  //将处理完的结点重新挂载到根结点
  document.getElementById(id).appendChild(dom)
}


var vm=new Vue({
  el:'app',
  data:{
    d1:'hello world'
  }
})

在这里插入图片描述

四、双向绑定

html

<!-- QAQshfit专用模板 -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>响应式数据绑定</title>
<style>
  
</style>
<script src="./index.js"></script>
</head>
<body>
  <div id="app">
    <input type="text" id="a" v-model="d1">
    {{d1}}
    <div>{{d2}}

      <div style="height:100px;width:100px;">{{d3}}</div>
    </div>
  </div>
</body>
<script>
  var vm=new Vue({
  el:'app',
  data:{
    d1:'hello world',
    d2:'我是外层div',
    d3:'我是内层div'

  }
})
</script>

</html>

js

/*
  该过程值完成了从model到view的初始化渲染,同时增加了对data中数据的监听,即model可以监听到view的变化了

  具体过程为:
    1.vue实例创建,vue方法执行。
    2.observe遍历data中的数据,传入defineReactive处理
    3.defineReactive中为每个数据绑定set和get方法,来对data中的数据进行监听
    4.vue将挂载对象的子节点进行劫持,传入data
    5.获取到子节点,并用compile进行处理,传入data
    6.将带有绑定属性的结点,将其值修改为vue中data的值
    7.将处理的结点返回,再进行挂载

    而在compile处理的过程中,每一个文本类型结点都实例化了一个Watcher
    实例的过程中会把自己添加到Dep当中
    当data中的数据发生改变,dep就会遍历并调用Watcher的更新方法



*/


//作用:给数据绑定set,和get,可以监听到model中数据的变化
//obj:data对象  key:data中的一个数据  val:view的值
function defineReactive(obj,key,val){
  var dep=new Dep();
  Object.defineProperty(obj, key, {
    get: function () {
      console.log('get调用')
      if(Dep.target) dep.addSub(Dep.target)
      return val
    },
    //data1发生改变时调用
    set: function (newVal) {
      console.log('set调用')
      
      //如果view的数据发生改变则更新data中的数据
      if(newVal===val) return
      val=newVal

      dep.notify()
      console.log(val)
    }    
  })
}

//给每一个数据进行绑定处理
function observe(obj){
  Object.keys(obj).forEach(function(key){
    defineReactive(obj,key,obj[key])
  })
}

// 用一个方法对node结点进行劫持处理
function nodeToFragment(node,data) {

  /*
    DocumentFragment(文档片段)可以看作节点容器,它可以包含多个子节点,当我们将它插入到DOM中时,只有它的子节点会插入目标节点,所以把它看作一组节点的容器。
  */
  //DocumentFragment处理节点,速度和性能远远优于直接操作DOM。

  var flag = document.createDocumentFragment()
  var child;

  // //node.firstChild返回node的第一个子节点
  // //下面原理是,如果子节点存在就添加到DocumentFragment当中
  
  // while (child = node.firstChild) {
  //   //对子节点进行处理
  //   compile(child,data)
  //   flag.append(child); //劫持node的所有子节点
  // }

  //对子节点进行遍历,每一个都进行处理
  for(var i=0;i<node.childNodes.length;i++){
    child=node.childNodes[i]
    compile(child,data) 
  }
  flag.append(child);


  return flag;
}


//对双向绑定和单向绑定进行处理
function compile(node, data) {
  //.表示任意字符 *表示任意数量
  var reg = /\{\{(.*)\}\}/


  console.log('结点类型' + node.nodeType)

  //结点类型为元素
  if (node.nodeType === 1) {
    //获取节点属性
    var attr = node.attributes;

    //解析属性
    for (var i = 0; i < attr.length; i++) {
      //如果属性为 v-model
      if (attr[i].nodeName == 'v-model') {
        //获取到v-model的值 即data的属性名
        var name = attr[i].nodeValue

        node.addEventListener('input',function(e){
          //给相应的data属性赋值,进而触发该属性的set方法
          data[name]=e.target.value;
        })

        node.value = data[name]  //data的值赋给node

        node.removeAttribute('v-model')
      }
    }

    if(node.childNodes.length>0){
      var child;
      for(var i=0;i<node.childNodes.length;i++){
        child=node.childNodes[i]
        arguments.callee(child,data)
      }
    }
    
  }

  //结点类型为text
  if (node.nodeType === 3) {
    console.log(node.nodeValue)
    if (reg.test(node.nodeValue)) {
      var name = RegExp.$1;  //获取匹配的字符串
      name = name.trim()
      
      console.log(data)
      // node.nodeValue = data[name]  //data的值赋给node
      
      new Watcher(data,node,name)
    }
  }
}

function Watcher(data,node,name) { 
  Dep.target=this
  this.name=name
  this.node=node
  this.vm=data
  this.update()
  Dep.target=null
}
Watcher.prototype={
  update:function(){
    this.get();
    this.node.nodeValue=this.value
  },
  get:function(){
    this.value=this.vm[this.name]
  }
}

function Dep() { 
  this.subs=[]
}
Dep.prototype={
  addSub:function(sub){
    this.subs.push(sub)
  },

  notify:function(){
    this.subs.forEach((sub)=>{
      sub.update()
    })
  }
}  
function Vue({ el, data } = {}) {
  this.data = data
  var id = el

  observe(this.data)

  // 返回一个新的dom结点
  var dom = nodeToFragment(document.getElementById(id), this.data)

  //将处理完的结点重新挂载到根结点
  document.getElementById(id).appendChild(dom)
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值