专栏首页小码农学习笔记Vue 面试必问:响应式原理是什么以及是如何实现的
原创

Vue 面试必问:响应式原理是什么以及是如何实现的

响应式原理

定义

响应式指的是组件 data 的数据一旦变化,立刻触发视图的更新。它是实现数据驱动视图的第一步。

监听 data 变化的核心 API

Vue 实现响应式的一个核心 API 是 Object.defineProperty。该方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

基本用法:

const data = {}
const name = 'zhangsan'
Object.defineProperty(data, 'name', {
  get: function() { 
    console.log('get')
    return name
  },
  set: function(newVal) {
    console.log('set')
    name = newVal
  }
})


// 测试
console.log(data.name) // get zhangsan
data.name = 'lisi'     // set

利用 Object.defineProperty 重写 getset,将对象属性的赋值和获取变成函数,我们可以实现一个简单的双向绑定。

如何监听 data 变化

共定义了三个函数:

  • updateView:模拟 Vue 更新视图的入口函数。
  • defineReactive:对数据进行监听的具体实现。
  • observer:调用该函数后,可对目标对象进行监听,将目标对象编程响应式的。

执行逻辑为:

定义一个对象 data => 调用 observer(data) 将对象变成响应式的 => 修改对象内的属性 => 更新视图

// 触发更新视图
function updateView() {
  console.log('视图更新')
}

// 重新定义属性,监听起来
function defineReactive(target, key, value) {
  // 核心 API
  Object.defineProperty(target, key, {
    get() {
      return value
    },
    set(newValue) {
      if (newValue !== value) {
        // 设置新值
        // 注意,value 一直在闭包中,此处设置完之后,再 get 时也是会获取最新的值
        value = newValue

        // 触发更新视图
        updateView()
      }
    }
  })
}

// 监听对象属性
function observer(target) {
  if (typeof target !== 'object' || target === null) {
    // 监听的不是对象或数组时,直接返回
    return target
  }

  // 重新定义各个属性(for in 也可以遍历数组)
  for (let key in target) {
    defineReactive(target, key, target[key])
  }
}

测试一下,会打印出两个 "视图更新" 字符串。

// 准备数据
const data = {
  name: 'zhangsan',
  age: 20
}

// 监听数据
observer(data)

// 测试
data.name = 'lisi'
data.age = 21

如何深度监听 data 变化

对于有嵌套属性的数据,例如:

// 准备数据
const data = {
  name: 'zhangsan',
  age: 20,
  info: {
    address: '北京' // 需要深度监听
  }
}

要想监听到 info.address 的变化,则需要深度监听,修改 defineReactive 方法即可:

  • 在刚进入 defineReactive 函数的时候,先调用 observer 对传进来的值进行判断,由于 info 是个对象,所以会对 info 遍历后再执行 defineReactive;而其它基本类型的值在 observer 中被直接返回。
  • 在设置新值时也要对新值进行深度监听,原因是新值也可能是个对象,需要监听到它里面的属性。
// 重新定义属性,监听起来
function defineReactive(target, key, value) {
  // 深度监听
  observer(value)

  // 核心 API
  Object.defineProperty(target, key, {
    get() {
      return value
    },
    set(newValue) {
      if (newValue !== value) {
        // 深度监听
        observer(newValue)

        // 设置新值
        // 注意,value 一直在闭包中,此处设置完之后,再 get 时也是会获取最新的值
        value = newValue

        // 触发更新视图
        updateView()
      }
    }
  })
}

Object.defineProperty 缺点

  • 深度监听时,需要递归到底,一次性计算量大
  • 无法监听新增属性/删除属性(所以开发中需要使用 Vue.set 和 Vue.delete 这两个 API 来增删 data 的属性)
  • 无法原生监听数组,需要特殊处理

如何监听数组变化

由于性能原因,Vue 不是通过 Object.defineProperty 来监听数组的。

对于数组,是通过重写数组方法来实现,共修改了两处:

  • 对原生数组原型做一个备份(防止后续的操作污染原生数组原型),基于这个备份创建一个新的数组,并扩展(在执行原方法前触发一次视图更新)它的方法。
  • observer 方法中,增加对数组的处理。

执行逻辑为:

定义一个对象 data => 调用 observer(data),在内部判断 data 是对象,则遍历该对象的每个属性并依次执行 defineReactive => defineReactive 内部的 observer(value) 碰到数组 nums,则将该数组的隐式原型赋值成我们重写之后的原型;除 nums 外的其它类型属性,走之前的逻辑 => 更新视图

// 触发更新视图
function updateView() {
  console.log('视图更新')
}

// 重新定义数组原型
const oldArrayProperty = Array.prototype
// 创建新对象,原型指向 oldArrayProperty ,再扩展新的方法不会影响原型
const arrProto = Object.create(oldArrayProperty);
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => {
  arrProto[methodName] = function () {
    updateView() // 触发视图更新
    oldArrayProperty[methodName].call(this, ...arguments)
  }
})

// 重新定义属性,监听起来
function defineReactive(target, key, value) {
  // 深度监听
  observer(value)

  // Object.defineProperty 部分的代码省略
  ……
}

// 监听对象属性
function observer(target) {
  if (typeof target !== 'object' || target === null) {
    // 监听的不是对象或数组时,直接返回
    return target
  }

  if (Array.isArray(target)) {
    target.__proto__ = arrProto
  }

  // 重新定义各个属性(for in 也可以遍历数组)
  for (let key in target) {
    defineReactive(target, key, target[key])
  }
}

