Vue中MVVM模式的双向绑定原理 和 代码的实现
- 模板编译(Compile)
- 数据劫持(Observer) Object.defineProperty
- 发布的订阅(Dep)
- 观察者(Watcher)
- 数据就是简单的javascript对象,需要将数据绑定到模板上
- 监听视图的变化,视图变化后通知数据更新,数据更新会再次导致视图的变化!
{{info}}
<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>
// 构建一个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(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(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)
}
// 观察者模式
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:
// 构建一个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)
}
}
Vue中MVVM模式的双向绑定原理 和 代码的实现的更多相关文章
- 前端笔记之微信小程序(二){{}}插值和MVVM模式&数据双向绑定&指令&API
一.双花括号{{}}插值和MVVM模式 1.1 体会{{}}插值 index.wxml的标签不是html的那些标签,这里的view就是div. {{}}这样的插值写法,叫做mustache语法.mus ...
- 解决Vue中文本输入框v-model双向绑定后数据不显示的问题
前言 项目中遇到一个问题就是在Vue中双向绑定对象属性时,手动赋值属性后输入框的数据不实时更新的问题. <FormItem label="地址" prop="eve ...
- vue中v-model的数据双向绑定(重要)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- Vue双向绑定原理,教你一步一步实现双向绑定
当今前端天下以 Angular.React.vue 三足鼎立的局面,你不选择一个阵营基本上无法立足于前端,甚至是两个或者三个阵营都要选择,大势所趋. 所以我们要时刻保持好奇心,拥抱变化,只有在不断的变 ...
- vue的双向绑定原理及实现
前言 使用vue也好有一段时间了,虽然对其双向绑定原理也有了解个大概,但也没好好探究下其原理实现,所以这次特意花了几晚时间查阅资料和阅读相关源码,自己也实现一个简单版vue的双向绑定版本,先上个成果图 ...
- vue双向绑定原理及实现
vue双向绑定原理及实现 一.总结 一句话总结:vue中的双向绑定主要是通过发布者-订阅者模式来实现的 发布 订阅 1.单向绑定和双向绑定的区别是什么? model view 更新 单向绑定:mode ...
- 通俗易懂了解Vue双向绑定原理及实现
看到一篇文章,觉得写得挺好的,拿过来给大家分享一下,刚好解答了一些困扰我的一些疑惑!!! 1. 前言 每当被问到Vue数据双向绑定原理的时候,大家可能都会脱口而出:Vue内部通过Object.defi ...
- vue的双向绑定原理浅析与简单实现
很久之前看过vue的一些原理,对其中的双向绑定原理也有一定程度上的了解,只是最近才在项目上使用vue,这才决定好好了解下vue的实现原理,因此这里对vue的双向绑定原理进行浅析,并做一个简单的实现. ...
- vue双向绑定原理分析
当我们学习angular或者vue的时候,其双向绑定为我们开发带来了诸多便捷,今天我们就来分析一下vue双向绑定的原理. 简易vue源码地址:https://github.com/jiangzhenf ...
随机推荐
- Noip2015Day2T3 运输计划
题目链接 problem 一棵n个点带边权的树,有m个条路径.选择一条边,将其权值变为0,使得长度最长的路径长度最小.求该长度最小为多少. solution 其实仔细一想并不难. 删除一条边会导致所有 ...
- 使用canal增量同步mysql数据库信息到ElasticSearch
本文介绍如何使用canal增量同步mysql数据库信息到ElasticSearch.(注意:是增量!!!) 1.简介 1.1 canal介绍 Canal是一个基于MySQL二进制日志的高性能数据同步系 ...
- 修改本地的host文件
在C:\Windows\System32\drivers\etc下有一个host文件, 在里面可以修改本地的域名,比如我文件里添加一行: 10.0.33.79 devsuite.easthope ...
- 转载-Java中LinkedList的一些方法—addFirst addFirst getFirst geLast removeFirst removeLast
Java中LinkedList的一些方法—addFirst addFirst getFirst geLast removeFirst removeLast 版权声明:本文为博主原创文章,遵循CC 4. ...
- Java反射方法总结
1.得到构造器的方法 Constructor getConstructor(Class[] params) -- 获得使用特殊的参数类型的公共构造函数, Constructor[] getConstr ...
- git 创建分支 提交到远程分支
git 创建分支 并 提交到远程分支 git branch 0.可以通过git branch -r 命令查看远端库的分支情况 1,从已有的分支创建新的分支(如从master分支),创建一个dev分支 ...
- idea使用lombok不生效的解决办法
file-->setting-->plugins点击下方的 browse repositories. 搜索lombok plugin. 安装后,重启. file-->setting- ...
- vue-基本动画
不使用动画 <div id="app"> <input type="button" value="toggle" @cli ...
- Vue中computed和watch的区别
在vue中computed和watch的真正区别是:computed产生于它的依赖,而watch产生于它的依赖的变化.只要依赖存在,我们就能访问到其对应的computed属性:但只有依赖发生了改变,我 ...
- find命令通过排序只保留最新的文件目录
find /usr/local/canal/logs/example -type d -name "*-*" | sort -nr | awk '{if (NR>=2){pr ...