当前位置: 首页 > news >正文

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

最近接触了一些面试者,当我问起“Vue 如何实现数据双向绑定”时,会脱口而出“数据劫持”,然后呢?然后就没有然后了╮(╯_╰)╭。确实,“数据劫持”是基础,但远不是面试官想听到的答案,不如花个十分钟看看本文,下次照着回答就好了

“双向绑定” 本身

要解答问题,首先要理解问题: 数据双向绑定 是一种模式,web语境下一般指数据从dom到JS对象之间的自动同步。DOM 与 JS 被隔离在两个不同的运行时上,互相之间需要通过命令式的 DOM接口 沟通:DOM 需要正确触发事件,将信息传输给JS程序;而JS也需要在状态变更后,有意识地调用适当的接口,改变DOM内容。这种方式会引起两个问题:

  1. 状态的管理与展现是完全剥离开的两套不同逻辑,需要刻意保持同步,这是很高的开发成本
  2. DOM 规范定义了不少接口,而且有兼容性问题,这是很高的学习成本

双向绑定通过各种各样的设计,将数据从 DOM 到 JS 或者从 JS 到 DOM 的同步过程,封装在框架本身,上层代码脱离了对底层接口的依赖,只需要关注状态管理逻辑。

Object.defineProperty

我们要讨论的第一个问题是,如何检测 JS 对象属性发生的变更?最简单粗暴的方法是“脏检查”,旧版本的Angular就是用的这种方法,在各种可能引发状态变更的事件后,启动一次脏检查。这种方法很直观,但实现上需要考虑很多问题:

  1. 脏检查过程中可能会触发新的数据变更,也就进入死循环了
  2. 脏检查的实现必须保留新旧两份数据的副本
  3. 脏检查必须预知所有可能触发状态变更的时机,也就意味着需要对部分原生接口进行包裹(包括 setTimeoutrequestNextAnimationFrame等)
  4. ...

Vue 则采用元编程接口 Object.defineProperty 实现的。 在组件初始化,会调用该接口,将对象属性包装为getset函数,将代码“埋入”属性是“获取”、“修改”行为中。看个简单例子,直观感受下:

const person = {};

// 嘿嘿,谁都改不了我的名字
Object.defineProperty(person, 'name', {
    get() {
        return 'van';
    },
    set(v) {
        console.log('they want change my name');
    }
});

console.log(person.name);
// van
person.name = 'tec';
// they want change my name
console.log(person.name);
// van

依赖管理方案

Object.defineProperty 只是解决了状态变更后,如何触发通知的问题,那要通知谁呢?谁会关心那些属性发生了变化呢?在 Vue 中,使用 Dep 解耦了依赖者与被依赖者之间关系的确定过程。简单来说:

  • 第一步,通过 Observer 提供的接口,遍历状态对象,给对象的每个属性、子属性都绑定了一个专用的 Dep 对象。这里的状态对象主要指组件当中的data属性。
  • 第二步,创建三中类型的watcher:

    1. 调用 initComputed 将 computed 属性转化为 watcher 实例
    2. 调用 initWatch 方法,将 watch 配置转化为 watcher 实例
    3. 调用 mountComponent 方法,为 render 函数绑定 watcher 实例
  • 第三步,状态变更后,触发 dep.notify() 函数,该函数再进一步触发 Watcher 对象 update 函数,执行watcher的重新计算。

对应下图:

图片描述

注意,Vue 组件中的 render 函数,我们可以单纯将其视为一种特殊的 computed 函数,在它所对应的 Watcher 对象发生变化时,触发执行render,生成新的 virutal-dom 结构,再交由 Vue 做diff,更新视图。

结语

本文到这里就结束了,更多内容可以尝试看看源码,代码里面的设计模式非常值得学习。

Vue 使用数据劫持作为底层支撑,又设计了一套精妙的依赖管理方案解耦依赖。但数据劫持方案也有其难以解决的痛点:

  1. 只能应用于简单对象
  2. 对数组无效,所以需要包装数组方法
  3. 属性劫持的出发点是“变”,所以vue无法很好接入“immutable”模式

相关文章:

  • input框限制只能输入正整数、字母、小数、汉字
  • MySQL 技术总结
  • 浅论各种调试接口(SWD、JTAG、Jlink、Ulink、STlink)的区别
  • 手把手教你如何安装Pycharm——靠谱的Pycharm安装详细教程
  • 腾讯TBS加载网页无法自适应记录
  • CPU状态信息us,sy,ni,id,wa,hi,si,st含义
  • QuickBI助你成为分析师——计算字段功能
  • 基于ASP.NET MVC 微信网页登录授权(scope为snsapi_base) 流程 上 获取OPENID
  • PHP数字字符串左侧补0、字符串填充和自动补齐的几种方法
  • composer移除依赖包
  • 在Linux,误删磁盘分区怎么恢复呢【转】
  • 纯html页面中js如何获得项目路径
  • Confluence 6 后台中的选择站点首页
  • 算法18-----判断是否存在符合条件的元素【list】
  • 邪恶的三位一体:机器学习、黑暗网络和网络犯罪
  • (十五)java多线程之并发集合ArrayBlockingQueue
  • [rust! #004] [译] Rust 的内置 Traits, 使用场景, 方式, 和原因
  • [译]前端离线指南(上)
  • Java知识点总结(JDBC-连接步骤及CRUD)
  • Python代码面试必读 - Data Structures and Algorithms in Python
  • Python进阶细节
  • SQL 难点解决:记录的引用
  • supervisor 永不挂掉的进程 安装以及使用
  • 二维平面内的碰撞检测【一】
  • 机器人定位导航技术 激光SLAM与视觉SLAM谁更胜一筹?
  • 基于Mobx的多页面小程序的全局共享状态管理实践
  • 离散点最小(凸)包围边界查找
  • 每天一个设计模式之命令模式
  • 如何打造100亿SDK累计覆盖量的大数据系统
  • 入职第二天:使用koa搭建node server是种怎样的体验
  • 使用parted解决大于2T的磁盘分区
  • 为物联网而生:高性能时间序列数据库HiTSDB商业化首发!
  • 验证码识别技术——15分钟带你突破各种复杂不定长验证码
  • 原生Ajax
  • 阿里云重庆大学大数据训练营落地分享
  • ​iOS安全加固方法及实现
  • #Linux(Source Insight安装及工程建立)
  • #调用传感器数据_Flink使用函数之监控传感器温度上升提醒
  • #数学建模# 线性规划问题的Matlab求解
  • (175)FPGA门控时钟技术
  • (博弈 sg入门)kiki's game -- hdu -- 2147
  • (二)JAVA使用POI操作excel
  • (附源码)apringboot计算机专业大学生就业指南 毕业设计061355
  • (附源码)基于SSM多源异构数据关联技术构建智能校园-计算机毕设 64366
  • (七)微服务分布式云架构spring cloud - common-service 项目构建过程
  • (十三)Java springcloud B2B2C o2o多用户商城 springcloud架构 - SSO单点登录之OAuth2.0 根据token获取用户信息(4)...
  • (四)Controller接口控制器详解(三)
  • (转)JAVA中的堆栈
  • (转)创业家杂志:UCWEB天使第一步
  • (转载)虚幻引擎3--【UnrealScript教程】章节一:20.location和rotation
  • .bat文件调用java类的main方法
  • .NET Core SkiaSharp 替代 System.Drawing.Common 的一些用法
  • .NET下ASPX编程的几个小问题
  • @Bean注解详解
  • @ConfigurationProperties注解对数据的自动封装