https://www.cnblogs.com/libin-1/p/6893712.html
https://www.jianshu.com/p/23180880d3aa
在网上查找许多资料, 可能是IT界的大佬们讲的太专业太深奥了, 也可能是我真的真的是一个名副其实的理工科生, 对文字理解能力差吧, 可我还是没懂, 后来让我这个从来不爱读书的孩子在书中找到了这个黄金屋, 偶然发现了有关绑定数据属性的问题, 下面我把我的理解跟大家分享一下,本文纯属个人理解,有哪里不对的地方请在评论区指出,大家一起学习共同进步。
所谓双向数据绑定, 无非就是数据层和视图层中的数据同步, 在写入数据时视图层实时的跟着更新, 之前在网上看到大佬们是这么描述的:
实现mvvm的双向绑定,是采用数据劫持结合发布者-订阅者模式的方式,通过ES5的 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>
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]);});}
根据此文章相关信息, 后来又写了一个简化版的Vue,希望可以帮助到大家,有兴趣的可以star/fork一下
如果想要仔细研究的可以参考一下他的博客或者http://www.cnblogs.com/canfoo/p/6891868.html
实现
简易实现 Object.definePropery 下的绑定原理
<body><span id="name"></span></body>
var data = {name: ''}// data bindingObject.definePropery(data, 'name',{get: function(){},set: function(newValue){document.getElementById('name').innerText = newValuedata.name = newValue}})// 页面监听 listenerdocument.getElementById('name').onchange = function(e){data.name = e.target.value}
实现 vue3.0 版本的 MVVM
这里采用Vue3.0最新的实现方式,用Proxy和Reflect来替代Object.definePropertypry的方式。至于Vue3.0为何不再采用2.0中Object.defineProperty的原因,我会在后续详写,先来介绍一下ES6里的Proxy与Reflect。
Proxy
Proxy是ES6里的新构造函数,它的作用就是代理,简单理解为有一个对象,不想完全对外暴露出去,想做一层在原对象操作前的拦截、检查、代理,这时候你就要考虑Proxy了。
const myObj = {_id: '我是myObj 的 ID',name: 'mvvm',age: 25}const myProxy = new Proxy(myObj, {get(target, propKey){if(propKey == 'age'){console.log('年龄很私密,禁止访问')return '*'}return target[propKey]}set(target, propKey, value, receiver){if(propKey == '_id'){console.log('无权修改ID')return}target[propKey] =value + (receiver.time || '')}})myProxy._id = 34;console.log(`age is: ${myProxy.age}`);myProxy.name = 'my name is Proxy';console.log(myProxy);const newObj = {time: ` [${new Date()}]`,};// 原对象原型链赋值Object.setPrototypeOf(myProxy, newObj);myProxy.name = 'my name is newObj';console.log(myProxy.name);/*** id无权修改* 年龄很私密,禁止访问* age is: ** { _id: '我是myObj的ID', name: 'my name is Proxy', age: 25 }* my name is newObj [Thu Mar 19 2020 18:33:22 GMT+0800 (GMT+08:00)]*/
