- v-model 指令实现双向数据绑定
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!--
v-model 指令实现双向数据绑定
value 也是 v-model 的值
-->
<div id="app">
<input type="text" v-model="text">
<p>{{ text }}</p>
</div>
<script>
let vm = new Vue({
el: '#app',
data: {
text: 'protein'
}
})
</script>
vue实现数据双向绑定主要是:采用 数据劫持结合发布者-订阅者模式 的方式,通过 Object.defineProperty() 来劫持各个属性的 setter/getter,在数据变动时发布消息给订阅者,触发相应监听回调。当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty 将它们转为 getter/setter。用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。
- 原理简析 代码实现
- 利用 Object.defineProperty() 对数据进行劫持,设置一个监听器 Observer,用来监听所有属性,如果属性上发上变化了,就需要告诉订阅者 Watcher 去更新数据,最后指令解析器 Compile 解析对应的指令,进而会执行对应的更新函数,从而更新视图,实现了双向绑定~
- defineProperty 版
<body>
<div id="divEle"></div>
<input type="text" id="inputEle">
<script>
let obj = { name: '' }
let newO = JSON.parse(JSON.stringify(obj))
Object.defineProperty(obj, 'name', {
get: function() {
return newO.name
},
set: function(v) {
newO.name = v
observer()
}
})
function observer() {
divEle.innerHTML = obj.name
inputEle.value = obj.name
}
inputEle.oninput = function() {
obj.name = this.value
}
setTimeout(_ => {
obj.name = 'hello'
}, 1000)
</script>
</body>
- Proxy 版
<body>
<div id="divEle"></div>
<input type="text" id="inputEle">
<script>
let obj = { name: '' }
const handler = {
get(target, prop) {
return target[prop]
},
set(target, prop, value) {
target[prop] = value
observer()
}
}
const p = new Proxy(obj, handler)
function observer() {
divEle.innerHTML = obj.name
inputEle.value = obj.name
}
inputEle.oninput = function() {
p.name = this.value
}
setTimeout(_ => {
p.name = 'hello'
}, 1000)
</script>
</body>
<div id="app">
<input type="text" v-model="name">
<input type="text" v-model="name">
<input type="text" v-model="age">
<p>{{name}}</p>
<p>{{age}}</p>
</div>
<script>
let data = { name: 'protein', age: 28 }
// 1 --- 获取元素节点
let app = document.getElementById('app')
let inputs = app.getElementsByTagName('input')
let nodeList = [...app.children].filter(item => item.nodeName != 'INPUT')
let cloneList = nodeList.map(item => item.cloneNode(true))
console.log(nodeList)
// 2 --- 初始渲染
for(let item of inputs) {
if(item.getAttribute('v-model')) {
item.value = data[item.getAttribute('v-model')]
}
// 4 --- 表单数据更新
item.oninput = function() {
data[this.getAttribute('v-model')] = this.value
}
}
let reg = /\{\{(\w+)}}/
nodeList.forEach(item => {
if(reg.test(item.innerHTML)) {
item.innerHTML = item.innerHTML.replace(reg, (...args) => data[args[1]])
}
})
// 3 --- 数据更新
Object.defineProperties(data, {
name: {
set(val) {
console.log(this == data) // true
for(let item of inputs) {
if(item.getAttribute('v-model') == 'name') {
item.value = val
}
}
cloneList.forEach((item, index) => {
nodeList[index].innerHTML = item.innerHTML.replace(/\{\{name}}/g, () => val)
})
}
},
age: {
}
})
data.name = 'tomato'
</script>
网友评论