new Vue
class Vue {
constructor(options) {
this.$options = options;
this.$data = options.data;
observe(options.data); // 为data做变化侦测
proxy(this); // 挂载data到vue实例 this.counter === this.$data.counter
new Compiler(options.el, this);
}
}
new Vue({
el: "#app",
data: {
counter: 1,
desc: "<span>天真</span>",
},
});
变化侦测
observe
function observe(obj) {
if (typeof obj !== "object" || obj === null) return obj;
new Observe(obj);
}
Observe
class Observe {
constructor(obj) {
// vue中数组与obj是单独处理
if (Array.isArray(obj)) {
// 数组处理
} else {
this.walk(obj);
}
}
walk() {
Object.keys(obj).forEach((key) => {
defineReactive(obj, key, obj[key]);
});
}
}
defineReactive
// 针对Object类型
function defineReactive(obj, key, val) {
observe(obj); // 解决对象嵌套
Object.defineProperty(obj, key, {
get() {
console.log("get", val);
return val;
},
set(newVal) {
if (newVal === val) return val;
console.log("set:", newVal);
val = newVal; // 闭包
},
});
}
/* 测试 */
const obj = {};
defineReactive(obj, "name", "tianzhen");
obj.name; // 触发get
obj.name = "zhentian"; // 触发set
proxy
function proxy(vm) {
Object.keys(vm.$data).forEach((key) => {
Object.defineProperty(vm, key, {
get() {
return vm.$data[key];
},
set(v) {
vm.$data[key] = v;
},
});
});
}
模版编译
<div id="app">
<p>{{counter}}</p>
<button @click="add"></button>
<p v-html="desc"></p>
</div>
compile
class Compiler {
constructor(el, vm) {
this.$el = document.querySelector(el);
this.$vm = vm;
this.$methods = vm.$methods;
this.$el && this.compile(this.$el);
}
compile(node) {
const childNodes = node.childNodes;
Array.from(childNodes).forEach((n) => {
if (this.isElement(n)) {
// 编译元素
this.compileElement(n);
// 元素子节点递归
if (n.childNodes.length > 0) {
this.compile(n);
}
} else if (this.isInterpolation(n)) {
// 编译文本插值
this.compileText(n);
}
});
}
isElement(node) {
return node.nodeType === 1;
}
isInterpolation(node) {
return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent);
}
// 编译文本插值 {{xxoo}}
compileText(node) {
// node.textContent = this.$vm[RegExp.$1]
this.update(node, RegExp.$1, "text");
}
compileElement(node) {
const attrs = node.attributes;
Array.from(attrs).forEach((attr) => {
const attrName = attr.name;
const exp = attr.value;
if (this.isDirective(attrName)) {
let dir = attrName.slice(2);
this[dir] && this[dir](node, exp);
}
if (this.isEvent(attrName)) {
const event = attrName.slice(1);
this.doEvent(node, event, exp);
}
});
}
isEvent(name) {
return name.startsWith("@");
}
isDirective(name) {
return name.startsWith("k-");
}
doEvent(node, event, exp) {
node.addEventListener(event, () => {
this.$methods[exp].call(this.$vm);
});
}
text(node, exp) {
// node.textContent = this.$vm[exp]
this.update(node, exp, "text");
}
html(node, exp) {
// node.innerHTML = this.$vm[exp]
this.update(node, exp, "html");
}
/*
动态值编译统一处理为update函数,方便进行依赖收集
依赖其实就是一个个watcher
*/
update(node, exp, dir) {
/*------初始化-----*/
const fn = this[dir + "Updater"];
const val = this.$vm[exp];
fn && fn(node, val);
/*------更新---------*/
new Watcher(this.$vm, exp, (val) => {
fn && fn(node, val);
});
}
textUpdater(node, val) {
node.textContent = val;
}
htmlUpdater(node, val) {
node.innerHTML = val;
}
}
Watcher
class Watcher {
constructor(vm, key, updater) {
this.$vm = vm;
this.$key = key;
this.updater = updater; // 收集的更新函数
Dep.target = this;
this.$vm[this.$key]; // 触发一次get,使自动收集依赖
Dep.target = null; // 相同依赖只收集一次;一个key对应一个dep
}
update() {
this.updater.call(this.$vm, this.$vm[this.$key]);
}
}
修改上面的defineReactive
function defineReactive(obj, key, val) {
observe(obj);
const dep = new Dep();
Object.defineProperty(obj, key, {
get() {
Dep.target && dep.add(Dep.target);
return val;
},
set(newVal) {
if (newVal === val) return val;
val = newVal; // 闭包
dep.notify(); // 更新
},
});
}
Dep
依赖收集到Dep中,更新也是使用dep更新
class Dep {
constructor() {
this.deps = [];
}
// 此处的dep是一个watcher实例
add(dep) {
this.deps.push(dep);
}
notify() {
this.deps.forEach((dep) => dep.update());
}
}