IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> Vue双向绑定原理 -> 正文阅读

[JavaScript知识库]Vue双向绑定原理

大家都知道,关于Vue的双向绑定,核心是Object.defineProperty()方法,那接下来我们就简单介绍一下!

Object.defineProperty()

语法:Object.defineProperty(obj,prop,descriptor)

  • obj——要在其上定义属性的对象。
  • prop——要定义或修改的属性的名称。
  • descriptor——将被定义或修改的属性描述符。

其实,简单点来说,就是通过此方法来定义一个值。调用,使用到了get方法,赋值,使用到了set方法。
例子:

let obj = {}
Object.defineProperty(obj, 'name', {
  get: function() {
    console.log('调用了get方法')
  },
  set: function(newVal) { 
    console.log('调用了set方法,方法的值是:' + newVal)
  }
})
obj.name // 打印:调用了get方法
obj.name = '吕小布' // 打印:调用了set方法,方法的值是:吕小布

当我们调用时候,就会自动打印出两行文字。注意:get 和 set 方法内部的 this 都指向 obj,这意味着 get 和 set 方法可以操作对象内部的值。另外,访问器属性(也就是Object.defineProperty()中的属性)的会”覆盖”同名的普通属性,因为访问器属性会被优先访问,与其同名的普通属性则会被忽略。

实现JS双向绑定

既然我们已经知道了,每当有改变的时候都会调用到set方法,我们可以根据此来实现一个双向绑定!

<body>
  <div id="app">
    <input id="a" type="text">
    <h1 id="b"></h1>
  </div>
  <script>
    const domA = document.getElementById('a')
    const domB = document.getElementById('b')
    let obj = {}
    let val = '吕'
    Object.defineProperty(obj, 'name', {
      get: function() {
        console.log('get val:' + val)
        return val
      },
      set: function(newVal) {
        val = newVal
        console.log('set val:' + val)
        domA.value = val
        domB.innerHTML = val
      }
    })
    domA.addEventListener('keyup', function(e) {
      obj.name = e.target.value
    })
  </script>
</body>

效果图:
在这里插入图片描述
在这里插入图片描述
此例实现的效果是:随文本框输入文字的变化,h1中会同步显示相同的文字内容;在js或控制台显式的修改 obj.name 的值,视图会同步更新。这样就实现了 model => view 以及 view => model 的双向绑定。通过添加事件监听keyup来触发调用set方法,而set在修改了访问器属性的同时,既改变了文本框的内容,也改变了h1标签内的文本。

实现Vue双向绑定

一、实现效果

我们真正想实现的双向绑定是这样的:
view:

<div id="app">
    <input v-model="text" type="text">
    {{text}}
</div>

model:

let vm = new Vue({
  el: 'app',
  data: {
    text: 'lvxiaobu'
  }
})

我们的工作:

  1. 将vm实例中的data中的内容绑定到输入框以及文本节点当中
  2. 当输入框改变时,vm实例中的data的内容也跟着改变,实现 【view => model】
  3. 当data中的内容发生变化的时候,输入框的内容以及文本节点的内容也发生变化,实现 【model=> view】

二、内容绑定原理

先来了解一下DocumentFragment。
说到内容绑定,我们不得不来介绍DocuemntFragment(碎片化文档)这个概念,简单的来讲,你可以把它认为是一个dom节点的容器,当你创造了10个节点,当每个节点都插入到文档当中都会引发一次浏览器的回流,也就是说浏览器要回流10次,十分消耗资源。而使用碎片化文档,也就是说我把10个节点都先放入到一个容器当中,最后我再把容器直接插入到文档就可以了!浏览器只回流了1次。

注意:还有一个很重要的特性是,如果使用appendChid方法将原dom树中的节点添加到DocumentFragment中时,会删除原来的节点。
举个例子,使用console.log(document.getElementById(‘app’))
在这里插入图片描述
可以看到,我的app中有两个子节点,一个元素节点,一个文本节点 ;但是,当我通过DocumentFragment 劫持数据一下后:

const app = document.getElementById('app')
// 把app的所有子节点都劫持过来
function nodeToFragment(node) {
  let fragment = document.createDocumentFragment()
  let child
  // 每次循环都是先把node.firstChild赋值给child,然后当child非空时,才会进入循环,并不是根据两者是否相等
  while (child = node.firstChild) {
    fragment.appendChild(child)
  }
  return fragment
}
let dom = nodeToFragment(app)
console.log(dom) // 看一下碎片文档
console.log(app) // 看一下被劫持后的app

在这里插入图片描述
注意:我的碎片化文档是将子节点都劫持了过来,而我的id为app的div内已经没有内容了。

同时要主要我while的判断条件。判断是否有子节点,因为我每次appendChild都把node中的第一个子节点劫持走了,node中就会少一个,直到没有的时候,child也就变成了undefined,也就终止了循环。

三、如何实现内容绑定

我们要考虑两个问题,一个是如何绑定到input上,另一个是如何绑定到文本节点中。

这样思路就来了,我们已经获取到了div的所有子节点了,就在DocumentFragment里面,然后对每一个节点进行处理,看是不是有跟vm实例中有关联的内容,如果有,修改这个节点的内容。然后重新添加入DocumentFragment中。

首先,我们写一个处理每一个节点的编译方法,如果有input绑定v-model属性或者有{{ xxx }}的文本节点出现,就进行内容替换,替换为vm实例中的data中的内容 :

// 编译方法
function compile(node, vm) {
  let reg = /\{\{(.*)\}\}/ // 匹配{{ xxx }}中的xxx
  // 如果是元素节点
  if (node.nodeType === 1) {
    // attributes包含了元素的所有属性
    let attr = node.attributes
    // 解析元素节点的所有属性
    for (let i = 0;i < attr.length;i++) {
      if (attr[i].nodeName == 'v-model') {
        let name = attr[i].nodeValue // 看看是与哪一个数据绑定
        node.value = vm.data[name] // 将data的值赋给该节点
        node.removeAttribute('v-model')
      }
    }
  }
  // 如果是文本节点
  if (node.nodeType === 3) {
    if (reg.test(node.nodeValue)) {
      // 获取到匹配的字符串xxx
      let name = RegExp.$1
      name = name.trim()
      node.nodeValue = vm.data[name]
    }
  }
}

然后,在向碎片化文档中添加节点时,每个节点都要处理一下:

// 把app的所有子节点都劫持过来,在向碎片化文档中添加节点时,每个节点都处理一下
function nodeToFragment(node, vm) {
  let fragment = document.createDocumentFragment()
  let child
  // 每次循环都是先把node.firstChild赋值给child,然后当child非空时,才会进入循环,并不是根据两者是否相等
  while (child = node.firstChild) {
    compile(child, vm)
    fragment.appendChild(child)
  }
  return fragment
}

创建Vue的构造函数:

// Vue构造函数
function Vue(options) {
  this.data = options.data
  let id = options.el
  // 这个this也就是实例化对象本身
  let dom = nodeToFragment(document.getElementById(id), this)
  // 处理完所有DOM节点后,重新将内容添加回去
  document.getElementById(id).appendChild(dom)
}
// 实例化Vue
let vm = new Vue({
  el: 'app',
  data: {
    text: 'lvxiaobu'
  }
})

以上完整代码:

<body>
  <div id="app">
    <input v-model="text" type="text">
    {{text}}
  </div>
  <script>
    // 编译方法
    function compile(node, vm) {
      let reg = /\{\{(.*)\}\}/ // 匹配{{ xxx }}中的xxx
      // 如果是元素节点
      if (node.nodeType === 1) {
        // attributes包含了元素的所有属性
        let attr = node.attributes
        // 解析元素节点的所有属性
        for (let i = 0;i < attr.length;i++) {
          if (attr[i].nodeName == 'v-model') {
            let name = attr[i].nodeValue // 看看是与哪一个数据绑定
            node.value = vm.data[name] // 将data的值赋给该节点
            node.removeAttribute('v-model')
          }
        }
      }
      // 如果是文本节点
      if (node.nodeType === 3) {
        if (reg.test(node.nodeValue)) {
          // 获取到匹配的字符串xxx
          let name = RegExp.$1
          name = name.trim()
          node.nodeValue = vm.data[name]
        }
      }
    }
    // 把app的所有子节点都劫持过来,在向碎片化文档中添加节点时,每个节点都处理一下
    function nodeToFragment(node, vm) {
      let fragment = document.createDocumentFragment()
      let child
      // 每次循环都是先把node.firstChild赋值给child,然后当child非空时,才会进入循环,并不是根据两者是否相等
      while (child = node.firstChild) {
        compile(child, vm)
        fragment.appendChild(child)
      }
      return fragment
    }
    // Vue构造函数
    function Vue(options) {
      this.data = options.data
      let id = options.el
      // 这个this也就是实例化对象本身
      let dom = nodeToFragment(document.getElementById(id), this)
      // 处理完所有DOM节点后,重新将内容添加回去
      document.getElementById(id).appendChild(dom)
    }
    // 实例化Vue
    let vm = new Vue({
      el: 'app',
      data: {
        text: 'lvxiaobu'
      }
    })
  </script>
</body>

效果图:
在这里插入图片描述
我们成功将内容都绑定到了输入框与文本节点上!

四、view => model

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章           查看所有文章
加:2022-04-30 08:37:41  更:2022-04-30 08:41:01 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 23:33:58-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码