vue数据双向绑定原理及简单实现
数据双向绑定原理简单概括的话就是:
View层影响Model层是通过对 ‘keyup’ 等事件的监听。
Model层影响View层是通过 Object.defineProperty( ) 方法劫持数据并结合发布订阅者模式的方式来实现数据的双向绑定。
(vue 3.0版本里用 Proxy 替代 Object.defineProperty)
当然不能只掌握到这个层面,下面介绍如何进行数据劫持以及发布订阅者模式:
1、Object.defineProperty( ) 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象
Object.defineProperty(obj, prop, descriptor)
- obj:要定义属性的对象。
- prop:要定义或修改的属性的名称。
- descriptor:要定义或修改的属性描述符。
这里我们主要是通过重定义属性描述符里的 get 和 set 方法进行数据劫持
get:属性的 getter 函数,如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。该函数的返回值会被用作属性的值。
set:属性的 setter 函数,如果没有 setter,则为 undefined。当属性值被修改时,会调用此函数。
总结:当我们修改数据层(Model)某个属性值(数据)时,就会触发重定义过的 set 方法去同步修改视图层(View)的数据
2、发布订阅者模式的应用
订阅者(Watcher)把自己想订阅的数据或事件添加到订阅者收集器(Dep),当某事件(set或get等)触发时,发布者(Observer)发布该事件到订阅者收集器,由订阅者收集器统一通知有订阅该事件的订阅者(Watcher)去执行相应的更新函数从而更新视图。(参考下图流程)
(图片转自参考文章一)
到此应该有个大概的思路了,下面根据原理图来介绍整个流程:
1、首先使用Object.defineProperty()中的 getter/setter 作为一个Observer(劫持器)去劫持data对象中的所有属性,在属性 set 的时候通知Dep(订阅者收集器)去通知相关订阅者。
2、实现一个 Watcher(订阅者),Watcher 就是收到 Dep 数据变化的通知后,会去执行相对应的更新函数来更新视图,同一个数据可能在多处被使用,所以订阅者不止一个;这也是 Dep 存在的意义,对 Watcher 集中起来统一管理。
3、Dep(订阅者收集器),里面存放每个数据对应的所有 Watcher,当Observer 的 set 方法被触发时,就调用 Dep 里面的的notify(通知)方法,逐条去通知所有的 Watcher 。
4、Complier是一个编译器,作用是解析模板指令,扫描和解析 vue 代码中每一个节点,先将节点转换为碎片化文档 DocumentFragment(性能优化,减少重排),再一次性 append 所有节点至目标 element 内,完成视图的初始化;同时编译器还担当着初始化 Watcher 的任务,即给 Watcher 绑定相关的更新函数 ,最终使 Watcher 添加到 Dep 中去。
简单实现(不包括Dep、Complier的实现):
<body> <div id="app"> <input type="text" id="txt"> <span id="show-txt"></span> </div> <script> var obj = {} Object.defineProperty(obj, 'val', { get: function () { return val }, set: function (newValue) { document.getElementById('txt').value = newValue document.getElementById('show-txt').innerHTML = newValue } }) document.addEventListener('keyup', function (e) { obj.val = e.target.value }) </script> </body>
掌握以上内容已经算是上道了(面试时解释完上面内容也差不多了),下面是较为完整的实现(加深理解):
1.首先实现一个劫持器,对数据进行劫持 function defineReactive(obj, key, val) { var dep = new Dep(); //创建dep订阅者收集器 Object.defineProperty(obj, key, { get: function() { if(Dep.target) { dep.addSub(Dep.target) } return val }, set: function(newVal) { if(newVal === val) { return } val = newVal; dep.notify(); //执行dep的通知函数去通知相关订阅者 } }) } 2、实现一个观察者,对于一个实例的每一个属性值都进行观察 function Observer(obj, vm) { for(let key of Object.keys(obj)) { defineReactive(vm, key, obj[key]); } } 3、实现dep的构造函数 function Dep() { this.subs = [] //用来收集订阅者 } Dep.prototype = { addSub(sub) { //添加订阅者的方法 this.subs.push(sub) }, notify() { //通知相关的所有订阅者执行更新函数 this.subs.forEach(function(sub) { sub.update(); }) } } 4、实现Watcher订阅者 function Watcher(vm, node, name) { Dep.target = this; //辨识订阅者者要添加到哪个dep收集器里 this.vm = vm; this.node = node; this.name = name; this.update(); Dep.target = null; } Watcher.prototype = { update() { this.get(); this.node.nodeValue = this.value //更新函数 }, get() { this.value = this.vm[this.name] //触发相应的get } } 5、实现编译器Complier function compile(node, vm) { var reg = /\{\{(.*)\}\}/; // 用正则来匹配{{messeage}} if(node.nodeType === 1) { //如果是元素节点 var attr = node.attributes; //解析元素节点的所有属性 for(let i = 0; i < attr.length; i++) { if(attr[i].nodeName == 'v-model') { var name = attr[i].nodeValue //看看是与哪一个数据相关 node.addEventListener('input', function(e) { //将与其相关的数据改为最新值 vm[name] = e.target.value }) node.value = vm.data[name]; //将data中的值赋予给该node node.removeAttribute('v-model') } } } //如果是文本节点,即{{messeage}}情况 if(node.nodeType === 3) { if(reg.test(node.nodeValue)) { var name = RegExp.$1; //获取到匹配的字符串 name = name.trim(); node.nodeValue = vm[name]; //将data中的值赋予给该node new Watcher(vm, node, name) //绑定一个订阅者 } } } //在向碎片化文档中添加节点时,每个节点都处理一下 function nodeToFragment(node, vm) { var fragment = document.createDocumentFragment(); var child; while(child = node.firstChild) { compile(child, vm); fragment.appendChild(child); } return fragment } 6、 Vue构造函数 function Vue(options) { this.data = options.data; observe(this.data, this) //给data中的所有属性值增添了observe var id = options.el; var dom = nodeToFragment(document.getElementById(id), this) //处理完所有节点后,重新把内容添加回去,减少重排 document.getElementById(id).appendChild(dom) }
(有兴趣更进一步研究的话,去看源码吧!)
参考文章:
VUE双向数据绑定原理及简单实现
Vue数据双向绑定原理及简单实现
理解VUE双向数据绑定原理和实现—赵佳乐
- 点赞 1
- 收藏
- 分享
- 文章举报
- Vue数据双向绑定原理及简单实现方法
- Vue双向数据绑定实现原理
- vue双向数据绑定的实现原理
- 【前端面试vue】vue响应式(双向数据绑定)原理及实现简例
- 面试总结:vue实现数据双向绑定的原理
- Vue实现双向绑定的原理以及响应式数据的方法
- 【学习笔记】剖析MVVM框架,简单实现Vue数据双向绑定
- 原生js简单实现双向数据绑定原理
- Angular和Vue双向数据绑定的实现原理(重点是vue的双向绑定)
- Angular和Vue双向数据绑定的实现原理(重点是vue的双向绑定)
- vue实现数据双向绑定原理
- vue实现数据双向绑定的原理
- 【vue】vue数据双向绑定实现原理
- 应用defineProperty简单实现vue的双向数据绑定
- 【学习笔记】Vue中实现双向数据绑定的原理
- Vue实现双向绑定的原理以及响应式数据
- vue响应式更新机制及不使用框架实现简单的数据双向绑定问题
- vue中数据双向绑定的实现原理
- vue.js双向数据绑定原理解析及模拟demo的实现
- 简单实现Vue数据双向绑定