Vue2中的数据双向绑定使用到了Object.defineProperty做代理, 建议先了解一下 Object.defineProperty这个属性
不多说,直接上代码
创建myVue.js
文件,在里面定义一个Vue
类,实现基本的双向数据绑定
class Vue {
constructor(options) {
//通过选择器获取根对象
this.$el = document.querySelector(options.el)
//数据创造之前
if (typeof options.beforeCreate == 'function') {
options.beforeCreate.bind(this)()
}
//保存options对象
this.$options = options
// this.$watchEvent[key] = [event1,event2,event3]
//设置一个对象专门保存修改更新的事件
this.$watchEvent = {}
//代理options的data数据,即:将数据绑定到this上
this.proxyData()
//劫持设置事件
this.observe()
//数据创造之后
if (typeof options.created == 'function') {
options.created.bind(this)()
}
//挂载之前
if (typeof options.beforeMount == 'function') {
options.beforeMount.bind(this)()
}
//把view的数据和事件进行绑定
this.complie(this.$el)
//挂载之后
if (typeof options.mounted == 'function') {
options.mounted.bind(this)()
}
}
proxyData() {
//循环通过get和set方法实现代理数据
for (let key in this.$options.data) {
Object.defineProperty(this, key, {
configurable: false, //是否可配置的 默认false
enumerable: true, //是否可通过循环读出 默认false
// writable 定义是否可以修改
get() {
//获取this[key]时,即返回options的data[key]
return this.$options.data[key]
},
set(val) {
this.$options.data[key] = val
}
})
}
}
observe() {
//劫持事件
for (let key in this.$options.data) {
//获取data里的值,保存到value上
let value = this.$options.data[key]
let that = this
Object.defineProperty(this.$options.data, key, {
configurable: false, //是否可配置的 默认false
enumerable: true, //是否可通过循环读出 默认false
// writable 定义是否可以修改
get() {
// console.log('触发获取内容事件')
//获取this[key]时,即返回options的data[key]
return value
},
set(val) {
// console.log('触发设置内容事件')
value = val
//触发key值的更新事件
if (that.$watchEvent[key]) {
that.$watchEvent[key].forEach((item, index) => {
item.update()
})
}
}
})
}
}
complie(cNode) {
console.log([this.$el])
cNode.childNodes.forEach((node, index) => {
if (node.nodeType == 1) {
//元素类型 getAttribute hasAttribute
//判断是否有v-html
if (node.hasAttribute('v-html')) {
let vmKey = node.getAttribute('v-html').trim()
// console.log(this[vmKey])
if (this.hasOwnProperty(vmKey)) {
node.innerHTML = this[vmKey]
let watcher = new Watch(this, vmKey, node, 'innerHTML')
if (this.$watchEvent[vmKey]) {
this.$watchEvent[vmKey].push(watcher)
} else {
this.$watchEvent[vmKey] = []
this.$watchEvent[vmKey].push(watcher)
}
//删除节点事件
node.removeAttribute('v-html')
}
}
//判断是否有v-model属性
if (node.hasAttribute('v-model')) {
let vmKey = node.getAttribute('v-model')
if (this.hasOwnProperty(vmKey)) {
node.value = this[vmKey]
let watcher = new Watch(this, vmKey, node, 'value')
if (this.$watchEvent[vmKey]) {
this.$watchEvent[vmKey].push(watcher)
} else {
this.$watchEvent[vmKey] = []
this.$watchEvent[vmKey].push(watcher)
}
//删除节点事件
node.removeAttribute('v-html')
}
node.addEventListener('input', (event) => {
this[vmKey] = node.value
})
}
//判断是否有@click属性
if (node.hasAttribute('@click')) {
let vmKey = node.getAttribute('@click').trim()
node.addEventListener('click', (event) => {
this.eventFn = this.$options.methods[vmKey].bind(this)
this.eventFn(event)
})
}
if (node.childNodes.length > 0) {
this.complie(node)
}
}
if (node.nodeType == 3) {
//文本类型
let reg = /\{\{(.*?)\}\}/g
let text = node.textContent
node.textContent = text.replace(reg, (match, vmKey) => {
// console.log(match, vmKey)
vmKey = vmKey.trim()
if (this.hasOwnProperty(vmKey)) {
node.innerHTML = this[vmKey]
let watcher = new Watch(this, vmKey, node, 'textContent')
if (this.$watchEvent[vmKey]) {
this.$watchEvent[vmKey].push(watcher)
} else {
this.$watchEvent[vmKey] = []
this.$watchEvent[vmKey].push(watcher)
}
}
return this[vmKey]
})
}
})
}
}
class Watch {
constructor(vm, key, node, attr, nodeType) {
this.vm = vm //vm是实例化的app对象
this.key = key //key即是绑定的vm触发的属性
this.node = node //node即,此vm[key]数据绑定的html节点
this.attr = attr //vm数据绑定的html节点的属性名称
}
update() {
//数据更新之前
if (typeof options.beforeUpdate == 'function') {
options.beforeUpdate.bind(this)()
}
this.node[this.attr] = this.vm[this.key]
//数据更新之后
if (typeof options.updated == 'function') {
options.updated.bind(this)()
}
}
}
然后我们就新建一个index.html文件,将myVue.js导入,进行测试
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="./js/myVue.js" type="text/javascript"></script>
</head>
<body>
<div id="app">
<input type="text" v-model="msg" name="" id="" value="" />
<h1>{{msg}}</h1>
<h2>{{name}}</h2>
<h1 v-html="msg"></h1>
<button type="button" @click="changeEvent">修改msg</button>
</div>
</body>
<script type="text/javascript">
let options = {
el: "#app",
data: {
msg: "hello world",
name: "老王",
},
methods: {
changeEvent() {
this.msg = "Vue!!!"
},
},
beforeCreate() {
console.log("beforeCreate")
},
created() {
console.log("created")
},
beforeMount() {
console.log("beforeMounte")
},
mounted() {
console.log("mounted")
},
beforeUpdate() {
console.log("beforeUpdate")
},
updated() {
console.log("updated")
},
}
let app = new Vue(options)
console.log(app)
</script>
</html>
然后就可以看到基本的数据双向绑定已经实现了