转-Vue双向绑定的原理

2024-09-01 02:48

本文主要是介绍转-Vue双向绑定的原理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Vue双向绑定的原理

一篇很不错的文章: Vue.js双向绑定的实现原理

原文:(http://blog.csdn.net/creabine/article/details/59201837)

主要的知识点:
1.Vue双向绑定原理(一)文档片段DocumentFragment
2.Vue双向绑定原理(二)访问器属性defineProperty()
2.设计模式:观察者(发布/订阅)模式;

大致思路: 首先Vue会使用documentfragment劫持根元素里包含的所有节点,这些节点不仅包括标签元素,还包括文本,甚至换行的回车。
然后Vue会把data中所有的数据,用defindProperty()变成Vue的访问器属性,这样每次修改这些数据的时候,就会触发相应属性的get,set方法。
接下来编译处理劫持到的dom节点,遍历所有节点,根据nodeType来判断节点类型,根据节点本身的属性(是否有v-model等属性)或者文本节点的内容(是否符合{{文本插值}}的格式)来判断节点是否需要编译。对v-model,绑定事件当输入的时候,改变Vue中的数据。对文本节点,将他作为一个观察者watcher放入观察者列表,当Vue数据改变的时候,会有一个主题对象,对列表中的观察者们发布改变的消息,观察者们再更新自己,改变节点中的显示,从而达到双向绑定的目的。

Demo

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>双向绑定demo</title>
<style>body{margin: 200px auto;width: 800px;}
</style>
</head>
<body><div id="app"><input type="text" id="input" v-model="text">{{ text }}</div><script>// Vue构造函数function Vue(options){this.data = options.data;//发布订阅模式,数据绑定var data = this.data;observe(data,this);var id = options.el;var dom = nodeToFragment(document.getElementById(id),this);//编译完成后返回到app中document.getElementById("app").appendChild(dom);}//遍历传入实例的data对象的属性,将其设置为Vue对象的访问器属性function observe(obj,vm){Object.keys(obj).forEach(function(key){defineReactive(vm,key,obj[key]);});}//访问器属性是对象中的一种特殊属性,它不能直接在对象中设置,而必须通过defineProperty()方法单独定义。//访问器属性的"值"比较特殊,读取或设置访问器属性的值,实际上是调用其内部特性:get和set函数。function defineReactive(obj,key,val){//这里用到了观察者(订阅/发布)模式,它定义了一种一对多的关系,让多个观察者监听一个主题对象,这个主题对象的状态发生改变时会通知所有观察者对象,观察者对象就可以更新自己的状态。//实例化一个主题对象,对象中有空的观察者列表var dep = new Dep();//将data的每一个属性都设置为Vue对象的访问器属性,属性名和data中相同//所以每次修改Vue.data的时候,都会调用下边的get和set方法。然后会监听v-model的input事件,当改变了input的值,就相应的改变Vue.data的数据,然后触发这里的set方法Object.defineProperty(obj,key,{get: function(){//Dep.target指针指向watcher,增加订阅者watcher到主体对象Depif(Dep.target){dep.addSub(Dep.target);}return val;},set: function(newVal){if(newVal === val){return}val = newVal;//console.log(val);//给订阅者列表中的watchers发出通知dep.notify();}});}//主题对象Dep构造函数function Dep(){this.subs = [];}//Dep有两个方法,增加观察者 和 发布消息Dep.prototype = {addSub: function(sub){this.subs.push(sub);},notify: function(){this.subs.forEach(function(sub){sub.update();});}}//DocumentFragment(文档片段)可以看作节点容器,它可以包含多个子节点,当我们将它插入到DOM中时,只有它的子节点会插入目标节点,所以把它看作一组节点的容器。使用DocumentFragment处理节点,速度和性能远远优于直接操作DOM。Vue进行编译时,就是将挂载目标的所有子节点劫持(真的是劫持)到DocumentFragment中,经过一番处理后,再将DocumentFragment整体返回插入挂载目标。//var dom = nodeToFragment(document.getElementById("app"));//console.log(dom);//返回到app中//document.getElementById("app").appendChild(dom);function nodeToFragment(node,vm){var flag = document.createDocumentFragment();var child;//劫持node的所有子节点(真的在dom树中消失了,所以要在下边重新返回搭到app中)while (child = node.firstChild){//先编译所有的子节点,再劫持到文档片段中compile(child,vm);flag.appendChild(child);}return flag;}//编译节点,初始化数据绑定function compile(node,vm){//该正则匹配的是 :{{任意内容}}var reg = /\{\{(.*)\}\}/;//节点类型为元素if(node.nodeType === 1){var attr = node.attributes;//解析属性,不同的属性不用的处理方式,这里只写了v-model属性for(var i=0;i<attr.length;i++){if (attr[i].nodeName == "v-model") {//获取节点中v-model属性的值,也就是绑定的属性名var name = attr[i].nodeValue;node.addEventListener("input",function(e){//当触发input事件时改变vue.data中相应的属性的值,进而触发该属性的set方法vm[name] = e.target.value;});//改变之后,通过属性名取得数据node.value = vm.data[name];//用完删,所以浏览器中编译之后的节点上没有v-model属性node.removeAttribute("v-model");}}}//节点类型为textif(node.nodeType === 3){//text是否满足文本插值的写法:{{任意内容}}if(reg.test(node.nodeValue)){//获取匹配到的字符串:这里的RegExp.$1是RegExp的一个属性//该属性表示正则表达式reg中,第一个()里边的内容,也就是//{{任意内容}} 中的  文本【任意内容】var name = RegExp.$1;//去掉前后空格,并将处理后的数据写入节点name = name.trim();//node.nodeValue = vm.data[name];//实例化一个新的订阅者watchernew Watcher(vm,node,name);return;}}}//观察者构造函数。//上边实例化新的观察者的时候执行这个函数:通过get()取得Vue.data中对应的数据,然后通过update()方法把数据更新到节点中。function Watcher(vm,node,name){//让全局变量Dep的target属性的指针指向该watcher实例Dep.target = this;this.vm = vm;this.node = node;this.name = name;//放入Dep.target才能update()?????????????????????????????????????????this.update();Dep.target = null;}// 观察者使用update方法,实际上是Watcher.prototype = {update: function(){this.get();this.node.nodeValue = this.value;},//获取data中的属性值 get: function(){this.value = this.vm[this.name]; //触发相应属性的get}}var vm = new Vue({el: "app",data: {text: "Hello Vue"}})</script>
</body>
</html>

这篇关于转-Vue双向绑定的原理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1125734

相关文章

Python中随机休眠技术原理与应用详解

《Python中随机休眠技术原理与应用详解》在编程中,让程序暂停执行特定时间是常见需求,当需要引入不确定性时,随机休眠就成为关键技巧,下面我们就来看看Python中随机休眠技术的具体实现与应用吧... 目录引言一、实现原理与基础方法1.1 核心函数解析1.2 基础实现模板1.3 整数版实现二、典型应用场景2

Java的IO模型、Netty原理解析

《Java的IO模型、Netty原理解析》Java的I/O是以流的方式进行数据输入输出的,Java的类库涉及很多领域的IO内容:标准的输入输出,文件的操作、网络上的数据传输流、字符串流、对象流等,这篇... 目录1.什么是IO2.同步与异步、阻塞与非阻塞3.三种IO模型BIO(blocking I/O)NI

Vue中组件之间传值的六种方式(完整版)

《Vue中组件之间传值的六种方式(完整版)》组件是vue.js最强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互引用,针对不同的使用场景,如何选择行之有效的通信方式... 目录前言方法一、props/$emit1.父组件向子组件传值2.子组件向父组件传值(通过事件形式)方

css中的 vertical-align与line-height作用详解

《css中的vertical-align与line-height作用详解》:本文主要介绍了CSS中的`vertical-align`和`line-height`属性,包括它们的作用、适用元素、属性值、常见使用场景、常见问题及解决方案,详细内容请阅读本文,希望能对你有所帮助... 目录vertical-ali

基于@RequestParam注解之Spring MVC参数绑定的利器

《基于@RequestParam注解之SpringMVC参数绑定的利器》:本文主要介绍基于@RequestParam注解之SpringMVC参数绑定的利器,具有很好的参考价值,希望对大家有所帮助... 目录@RequestParam注解:Spring MVC参数绑定的利器什么是@RequestParam?@

浅析CSS 中z - index属性的作用及在什么情况下会失效

《浅析CSS中z-index属性的作用及在什么情况下会失效》z-index属性用于控制元素的堆叠顺序,值越大,元素越显示在上层,它需要元素具有定位属性(如relative、absolute、fi... 目录1. z-index 属性的作用2. z-index 失效的情况2.1 元素没有定位属性2.2 元素处

Python实现html转png的完美方案介绍

《Python实现html转png的完美方案介绍》这篇文章主要为大家详细介绍了如何使用Python实现html转png功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 1.增强稳定性与错误处理建议使用三层异常捕获结构:try: with sync_playwright(

Vue 调用摄像头扫描条码功能实现代码

《Vue调用摄像头扫描条码功能实现代码》本文介绍了如何使用Vue.js和jsQR库来实现调用摄像头并扫描条码的功能,通过安装依赖、获取摄像头视频流、解析条码等步骤,实现了从开始扫描到停止扫描的完整流... 目录实现步骤:代码实现1. 安装依赖2. vue 页面代码功能说明注意事项以下是一个基于 Vue.js

CSS @media print 使用详解

《CSS@mediaprint使用详解》:本文主要介绍了CSS中的打印媒体查询@mediaprint包括基本语法、常见使用场景和代码示例,如隐藏非必要元素、调整字体和颜色、处理链接的URL显示、分页控制、调整边距和背景等,还提供了测试方法和关键注意事项,并分享了进阶技巧,详细内容请阅读本文,希望能对你有所帮助...

JAVA封装多线程实现的方式及原理

《JAVA封装多线程实现的方式及原理》:本文主要介绍Java中封装多线程的原理和常见方式,通过封装可以简化多线程的使用,提高安全性,并增强代码的可维护性和可扩展性,需要的朋友可以参考下... 目录前言一、封装的目标二、常见的封装方式及原理总结前言在 Java 中,封装多线程的原理主要围绕着将多线程相关的操