Vue中MVVM模式的双向绑定原理 和 代码的实现
今天带大家简单的实现MVVM模式,Object.defineProperty代理(proxy)数据
MVVM的实现方式:
- 模板编译(Compile)
- 数据劫持(Observer) Object.defineProperty
- 发布的订阅(Dep)
- 观察者(Watcher)
MVVM:
- 数据就是简单的javascript对象,需要将数据绑定到模板上
- 监听视图的变化,视图变化后通知数据更新,数据更新会再次导致视图的变化!
下面是实现方法:
---------------------------------------demo-start--
这是我打的demo:
{{message}}
{{info}} {{message}}
---------------------------------------demo-end--
demo图例:
简单的mock Vue MVVM:
html内容:
<body> <div id="app"> <!-- 测试data数据:实现双向绑定 --> <input type="text" id="input" /> <div> {{message}} <div> {{message}} </div> </div> {{info}} </div> <!-- 简单实现 Vue MVVM模式 --> <script src="ziChin_mock_vue.js"></script> <script> let message = '子卿的初始message' // 实例MVVM: var vm = new Vue({ el: '#app', data: { message, info:'初始info' } }) // 利用oninput输入框测试双向绑定: let input = document.querySelector('#input') input.value = message input.oninput = function (e) { vm.$data.message = e.target.value } </script> </body>
ziChin_mock_vue.js文件:
// 构建一个MVVM实例(ES6实现) class Vue { constructor(options) { // 初始化变量 this.$options = options this.$el = options.el this.$data = options.data // 1.监听数据 this.observer(this.$data) // 2.编译模版 this.compile(this.$el) } compile(el) { // ... } observer(data) { // ... } } // 观察者模式 class Dep { // ... } class Watcher { // 订阅信息 // ... }
observer 监听数据以便更新视图(数据劫持):
observer(data) { Object.keys(data).forEach(key => { let dep = new Dep() let value = data[key] // 数据劫持的核心方法: Object.defineProperty(data, key, { configurable: true, enumerable: true, get() { if (Dep.target) { dep.addSub(Dep.target) // 把订阅信息缓存起来 } return value }, set(newValue) { dep.notify(newValue, value) value = newValue } }) }) }
compile 编译模版(这里我没有用虚拟Node):
compile(el) { let element = document.querySelector(el) let childNodes = element.childNodes const compileNodes = childNodes => { // 递归 Array.from(childNodes).forEach(node => { if (node.nodeType === 3) { // 文本节点 let reg = /\{\{\s*(\S*)\s*\}\}/ let dataKey = null node.textContent = node.textContent.replace(reg, ($0, $1) => { dataKey = $1 return this.$data[dataKey] }) if (dataKey !== null) { // 监听(视图与数据一一对应) new Watcher(this, dataKey, (newValue, value) => { node.textContent = node.textContent.replace(value, newValue) }) } } else if (node.nodeType === 1) { // 标签节点 compileNodes(node.childNodes) } }) } compileNodes(childNodes) }
Dep、Watcher 观察者模式:
// 观察者模式 class Dep { constructor() { this.subs = [] } addSub(sub) { // 缓存订阅内容 this.subs.push(sub) } notify(newValue, value) { // 发布信息 this.subs.forEach(item => item.update(newValue, value)) } } class Watcher { constructor(vm, dataKey, cb) { Dep.target = this vm.$data[dataKey] // 触发Object中get函数的 --> addSub,缓存订阅内容 Dep.target = null this.cb = cb } update(newValue, value) { this.cb(newValue, value) // 由notify触发 } }
所有代码:
html:
index.html
<!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>ziChin_mock_vue</title> </head> <body> <div id="app"> <!-- 测试data数据:实现双向绑定 --> <input type="text" id="input" /> <div> {{message}} <div> {{message}} </div> </div> {{info}} </div> <!-- 简单实现 Vue MVVM模式 --> <script src="ziChin_mock_vue.js"></script> <script> let message = '子卿的初始message' // 实例MVVM: var vm = new Vue({ el: '#app', data: { message, info:'初始info' } }) // 利用oninput输入框测试双向绑定: let input = document.querySelector('#input') input.value = message input.oninput = function (e) { vm.$data.message = e.target.value } </script> </body> </html>
js:
ziChin_mock_vue.js
// 构建一个MVVM实例(ES6实现) class Vue { constructor(options) { // 初始化变量 this.$options = options this.$el = options.el this.$data = options.data // 1.监听数据以便更新视图(数据劫持) this.observer(this.$data) // 2.编译模版(这里我没有用虚拟Node) this.compile(this.$el) } compile(el) { let element = document.querySelector(el) let childNodes = element.childNodes const compileNodes = childNodes => { Array.from(childNodes).forEach(node => { if (node.nodeType === 3) { // 文本节点 let reg = /\{\{\s*(\S*)\s*\}\}/ let dataKey = null node.textContent = node.textContent.replace(reg, ($0, $1) => { dataKey = $1 return this.$data[dataKey] }) if (dataKey !== null) { // 监听(视图与数据一一对应) new Watcher(this, dataKey, (newValue, value) => { node.textContent = node.textContent.replace(value, newValue) }) } } else if (node.nodeType === 1) { // 标签节点 compileNodes(node.childNodes) } }) } compileNodes(childNodes) } observer(data) { Object.keys(data).forEach(key => { let dep = new Dep() let value = data[key] Object.defineProperty(data, key, { configurable: true, enumerable: true, get() { if (Dep.target) { dep.addSub(Dep.target) } return value }, set(newValue) { dep.notify(newValue, value) value = newValue } }) }) } } // 观察者模式 class Dep { constructor() { this.subs = [] } addSub(sub) { this.subs.push(sub) } notify(newValue, value) { this.subs.forEach(item => item.update(newValue, value)) } } class Watcher { constructor(vm, dataKey, cb) { Dep.target = this vm.$data[dataKey] Dep.target = null this.cb = cb } update(newValue, value) { this.cb(newValue, value) } }
生活不易,请继续努力,在未来的路上,愿你步伐坚定且内心温柔。——ziChin