XingYun blog
  • JS基础

    • 图解js原型链
    • JS Event Loop
    • 对象的底层数据结构
    • 让你的JavaScript代码简单又高效
    • 函数参数按值传递
    • 判断数据类型
    • 浮点数精度问题和解决办法
    • 常用方法snippet
    • 实现Promise
    • 防抖和节流
    • 巧用sort排序
  • CSS && HTML

    • CSS也需要性能优化
    • class命名规范
    • em、px、rem、vh、vw 区别
    • CSS揭秘阅读笔记
  • 浏览器

    • 浏览器是如何渲染页面的
    • 重排和重绘
    • BOM浏览器对象模型
    • DOM事件
    • 浏览器存储
  • 数据结构

    • JS实现链表
    • JS实现栈与栈应用
    • JS实现常见排序
    • 哈夫曼编码
    • MD5算法
  • vue原理浅析

    • Vue虚拟dom与Diff算法
    • 前端打包文件的缓存机制
    • vue数组为什么不是响应式
    • v-for为什么不能用index做key
  • 前端工程化

    • 浏览器是如何渲染页面的
    • 前端打包需要gzip压缩吗
    • 前端打包文件的缓存机制
    • webpack loader和plugin
  • 轮子&&组件库

    • 实现水波浪进度球
  • 文字转语音mp3文件
  • 文件上传前后端实现
  • moment.js给定时间获取自然月、周的时间轴
  • 实现文件上传功能
  • 批量下载照片
  • leaflet改变坐标原点
  • 网络

    • 有了MAC地址 为什么还需要IP地址
    • 为什么IP地址老是变
    • 我们为什么需要IPV6
    • TCP与UDP
  • 计算机组成原理

    • ASCII、Unicode、UTF-8和UTF-16
  • VSCode

    • VSCode图片预览插件 Image preview
    • rsync:linux间的高效传输工具

XingYun

