实现vue2.0响应式的基本思路
注意,这里只是实现思路的还原,对于里面各种细节的实现,比如说数组里面数据的操作的监听,以及对象嵌套这些细节本实例都不会涉及到,如果想了解更加细节的实现,可以通过阅读源码 observer文件夹以及instance文件夹里面的state文件具体了解。#
首先,我们先定义好实现vue对象的结构#
1 2 3 4 5 6 7 | class Vue { constructor(options) { this .$options = options; this ._data = options.data; this .$el = document.querySelector(options.el); } } |
第一步:将data下面的属性变为observable#
使用Object.defineProperty对数据对象做属性get和set的监听,当有数据读取和赋值操作时则调用节点的指令,这样使用最通用的=等号赋值就可以触发了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | //数据劫持,监控数据变化 function observer(value, cb){ Object.keys(value).forEach((key) => defineReactive(value, key, value[key] , cb)) } function defineReactive(obj, key, val, cb) { Object.defineProperty(obj, key, { enumerable: true , configurable: true , get: ()=>{ return val }, set: newVal => { if (newVal === val) return val = newVal } }) } |
第二步:实现一个消息订阅器#
很简单,我们维护一个数组,这个数组,就放订阅者,一旦触发notify,订阅者就调用自己
的update方法
1 2 3 4 5 6 7 8 9 10 11 | class Dep { constructor() { this .subs = [] } add(watcher) { this .subs.push(watcher) } notify() { this .subs.forEach((watcher) => watcher.cb()) } } |
每次set函数,调用的时候,我们触发notify,实现更新
那么问题来了。谁是订阅者。对,是Watcher。。一旦 dep.notify()就遍历订阅者,也就是Watcher,并调用他的update()方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | function defineReactive(obj, key, val, cb) { const dep = new Dep() Object.defineProperty(obj, key, { enumerable: true , configurable: true , get: ()=>{ return val }, set: newVal => { if (newVal === val) return val = newVal dep.notify() } }) } |
第三步:实现一个 Watcher#
Watcher的实现比较简单,其实就是执行数据变化时我们要执行的操作
1 2 3 4 5 6 7 8 9 10 11 12 | class Watcher { constructor(vm, cb) { this .cb = cb this .vm = vm } update(){ this .run() } run(){ this .cb.call( this .vm) } } |
第四步:touch拿到依赖#
上述三步,我们实现了数据改变可以触发更新,现在问题是我们无法将watcher与我们的数据联系到一起。
我们知道data上的属性设置defineReactive后,修改data 上的值会触发 set。那么我们取data上值是会触发 get了。所以可以利用这一点,先执行以下render函数,就可以知道视图的更新需要哪些数据的支持,并把它记录为数据的订阅者。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | function defineReactive(obj, key, val, cb) { const dep = new Dep() Object.defineProperty(obj, key, { enumerable: true , configurable: true , get: ()=>{ if (Dep.target){ dep.add(Dep.target) } return val }, set: newVal => { if (newVal === val) return val = newVal dep.notify() } }) } |
最后我们来看用一个代理实现将我们对data的数据访问绑定在vue对象上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | _proxy(key) { const self = this Object.defineProperty(self, key, { configurable: true , enumerable: true , get: function proxyGetter () { return self._data[key] }, set: function proxySetter (val) { self._data[key] = val } }) } Object.keys(options.data).forEach(key => this ._proxy(key)) |
下面就是整个实例的完整代码
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 | class Vue { constructor(options) { this .$options = options; this ._data = options.data; this .$el =document.querySelector(options.el); Object.keys(options.data).forEach(key => this ._proxy(key)) observer(options.data) watch( this , this ._render.bind( this ), this ._update.bind( this )) } _proxy(key) { const self = this Object.defineProperty(self, key, { configurable: true , enumerable: true , get: function proxyGetter () { return self._data[key] }, set: function proxySetter (val) { self._data[key] = val } }) } _update() { console.log( "我需要更新" ); this ._render.call( this ) } _render() { this ._bindText(); } _bindText() { let textDOMs= this .$el.querySelectorAll( '[v-text]' ), bindText; for ( let i=0;i<textDOMs.length;i++){ bindText=textDOMs[i].getAttribute( 'v-text' ); let data = this ._data[bindText]; if (data){ textDOMs[i].innerHTML=data; } } } } function observer(value, cb){ Object.keys(value).forEach((key) => defineReactive(value, key, value[key] , cb)) } function defineReactive(obj, key, val, cb) { const dep = new Dep() Object.defineProperty(obj, key, { enumerable: true , configurable: true , get: ()=>{ if (Dep.target){ dep.add(Dep.target) } return val }, set: newVal => { if (newVal === val) return val = newVal dep.notify() } }) } function watch(vm, exp, cb){ Dep.target = new Watcher(vm,cb); return exp() } class Watcher { constructor(vm, cb) { this .cb = cb this .vm = vm } update(){ this .run() } run(){ this .cb.call( this .vm) } } class Dep { constructor() { this .subs = [] } add(watcher) { this .subs.push(watcher) } notify() { this .subs.forEach((watcher) => watcher.cb()) } } Dep.target = null ; var demo = new Vue({ el: '#demo' , data: { text: "hello world" } }) setTimeout( function (){ demo.text = "hello new world" }, 1000) |
1 2 3 4 5 | <body> <div id= "demo" > <div v-text= "text" ></div> </div> </body> |
上面就是整个vue数据驱动部分的整个思路。如果想深入了解更细节的实现,建议深入去看vue这部分的代码。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何打造一个高并发系统?
· .NET Core GC压缩(compact_phase)底层原理浅谈
· 现代计算机视觉入门之:什么是图片特征编码
· .NET 9 new features-C#13新的锁类型和语义
· Linux系统下SQL Server数据库镜像配置全流程详解
· Sdcb Chats 技术博客:数据库 ID 选型的曲折之路 - 从 Guid 到自增 ID,再到
· 语音处理 开源项目 EchoSharp
· 《HelloGitHub》第 106 期
· Huawei LiteOS基于Cortex-M4 GD32F4平台移植
· mysql8.0无备份通过idb文件恢复数据过程、idb文件修复和tablespace id不一致处