首先看效果截图
图片.png
// 观察者(发布订阅) 观察者 被观察者
class Dep {
constructor() {
this.subs = [];
}
addSub(watcher) {
this.subs.push(watcher);
}
notify() {
this.subs.forEach(wather => wather.update());
}
}
//new Watcher
class Watcher {
constructor(vm, expr, callb) {
this.vm = vm;
this.expr = expr;
this.cb = callb;
//默认先存放老值
this.oldValue = this.get();
}
get() {
Dep.target = this;
let value = CompileUtil.getVal(this.vm, this.expr);
Dep.target = null;
return value;
}
update() {
//更新操作,数据变化后会调用观察者的update方法
let newVal = CompileUtil.getVal(this.vm, this.expr);
if (newVal != this.oldValue) {
this.cb(newVal);
}
}
}
//实现数据劫持功能=>解构赋值
class Observer {
constructor(data) {
console.log(data);
this.observer(data);
}
observer(data) {
//依次遍历,先判断合法性,如果数据没写是空的就不用监控了
//if object => observer 如果是对象就观察
if (data && typeof data == "object") {
//if object => for
for (let key in data) {
this.defineReactive(data, key, data[key]);
//data数据定义key属性 值是data[key]
}
}
}
defineReactive(obj, key, value) {
let dep = new Dep();
this.observer(value);
Object.defineProperty(obj, key, {
get() {
//创建watcher时会 取到对应的内容并且把watcher放到全局上
Dep.target && dep.addSub(Dep.target);
//默认get方法取它未来的value
return value;
},
set: newVal => {
//{school:{name:'imycode'}} school={}
if (newVal != value) {
this.observer(newVal);
value = newVal;
dep.notify();
//if current time value==newVal 如果
//当前的老值和新值一样就不要进到这个if里
//赋值
}
}
});
}
}
//基础类
class Compiler {
constructor(el, vm) {
// console.log(el);
this.vm = vm;
//判断el属性是不是一个元素,如果不是元素那就获取他
this.el = this.isElementNode(el) ? el : document.querySelector(el);
// console.log(this.el); //dom节点
let fragment = this.node2fragment(this.el);
// console.log(fragment);
//再内存中处理好
//节点的内容进行替换
//编译模板数据驱动
this.compile(fragment);
//然后再渲染给页面
this.el.appendChild(fragment);
}
//编译文本的-看看有没{}
isDirective(attrName) {
return attrName.startsWith("v-");
}
compileText(node) {
//{{xxx}} {{aaa}}
let content = node.textContent;
// console.log(content, "内容");
if (/\{\{(.+?)\}\}/.test(content)) {
// console.log(content); //找到所有文本元素开始填充
CompileUtil["text"](node, content, this.vm);
}
}
//编译元素的-看看有没v-model
compileElement(node) {
//取dom元素的属性
let attributes = node.attributes; //类数组
// console.log(attributes);
//判断这些属性里有没有v-model的如果有我就找到了
[...attributes].forEach(attr => {
//v-model="school.name" type="text"
// console.log(attr);
let { name, value: expr } = attr;
//结构 - v-model="school.name"
// console.log(name, value);
//判断name是不是v-开头的,是不是指令
if (this.isDirective(name)) {
let [, directive] = name.split("-");
// console.log(node, "element");
//找到了
CompileUtil[directive](node, expr, this.vm);
//需要调用不同的指令来处理 - v-model="school.name"
}
});
} //核心的编译方法
compile(node) {
//用来编译内存中的dom节点
let childNodes = node.childNodes;
// console.log(childNodes);
[...childNodes].forEach(child => {
//是不是元素,不是元素就是文本
//元素有v-model 文本有{{...}}
if (this.isElementNode(child)) {
// console.log("element ", child);
this.compileElement(child);
// 如果是元素的话再去遍历子节点
this.compile(child);
} else {
// console.log("text ", child);
this.compileText(child);
}
});
}
//把节点移动到内存中
node2fragment(node) {
//把当前元素中的东西都拿到
//一个一个拿childnode儿子节点,都拿到
let fragment = document.createDocumentFragment();
//创建一个文档碎片
let firstChild;
//node指最外层的div#app
while ((firstChild = node.firstChild)) {
//不停拿第一个塞到内存最后页面中的节点都没有了就为null循环自然结束
//拿一个少一个不用担心死循环
//appendChild具有移动性
// console.log(firstChild);
fragment.appendChild(firstChild);
}
return fragment;
}
isElementNode(node) {
//是不是元素节点
return node.nodeType === 1; //undefined===1? 不相等
} //=>#app false
}
//编译工具
CompileUtil = {
getVal(vm, expr) {
//vm.$data 'school.name' 根据表达式取到最终的数据
return expr.split(".").reduce((data, current) => {
return data[current];
}, vm.$data);
},
model(node, expr, vm) {
//给输入框赋予value属性 node.value=xx
// vm[expr]=vm.$data['...']错误
let fn = this.updater["modelUpdater"];
new Watcher(vm, expr, newVal => {
fn(node, newVal);
});
let value = this.getVal(vm, expr); //返给我imycode
fn(node, value);
}, //node节点expr表达式school.name, vm当前实例
html() {
//node.innerHTML=xx
},
//遍历表达式将内容重新替换成一个完整的内容返还回去
getContentValue(vm, expr) {
return expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
return this.getVal(vm, args[1]);
});
},
text(node, expr, vm) {
let fn = this.updater["textUpdater"];
//expr=>{{a}} {{b}} {{c}} => a b c
let content = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
//给表达式每个人都加上观察者,只要数据变了我就更新, 每个大括号
new Watcher(vm, args[1], () => {
fn(node, this.getContentValue(vm, expr)); //返回了一个全的字符串
});
return this.getVal(vm, args[1]);
});
//最终文本内容
fn(node, content);
},
updater: {
//把数据插入到节点中
modelUpdater(node, value) {
node.value = value;
},
htmlUpdater() {},
//处理文本节点
textUpdater(node, value) {
node.textContent = value;
}
}
};
class Vue {
constructor(options) {
//this.$el $data $options
this.$el = options.el;
// console.log("options.el: " + options.el);
this.$data = options.data;
// console.log("options.data: " + options.data);
//把数据全部转化成Object.defineProperty来定义
new Observer(this.$data);
//这个根元素存在编译模板
if (this.$el) {
new Compiler(this.$el, this);
}
}
}
这里需要注意的是,现在只做到了数据驱动视图,视图无法更改model
网友评论