vue的工作机制
初始化
在 new Vue() 时会调用_init()进行初始化,会初始化各种实例方法、全局方法、执行一些生命周期、初始化
props、data等状态。其中最重要的是data的「响应化」处理。 初始化之后调用 $mount 挂载组件,主要执行编译和首次更新
编译
编译模块分为三个阶段
1. parse:使用正则解析template中的vue的指令(v-xxx) 变量等等 形成抽象语法树AST
2. optimize:标记一些静态节点,用作后面的性能优化,在diff的时候直接略过
3. generate:把第一部生成的AST 转化为渲染函数 render function
更新
数据修改触发setter,然后监听器会通知进行修改,通过对比新旧vdom树,得到最小修改,就是 patch ,然后只 需要把这些差异修改即可
实现kvue
kvue源码
// new KVue({
// data: {
// msg: 'hello'
// }
// })
class KVue {
constructor(options) {
this.$options = options;
this.$data = options.data;
// 响应化
this.observe(this.$data);
// 测试代码
// new Watcher(this, 'test');
// this.test;
// 创建编译器
new Compile(options.el, this);
if (options.created) {
options.created.call(this);
}
}
// 递归遍历,使传递进来的对象响应化
observe(value) {
if (!value || typeof value !== "object") {
return;
}
// 遍历
Object.keys(value).forEach(key => {
// 对key做响应式处理
this.defineReactive(value, key, value[key]);
this.proxyData(key);
});
}
// 在vue根上定义属性代理data中的数据
proxyData(key) {
Object.defineProperty(this, key, {
get() {
return this.$data[key];
},
set(newVal) {
this.$data[key] = newVal;
}
});
}
//
defineReactive(obj, key, val) {
// 递归
this.observe(val);
// 创建Dep实例:Dep和key一对一对应
const dep = new Dep();
// 给obj定义属性
Object.defineProperty(obj, key, {
get() {
// 将Dep.target指向的Watcher实例加入到Dep中
Dep.target && dep.addDep(Dep.target);
return val;
},
set(newVal) {
if (newVal !== val) {
val = newVal;
dep.notify();
}
}
});
}
}
// Dep:管理若干watcher实例,它和key一对一关系
class Dep {
constructor() {
this.deps = [];
}
addDep(watcher) {
this.deps.push(watcher);
}
notify() {
this.deps.forEach(watcher => watcher.update());
}
}
// 保存ui中依赖,实现update函数可以更新之
class Watcher {
constructor(vm, key, cb) {
this.vm = vm;
this.key = key;
this.cb = cb;
// 将当前实例指向Dep.target
Dep.target = this;
this.vm[this.key];// 读一次key触发getter
Dep.target = null;
}
update() {
this.cb.call(this.vm, this.vm[this.key])
// console.log(`${this.key}属性更新了`);
}
}
compile.js
// 遍历模板,将里面的插值表达式处理
// 另外如果发现k-xx, @xx做特别处理
class Compile {
constructor(el, vm) {
this.$vm = vm;
this.$el = document.querySelector(el);
if (this.$el) {
// 1.$el中的内容搬家到一个fragment,提高操作效率
this.$fragment = this.node2Fragment(this.$el);
// console.log(this.$fragment);
// 2.编译fragment
this.compile(this.$fragment);
// console.log(this.$fragment);
// 3.将编译结果追加至宿主中
this.$el.appendChild(this.$fragment);
}
}
// 遍历el,把里面内容搬到新创建fragment中
node2Fragment(el) {
const fragment = document.createDocumentFragment();
let child;
while ((child = el.firstChild)) {
// 由于appenChild是移动操作
fragment.appendChild(child);
}
return fragment;
}
// 把动态值替换,把指令和事件做处理
compile(el) {
// 遍历el
const childNodes = el.childNodes;
Array.from(childNodes).forEach(node => {
if (this.isElement(node)) {
// console.log("编译元素:" + node.nodeName);
// 如果是元素节点,我们要处理指令k-xx,事件@xx
this.compileElement(node);
} else if (this.isInterpolation(node)) {
// console.log("编译文本:" + node.textContent);
this.compileText(node);
}
// 递归子元素
if (node.childNodes && node.childNodes.length > 0) {
this.compile(node);
}
});
}
isElement(node) {
return node.nodeType === 1;
}
// 插值表达式判断
isInterpolation(node) {
// 需要满足{{xx}}
return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent);
}
compileElement(node) {
// 查看node的特性中是否有k-xx,@xx
const nodeAttrs = node.attributes;
Array.from(nodeAttrs).forEach(attr => {
// 获取属性名称和值 k-text="abc"
const attrName = attr.name; // k-text
const exp = attr.value; // abc
// 指令:k-xx
if (attrName.indexOf("k-") === 0) {
const dir = attrName.substring(2); // text
// 执行指令
this[dir] && this[dir](node, this.$vm, exp);
} else if(attrName.indexOf('@') === 0) {
// 事件 @click="handlClick"
const eventName = attrName.substring(1); // click
this.eventHandler(node, this.$vm, exp, eventName);
}
});
}
text(node, vm, exp) {
this.update(node, vm, exp, "text");
}
// 双向数据绑定
model(node, vm, exp) {
// update是数据变了改界面
this.update(node, vm, exp, "model");
// 界面变了改数值
node.addEventListener("input", e => {
vm[exp] = e.target.value;
});
}
modelUpdator(node, value) {
node.value = value;
}
html(node, vm, exp) {
this.update(node, vm, exp, "html");
}
htmlUpdator(node, value) {
node.innerHTML = value;
}
eventHandler(node, vm, exp, eventName){
// 获取回调函数
const fn = vm.$options.methods && vm.$options.methods[exp];
if(eventName && fn) {
node.addEventListener(eventName, fn.bind(vm))
}
}
// 把插值表达式替换为实际内容
compileText(node) {
// {{xxx}}
// RegExp.$1是匹配分组部分
// console.log(RegExp.$1);
const exp = RegExp.$1;
this.update(node, this.$vm, exp, "text");
}
// 编写update函数,它可复用
// exp是表达式, dir是具体操作:text,html,model
update(node, vm, exp, dir) {
const fn = this[dir + "Updator"];
fn && fn(node, vm[exp]);
// 创建Watcher
// new Vue({
// data: {
// xxx: 'bla'
// }
// })
// exp就是xxx
new Watcher(vm, exp, function() {
fn && fn(node, vm[exp]);
});
}
textUpdator(node, value) {
node.textContent = value;
}
}