1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 | // 创建一个Mvvm构造函数 // 这里用es6方法将options赋一个初始值,防止没传,等同于options || {} function Mvvm(options = {}) { // vm.$options Vue上是将所有属性挂载到上面 // 所以我们也同样实现,将所有属性挂载到了$options console.log(options) this .$options = options; // this._data 这里也和Vue一样 let data = this ._data = this .$options.data; // 数据劫持 observe(data); // this 代理了this._data for ( let key in data) { Object.defineProperty( this , key, { configurable: true , get () { return this ._data[key]; // 如this.a = {b: 1} }, set (newVal) { this ._data[key] = newVal; } }); } // 初始化computed,将this指向实例 initComputed.call( this ); // 编译 new Compile(options.el, this ); // 所有事情处理好后执行mounted钩子函数 options.mounted.call( this ); // 这就实现了mounted钩子函数 } // 创建一个Observe构造函数 // 写数据劫持的主要逻辑 function Observe(data) { let dep = new Dep(); // 所谓数据劫持就是给对象增加get,set // 先遍历一遍对象再说 for ( let key in data) { // 把data属性通过defineProperty的方式定义属性 let val = data[key]; observe(val); // 递归继续向下找,实现深度的数据劫持 Object.defineProperty(data, key, { configurable: true , get () { Dep.target && dep.addSub(Dep.target); return val; }, set (newVal) { // 更改值的时候 if (val === newVal) { // 设置的值和以前值一样就不理它 return ; } val = newVal; // 如果以后再获取值(get)的时候,将刚才设置的值再返回去 observe(newVal); // 当设置为新值后,也需要把新值再去定义成属性 dep.notify(); // 让所有watcher的update方法执行即可 } }); } } // 外面再写一个函数 // 不用每次调用都写个new // 也方便递归调用 function observe(data) { // 如果不是对象的话就直接return掉 // 防止递归溢出 if (!data || typeof data !== 'object' ) return ; return new Observe(data); } // 创建Compile构造函数 function Compile(el, vm) { // 将el挂载到实例上方便调用 vm.$el = document.querySelector(el); console.log(vm.$el) // 在el范围里将内容都拿到,当然不能一个一个的拿 // 可以选择移到内存中去然后放入文档碎片中,节省开销 let fragment = document.createDocumentFragment(); console.log(fragment) console.log(vm.$el.firstChild) while (child = vm.$el.firstChild) { fragment.appendChild(child); // 此时将el中的内容放入内存中 } console.log(fragment.childNodes) // 对el里面的内容进行替换 function replace(frag) { Array. from (frag.childNodes).forEach(node => { console.log(node, node.textContent) let txt = node.textContent; let reg = /\{\{(.*?)\}\}/g; // 正则匹配{{}} if (node.nodeType === 3 && reg.test(txt)) { // 即是文本节点又有大括号的情况{{}} // console.log(RegExp.$1); // 匹配到的第一个分组 如: a.b, c // let arr = RegExp.$1.split('.'); // console.log(arr) // let val = vm; // arr.forEach(key => { // val = val[key]; // 如this.a.b // }); // // 用trim方法去除一下首尾空格 // node.textContent = txt.replace(reg, val).trim(); function replaceTxt() { node.textContent = txt.replace(reg, (matched, placeholder) => { console.log(placeholder); // 匹配到的分组 如:song, album.name, singer... new Watcher(vm, placeholder, replaceTxt); // 监听变化,进行匹配替换内容 return placeholder.split( '.' ).reduce((val, key) => { return val[key]; }, vm); }); }; // 替换 replaceTxt(); } if (node.nodeType === 1) { // 元素节点 let nodeAttr = node.attributes; // 获取dom上的所有属性,是个类数组 console.log(node.attributes) Array. from (nodeAttr).forEach(attr => { let name = attr.name; // v-model type let exp = attr.value; // c text if (name.includes( 'v-' )) { node.value = vm[exp]; // this.c 为 2 } console.log(attr.value) // debugger // 监听变化 new Watcher(vm, exp, function(newVal) { node.value = newVal; // 当watcher触发时会自动将内容放进输入框中 }); node.addEventListener( 'input' , e => { debugger let newVal = e.target.value; // 相当于给this.c赋了一个新值 // 而值的改变会调用set,set中又会调用notify,notify中调用watcher的update方法实现了更新 vm[exp] = newVal; console.log(vm) }); }); } // 如果还有子节点,继续递归replace if (node.childNodes && node.childNodes.length) { replace(node); } }); // 监听变化 // 给Watcher再添加两个参数,用来取新的值(newVal)给回调函数传参 new Watcher(vm, RegExp.$1, newVal => { node.textContent = txt.replace(reg, newVal).trim(); }); } replace(fragment); // 替换内容 vm.$el.appendChild(fragment); // 再将文档碎片放入el中 } // 发布订阅模式 订阅和发布 如[fn1, fn2, fn3] function Dep() { // 一个数组(存放函数的事件池) this .subs = []; } Dep.prototype = { addSub(sub) { this .subs.push(sub); }, notify() { // 绑定的方法,都有一个update方法 this .subs.forEach(sub => sub.update()); } }; // 监听函数 // 通过Watcher这个类创建的实例,都拥有update方法 function Watcher(vm, exp, fn) { this .fn = fn; // 将fn放到实例上 this .vm = vm; this .exp = exp; // 添加一个事件 // 这里我们先定义一个属性 Dep.target = this ; console.log(exp) let arr = toString(exp).split( '.' ); let val = vm; arr.forEach(key => { // 取值 val = val[key]; // 获取到this.a.b,默认就会调用get方法 }); Dep.target = null ; } Watcher.prototype.update = function() { // notify的时候值已经更改了 // 再通过vm, exp来获取新的值 console.log( this .exp) let arr = toString( this .exp).split( '.' ); let val = this .vm; arr.forEach(key => { val = val[key]; // 通过get获取到新的值 }); console.log( this ) this .fn(val); }; let watcher = new Watcher(() => console.log(111)); // let dep = new Dep(); dep.addSub(watcher); // 将watcher放到数组中,watcher自带update方法, => [watcher] dep.addSub(watcher); dep.notify(); // 111, 111 function initComputed() { let vm = this ; let computed = this .$options.computed; // 从options上拿到computed属性 {sum: ƒ, noop: ƒ} // 得到的都是对象的key可以通过Object.keys转化为数组 Object.keys(computed).forEach(key => { // key就是sum,noop Object.defineProperty(vm, key, { // 这里判断是computed里的key是对象还是函数 // 如果是函数直接就会调get方法 // 如果是对象的话,手动调一下get方法即可 // 如: sum() {return this.a + this.b;},他们获取a和b的值就会调用get方法 // 所以不需要new Watcher去监听变化了 get : typeof computed[key] === 'function' ? computed[key] : computed[key]. get , set () {} }); }); } |
二:
index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | <!DOCTYPE html> <html lang= "en" > <head> <meta charset= "UTF-8" > <title>双向绑定实现</title> <style> #app { text-align: center; } </style> </head> <body> <div id= "app" > <h2>{{title}}</h2> <input v-model= "name" > <h1>{{name}}</h1> <button v- on :click= "clickMe" >点击!</button> </div> </body> <script src= "js/observer.js" ></script> <script src= "js/watcher.js" ></script> <script src= "js/compile.js" ></script> <script src= "js/index.js" ></script> <script> new Vue({ el: '#app' , data: { title: 'vue code' , name: '练习' }, methods: { clickMe() { this .title = 'vue code click' ; } }, mounted() { window.setTimeout(() => { this .title = '1秒' ; }, 1000); } }); </script> </html> |
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | function Vue(options) { var self = this ; this .data = options.data; this .methods = options.methods; Object.keys( this .data).forEach(function(key) { console.log(key); // title name self.proxyKeys(key); }); observe( this .data); new Compile(options.el, this ); options.mounted.call( this ); // 所有事情处理好后执行mounted函数 } Vue.prototype = { proxyKeys: function(key) { var self = this ; Object.defineProperty( this , key, { enumerable: false , configurable: true , get : function() { return self.data[key]; }, set : function(newVal) { self.data[key] = newVal; } }); } }; |
observer.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | function Observer(data) { this .data = data; this .walk(data); } Observer.prototype = { walk: function(data) { var self = this ; Object.keys(data).forEach(function(key) { self.defineReactive(data, key, data[key]); }); }, defineReactive: function(data, key, val) { var dep = new Dep(); Object.defineProperty(data, key, { enumerable: true , configurable: true , get : function getter() { if (Dep.target) { console.log(Dep.target) dep.addSub(Dep.target); } return val; }, set : function setter(newVal) { if (newVal === val) { return ; } val = newVal; dep.notify(); } }); } }; function observe(val, vm) { if (!val || typeof val !== 'object' ) { return ; } return new Observer(val); } function Dep() { this .subs = []; } Dep.prototype = { addSub: function(sub) { this .subs.push(sub); }, notify: function() { this .subs.forEach(function(sub) { console.log(sub.update) sub.update(); }); } }; Dep.target = null ; |
watcher.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | function Watcher(vm, exp, cb) { this .vm = vm; this .exp = exp; this .cb = cb; this .value = this . get (); // 将自己添加到订阅器的操作 } Watcher.prototype = { update: function() { this .run(); }, run: function() { var value = this .vm.data[ this .exp]; var oldVal = this .value; if (value !== oldVal) { this .value = value; this .cb.call( this .vm, value, oldVal); } }, get : function() { Dep.target = this ; // 缓存自己 var value = this .vm.data[ this .exp]; // 强制执行监听器里的get函数 Dep.target = null ; // 释放自己 return value; } } |
compile.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | function Compile(el, vm) { this .vm = vm; this .el = document.querySelector(el); this .fragment = null ; this .init(); } Compile.prototype = { init: function() { if ( this .el) { this .fragment = this .nodeToFragment( this .el); this .compileElement( this .fragment); this .el.appendChild( this .fragment); } else { console.log( 'Dom元素不存在' ); } }, nodeToFragment: function(el) { var fragment = document.createDocumentFragment(); var child = el.firstElementChild; while (child) { // 将Dom元素移入fragment中 fragment.appendChild(child); child = el.firstElementChild; } return fragment; }, compileElement: function(el) { var self = this ; var childNodes = el.childNodes; [].slice.call(childNodes).forEach(function(node) { var reg = /\{\{(.*)\}\}/; var text = node.textContent; if (self.isElementNode(node)) { self.compile(node); } else if (self.isTextNode(node) && reg.test(text)) { self.compileText(node, reg.exec(text)[1]); } if (node.childNodes && node.childNodes.length) { self.compileElement(node); } }); }, compile: function(node) { var self = this ; var nodeAttrs = node.attributes; Array.prototype.forEach.call(nodeAttrs, function(attr) { var attrName = attr.name; if (self.isDirective(attrName)) { var exp = attr.value; var dir = attrName.substring(2); // model on:click if (self.isEventDirective(dir)) { // 事件命令 self.compileEvent(node, self.vm, exp, dir); } else { // v-model指令 self.compileModel(node, self.vm, exp, dir); } node.removeAttribute(attrName); } }); }, compileText: function(node, exp) { var self = this ; var initText = this .vm[exp]; this .updateText(node, initText); new Watcher( this .vm, exp, function(value) { self.updateText(node, value); }); }, compileEvent: function(node, vm, exp, dir) { var eventType = dir.split( ':' )[1]; var cb = vm.methods && vm.methods[exp]; if (eventType && cb) { node.addEventListener(eventType, cb.bind(vm), false ); } }, compileModel: function(node, vm, exp, dir) { var self = this ; var val = this .vm[exp]; // name this .modelUpdater(node, val); new Watcher( this .vm, exp, function(value) { self.modelUpdater(node, value); }); node.addEventListener( 'input' , function(e) { var newVal = e.target.value; if (val === newVal) { return ; } self.vm[exp] = newVal; val = newVal; }); }, updateText: function(node, value) { node.textContent = typeof value === 'undefined' ? '' : value; }, modelUpdater: function(node, value, oldVal) { node.value = typeof value === 'undefined' ? '' : value; }, isDirective: function(attr) { return attr.indexOf( 'v-' ) === 0; }, isEventDirective: function(dir) { return dir.indexOf( 'on:' ) === 0; }, isElementNode: function(node) { return node.nodeType === 1; }, isTextNode: function(node) { return node.nodeType === 3; } }; |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