看了一些关于双向绑定的文章,如今来整理一下思路。
首先实现双向绑定有三个步骤:html
- 须要一个方法来识别哪个的view被绑定了相应的数据
- 须要监视数据和view的变化
- 须要将全部变化传播到绑定的对象和对应的view
为了解决第一个问题,要在对应的dom上添加相应的data-bind-<prop_name>属性,好比:vue
num: <input type="number" data-bind-num> <div data-bind-num></div>
为了解决第二个问题,一方面监听数据改变,须要这样添加一个set()方法进行监听:git
const Vue = { data: { num: 0 }, set(key, val) { this.data[key] = val } }
规定经过set(key, val)的方式来修改数据。
另外一边监听对应视图改变就直接监听input事件。github
为了解决第三个问题就须要用发布订阅模式实现一个事件枢纽:dom
const EventHub = { callbacks: {}, on(eventName, callback){ this.callbacks[eventName] = this.callbacks[eventName] || []; this.callbacks[eventName].push(callback); }, emit(eventName, ...rest){ this.callbacks[eventName] = this.callbacks[eventName] || []; for(let i = 0; i < this.callbacks[eventName].length; i++){ this.callbacks[eventName][i].call(this,...rest); } } }
一方面将数据层的变化传播到视图,须要用特定名称与dom上的属性对应:优化
//触发事件就修改视图 eventHub.on('num:change', (val) => { $(`input[data-bind-num]`).val(val) $(`div[data-bind-num]`).text(val) }) //经过set()修改data来触发对应的change事件 set(key, val) { this.data[key] = val EventHub.emit('num:change', val) }
将视图层的变化传播到数据:this
$(`input[data-bind-num]`).on('input', function() { let val = $(this).val() === '' ? 0 : parseInt($(this).val()) Vue.set(key, val) })
至此双向绑定就实现完成!可是这样一个个写事件名和属性名有点蠢,优化一下spa
const fn = (prop_name) => { return { dataBind: `data-bind-${prop_name}`,//对应dom的data属性名 eventName: `${prop_name}:change`//对应数据的change事件名称 } } //给全部data绑定change事件,给全部data对应的view绑定input事件 Object.keys(Vue.data).map((key) => { //data修改改变view EventHub.on(fn(key).eventName, (val) => { $(`input[${fn(key).dataBind}]`).val(val) $(`div[${fn(key).dataBind}]`).text(val) }) //view改变data $(`input[${fn(key).dataBind}]`).on('input', function() { let val = $(this).val() === '' ? '' : parseInt($(this).val()) Vue.set(key, val) }) })
这样实现的双向绑定依赖于用set()来改变数据,而咱们都但愿经过 vm.property = value
这种方式直接来修改数据,这就须要用到Object.defineProperty()
来劫持各个数据的getter
,setter
。双向绑定
//给各个数据添加监听器,用数据劫持替换原先的set(key,value) const Observer = { mapProp(obj) { if(!obj || typeof obj !== 'object') { return } Object.keys(obj).map((key) => { this.defineReactive(obj, key, obj[key]) }) }, defineReactive(obj, key, val) { this.mapProp(val) Object.defineProperty(obj, key, { enumerable: true, // 可枚举 configurable: false, // 不能再define get() { return val }, set(newVal) { console.log(`数据 ${key} 从${val}->${newVal}`) //当数据变化就贵触发对应的change事件 EventHub.emit(fn(key).eventName, newVal) val = newVal } }) } }
这样只须要调用一次Observer.mapProp(Vue.data)
就会监听全部data,原先的set()均可以用直接赋值代替。rest
改变data效果:
修改input效果:
文章相关代码已经同步到 Github,欢迎查阅~