测试一下,执行 data.nums.push(4) 时会打印出 "视图更新" 字符串并在数组末尾添加进元素。

// 准备数据
const data = {
  name: 'zhangsan',
  age: 20,
  info: {
    address: '北京'
  },
  nums: [10, 20, 30] // 需要监听的数组
}

// 监听数据
observer(data)

// 测试
data.nums.push(4) // 监听数组

文章持续更新,本文 GitHub 前端修炼小册 已经收录,欢迎 Star。如对文章内容有不同见解,欢迎留言交流。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 【面试技巧】当面试官问你glide的时候,是想问什么?glide生命周期如何实现?

    去面试的时候,我们也经常被问到这样的问题:项目用什么图片加载框架?为什么选择这个框架?glide是现在主流的图片加载框架,被问到的概率非常高。面试官这样问,最想...

    Android技术干货分享
  • 当面试官问你glide的时候,是想问什么?glide生命周期如何实现?

    去面试的时候,我们也经常被问到这样的问题:项目用什么图片加载框架?为什么选择这个框架?glide是现在主流的图片加载框架,被问到的概率非常高。面试官这样问,最想...

    Android技术干货分享
  • 供应链管理面临的主要问题以及成功实施方案的途径是什么?

    当前电子商务的浪潮席卷全球使人们同时面临机遇和挑战。本文以供应链管理理论为基础,论述电子商务环境下供应链管理模式的变革,并对我国企业在电子商务环境下实施供应链管...

    数商云市场营销总监
  • 前端面试题Vue答案

    追问:computed 和 watch 应用场景? 关键词 computed+缓存

    酷走天涯
  • React 毁了 Web 开发!

    React 是一个很好的库,对于Web开发很重要,因为它引入了声明式与反应式模板,这在当时是每个人都需要的范式转变。当时(也就是6~7年前),我们面临着需要的范...

    开发者技术前线
  • 记录面试中一些回答不够好的题(Vue 居多)

    grid 学习:https://www.jianshu.com/p/d183265a8dad

    三毛
  • 浅析Vuex及相关面试题答案

    自从学习了Vue框架,其中必不可少的会用到vuex这个核心插件,而且在做项目的时候,基本都会使用,可能你会使用vuex状态管理,但是对vuex原理存在着或多或少...

    前端开发博客
  • Vue3 对 Web 应用性能的改进[每日前端夜话0xE1]

    有关即将发布的 Vue.js 的第 3 个主要版本的信息越来越多。通过下面的讨论,虽然还不能完全确定其所有内容,但是我们可以放心地认为,它将是对当前版本(已经非...

    疯狂的技术宅
  • Vue 中的 Props 与 Data 细微差别,你知道吗?

    这些方法一开始可能会让人感到困惑,因为它们做的事情很相似,而且也不清楚什何时使用props,何时使用data。

    前端小智@大迁世界
  • 为什么 Vue3.0 要重写响应式系统

    面试的时候经常被问到 响应式 相关的内容,而Vue3.0 更新后,面试官又有了新的武器;

    西岭老湿
  • 「前端架构」React和Vue -CTO的选择正确框架的指南

    快速总结:为项目选择正确的javascript框架或库是CTO和项目经理的基本任务。然而,选择的范围很大程度上取决于几个因素,如项目时间、学习曲线、框架性能和团...

    首席架构师智库
  • Vue 应用单元测试的策略与实践 01 - 前言和目标

    1. 在 TDD 做完 Tasking 列完实例化数据之后,完全没有 UT 基础不知道该怎么写单元测试?

    JimmyLv_吕靖
  • 石桥码农:Vue3 与 Vue2 在响应机制的实现上有什么差别?

    vue 开发者可能都遇到过这样一个问题:如果模板中数据绑定的是一个数组,我们在 js 代码里面,直接以索引方式改变数组元素的值,有时候视图并不会按照我们的期许更...

    程序员LIYI
  • 横扫9家大厂前端校招offer

    我就读于北京理工大学软件工程专业,是一名大四学生。从大一开始投入以前端为主的全栈开发,独立开发过多个中型和小型项目,是 佬铁|宿舍市集 小程序的社区创始人及独立...

    前端迷
  • 那些Vue开发遇到的坑---响应式系统

    Vue是目前使用较为广泛的前端框架之一。相比React,Vue更容易学习上手。毕竟在React中万物皆JavaScript。这让一些习惯于编写HTML+Java...

    yuanyi928
  • 34道Vue面试题系列:Vue中如何检测数组变化?

    本次解析本套高级前端的Vue面试题的第三问,Vue中是如何检测数组变化的,如果对这一问也有所不熟悉的,请一起学习吧。

    用户1462769
  • 全网首发:Vue3.0+Vite避坑指南!升级后有新增了哪些看点?

    Vue2.0 全线升级,升级后的新版本 Vue3.0 凭借新特性,新工具,一经问世便在 IT 圈中引起广泛的讨论:

    用户3806669
  • 使用 Vue 3.0,你可能不再需要Vuex了

    Vuex 是一个很棒的状态管理库。它很简单,并与 Vue 集成的非常好。为什么会有人放弃 Vuex ? 原因可能是即将发布的 Vue3 版本公开了底层的响应式系...

    前端迷
  • 使用 Vue 3.0,你可能不再需要Vuex了

    Vuex 是一个很棒的状态管理库。它很简单,并与 Vue 集成的非常好。为什么会有人放弃 Vuex ? 原因可能是即将发布的 Vue3 版本公开了底层的响应式系...

    ConardLi

扫码关注云+社区

领取腾讯云代金券