收藏好这篇,别再只说“数据劫持”了

最近接触了一些面试者 , 当我问起“Vue 如何实现数据双向绑定”时 , 会脱口而出“数据劫持” , 然后呢?然后就没有然后了╮(╯_╰)╭ 。确实 , “数据劫持”是基础 , 但远不是面试官想听到的答案 , 不如花个十分钟看看本文 , 下次照着回答就好了“双向绑定” 本身
要解答问题 , 首先要理解问题: 数据双向绑定 是一种模式 , web语境下一般指数据从dom到JS对象之间的自动同步 。DOM 与 JS 被隔离在两个不同的运行时上 , 互相之间需要通过命令式的 DOM接口 沟通:DOM 需要正确触发事件 , 将信息传输给JS程序;而JS也需要在状态变更后 , 有意识地调用适当的接口 , 改变DOM内容 。这种方式会引起两个问题:
状态的管理与展现是完全剥离开的两套不同逻辑 , 需要刻意保持同步 , 这是很高的开发成本DOM 规范定义了不少接口 , 而且有兼容性问题 , 这是很高的学习成本
【收藏好这篇,别再只说“数据劫持”了】双向绑定通过各种各样的设计 , 将数据从 DOM 到 JS 或者从 JS 到 DOM 的同步过程 , 封装在框架本身 , 上层代码脱离了对底层接口的依赖 , 只需要关注状态管理逻辑 。
.
我们要讨论的第一个问题是 , 如何检测 JS 对象属性发生的变更?最简单粗暴的方法是“脏检查” , 旧版本的就是用的这种方法 , 在各种可能引发状态变更的事件后 , 启动一次脏检查 。这种方法很直观 , 但实现上需要考虑很多问题:

收藏好这篇,别再只说“数据劫持”了

文章插图
脏检查过程中可能会触发新的数据变更 , 也就进入死循环了脏检查的实现必须保留新旧两份数据的副本脏检查必须预知所有可能触发状态变更的时机 , 也就意味着需要对部分原生接口进行包裹(包括 、Frame等)...
Vue 则采用元编程接口 . 实现的 。在组件初始化 , 会调用该接口 , 将对象属性包装为get、set函数 , 将代码“埋入”属性是“获取”、“修改”行为中 。看个简单例子 , 直观感受下:
const person = {};// 嘿嘿 , 谁都改不了我的名字Object.defineProperty(person, 'name', {get() {return 'van';},set(v) {console.log('they want change my name');}});console.log(person.name);// vanperson.name = 'tec';// they want change my nameconsole.log(person.name);// van
依赖管理方案
. 只是解决了状态变更后 , 如何触发通知的问题 , 那要通知谁呢?谁会关心那些属性发生了变化呢?在 Vue 中 , 使用 Dep 解耦了依赖者与被依赖者之间关系的确定过程 。简单来说:
对应下图:
收藏好这篇,别再只说“数据劫持”了

文章插图
注意 , Vue 组件中的函数 , 我们可以单纯将其视为一种特殊的函数 , 在它所对应的对象发生变化时 , 触发执行 , 生成新的 -dom 结构 , 再交由 Vue 做diff , 更新视图 。
结语
本文到这里就结束了 , 更多内容可以尝试看看源码 , 代码里面的设计模式非常值得学习 。
Vue 使用数据劫持作为底层支撑 , 又设计了一套精妙的依赖管理方案解耦依赖 。但数据劫持方案也有其难以解决的痛点: