先说一下,我们需要达到的目标.
- 当
model
的data
发生变化时,dom
元素的value
要发生变化. - 当
dom
元素的value
,发生变化时,model
的data
要发生变化.
核心思想即是:
- 我们需要监控到
model.data
的set
,当执行set
的时候,改变对应的dom.value
- 我们需要监控到
dom.value
的set
,当执行到set
的时候,改变对应的model.value
1.Object.defineProperty 设置自定义对象的 set
由于,我们需要监控到model.value
的set
.
常规做法,一般是不能做到了.
let model = {}
model.value = 'value' // 这里设置上了就设置上了,没有办法获取到设置到的动作.
使用 Object.defineProperty
来监听model.value
属性的set
行为.
let value = undefined
let model = {}
Object.defineProperty(model, 'value', {
set: function (newVal) {
// 在这检控到了model.value值的set设置时机
value = newVal
console.log('model.value setted!!!')
},
get: function () {
return value
}
})
model.value='aaaaa'
model.value setted!!!
可以正常监控到 model.value
属性的 set
时机.
2.dom是HTMLElement对象,能用 Object.defineProperty来设置某个属性的set吗?
由于 dom 元素,本质也是一个 HTMLElement
对象.
所以,如果.value
属性内部设置的configurable:true
的话,我们是可以利用Object.defineProperty
来重新设置 .value
的 set
,拿到dom
的.value
修改时机的.
按照一样的逻辑,使用Object.defineProperty来设置dom元素的value的set时机
// 拿到input的dom对象
let defaultInputVal = undefined
let input = document.querySelector('#input')
Object.defineProperty(input, 'value', {
set: function (newVal) {
defaultInputVal = newVal
console.log('input.dom.value setted!')
},
get: function () {
return defaultInputVal
}
})
结果发现,使用同样的办法,在dom
上无法使用 Object.defineProperty
拿到属性.
如果其内部定义 value
属性设置的 configurable:false
的话,那么在我这里再次定义,也没有报错误:
Cannot redefine property:value
反正不管怎么样,自定义对象的那套 Object.defineProperty
想利用 set
方式拿到 DOM
对象某个属性的 set
时机是拿不到了.
3.利用事件拿到 dom 对象属性的 set 时机
那么,就退而求其次的使用事件吧.
(反正最终是要拿到dom对象的.value属性赋值的那个时机)
let input = document.querySelector('#input')
input.addEventListener('keyup', function (e) {
console.log(e.target.value)
}, false)
实现数据的双向绑定.
现在我们有的:
- 利用
Object.defineProperty
拿到的了model.value
的set
时机 - 利用
事件
拿到了dom
元素的.value
的set
时机. - 接下来的就简单了,在
model.value.set
内部同时修改dom.value
- 在
dom.value.set
内部修改model.value
代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Object.defineProperty</title>
</head>
<body>
<h3>利用Object.defineProperty & dom.event 实现数据双向绑定</h3>
<input type="text" id="input">
<div id="contentText"></div>
</body>
<script>
let value = undefined
let contentText = document.querySelector('#contentText')
let model = {}
Object.defineProperty(model, 'value', {
set: function (newVal) {
// 在这检控到了model.value值的set设置时机
value = newVal
// 把model的新值赋值给dom
input.value = newVal
contentText.textContent = newVal
},
get: function () {
return value
}
})
let input = document.querySelector('#input')
input.addEventListener('keyup', function (e) {
// console.log(e.target.value)
// 在这里拿到了dom的value更新值
// 把dom的新值赋值给model
model.value = e.target.value
console.log(`input的数据改变了,model.value=${model.value}`)
}, false)
</script>
</html>
效果:
4.补充
其实本质上,只要拿到了model.value
的set
设置时机,在结合dom
本身支持的事件机制,就能实现数据的双向绑定.
ES6 新推出的 Proxy
也能监控到 Obj
的 set
,所以也能完成这样的双向绑定.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>利用 Proxy 来实现数据的双向绑定</title>
</head>
<body>
<h3>利用ES6提供的Proxy来实现数据属性双向绑定</h3>
<input type="text" id="input">
<div id="textContent"></div>
</body>
<script>
let input = document.querySelector('#input')
let textContent = document.querySelector('#textContent')
input.addEventListener('keyup', function (e) {
proxyModel.value = e.target.value
console.log(`model.value=${proxyModel.value}`)
}, false)
let model = {
value: undefined
}
let proxyModel = new Proxy(model, {
get: function (obj, prop) {
return obj[prop]
},
set: function (obj, prop, val) {
obj[prop] = val
input.value = val
textContent.textContent = val
}
})
</script>
</html>
结果:
总结
双向绑定实现起来其实非常简单.无非就下面三步.
- 利用
Proxy
或者Object.defineProperty
都能设置属性的set
- 利用
Event
机制,拿到dom
元素设置值的时机. - 在双方对应的
set
时机里来实现数据的双向绑定.