学会一个手写一个简单的vue(1)-含视频

📅 2026/7/5 21:31:35
学会一个手写一个简单的vue(1)-含视频
文章目录准备工作vue.jsObserver.jsObserverdepwatchercompiler.js代码地址https://github.com/Zourunfa/imitate_vue/tree/main本片文章的讲解步骤不完全按照视频里写代码的思路但是有详细注释准备工作先看下面这张图我是按照这张图的思路一步一步写的由于时间原因 视频录制中已经把vue的响应式核心的部分串联但是编译解析部分还只写了mustuche,v-html,v-text的编译解析代码后面会进一步完善v-model,v-bind等一系列指令先建立一个文件夹里面放置index.html如下然后建立下面引入的几个文件 observer.js和compiler.jsvue.js!DOCTYPEhtmlhtml langenheadmeta charsetUTF-8/meta http-equivX-UA-CompatiblecontentIEedge/meta nameviewportcontentwidthdevice-width, initial-scale1.0/titleDocument/title/headbodydiv idappp{{emp.name}}/pp v-textemp.hobby/pdiv v-htmlusername/div/divscript srcvue.js/scriptscript srcobserver.js/scriptscript srccompiler.js/script!--script srchttps://cdn.jsdelivr.net/npm/vue2.5.16/dist/vue.js/script--scriptletvmnewVue({el:#app,data:{username:阿锋,msg:asdasdsadd,emp:{name:af,age:21,slary:8000,hobby:guitar,},},methods:{handleClick(){console.log(this.emp.name);this.emp.name你好啊;},},});/script/body/htmlvue.js这就相当于new Vue的入口文件classVue{constructor(options){this.$dataoptions.data;this.$eloptions.el;// console.log(options);this._init();}_init(){// 1.实现一个数据观察者newObserver(this.$data);// 2.实现一个指令的解析器newCompiler(this.$el,this);}}Observer.js这部分有三个类分别是Observer类watcher类dep类。分别对应上面响应式图的三个注意上面视图的标号有对应的代码在注释里面 标注 提出来看 更方便理解ObserverclassObserver{constructor(data){// console.log(data);this.observe(data);}observe(data){if(datatypeofdataobject){// console.log(Object.keys(data));Object.keys(data).forEach((key){// console.log(劫持监听所有属性);// 对应标号1this.defineReactive(data,key,data[key]);});}}defineReactive(obj,key,value){// 递归遍历this.observe(value);constdepnewDep();Object.defineProperty(obj,key,{enumerable:true,configurable:false,get(){// 编译之前 初始化的时候// 订阅数据变化时玩dep中添加观察者// 对应标号4 和 标号 2console.log(getter劫持${key}时 订阅数据变化时往dep中添加观察者watcher,);Dep.targetdep.addSub(Dep.target);returnvalue;},set:(newVal){this.observe(newVal);if(newVal!value){valuenewVal;}// 告诉dep 通知变化// 对应标号3console.log(setter设置新值时 调用dep.notify通知变化);dep.notify();},});}}dep// 收集watcher 通知更新classDep{constructor(){this.subs[];}// 收集观察者addSub(watcher){this.subs.push(watcher);}// 通知观察者更新notify(){// 通知标号3console.log(通知wacher更新,this.subs);this.subs.forEach((w){w.update();});}}watcherclassWatcher{constructor(expr,vm,cb){this.vmvm;this.exprexpr;this.cbcb;// 把旧值保存this.oldValthis.getSub(expr,vm);}getSub(expr,vm){//当获取旧值时把watcher挂载到当前dep的target上console.log(当在watcher中获取旧值${this.expr}时把watcher挂载到当前dep的target上,);// 对应标号5 编译时会使用new Watcher来使用这个函数Dep.targetthis;constvaluecompMidWare.getValue(expr,vm);//用完之后一定要销毁掉不然会有重复的watcherDep.targetnull;returnvalue;}update(){constnewValcompMidWare.getValue(this.expr,this.vm);this.cb(newVal);}}compiler.js这个文件主要是编译解析vue指令 然后需要注意的是我下面代码的compMidWare就相当于图上的updater注意上面视图的标号有对应的代码在注释里面 标注 提出来看 更方便理解classCompiler{constructor(el,vm){this.eldocument.querySelector(el);this.vmvm;// 1获取文档碎片对象 放入内存中会减少页面的回流和重绘constfragmentthis.createFragment(this.el);// console.log(this.el);// 2编译模板this.compile(fragment);// 3追加子元素到根元素上this.el.appendChild(fragment);}createFragment(el){// 创建文档碎片对象constfragmentdocument.createDocumentFragment();letfirstChild;// 注意下面是是赋值 拿到就填进去 并且判断是不是有child// 把所有节点都追加到fragment对象中while((firstChildel.firstChild)){fragment.appendChild(firstChild);}returnfragment;}compile(fragment){constchildNodesfragment.childNodes;// console.log(childNodes);[...childNodes].forEach((node){if(node.nodeType1){// 如果是元素节点就编译它this.compileNode(node);}else{// 文本节点// 编译文本节点this.compileText(node);}// 递归node.childNodesthis.compile(node);});}compileNode(node){// console.log(node);constattributesnode.attributes;// console.log(attributes);[...attributes].forEach((attr){// console.log(attr);const{name,value}attr;// console.log(name, value);// s.startsWithconstexprvalue;if(name.startsWith(v-)){constorderNamename.split(-)[1];// console.log(orderName);compMidWare[orderName](expr,node,this.vm);}//});}compileText(node){consttextnode.textContent;// console.log(text);if(/\{\{(.?)\}\}/.test(text)){// console.log(node);constexprtext.replace(/\{\{(.?)\}\}/,(...args){// console.log(args);returnargs[1];});// console.log(expr);console.log(expr);compMidWare[text](expr,node,this.vm);}}}constcompMidWare{// 这里用到reduce很巧妙 . 之前的pres刚好就是 . 之后的上一级对象getValue(expr,vm){returnexpr.split(.).reduce((pres,cur){returnpres[cur];},vm.$data);},html(expr,node,vm){// console.log(expr, node, vm);constvaluethis.getValue(expr,vm);// console.log(value);// 绑定观察者将来数据发生变化 触发这里的回调 进行更新// 对应标号5newWatcher(expr,vm,(newValue){// console.log(newValue);this.toView.htmlToView(node,newValue);});// 对应标号7console.log(更新当前${expr}-${value}到视图上);this.toView.htmlToView(node,value);},text(expr,node,vm){// console.log(expr, node, vm);// console.log(expr);constvaluethis.getValue(expr,vm);// console.log(value);// 绑定一个watcher对值进行监听 标号5newWatcher(expr,vm,(newValue){// console.log(newValue);console.log(订阅数据变化绑定更新函数更新${expr}为${newValue});this.toView.textToView(node,newValue);});// // 对应标号7console.log(更新当前${expr}-${value}到视图上);this.toView.textToView(node,value);},on(){},model(){},toView:{htmlToView(node,value){node.innerHTMLvalue;},textToView(node,value){node.textContentvalue;},},};