数据劫持
先对data中的数据进行劫持并挂载到vue实例上,这时每个数据对象都可以模拟是一个订阅者,当数据发生改变,发布者会通知(调用notify方法)每一个订阅者去调用update方法进行更新,然后通过编译器编译渲染到视图上,当视图发生改变了,每个订阅者会向发布者进行订阅,并返回到进行数据更新,进行数据同步(注释仅个人理解,如有不对,请指教)
class Vue {
constructor(options) {
this.$options = options;
this._data = options.data;
this.$el = typeof options.el === "string" ? document.querySelector(options.el) : options.el
// 数据劫持
this._proxyData(this._data);
// 观察者模式
new Observer(this._data)
// 编译
new Complier(this);
}
_proxyData(data) {
Object.keys(this._data).forEach(key => {
Object.defineProperty(this, key, {
get() {
return data[key]
},
set(nValue) {
if (data[key] === nValue) {
return
}
data[key] = nValue;
}
})
})
}
}
观察者模式
class Observer {
constructor(data) {
this.walk(data)
}
// 遍历
walk(data) {
// 判断data是否存在,data是不是对象 如果不是或不存在则不处理
// 开始 传入data:{} 是对象
if (!data || typeof data !== "object") {
return
}
// 遍历data中的每一个属性名key 拿到key
// object.keys(),将data中的属性名存在一个数组中 [msg,crq,...]
Object.keys(data).forEach(key => {
// 挂载到vue实例中,对拿到的key值进行代理 key:msg data[key]:"hello vue"
this.defineReactive(data, key, data[key])
})
}
// 定义响应式数据 将属性属性值挂载到vue实例上
defineReactive(data, key, value) {
// 对每一个key进行发布和监听,在这里实例化publisher
let publisher = new Publisher();
let that = this;
// 调用walk判断传进来的 value 也就是walk中data遍历后的属性值data[key]是不是对象,如果是对象,继续遍历,不是则返回,例如遍历data后key:crq data[key]:{agr:...,sex:...}
this.walk(value);
// 将属性值挂载到vue实例上
// 这里将key进行代理,为每一个key添加get和set方法
Object.defineProperty(data, key, {
get() {
// 收集依赖 添加观察者
// publisher.target 就相当于添加的观察者的形参
Publisher.target && publisher.addSub(Publisher.target);
return value
},
set(nValue) {
if (value === nValue) {
return
}
value = nValue
// 如果数据修改或新添加数据,重新调用walk判断是否是对象然后进行挂载
that.walk(nValue)
// 发布通知,监测到数据发生改变,通知观察者更新数据
publisher.notify()
}
})
}
}
发布者
class Publisher {
constructor() {
// 这里初始化subs空数组 准备用来储存观察者
this.subs = [];
}
// 将观察者添加到数组中
addSub(sub) {
// 判断 观察者是否存在,并且观察者上是否有更新方法
// 如果有 添加到subs数组中
if (sub && sub.update) {
this.subs.push(sub);
}
}
notify() {
// 通知每一个观察者 调用update方法更新
console.log("notify");
this.subs.forEach(w => {
w.update();
console.log("w.up");
});
}
}
订阅者(观察者)
class Wathcher {
constructor(data, key, cb) {
this.data = data;
this.key = key;
this.cb = cb
Publisher.target = this
this.oldValue = data[key]
}
// 更新视图
// 更新前提是数据一定要发生改变,数据要和之前对比,如果没有变化就不更新
// 获取发布者通知的信息
update() {
console.log("watcher--update");
let newValue = this.data[this.key];
// console.log(newValue);
if (newValue === this.oldValue) {
return
}
// 如果数据改变,dom更新
this.cb(newValue);
}
}
编译器
class Complier {
constructor(vm) {
this.el = vm.$el;
this.data = vm._data;
this.complie(this.el)
}
complie(el) {
let childNodes = el.childNodes
Array.from(childNodes).forEach(node => {
// 代码分割
if (this.isTextNode(node)) {
// 文本处理
this.complieText(node)
} else if (this.isElementNode(node)) {
// 元素处理
this.complieElement(node)
}
// 当元素中包含文本和插值表达式时,需要再调用编译,编译出元素中嵌套的插值表达式和文本
// 判断编译一遍后,里面是否还有节点,如果存在节点则再调用一遍编译 complie
if (node.childNodes && node.childNodes.length > 0) {
this.complie(node)
}
})
}
// 编译元素
complieElement(node) {
// 获取所有属性
let attributes = node.attributes
// 遍历所有属性
Array.from(attributes).forEach(attr => {
// 获取属性名
// console.log(attr);
let attrName = attr.name;
// console.log(attrName);
// 判断属性是否是 v- 开头 如果是
if (this.isDirective(attrName)) {
// 获取v-后面部分 指令名
attrName = attrName.substring(2)
console.log(attrName);
// 属性值 data[key]
let key = attr.value
// text - 映射方法,不同指令 不同函数
this.update(node, attrName, key)
console.log(this.data[key]);
}
})
}
// 更新
update(node, attrName, key) {
// 判断 不同指令调用不同函数
// if (attrName === "text") {
// this.textUpdate(node, attrName, key)
// }
// if (attrName === "model") {
// this.modelUpdate(node, attrName, key)
// }
// fn 为声明的变量, 不能直接调用,要改变this指向 指向complier
// 声明fn 代替各种判断
let fn = this[attrName + 'Update'];
fn && fn.call(this, node, attrName, key)
console.log(key);
}
// 属性名为 text 时
textUpdate(node, attrName, key) {
console.log(key);
// 更新节点文本内容 这里this.data[key]
node.textContent = this.data[key];
console.log(this.data[key]);
// 将数据传入watcher在watcher里面判断,如果数据发生改变,则调用watcher中的update方法,更新视图中数据
new Wathcher(this.data, key, (newValue) => {
console.log("txtup");
node.textContent = newValue
})
}
// 属性名为 model 时 model 实现双向数据绑定
modelUpdate(node, attrName, key) {
console.log(key);
// console.log(node);
// input 里面文本是value
node.value = this.data[key]
console.log(this.data[key]);
// key = node.value
// console.log(node.value);
new Wathcher(this.data, key, (newValue) => {
node.value = newValue
})
node.addEventListener('input', () => {
this.data[key] = node.value
})
}
// 编译文本
complieText(node) {
// console.log(node);
// console.log(node.textContent);
let value = node.textContent //内容
let reg = /\{\{(.+?)\}\}/ // 正则规则
// 判断 传入的文本内容中是否有正则表达式,如果有,则获取表达式中的值
if (reg.test(value)) {
// 获取:插值表达式的变量名
let k = RegExp.$1.trim()
console.log(k);
// 替换
node.textContent = value.replace(reg, this.data[k])
// 数据变化 - 通知更新 - update - cb - dom
new Wathcher(this.data, k, (newValue) => {
node.textContent = newValue
})
}
}
// 节点判断
// 判断属性名是否有v-
isDirective(attrName) {
// 返回值为boolen
return attrName.startsWith("v-")
}
isTextNode(node) {
return node.nodeType === 3
}
isElementNode(node) {
return node.nodeType === 1
}
isAttrNode(node) {
return node.nodeType === 2
}
}