直播课(1)如何通过数据劫持实现Vue(mvvm)框架
19.6.28更新:
这篇博客比较完善:将每一部分都分装在单独的js文件中:
半个月前看的直播课,现在才自己敲了一遍,罪过罪过
预览:

思路:
简单实现
Vuemvvm的双向数据绑定,需要以下几个步骤:
实现一个入口,把 指令渲染,数据劫持
实现指令渲染,包括层级嵌套的标签,文本
数据劫持
订阅发布
1.实现一个入口文件
let vm = new Kvue({
el: "#app",
data: {
message: "测试数据",
options: "123",
name: "张三"
}
})
2.替换{{}}中的数据
class Kvue {
constructor(options) {
// 将传入的数据挂载到 Kvue 上
this.$options = options
this._data = options.data
// 编译 {{}},此时需要把编译的范围当做入参
this.compile(options.el)
}
// 模板替换
compile(el) {
// 获取挂载点
let element = document.querySelector(el)
this.compileNode(element)
}
// 递归节点
compileNode(element) {
// 获取 childNodes
let childNodes = element.childNodes
// 将 childNodes 转换为 真正的数组
Array.from(childNodes).forEach(node => {
// 文本节点 nodeType = 3
if(node.nodeType == 3) {
// console.log(node)
// 获取节点内容
let nodeContent = node.textContent
// 使用正则匹配{{}},去除其中的空格
let reg = /\{\{\s*(\S*)\s*\}\}/
if(reg.test(nodeContent)) {
// console.log(RegExp.$1)
node.textContent = this._data[RegExp.$1]
}
} else if (node.nodeType == 1) {
// 标签节点
let attrs = node.attributes
// console.log(attrs)
// 遍历标签节点
Array.from(attrs).forEach(attr => {
// 获取标签的属性
let attrName = attr.name
// 获取标签的值
let attrValue = attr.value
// console.log(attrValue)
// 匹配是否是 k- 开头的指令
if(attrName.indexOf('k-') == 0) {
// 获取 k- 后面的部分,
attrName = attrName.substr(2)
// console.log(attrName)
// 目的是防止用户自定义 k-holle 的属性
if(attrName == "model") {
// 将 data 中的对应值赋给此节点
node.value = this._data[attrValue]
}
// 监听 input 变化
node.addEventListener('input', e => {
console.log(e.target.value)
this._data[attrValue] = e.target.value
})
}
})
}
// 递归判断是否有子节点
if(node.childNodes.length > 0) {
this.compileNode(node)
}
})
}
}
3.数据劫持
认识 defineProperty()
// let obj = {name: "张三"}
// console.log(obj);
// obj.name = "李四"
// 数据劫持
let obj = Object.defineProperty({}, "name", {
configurable: true, // 可配置
enumerable: true, // 枚举
get() {
console.log("get");
return "张三" // 必须 return
},
set(newValue) {
console.log("set", newValue);
}
})
console.log(obj);
实现数据劫持
// 数据劫持
observer(data) {
Object.keys(data).forEach(key => {
let value = data[key]
Object.defineProperty(data, key, {
configurable: true,
enumrable: true,
get() {
return value
},
set(newValue) {
// console.log("set", newValue)
value = newValue
}
})
})
}
现在实现了数据劫持,那么数据变化,就需要通知 observer 去更新视图,这时就需要一个订阅发布模式
4.订阅发布,视图更新
订阅发布模式:
demo:
老王给孩子或者邻居通过电话讲故事,但是有时候电话没人接,老王需要重新打一次。这时就想到了发布订阅模式:老王将讲的故事录成视频,存到网上,然后孩子和邻居注册报备一下,老王知道谁订阅了他的故事,然后老王群发一个消息,让他们自己去看
// 发布订阅模式
// 老王,订阅收集器
class Dep {
constructor() {
// 把 孩子 邻居 放在一个容器中存起来
this.subs = []
} // 注册报备
addSub(sub) {
this.subs.push(sub)
} // 发布视频,通知 孩子 邻居 更新
notify() {
this.subs.forEach(v => {
v.update();
})
}
} // 订阅者 孩子,邻居
class Watcher {
constructor() { }
//
update() {
console.log('更新了');
}
} // 实力化 老王
let dep = new Dep() // 孩子 邻居
let watcher1 = new Watcher()
let watcher2 = new Watcher()
let watcher3 = new Watcher() // 孩子 邻居 注册报备
dep.addSub(watcher1)
dep.addSub(watcher2)
dep.addSub(watcher3) // 发布视频
dep.notify()
MVVM实现订阅发布
在数据劫持结合订阅发布模式实现视图更新(难点)
// 发布订阅模式
class Dep {
constructor() {
this.subs = []
} addSub(sub) {
this.subs.push(sub)
} notify(newValue) {
this.subs.forEach(v => {
// console.log(newValue)
v.update(newValue);
})
}
} class Watcher {
constructor(vm, exp, cb) {
// 在更新时,实例化,在什么位置加呢?在调取数据时添加Watcher,但是在加的时候先声明处订阅收集器——老王 —— get() {}
// 防止重复添加
Dep.target = this
// 触发 get 方法
vm._data[exp]
// 改变视图的回调
this.cb = cb
// 防止重复添加
Dep.target = null
}
update(newValue) {
console.log('更新了', newValue)
// 改变视图
this.cb(newValue)
}
}
总结
简单实现vue的双向绑定,没有涉及复杂的对象
代码冗余,没有抽离
Kvue 类太复杂,没有把 数据劫持,订阅发布,代码编译 抽离成单独的 js 文件
未完待续。。。
全部代码
index.html
<head>
<meta charset="UTF-8">
<title>如何通过数据劫持实现Vue(mvvm)框架</title>
<script src="./kvue.js"></script>
</head> <body>
<div id="app">
{{message}}
<p>{{message}}</p>
<hr>
<input type="text" k-model="name">
{{name}}
</div>
<script>
let vm = new Kvue({
el: '#app',
data: {
message: '测试数据',
name: '张三'
}
})
// 模拟数据改变,实现视图更新
setTimeout(() => {
vm._data.message = "修改的值"
}, 2000)
// vm._data.message = "修改的值"
// vm._data.name = "ls"
// vm.message
// vm.options
</script>
</body>
kvue.js
class Kvue {
constructor(options) {
// 将传入的数据挂载到 Kvue 上
this.$options = options
this._data = options.data
// 劫持数据 defineProperty()
this.observer(this._data)
// 编译 {{}},此时需要把编译的范围当做入参
this.compile(options.el)
}
// 数据劫持
observer(data) {
Object.keys(data).forEach(key => {
let value = data[key]
// 订阅收集器
let dep = new Dep()
// 数据劫持
Object.defineProperty(data, key, {
configurable: true, // 可配置
enumrable: true, // 枚举
// get 需要触发
get() {
// 如果 Dep 中有 target,添加addSub()
if(Dep.target) {
dep.addSub(Dep.target)
}
return value // 必须 return
},
set(newValue) {
// console.log("set", newValue)
if(newValue !== value)
value = newValue
// 当改变时 通知 update(),更新UI视图
dep.notify(newValue)
}
})
})
}
// 模板替换
compile(el) {
// 获取挂载点
let element = document.querySelector(el)
this.compileNode(element)
}
// 递归节点
compileNode(element) {
// 获取 childNodes
let childNodes = element.childNodes
// 将 childNodes 转换为 真正的数组
Array.from(childNodes).forEach(node => {
// 文本节点 nodeType = 3
if(node.nodeType == 3) {
// console.log(node)
// 获取节点内容
let nodeContent = node.textContent
// 使用正则匹配{{}},去除其中的空格
let reg = /\{\{\s*(\S*)\s*\}\}/
if(reg.test(nodeContent)) {
// console.log(RegExp.$1)
node.textContent = this._data[RegExp.$1]
// 初次渲染 实例化 Watcher,并且防止递归过程中重复添加
// 将 this 传进来,目的是传 this 下的 data, 还有 下标 cb 是回调,作用是更新视图,不建议在 订阅发布中更新视图
new Watcher(this, RegExp.$1, newValue => {
// 更新视图
// console.log(newValue)
node.textContent = newValue
})
}
} else if (node.nodeType == 1) {
// 标签节点
let attrs = node.attributes
// console.log(attrs)
// 遍历标签节点
Array.from(attrs).forEach(attr => {
// 获取标签的属性
let attrName = attr.name
// 获取标签的值
let attrValue = attr.value
// console.log(attrValue)
// 匹配是否是 k- 开头的指令
if(attrName.indexOf('k-') == 0) {
// 获取 k- 后面的部分,
attrName = attrName.substr(2)
// console.log(attrName)
// 目的是防止用户自定义 k-holle 的属性
if(attrName == "model") {
// 将 data 中的对应值赋给此节点
node.value = this._data[attrValue]
}
// 监听 input 变化
node.addEventListener('input', e => {
this._data[attrValue] = e.target.value
})
// 注册
new Watcher(this, attrValue, newValue => {
node.value = newValue
})
}
})
}
// 递归判断是否有子节点
if(node.childNodes.length > 0) {
this.compileNode(node)
}
})
}
}
// 发布订阅模式
class Dep {
constructor() {
this.subs = []
}
addSub(sub) {
this.subs.push(sub)
}
notify(newValue) {
this.subs.forEach(v => {
// console.log(newValue)
v.update(newValue);
})
}
}
class Watcher {
constructor(vm, exp, cb) {
// 在更新时,实例化,在什么位置加呢?在调取数据时添加Watcher,但是在加的时候先声明处订阅收集器——老王 —— get() {}
// 防止重复添加
Dep.target = this
// 触发 get 方法
vm._data[exp]
// 改变视图的回调
this.cb = cb
// 防止重复添加
Dep.target = null
}
update(newValue) {
console.log('更新了', newValue)
// 改变视图
this.cb(newValue)
}
}
直播课(1)如何通过数据劫持实现Vue(mvvm)框架的更多相关文章
- 对数据劫持 OR 数据代理 的研究------------引用
数据劫持,也叫数据代理. 所谓数据劫持,指的是在访问或者修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作或者修改返回结果.比较典型的是 Object.defineProperty() 和 ...
- 《数据持久化与鸿蒙的分布式数据管理能力》直播课答疑和PPT分享
问:hi3861开发板支持分布式数据库吗? 目前,分布式数据库仅支持Java接口,因此Hi3861没有现成的API用于操作分布式数据库. 问:分布式数据管理包括搜索吗? 分布式数据管理包括融合搜索能力 ...
- .4-Vue源码之数据劫持(2)
开播了开播了! vue通过数据劫持来达到监听和操作DOM更新,上一节简述了数组变化是如何监听的,这一节先讲讲对象属性是如何劫持的. // Line-855 Observer.prototype.wal ...
- Vue之九数据劫持实现MVVM的数据双向绑定
vue是通过数据劫持的方式来做数据绑定的,其中最核心的方法便是通过Object.defineProperty()来实现对属性的劫持,达到监听数据变动的目的. 如果不熟悉defineProperty,猛 ...
- Vue 核心之数据劫持
前端界空前繁荣,各种框架横空出世,包括各类mvvm框架横行霸道,比如Angular.Regular.Vue.React等等,它们最大的优点就是可以实现数据绑定,再也不需要手动进行DOM操作了,它们实现 ...
- vue双向绑定(数据劫持+发布者-订阅者模式)
参考文献:https://www.cnblogs.com/libin-1/p/6893712.html 实现mvvm主要包含两个方面,数据变化更新视图,视图变化更新数据. 关键点在于data如何更新v ...
- 免费在线直播课,送给所有IT项目经理
[免费在线直播课,送给所有IT项目经理]项目管理培训领域的老资格——光环国际,精心策划了一门一个半小时的在线直播课,送给所有辛苦的IT项目经理们.[直播主题]变化时代IT项目经理的成长要求[直播内容 ...
- php特级课---5、网络数据转发原理
php特级课---5.网络数据转发原理 一.总结 一句话总结: OSI七层模型 路由器 交换机 ARP 代理ARP 1.OSI7层模型? 电缆 MAC地址 ip 端口 应用 1层 通信电缆 2层 原M ...
- Vue框架核心之数据劫持
本文来自网易云社区. 前瞻 当前前端界空前繁荣,各种框架横空出世,包括各类mvvm框架横行霸道,比如Angular.Regular.Vue.React等等,它们最大的优点就是可以实现数据绑定,再也不需 ...
随机推荐
- ubi问题总结
一.挂载成功后,使用正常.有时会出现:UBIFS error (pid 76): ubifs_read_node: bad node type (255 but expected 1)UBIFS er ...
- SPFA和堆优化的Dijk
朴素dijkstra时间复杂度$O(n^{2})$,通过使用堆来优化松弛过程可以使时间复杂度降到O((m+n)logn):dijkstra不能用于有负权边的情况,此时应使用SPFA,两者写法相似. 朴 ...
- POJ 3274:Gold Balanced Lineup 做了两个小时的哈希
Gold Balanced Lineup Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 13540 Accepted: ...
- [Codeforces]1263E Editor
The development of a text editor is a hard problem. You need to implement an extra module for bracke ...
- Mongoose多表查询
文章来自 两个表关联查询aggregate 多个表关联查询aggregate populate多表关联查询 多表查询的两个方式 一个是aggregate聚合 一个是populate Schema的外表 ...
- python实现进程的三种方式及其区别
在python中有三种方式用于实现进程 多进程中, 每个进程中所有数据( 包括全局变量) 都各有拥有⼀份, 互不影响 1.fork()方法 ret = os.fork() if ret == 0: # ...
- 028、Java中的关系运算符
01.代码如下: package TIANPAN; /** * 此处为文档注释 * * @author 田攀 微信382477247 */ public class TestDemo { public ...
- docker安装centos7镜像
拉取centos7镜像[root@localhost ~]# docker pull centos:71启动镜像centos7,如果不指定 /bin/bash,容器运行后会自动停止[root@loca ...
- 前端 移动端H5页面 DEBUG
下载网址:https://github.com/Tencent/vConsole 把这个JS复制到项目里面 然后引入到HTML中 然后在JS上面实例化一下即可 页面就会有一个绿色的,然后点击一下就可以 ...
- Robot set variable if
${strid} Set Variable If '${row}' =='2' LFFD_TANK_RAMP ... '${row}' =='3' LFFD_TANK_LANDING