浅谈Vue双向数据绑定的原理

   所谓双向数据绑定, 无非就是数据层和视图层中的数据同步, 在写入数据时视图层实时的跟着更新, 之前在网上看到大佬们是这么描述的:

   

实现mvvm的双向绑定,是采用数据劫持结合 发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。就必须要实现以下几点:
1、实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者
2、实现一个指令解析器Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
3、实现一个Watcher,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图
4、mvvm入口函数,整合以上三者

一共分四步, 每一步都有大堆解释和一大堆的代码, 然而我真的只是一个名副其实的理工科生, 看到了文中的发布者-订阅者模式, 于是乎我去网上各种百度, 个人理解就是getter函数里面执行的任务就是watcher订阅者, 而setter函数执行的任务就是发布者; 相信很多人看过了这个也是一知半解, 下面我来解释一波:

ECMAScript中有两种属性: 数据属性和访问器属性, 数据属性一般用于存储数据数值, 访问器属性对应的是set/get操作, 不能直接存储数据值, 每种属性下面又都含有四个特性.下面介绍一下:

数据属性

1.[[Configurable]]: 表示能否通过delete将属性删除,能否把属性修改为访问器属性, 默认为false。当把属性Configurable设置为false后,该属性不能通过delete删除,并且也无法再将该属性的Configurable设置回true
2.[[Enumerable]]: 表示属性可否被枚举(即是否可以通过for in循环返回),默认false
3.[[Writable]]: 表示属性是否可写(即是否可以修改属性的值),默认false
4.[[Value]]: 该属性的数据值, 默认是undefined

访问器属性

1.[[Configurable]]: 表示能否通过delete将属性删除,能否把属性修改为数据属性, 默认为false。当把属性Configurable设置为false后,该属性不能通过delete删除,并且也无法再将该属性的Configurable设置回true
2.[[Enumerable]]: 表示属性可否被枚举(即是否可以通过for in循环返回),默认false
3.[[Get]]: 读取属性时调用的函数, 默认为undefined
4.[[Set]]: 写入属性时调用的函数, 默认是undefined

在修改属性的特性或者定义访问器属性的时候, 需要借助ECMAScript 5中的一个方法: Object.defineProperty(), 这个方法接收三个参数: 属性所在对象, 属性的名字, 描述符对象; 为对象定义多个属性的话, 就用函数的复数写法:Object.defineProperties();

那么通过这个ES5的方法就可以直接很简单粗暴的说明双向绑定的原理:

<input type="text" id="inp" /> <div id="box"></div> <script> let obj = {}; let oInp = document.getElementById('inp'); let oBox = document.getElementById('box'); Object.defineProperty(obj, 'name', { configurable: true, enumerable: true, get: function() { console.log(111) return val; }, set: function(newVal) { oInp.value = newVal; oBox.innerHTML = newVal; } }); oInp.addEventListener('input', function(e) { obj.name = e.target.value; }); obj.name = '苏日俪格'; 

那么实现数据双向绑定的核心就是利用为每一个属性都创建了订阅者的实例对象, 以便观察, getter函数里面返回一个value值,在setter函数中写入修改后的值并调用update方法更新视图的数据值, Configurable和Enumerable这两个特性描述符默认是true, 因此不用写

function defineReactive (obj, key, val) { var dep = new Dep(); //这是一个构造函数 其原型是为属性添加订阅者 Object.defineProperty(obj, key, { get: function() { if(Dep.target) { dep.addSub(Dep.target); //添加订阅者到Dep实例对象 } return val; // 返回监听到的value值 }, set: function (newVal) { if(newVal === val) return; val = newVal; // 写入新的value值 dep.notify(); // 作为发布者发出通知 然后dep会迭代调用各自的update方法来更新视图 } }); } function observe(obj, vm) { Object.keys(obj).forEach(function(key) { defineReactive(vm, key, obj[key]); }); }


猜你喜欢

转载自www.cnblogs.com/ccy-19951124/p/12025065.html