冲!
  • JS基础

    • 图解js原型链
    • JS Event Loop
    • 对象的底层数据结构
    • 让你的JavaScript代码简单又高效
    • 函数参数按值传递
    • 判断数据类型
    • 浮点数精度问题和解决办法
    • 常用方法snippet
    • 实现Promise
    • 防抖和节流
    • 巧用sort排序
  • CSS && HTML

    • CSS也需要性能优化
    • class命名规范
    • em、px、rem、vh、vw 区别
    • CSS揭秘阅读笔记
  • 浏览器

    • 浏览器是如何渲染页面的
    • 重排和重绘
    • BOM浏览器对象模型
    • DOM事件
    • 浏览器存储
  • 数据结构

    • JS实现链表
    • JS实现栈与栈应用
    • JS实现常见排序
    • 哈夫曼编码
    • MD5算法
  • vue原理浅析

    • Vue虚拟dom与Diff算法
    • 前端打包文件的缓存机制
    • vue数组为什么不是响应式
    • v-for为什么不能用index做key
  • 前端工程化

    • 浏览器是如何渲染页面的
    • 前端打包需要gzip压缩吗
    • 前端打包文件的缓存机制
    • webpack loader和plugin
  • 轮子&&组件库

    • 实现水波浪进度球
  • 文字转语音mp3文件
  • 文件上传前后端实现
  • moment.js给定时间获取自然月、周的时间轴
  • 实现文件上传功能
  • 批量下载照片
  • leaflet改变坐标原点
  • 网络

    • 有了MAC地址 为什么还需要IP地址
    • 为什么IP地址老是变
    • 我们为什么需要IPV6
    • TCP与UDP
  • 计算机组成原理

    • ASCII、Unicode、UTF-8和UTF-16
  • VSCode

    • VSCode图片预览插件 Image preview
    • rsync:linux间的高效传输工具
  • 3个提升Vue性能的写法
  • 重读Vue文档
  • moment.js给定时间获取自然月、周的时间轴
  • Vue虚拟dom与Diff算法
  • 深入响应式原理
  • Echart样例
  • 我的Vue指令库
  • 滚动到底部加载更多
  • 实现一键换肤
  • 手写Vue数据劫持
    • vue-router核心原理与手写实现
    • computed和watch
    • vue数组为什么不是响应式
    • v-for为什么不能用index做key
    • webpack loader和plugin
    • keep-alive组件原理
    • vue插槽进化
    • Vue多层嵌套组件
    • vue生命周期hook
    • vue监听dom元素的resize事件
    • 前端打包需要gzip压缩吗
    • 实现水波浪进度球
    • Vue
    XingYun
    2022-04-13
    目录

    手写Vue数据劫持

    基于 Vue2 版本

    # 对象劫持

    # 原理 Object.defineProperty()

    函数签名: 参考 MDN

    语法
    Object.defineProperty(obj, prop, descriptor)

    参数
    obj: 要定义属性的对象。
    prop: 要定义或修改的属性的名称或 Symbol 。
    descriptor : 要定义或修改的属性描述符。

    返回值
    被传递给函数的对象。

    举个例子:

    var message = 'hello world'
    const data = {}
    Object.defineProperty(data, 'message', {
      get() {
        return message
      },
      set(newVal) {
        message = newVal
      }
    })
    data.message // 'hello world'
    data.message = 'test' // 'test'
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    当我们读取或者设置被定义的属性时,就会执行 get 或者 set 方法,也就是在这两个方法中,我们实现数据劫持,可以在这里添加一些我们附加的操作,而不只是简单的读写。

    那么在 vue 中,是怎么实现对 data 中所有的属性做到数据劫持的呢?其实原理都一样,我们只需遍历所有 data 对象中的所有属性,并对每一个属性使用 Object.defineProperty 劫持即可,当属性的值发生变化的时候,我们执行一系列的渲染视图的操作。

    下面我模拟实现 Vue 数据劫持的过程。

    const data = {
      name: '小明',
      age: 25,
      info: {
        address: '上海'
      }
    }
    
    function renderView(name, newVal) {
      console.log(name + ' 更新为了 ' + newVal + ' ,视图正在更新。。。')
    }
    
    function observerObject(target, name, value) {
      if (typeof value === 'object' || Array.isArray(target)) {
        observer(value)
      }
      Object.defineProperty(target, name, {
        get() {
          return value
        },
        set(newVal) {
          if (newVal !== value) {
            if (typeof value === 'object' || Array.isArray(value)) {
              observer(value)
            }
            value = newVal
          }
          renderView(name, newVal)
        }
      })
    }
    function observer(target) {
      if (typeof target !== 'object' || !target) {
        return target
      }
      for (const key in target) {
        if (target.hasOwnProperty(key)) {
          const value = target[key]
          observerObject(target, key, value)
        }
      }
    }
    observer(data)
    
    data.name = '帅锅'
    data.info.address = '成都'
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46

    这段代码执行后控制台输出

    // name 更新为了 帅锅 ,视图正在更新。。。
    // address 更新为了 成都 ,视图正在更新。。。
    
    1
    2

    # 数组劫持

    Object.defineProperty 只能够作用在对象上,那么 vue 中,对数组是怎么实现数据劫持的呢? 只需要修改数组的原型方法,往这些方法里添加一些视图渲染的操作。

    const oldArrayProperty = Array.prototype
    
    const newArrayProperty = Object.create(oldArrayProperty)
    
    ;['pop', 'push', 'shift', 'unshift', 'splice', 'reverse'].forEach((method) => {
      newArrayProperty[method] = function() {
        oldArrayProperty[method].call(this, ...arguments)
        renderView()
      }
    })
    
    // 在observer函数中加入数组的判断,如果传入的是数组,则改变数组的原型对象为我们修改过后的原型。
    if (Array.isArray(target)) {
      target.__proto__ = newArrayProperty
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    这样在调用数组的这些方法时就会及时调用 renderView 更新视图

    # 总结

    上面代码就是 vue2x 版本数据劫持的原理实现(和源代码区别很大但原理相同)。

    对象劫持

    现在我们可以看出 Object.defineProperty 的一些问题

    • 递归遍历所有的对象的属性,这样如果我们数据层级比较深的话,是一件很耗费性能的事情

    • 只能应用在对象上,不能用于数组

    • 只能够监听定义时的属性,不能监听新加的属性,这也就是为什么在 vue 中要使用 Vue.set 的原因,删除也是同理

    数组劫持

    数组劫持也能通过遍历去劫持每一个 item, 然而 Vue 采用的是修改原新方法方式实现, 因为一般开发中,对象属性多的最多几十个,量级小,而数组可能成百上千上万,如果每个 item 都劫持太耗费性能且不可控。

    #Vue
    上次更新: 2023/04/05, 09:41:10
    实现一键换肤
    vue-router核心原理与手写实现

    ← 实现一键换肤 vue-router核心原理与手写实现→

    最近更新
    01
    JavaScript-test
    07-20
    02
    二维码的原理
    07-20
    03
    利用ChatGPT优化代码
    07-20
    更多文章>
    Theme by Vdoing | Copyright © 2021-2023 XingYun | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式