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.targetDep.target = this;this.vm[this.key];// 读一次key触发getterDep.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.编译fragmentthis.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) {// 遍历elconst childNodes = el.childNodes;Array.from(childNodes).forEach(node => {if (this.isElement(node)) {// console.log("编译元素:" + node.nodeName);// 如果是元素节点,我们要处理指令k-xx,事件@xxthis.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,@xxconst nodeAttrs = node.attributes;Array.from(nodeAttrs).forEach(attr => {// 获取属性名称和值 k-text="abc"const attrName = attr.name; // k-textconst exp = attr.value; // abc// 指令:k-xxif (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); // clickthis.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,modelupdate(node, vm, exp, dir) {const fn = this[dir + "Updator"];fn && fn(node, vm[exp]);// 创建Watcher// new Vue({// data: {// xxx: 'bla'// }// })// exp就是xxxnew Watcher(vm, exp, function() {fn && fn(node, vm[exp]);});}textUpdator(node, value) {node.textContent = value;}}
