其实搞懂响应式不用死记概念,本质就是怎么让“数据变了的时候,页面/逻辑自动跟着变”——除了现在主流的 Proxy,以前不少框架和手写方案也有自己的玩法,说几个实际开发里见过或用过的:
先明确个前提:不管哪种方法,核心都是解决“怎么知道数据变了”和“变了之后要通知谁”这两个问题,只是思路不一样。
这应该是很多人最早接触的响应式实现了,Vue 2 就是靠它撑起来的。原理特别实在:给对象的每个属性单独装“监听器”。
举个例子,你有个 data = { name: 'Vue', version: 2 }
,要让 name
变的时候页面更新,就得这么搞:
用 Object.defineProperty
把 name
重新包装一遍,加两个钩子:
data.name
(比如页面渲染要用到),就偷偷记下来“谁在用这个值”(这步叫“依赖收集”); data.name
(比如用户点了按钮),就去通知之前记下来的“使用者”:“快更新!数据变了!”但这办法有两个绕不开的坑:
一是只能盯已有的属性。如果后来想加个新属性,比如 data.author = 'Evan'
,直接加是没反应的——因为没给这个新属性装 getter/setter,所以 Vue 2 才要搞个 $set
专门干这事;
二是对数组不友好。比如你想改数组的某一项 arr[0] = 1
,或者直接改长度 arr.length = 0
,这两种操作它监听不到,所以 Vue 2 只能偷偷重写数组的 push
、splice
这些常用方法,才能勉强覆盖数组操作。
现在除了要兼容 IE 的老项目,基本没人主动用这个了,但理解它能帮你搞懂响应式的核心逻辑。
这个思路特别直白,甚至有点“粗暴”——不主动盯数据,而是等可能改数据的操作(比如用户点按钮、发完请求)结束后,集中搞一次“大检查”。
比如页面里用了 data.count
,流程大概是这样:
data.count
当前的值存下来(比如是 0); 这种方式的好处是实现简单——不用折腾属性拦截,不用管对象还是数组,反正最后统一对比就行。但缺点也很明显:性能拉胯。如果页面里要监控的数据多,每次检查都要遍历所有数据,数据量大的时候页面会明显卡一下,所以后来 Angular 也放弃了这个思路,换成了类似 Vue 的响应式方案。
这个不算具体技术,更像一种设计思路,但几乎所有响应式框架底层都用到了——本质就是“消息通知”:数据是“发布者”,用数据的地方(比如页面、逻辑函数)是“订阅者”,中间搞个“消息中心”牵线。
举个手写的例子,比如你想让 data.count
变的时候,页面上的数字和控制台日志都更新:
data.count
时,就让页面的更新函数去“订阅”这个数据——也就是告诉消息中心:“我要听 data.count
的动静”; data.count
时,就让 data.count
这个“发布者”通知消息中心:“我变了!”; 这种方式的灵活性特别高,不管你用 Proxy 还是 Object.defineProperty,都能套这个骨架。比如自己写个简单的状态管理,或者给某个组件单独加响应式逻辑,用这个思路写起来又清晰又好控制。
这个算个“历史遗留问题”了——早年 ES6 提案里有个原生 API 叫 Object.observe
,本来是想让浏览器直接支持监听对象变化,不用开发者自己写拦截逻辑。
用法特别简单,比如:
const obj = { name: 'test' };
Object.observe(obj, changes => {
changes.forEach(change => {
console.log(`属性 ${change.name} 从 ${change.oldValue} 改成了 ${change.object[change.name]}`);
});
});
obj.name = 'new test'; // 会自动触发上面的回调
但这玩意儿后来被 ES 标准砍掉了——主要是因为太“死板”,没法灵活控制监听的范围(比如只想监听某个属性,不想监听整个对象),而且当时浏览器兼容性也差,最后没人用了。现在提这个,主要是避免有人在旧文档里看到这个 API 去踩坑。
现在前端圈里,真正常用的就两类:
其实响应式没那么玄乎,核心就是“盯数据”和“发通知”,不管用哪种方法,把这两个逻辑理清楚,就算搞懂了。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。