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

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

看点


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


  • 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 任务完成


相关文章
|
2月前
|
JavaScript API
卷死了!再不学vue3就没有人要你了!速来围观vue3新特性
该文章强调了学习Vue3的重要性,并详细介绍了Vue3相较于Vue2的新特性与改进,包括Composition API、响应式系统的变化以及其他API的更新等内容。
|
6月前
|
存储 JSON JavaScript
一盏茶的时间,带你轻松上手Pinia
Pinia,让状态管理再无难题! 作为Vue.js官方推荐的新星级管理库,Pinia为开发者带来前所未有的顺滑体验。你还在为复杂难懂的状态管理代码头疼吗?别急,用Pinia你可以告别一切烦恼!
|
6月前
|
缓存 移动开发 JavaScript
vue核心面试题汇总【查缺补漏】(一)
vue核心面试题汇总【查缺补漏】(一)
132 0
|
6月前
|
缓存 JavaScript 前端开发
vue核心面试题汇总【查缺补漏】(二)
vue核心面试题汇总【查缺补漏】(二)
|
JavaScript 程序员
使用Vue解决一下吃饭选择困难症
使用Vue解决一下吃饭选择困难症
|
设计模式 IDE 测试技术
让你更高效的开发技巧,少加班多冒泡!
作为开发者,你是否希望提高工作效率,减少加班时间,还能与团队更好地合作?没问题!本文为你准备了实用的技巧。比如,选择适合你的IDE工具、合理规划任务、提前搞清楚需求、经常进行代码审查和重构等等。只要你掌握了这些技巧,你将能轻松应对开发挑战,成为一名高效而受欢迎的开发者!千万别错过哦!
79 0
|
JavaScript
Vue回炉重造之搭建考试答卷系统
答题过程中,防止用户中途退出或者其他不可抗力因素阻碍答题,在每次选择都要请求下接口(接口状态为未交卷,只是保存用户的答题进度)。 选择答题区与答题卡必须同步,另外右侧的进度条也是同步的。
|
前端开发
前端学习笔记202303学习笔记第二天-前端工程化概念1
前端学习笔记202303学习笔记第二天-前端工程化概念1
71 0