坚持造轮子第三天 - 数据响应式

简介: 坚持造轮子第三天 - 数据响应式

看点


  • 针对大厂笔试、面试必考手写题目


  • TDD方式开发


  • 配合视频讲解


造轮子计划


(计划赶不上变化 随时迭代 欢迎留言 随时摸鱼)


  • 框架基础







  • 洋葱圈Compose


  • Flux模式


  • Promise


  • Thunk


  • HTML编译器


  • Pipe管道


  • 原生Ajax


  • JS基础


  • Promise.all/race


  • 路由


  • new


  • call/apply/bind


  • Object.create


  • 深拷贝、浅拷贝


  • 算法、设计模式


  • 二分查找


  • 快排


  • 二分查找


  • 冒泡排序


  • 选择排序


  • 订阅发布


  • 斐波那契算法


  • 去重


响应式是什么


首先我们说说什么是响应式。数据模型发生变化可以发出相应(比如: 调用一个函数)就叫响应式。


具体到我们MVVM中 ViewModel的需要就是数据变了需要视图作出响应。


需求


如果用Jest用例便表示就是这样


对象响应


it('测试数据改变时 是否被响应', () => {
  const data = reactive({
    name: 'abc',
    age: {
      n: 5
    }
  })
  // Mock一个响应函数
  const fn = jest.fn()
  const result = fn()
  // 设置响应函数
  effect(fn)
  // 改变数据
  data.name = 'efg'
  // 确认fn生效
  expect(fn).toBeCalled()
})


假定我们需要的是数据data变化时可以触发fn函数也就是作出相应,当然相应一般是触发视图更新当然也可以不是。我们这里面用jest做了一个Mock函数来检测是否作出相应。


最后代码expect(fn).toBeCalled()有效即代表测试通过也就是作出了相应


数组响应


it('测试数组中数据改变时 是否被响应', () => {
  const data = reactive({
    ary: [
      'a'
    ]
  })
  // Mock一个响应函数
  const fn = jest.fn()
  // 设置响应函数
  effect(fn)
  // 改变多层数据
  data.ary.push('b')
  // 确认fn生效
  expect(fn).toBeCalled()
})


深层嵌套响应


it('测试多层数据中改变时 是否被响应', () => {
  const data = reactive({
    age: {
      n: 5
    }
  })
  // Mock一个响应函数
  const fn = jest.fn()
  // 设置响应函数
  effect(fn)
  // 改变多层数据
  data.age.n = 1
  // 确认fn生效
  expect(fn).toBeCalled()
})


功能实现


Vue普遍走的就是数据劫持方式。不同的在于使用DefineProperty还是Proxy。也就是一次一个属性劫持还是一次劫持一个对象。当然后者比前者听着就明显有优势。这也就是Vue3的响应式原理。


Proxy/Reflect是在ES2015规范中加入的,Proxy可以更好的拦截对象行为,Reflect可以更优雅的操纵对象。 优势在于


  • 针对整个对象定制 而不是对象的某个属性,所以也就不需要对keys进行遍历。


  • 支持数组,这个DefineProperty不具备。这样就省去了重载数组方法这样的Hack过程。


  • Proxy 的第二个参数可以有 13 种拦截方法,这比起 Object.defineProperty() 要更加丰富


  • Proxy 作为新标准受到浏览器厂商的重点关注和性能优化,相比之下 Object.defineProperty() 是一个已有的老方法


  • 可以通过递归方便的进行对象嵌套。


DefineProperty(Vue2风格)


下面展示的是vue2的实现方式是通过Object.defineProperty来重新定义getter,setter方法实现的。


let effective
function effect(fun) {
    effective = fun
}
function reactive(data) {
    if (typeof data !== 'object' || data === null) {
        return data
    }
    Object.keys(data).forEach(function (key) {
        let value = data[key]
        Object.defineProperty(data, key, {
            emumerable: false,
            configurable: true,
            get: () => {
                return value
            },
            set: newVal => {
                if (newVal !== value) {
                    effective()
                    value = newVal
                }
            }
        })
    })
    return data
}
module.exports = {
    effect, reactive
}


当然还有两个重要的问题需要处理 第一个就是这样做只能做浅层响应 也就是如果是第二层就不行了。


it('测试多层数据中改变时 是否被响应', () => {
        const data = reactive({
            age: {
                n: 5
            }
        })
        // Mock一个响应函数
        const fn = jest.fn()
        // 设置响应函数
        effect(fn)
        // 改变多层数据
        data.age.n = 1
        // 确认fn生效
        expect(fn).toBeCalled()
    })


比如以下用例 就过不去了 当然解决的办法是有的 递归调用就好了



当然这样也递归也带来了性能上的极大损失 这个大家先记住。


数组函数劫持(Vue2风格)


然后是数组问题 数组问题我们可以通过函数劫持的方式解决


