摘要:源码自问自答响应式原理最近看了源码和源码分析类的文章感觉明白了很多,但是仔细想想却说不出个所以然。会在对象的这个被获取时触发,会在这个对象的被修改时触发。在初始化时,将对象上的所有,都包装成拥有和的属性。
vue 源码自问自答-响应式原理
最近看了 Vue 源码和源码分析类的文章,感觉明白了很多,但是仔细想想却说不出个所以然。
所以打算把自己掌握的知识,试着组织成自己的语言表达出来
不打算平铺直叙的写清楚 vue 源码的前因后果和全部细节,而是以自问自答的形式,回答我自己之前的疑惑,
如果有错误的地方,欢迎指正哈~
Vue 的双向数据绑定原理Vue 实现响应式的核心 API 是 ES5 的 Object.defineProperty(obj,key,descriptor),Vue 的「响应式」和「依赖收集」都依靠这个 API
它接受 3 个参数,分别是 obj / key / 描述符,返回的是一个包装后的对象
它的作用就是,用这个 API 包装过后的对象可以拥有 getter 和 setter 函数。
getter 会在对象的这个 key 被获取时触发,setter 会在这个对象的 key 被修改时触发。
一个 Vue 项目的开始, 通常是从 Vue 构造函数的实例化开始的。
new Vue()的时候会执行一个_init()方法,会初始化属性,比如 props/event/生命周期钩子,也包括 data 对象的初始化。
Vue 在初始化时,将 data 对象上的所有 key,都包装成拥有 getter 和 setter 的属性。
渲染页面时,会执行 render function(无论是用 template 还是 render 最终都会生成 render 函数)
执行 render function 会获取 data 对象上的属性,所以会触发对应属性的 getter 函数
getter 触发时,主要就做 2 个事情。 1.把值返回 2.依赖收集,也就是讲 watcher 存放到 Dep 实例的一个队列里。
当修改对象的值触发 setter,setter 同样是做 2 个事情。1.把 newVal 设置好 2.用一个循环通知之前存放在 dep 实例中 watcher 对象们,watcher 对象调用各自的 update 方法来更新视图
实现双向数据绑定的demo1 - 忽略「收集依赖」的版本function cb() { console.log("更新视图"); } function defineReactve(obj, key, val) { Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: () => { console.log("触发了getter"); return val; }, set: newVal => { console.log("触发了setter"); if (newVal === val) return; val = newVal; cb() } }); } function observe(data) { function walk(data) { Object.keys(data).forEach(key => { if (typeof data[key] === "object") { walk(data[key]); } else { defineReactve(data, key, data[key]); } }); } walk(data); } class Vue { constructor(options) { this._data = options.data; observe(this._data); } } var vm = new Vue({ data: { msg: "test", person: { name: "ziwei", age: 18 } } }); vm._data.person.name = "hello" // 触发setter和cb函数,从而视图更新实现双向数据绑定的demo2 - 「收集依赖」的版本
function defineReactve( obj, key, val ) { const dep = new Dep() Object.defineProperty( obj, key, { enumerable: true, configurable: true, get: () => { console.log( "触发了getter" ); dep.addSub(Dep.target) return val; }, set: newVal => { console.log( "触发了setter" ); if ( newVal === val ) return; val = newVal; dep.notify() // 通知队列的wather去update视图 } } ); } function observe( data ) { function walk( data ) { Object.keys( data ).forEach( key => { if ( typeof data[ key ] === "object" ) { walk( data[ key ] ); } else { defineReactve( data, key, data[ key ] ); } } ); } walk( data ); } class Dep{ constructor(){ this.subs = [] } addSub(){ this.subs.push(Dep.target) } notify(){ this.subs.forEach(sub => { sub.update() }) } } Dep.target = null class Watcher{ constructor(){ Dep.target = this } update(){ console.log("update更新视图啦~") } } class Vue { constructor( options ) { this._data = options.data; observe( this._data ); new Watcher() // 模拟页面渲染,触发getter,依赖收集的效果 this._data.person.name } } var vm = new Vue( { data: { msg: "test", person: { name: "ziwei", age: 18 } } } ); vm._data.person.name = "hello"一些省略掉的环节
这样就是 Vue 响应式的一个基本原理,不过我描述的过程中,也省略了很多环节,比如
Vue 是如何实现给 data 对象上的属性都拥有 getter 和 setter 的
为什么要进行「依赖收集」,
如何避免重复「收集依赖」
watcher 调用 update,也并不是直接更新视图。实现上中间还有 patch 的过程以及使用队列来异步更新的策略。
Vue 是如何实现给 data 对象上的属性都拥有 getter 和 setter 的
通过循环data对象,给对象的每一个key,用Object.defineProperty包装 遍历时,如果发现data[key]也是对象的话,需要用递归
为什么要进行「依赖收集」?
举2个场景的栗子
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/98913.html
摘要:用伪代码来模拟下长轮询的过程前端利用下面函数进行请求后端代码做如下更改利用随机数的大小来模拟是否有新数据有新数据来了长轮询的确减少了请求的次数,但是它也有着很大的问题,那就是耗费服务器的资源。 写在前面 最近由于利用node重构某个项目,项目中有一个实时聊天的功能,于是就研究了一下聊天室,在线demo|源码,欢迎大家反馈。这个聊天室的主要利用到了socket.io和express。这个...
阅读 1932·2019-08-30 15:54
阅读 3581·2019-08-29 13:07
阅读 3100·2019-08-29 12:39
阅读 1772·2019-08-26 12:13
阅读 1528·2019-08-23 18:31
阅读 2136·2019-08-23 18:05
阅读 1829·2019-08-23 18:00
阅读 1027·2019-08-23 17:15