通过数据劫持实现vue(mvvm)框架
- vue中最简单的表达式的使用
- 如何实现渲染表达式里面的数据
- 修改数据后如何更新视图 4-5
- 如何劫持数据监听数据改变(拿到改变的数据)
- 如何通过发布订阅模式通知数据更新
- (多层数据如何渲染)
- v-model的初次渲染
- 精简代码实现双向绑定
1.vue中最简单的表达式的使用
- el :绑定哪个dom。 [el: 语法固定]
- data: 显示数据。 [data: 语法固定;data下面的msg可以随便定义]
- html 显示:{{msg}} 2个大括号
<!DOCTYPE html>
<head>
<!-- <script src="https://unpkg.com/vue/dist/vue.js"></script> -->
<script src="kvue.js"></script>
</head>
<body>
<div id="app">
{{ message}}
<p> {{message}}</p>
<input type="text" k-model="name"/>{{name}}
</div>
</body>
<script>
let vm=new Kvue({
el:"#app",
data:{
message:"test",name:'张三'
}
})
vm._data.message="新消息"
vm._data.name="新名字"
</script>
</html>
2-8. vue底层实现

class Kvue {
constructor(options) {
// $options防止与配置的数据冲突
this.$options = options;
this._data = options.data;
// 数据劫持,更新视图
this.observer(this._data);
this.compile(options.el);
}
// 2.实现渲染表达式里面的数据
compile(el) {
let element = document.querySelector(el); //获取根节点
this.compileNode(element);
}
// 2.实现渲染表达式里面的数据
compileNode(element) {
let childNodes = element.childNodes; //获取根节点的所有的子节点
// console.log(childNodes); //NodeList(3) [text, p, text]
// console.log(typeof childNodes);//object
// 子节点object转成数组再遍历处理
Array.from(childNodes).forEach(node => {
if (node.nodeType == 3) {
// 处理文本
// console.log(node);
let nodeContent = node.textContent;
// console.log(nodeContent);
let reg = /\{\{\s*(\S*)\s*\}\}/;
if (reg.test(nodeContent)) {
console.log(RegExp.$1); //正则表达式匹配的第一个子匹配(以括号为标志)字符串
node.textContent = this._data[RegExp.$1]; //把data里面的值赋给对应的参数,更新视图
// 5. 如何通过发布订阅模式通知数据更新-通知更新
// 实例化,回调用于更新视图
new Watcher(this, RegExp.$1, newValue => {
node.textContent = newValue; //更新视图
});
}
} else if (node.nodeType == 1) {
// 处理标签
//console.log("标签", node);
let attrs = node.attributes;
console.log(attrs);
Array.from(attrs).forEach(attr => {
//console.log(attr);
let attrName = attr.name;
let attrValue = attr.value;
// 7. v-model(k-model)的初次渲染
if (attrName.indexOf("k-") == 0) {
attrName = attrName.substr(2);
// console.log(attrName);
if (attrName == "model") {
node.value = this._data[attrValue];
}
// 8. 精简代码实现双向绑定
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);
}
});
}
}
// 4. 如何劫持数据监听数据改变 this.observer(this._data);
observer(data) {
Object.keys(data).forEach(key => {
let value = data[key];
let dep = new Dep();
Object.defineProperty(data, key, {
configurable: true,
enumerable: true,
get() {
if (Dep.target) {
dep.addSub(Dep.target);//添加一个观察者对象
}
return value;
},
set(newValue) {
console.log("newValue", newValue);
if (newValue != value) value = newValue;
dep.notify(newValue);//通知所有订阅者
}
});
});
}
// 5. 如何通过发布订阅模式通知数据更新
// 发布订阅
class Dep {
constructor() {
this.subs = [];//定义 subs 数组,用来收集订阅者 Watcher
}
/*添加一个观察者对象*/
addSub(sub) {
this.subs.push(sub);
}
/*通知所有订阅者*/
// 当劫持到数据变更的时候,通知订阅者 Watcher 进行 update 操作
notify(newValue) {
this.subs.forEach(v => {
v.update(newValue);
});
}
}
// 通知更新
// 当 Dep 发出消息传递(notify)的时候,订阅 Dep 的 Watchers 会进行自己的 update 操作。
class Watcher {
// vm从this来,new Watcher(this, 下标,callback)
constructor(vm, exp, cb) {
Dep.target = this;
vm._data[exp];
this.cb = cb;
Dep.target = null;
}
update(newValue) {
console.log("update", newValue);
// newValue传给回调函数用于更新视图,只提供修改数据,不通过更新服务(不同地方不同)
this.cb(newValue);
}
}
知识点
- 正则表达式--获取{{}}里面的字符串
var reg = /\{\{\s*(\S*)\s*\}\}/;
reg.test("{{abcdefg}}");
RegExp.$1 // "abcdefg"
- 数据劫持--监听数据改变
数据劫持的核心方法就是使用Object.defineProperty把属性转化成getter/setter。(因为这个是 ES5 中的方法,所以这也是 Vue 不支持 ie8 及以下浏览器的原因之一。)在数据传递变更的时候,会进入到我们封装的Dep和Watcher中进行处理。
// let obj = { name: "zhangsan" };
// console.log(obj);
// obj.name = "newname";
let obj = Object.defineProperty({}, "name", {
configurable: true,
enumerable: true,
get() {
console.log("get");
return "zhangsan";
},
set(newValue) {
console.log("set", newValue);
}
});
console.log(obj);
- 发布订阅模式--通知数据更新,要改数据的地方实例化Watcher
发布订阅模式,它定义了一种一对多的关系,可以使多个观察者对象对一个主题对象进行监听,当这个主题对象发生改变时,依赖的所有对象都会被通知到。
// 发布订阅
class Dep {
constructor() {
this.subs = [];
}
addSub(sub) {
this.subs.push(sub);
}
notify() {
this.subs.forEach(v => {
v.update();
});
}
}
class Watcher {
constructor() {}
update() {
console.log("update");
}
}
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();
- vue合并使用
数据劫持的时候,数据的获取或者修改的时候,都会做出对应的操作。这些操作的目的很简单,就是“通知”到“中转站”。这个“中转站”主要就是对数据的变更起通知作用以及存放依赖这些数据的“地方”。这个"中转站"就是由"Dep"和“Watcher” 类构成的。每个被劫持的数据都会产生一个这样的“中转站”

observer(data) {
Object.keys(data).forEach(key => {
let value = data[key];
let dep = new Dep();
Object.defineProperty(data, key, {
configurable: true,
enumerable: true,
get() {
if (Dep.target) {
dep.addSub(Dep.target);
}
return value;
},
set(newValue) {
console.log("newValue", newValue);
if (newValue != value) value = newValue;
dep.notify(newValue);
}
});
});
}
// 发布订阅
// Dep 类是用来做依赖收集的,但是也有通知对应的订阅者的作用 ,让它执行自己的操作
class Dep {
constructor() {
this.subs = [];//定义 subs 数组,用来收集订阅者 Watcher
}
/*添加一个观察者对象*/
addSub(sub) {
this.subs.push(sub);
}
/*通知所有订阅者*/
// 当劫持到数据变更的时候,通知订阅者 Watcher 进行 update 操作
notify(newValue) {
this.subs.forEach(v => {
v.update(newValue);
});
}
}
网友评论