const oldArrayPrototype = Array.prototype
const proto = Object.create(oldArrayPrototype);
['push','pop','shift','unshift','splice','sort','reverse'].forEach(method => {
    // 函数劫持
    proto[method] = function(){
        effective()
        oldArrayPrototype[method].call(this,...arguments)
    }
})
// 数组通过数据劫持提供响应式
if(Array.isArray(data)){
    data.__proto__ = proto
}


Proxy(Vue3风格)


新版的Vue3使用ES6的Proxy方式来解决这个问题。之前遇到的两个问题就简单的多了。首先Proxy是支持数组的也就是数组是不需要做特别的代码的。对于深层监听也不不必要使用递归的方式解决。当get是判断值为对象时将对象做响应式处理返回就可以了。大家想想这个并不不是发生在初始化的时候而是设置值得时候当然性能上得到很大的提升。


function reactive(data) {
    if (typeof data !== 'object' || data === null) {
        return data
    }
    const observed = new Proxy(data, {
        get(target, key, receiver) {
            // Reflect有返回值不报错
            let result = Reflect.get(target, key, receiver)
            // 多层代理
            return typeof result !== 'object' ? result : reactive(result) 
        },
        set(target, key, value, receiver) {
            effective()
            // proxy + reflect
            const ret = Reflect.set(target, key, value, receiver)
            return ret
        },
        deleteProperty(target,key){
            const ret = Reflect.deleteProperty(target,key)
            return ret
        }
    })
    return observed
}


当然目前还是优缺点的缺点,比如兼容性问题目前IE11就不支持Proxy。不过相信ES6的全面支持已经是不可逆转的趋势了,这都不是事。


测试


OK 任务完成


相关文章
|
4月前
|
JavaScript API 开发者
十分钟 带你强势入门 vue3
十分钟 带你强势入门 vue3
75 1
|
6月前
|
移动开发 资源调度 JavaScript
【Vue 2】一个高效的低代码表单,可视化设计,一键生成源码,享受更多摸鱼时间!!
【Vue 2】一个高效的低代码表单,可视化设计,一键生成源码,享受更多摸鱼时间!!
|
6月前
|
开发者 Java Spring
【绝技揭秘】掌握Vaadin数据绑定:一键同步Java对象,告别手动数据烦恼,轻松玩转Web应用开发!
【8月更文挑战第31天】Vaadin不仅是一个功能丰富的Java Web应用框架,还提供了强大的数据绑定机制,使开发者能轻松连接UI组件与后端Java对象,简化Web应用开发流程。本文通过创建一个简单的用户信息表单示例,详细介绍了如何使用Vaadin的`Binder`类实现数据绑定,包括字段与模型属性的双向绑定及数据验证。通过这个示例,开发者可以更专注于业务逻辑而非繁琐的数据同步工作,提高开发效率和应用可维护性。
107 0
|
存储 JSON 算法
一杯茶的时间入门Vue新的状态管理库-2
Pinia 插件 Pinia 生态已有许多插件,可以扩展更多功能: pinia-plugin-persistedstate:数据持久化 pinia-plugin-debounce:防抖修改状态 pinia-plugin-pinia-observable:转换成 Observable
123 0
|
9月前
|
存储 JSON JavaScript
一盏茶的时间,带你轻松上手Pinia
Pinia,让状态管理再无难题! 作为Vue.js官方推荐的新星级管理库,Pinia为开发者带来前所未有的顺滑体验。你还在为复杂难懂的状态管理代码头疼吗?别急,用Pinia你可以告别一切烦恼!
|
JavaScript 程序员
使用Vue解决一下吃饭选择困难症
使用Vue解决一下吃饭选择困难症
|
存储 JavaScript API
一杯茶的时间入门Vue新的状态管理库-1
Pinia 的优势 相比 Vuex,Pinia 有以下优点:
131 3
|
设计模式 IDE 测试技术
让你更高效的开发技巧,少加班多冒泡!
作为开发者,你是否希望提高工作效率,减少加班时间,还能与团队更好地合作?没问题!本文为你准备了实用的技巧。比如,选择适合你的IDE工具、合理规划任务、提前搞清楚需求、经常进行代码审查和重构等等。只要你掌握了这些技巧,你将能轻松应对开发挑战,成为一名高效而受欢迎的开发者!千万别错过哦!
95 0
农场养成种树游戏开发逻辑源码解析
开发一个农场养成种树游戏可以为玩家提供种植和养护树木的体验,同时也可以学习有关农业和环境保护的知识。 以下是一个简单的农场养成种树游戏的开发源码demo,供参考:
|
存储 前端开发 JavaScript
React修仙笔记,筑基初期之更新数据
在之前的一篇文章中我们有了解到react函数组件和class组件,以及react数据流,状态提升,以及react设计哲学,在我们了解了这些基本的知识后,我们需要了解react内部更深的一些知识
115 0
React修仙笔记,筑基初期之更新数据