双向数据绑定:
数据改变视图改变,视图改变数据改变
双向数据绑定的原理:
- 通过数据劫持结合发布订阅者模型来实现的,首先对数据进行监听,然后当监听的属性发生变化时则告诉订阅者是否需要更新若更新就会执行对应的更新函数从而更新视图
- 通过Object.defineProperty()来劫持各个属性的setter, getter,在数据发生变动时通知Vue实例,触发相应的getter和setter回调函数。
当把一个普通 Javascript 对象传给Vue 实例来作为它的 data 选项时, Vue 将遍历它的属性,用Object.defineProperty将它们转为 getter/setter。用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。
<!--Vue双向数绑定的实现 -->
<template>
<div>
<input type="text" v-model="message">
<p>输入的内容: {{ message }}</p>
</div>
</template>
<script>
export default {
name: "App",
data () {
return {
message: '2121'
}
}
}
</script>
// 原生js模拟实现双向数据绑定
// HTML 代码
<script src="./Vue.js"></script>
<body>
<div id="app">
{{str}}
<h1>{{str}}</h1>
<h2>{{name}}</h2>
<button @click="cli">按钮</button>
<input type="text" v-model="str">
</div>
<script>
new Vue({
el: "#app",
data: {
str: 'I miss !',
name: 'jack'
},
methods: {
cli () {
// 点击没有效果没有对事件进行处理!·
alert("Hello,Vue")
console.log(this.str, this.name)
}
}
})
</script>
</body>
// Vue.js
// ①创建了一个Vue类
class Vue {
// options 参数接收 new Vue({}}里面的所有内容
// 这段代码定义了一个简化版的Vue类,将传入的配置对象中的元素选择器、数据对象等属性赋值给Vue实例,并通过数据代理和响应式处理实现了数据的双向绑定
constructor(options) {
console.log('options', options)
// ① 获取到数据
this.$el = document.querySelector(options.el) // #app
this.$data = options.data // 获取到要进行响应的数据
this.$options = options
this.$watchEvent = {} // 储数据监听的事件回调
this.proxyData() // 进行数据代理
this.observe() // 响应式数据的处理
this.compile(this.$el) // 模版编译应该放在最后面、
}
// ② 对数据进行代码
proxyData () {
for (let key in this.$data) {
// 这里的this指向整个Vue
Object.defineProperty(this, key, {
get () {
// 这里因为双向绑定的是 str 所以获取到的是 I miss ! 或者说这里每次获取的都是新输入的值
return this.$data[key] // 通过key 访问到具体的值
},
set (val) {
// 传入的新值 val 赋给数据对象中对应的属性。
this.$data[key] = val
}
})
}
}
// ③ 观察者模式
// 实现数据的观察(observable)和更新机制
observe () {
// 当我们观察到数据端有更新,就在这个时候把对应的属性劫持起来在进行操作,就是把对应的文本模版给它同步更新
for (let key in this.$data) {
let value = this.$data[key] // 遍历之后获取到data里面的所有数据
let _this = this // 这里的this还是指向整个Vue
Object.defineProperty(this.$data, key, {
get () {
return value // 获取到数据的变化
},
set (val) {
value = val
// update 调用update 实现数据的更新 当数据变化的时候调用Watch 里面的update方法进行数据更新
_this.$watchEvent[key].forEach((item, index) => {
item.update()
})
}
})
}
}
// ④ 编译解析模板
compile (node) {
node.childNodes.forEach((item, index) => {
// item.nodeType 3 文本节点 1 元素节点
if (item.nodeType === 1) {
if (item.hasAttribute('@click')) {
// 拿到事件绑定的方法名
let vmKey = item.getAttribute("@click")
vmKey = vmKey.trim() // 对空格进行处理 获取到的是定义的cli 点击事件
console.log('item-61', item)
// 当元素节点被点击时,执行绑定的方法
item.addEventListener("click", () => { // 给整个标签内容增加一个点击事件
this.$options.methods[vmKey].call(this)
})
}
// 考虑到单标签和标签里面没有内容的情况
if (item.childNodes.length > 0) {
// 如果当前元素节点有子节点,递归调用 compile() 方法对子节点进行编译。
this.compile(item)
}
}
// 双向绑定的事件
if (item.nodeType === 1) {
if (item.hasAttribute('v-model')) {
// 拿到事件绑定的方法名
let vmKey = item.getAttribute("v-model")
vmKey = vmKey.trim() // 对空格进行处理
// 先实现单向绑定将值赋给输入框
item.value = this[vmKey]
// 写输入框的input事件
item.addEventListener("input", () => {
// this[vmKey] = item.value item.value = this[vmKey] 这两步赋值的操作就完成了双向绑定
this[vmKey] = item.value
})
}
// 考虑到单标签和标签里面没有内容的情况
if (item.childNodes.length > 0) {
this.compile(item)
}
}
if (item.nodeType === 3) {
let reg = /\{\{(.*?)\}\}/g
let text = item.textContent
item.textContent = text.replace(reg, (match, vmKey) => {
vmKey = vmKey.trim()
let watcher = new Watch(this, vmKey, item, 'textContent')
if (this.$watchEvent[vmKey]) {
// 一个属性可能绑定多个文本模版的
this.$watchEvent[vmKey].push(watcher)
} else {
this.$watchEvent[vmKey] = []
this.$watchEvent[vmKey].push(watcher)
}
return this.$data[vmKey]
})
}
})
}
}
//3-1 该类的作用是创建 Watch 实例对象,用于监听指定的数据项,并在数据变化时更新对应的视图
// 文本里面进行视图更新的类
// vm:表示视图的 ViewModel(视图模型)对象。
// key:表示 ViewModel 中的一个属性名,用来获取要监听的数据。
// node:表示要更新的 DOM 元素节点。
// attr:表示要更新的 DOM 元素节点的属性。
class Watch {
constructor(vm, key, node, attr) {
this.vm = vm
this.key = key
this.node = node
this.attr = attr
}
// update 方法进行视图的更新
update () { // item.textContent=this.$dara[vmKey]
this.node[this.attr] = this.vm[this.key]
}
}
上面的代码中主要的意思是:
①创建一个Vue类,通过constructor接收options参数,并初始化一下属性
②proxyData 方法用来进行数据代理,通过 Object.defineProperty 方法将 this.$data 中的属性代理到 Vue 实例上,实现了数据的访问和修改时的监听
③ observe 方法实现了观察者模式,通过 Object.defineProperty 监听数据的变化,并在数据变化时调用相应的更新方法
在vue2中实现双向数据绑定主要用到了
④ ompile 方法用来编译解析模板,遍历节点并处理元素节点的 @click 事件以及 v-model 指令,同时也处理文本节点中的 {{}} 插值表达式,创建 Watch 实例来监听数据变化,并在数据变化时更新对应的视图。
⑤最后还定义了 Watch 类,用于创建 Watch 实例对象,用于监听指定的数据项,并在数据变化时更新对应的视图
Object.defineProperty(obj, prop, descriptor)
//obj:要定义属性的对象。
//prop:一个字符串或 Symbol,指定了要定义或修改的属性键。
//descriptor:要定义或修改的属性的描述符。
参考: