new Vue 做了什么
- 传入options配置项,通过属性保存传入的属性
- 将传入的data成员转化为响应式数据,并挂载到vue实例中
- 调用Observer对象,监听数据变化,添加watcher
- 通用compiler对象,解析指令和插值表达式
- compiler解析中遇到数据更新调用watcher,更新视图
class Vue {
constructor(options) {
// 1. 通过属性保存选项中的数据
this.$options = options || {};
this.$data = options.data || {};
this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el;
// 2. 把data中的成员转换成getter合setter,注入到vue实例中
this._definePropertyHandler(this.$data);
// 3. 调用observer对象, 监听数据的变化
new Observer(this.$data);
// 4. 调用compiler对象,解析指令和插值表达式
new Compiler(this);
}
_definePropertyHandler(data) {
Object.keys(data).forEach((key) => {
Object.defineProperty(this, key, {
enumerable: true,
configurable: true,
get() {
return data[key];
},
set(newValue) {
if (newValue === data[key]) return;
data[key] = newValue;
}
})
})
}
}
创建observe
class Observer {
constructor(data) {
this.walk(data);
}
walk(data) {
if (!data || typeof data !== 'object') return;
Object.keys(data).forEach((key) => {
this.defineReactive(data, key, data[key])
})
}
defineReactive(obj, key, value) {
const _this = this;
let dep = new Dep(); // 负责收集依赖
// 需要传递第三个参数的原因是 如果通过obj[key] 去访问的话会造成死循环 一直访问get方法 局部变量没有被释放掉的原因是
// 外部因为了get这个方法 就相当于造成了闭包 所以value这个局部变量不会被释放掉
// 再次调用walk方法, 让data中的对象属性也变成响应式对象
this.walk(value);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
// 添加依赖
Dep.target && dep.addSub(Dep.target);
return value
},
set(newValue) {
if (newValue === value) return;
value = newValue;
_this.walk(newValue);
// 发送通知
dep.notify();
}
})
}
}
创建一个观察者模式用于发送更新通知及添加依赖
class Dep {
constructor() {
// 存储所有的观察者
this.subs = [];
}
// 添加观察者
addSub(sub) {
(sub && sub.update) && this.subs.push(sub);
}
// 发送通知
notify() {
this.subs.forEach((sub) => {
sub.update();
})
}
}
最后创建一个compiler文件用来进行解释指令及插值表达式
// 负责编译模板 解析指令和插值表达式 负责页面的首次渲染 当数据变化后重新渲染视图
class Compiler {
constructor(vm) {
this.el = vm.$el;
this.vm = vm;
this.compile(this.el);
}
// 编译模板 处理文本节点和元素节点
compile(el) {
let childNodes = el.childNodes;
Array.from(childNodes).forEach(node => {
if (this.isTextNode(node)) {
// 处理文本节点
this.compilerText(node);
}
if (this.isElementNode(node)) {
// 处理元素节点
this.compileElement(node);
}
// 判断node节点, 是否有子节点, 如果有子节点,要递归调用compiler
if (node.childNodes && node.childNodes.length) {
this.compile(node);
}
})
}
// 编译元素节点 处理指令
compileElement(node) {
console.log(node.attributes)
// 遍历所有的属性节点
Array.from(node.attributes).forEach((attr) => {
let attrName = attr.name;
// 判断是否是指令
if (this.isDirective(attrName)) {
// v-text --- > text
attrName = attrName.substr(2);
let key = attr.value;
this.update(node, key, attrName)
}
})
}
update(node, key, attrName) {
let updateFn = this[`${attrName}Updater`]
updateFn && updateFn.call(this, node, this.vm[key], key)
}
// 处理v-text指令
textUpdater(node, value, key) {
node.textContent = value;
new Watcher(this.vm, key, (newValue) => {
node.textContent = newValue;
})
}
// 处理v-model
modelUpdater(node, value, key) {
node.value = value;
new Watcher(this.vm, key, (newValue) => {
node.value = newValue;
})
// 双向绑定
node.addEventListener('input', () => {
this.vm[key] = node.value
})
}
// 编译文本节点 处理插值表达式
compilerText(node) {
// ()有分组的意思 .+?是用来匹配{{}}中间的内容
let reg = /\{\{(.+?)\}\}/;
let value = node.textContent;
if (reg.test(value)) {
// 调用正则构造函数 $1是第一个分组的值 $2是第二个分组的值
let key = RegExp.$1.trim();
node.textContent = value.replace(reg, this.vm[key])
// 创建watcher对象 当数据改变更新视图
new Watcher(this.vm, key, (newValue) => {
node.textContent = newValue;
})
}
console.dir(node)
}
// 判断是不是指令
isDirective(attrName) {
return attrName.startsWith('v-');
}
// 判断是不是文本节点
isTextNode(node) {
return node.nodeType === 3;
}
// 判断节点是否是元素节点
isElementNode(node) {
return node.nodeType === 1;
}
}
watcher代码
class Watcher {
constructor(vm, key, cb) {
this.vm = vm;
// data中的属性名称
this.key = key;
// 回调函数负责更新视图
this.cb = cb;
// 把watcher对象记录到dep类的静态属性target
Dep.target = this;
// 触发get方法 在get方法中会调用addSub
this.oldValue = vm[key];
// 防止重复添加
Dep.target = null;
}
// 当数据变化的时候更新视图
update() {
let newValue = this.vm[this.key];
if (this.oldValue === newValue) {
return;
}
this.cb(newValue);
}
}
只是实现了数据的更新及简单指令 gitee代码链接:gitee.com/xuxuqingson…