不可变数据结构和函数式编程的关系?

文章探讨了函数式编程中数据不可变性的概念,以及它在React开发中的优点,如简化开发、数据回溯和减少副作用。提到JavaScript的对象可变性可能导致的问题,并介绍了浅拷贝、深拷贝等解决方案。文章推荐了两个库,immutable.js和immer.js,用于实现高效的数据不可变性,通过结构共享和惰性操作优化内存使用。最后,文章展示了这两个库的简单使用示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

不可变数据结构和函数式编程的关系?

函数式编程

  • 相对于命令式编程 更看重函数的执行目的而不是执行过程。
  • 函数是一等公民 意味着函数优先和提倡函数组合。
  • 函数可以和其他数据一样作为参数传递或者作为返回值返回
  • 数据不可变性是函数式编程中推崇的重要概念
  • 数据不可变在react开发中有着广泛应用:好处在于开发简单,数据可回溯,减少副作用减少bug出现。

数据可变可能会带来的问题?

  • javascript中的对象一般是可变的,因为使用了引用赋值,新的对象简单引用了原始对象
  • 所以当我们改变了新的对象会影响到原始对象
  • 可变数据会有效的利用内存,但是如果随着应用变得越来越复杂 也会造成非常大的隐患
  • 为了解决这个问题,我们一般会复制一个新对象,再在新对象上做修改,但是这会造成更多的性能问题和内存浪费。

深拷贝

function deepCopy(obj) {
  // 基础数据类型直接返回
  if (!obj && typeof obj !== "object") {
    return obj;
  }
  // 如果是dom节点 直接clone一个
  if (obj.nodeTpe && "cloneNode" in obj) {
    return obj.cloneNode(true);
  }
  // 如果是Date类型
  if (getType(obj) === "date") {
    return new Date(obj.getTime());
  }
  // 如果是RegExp
  if (getType(obj) === "regexp") {
    return new RegExp(obj.source);
  }
  const result = isArray(obj)
    ? []
    : obj.constructor
    ? new obj.constructor()
    : {};
  for (let key in obj) {
    if (Object.prototype.hasOwnProperty(obj, key)) {
      result[key] = deepCopy(obj[key]);
    }
  }
  return result;
}

如何解决这个问题?

  • 我们一般使用浅拷贝或者深拷贝来处理来防止对象被随意篡改
  • 方法有:object.assign,扩展运算符…,Object.freeze等浅拷贝方法
  • 但是这些方法只适用于浅层级的处理,对象层级如果比较深,还是需要特殊处理的
  • 我们可以使用javascript的一些函数式api如slice,filter,map,reduce配合一些浅拷贝操作
  • 来实现数据的不可变,但这无疑会增加开发的难度
  • 不可变数据结构immutable.js和immer.js是两种解决方案
  • 使用了结构共享 如果对象树中一个节点发生变化,只修改这个节点和受他影响的父节点,其他节点进行共享。

immutable 不可变数据

immutable三大特性
  - 持久化数据结构
  - 结构共享
  - 惰性操作(懒初始化)
  • immutable 内部实现了一套完整的 Persistent Data Structure,还有很多易用的数据类型。像 Collection、List、Map、Set、Record、Seq。
immutable优势
- 降低复杂度
- 节省内存
- 方便回溯

简单使用Map和List:

Map
const immutable = require("immutable");
const assert = require("assert");
let obj1 = immutable.Map({ name: 'benpaoyinfu', age: 8 });
let obj2 = obj1.set('name', 'benpaoyinfu2');
let obj3 = obj2.update('age', x => x + 1);
let obj4 = obj3.merge({ home: '上海' });
console.log(obj1, obj2, obj3, obj4);

let obj6 = immutable.fromJS({ user: { name: 'benpaoyinfu', age: 18 }, 'k': 'v' });
let obj7 = obj6.setIn(['user', 'name'], 'benpaoyinfu');
let obj8 = obj7.updateIn(['user', 'age'], x => x + 1);
let obj9 = obj8.mergeIn(["user"], { home: '上海' });
console.log(obj6, obj7, obj8, obj9);

console.log(obj6.get('user'));

console.log(obj6.getIn(['user', 'name']));
console.log(...obj6.keys());
console.log(...obj6.values());
console.log(...obj6.entries());

var map1 = immutable.Map({ name: 'benpaoyinfu', age: 9 });
var map2 = immutable.Map({ name: 'benpaoyinfu', age: 9 });
assert(map1 !== map2);
assert(Object.is(map1, map2) === false);
assert(immutable.is(map1, map2) === true); 
List
let immutable = require('immutable');
let arr1 = immutable.fromJS([1, 2, 3]);
console.log(arr1.size);
let arr2 = arr1.push(4);
console.log(arr2);
let arr3 = arr2.pop();
console.log(arr3);
let arr4 = arr3.update(2, x => x + 1);
console.log(arr4);
let arr5 = arr4.concat([5, 6]);
console.log(arr5);
let arr6 = arr5.map(item => item * 2);
console.log(arr6);
let arr7 = arr6.filter(item => item >= 10);
console.log(arr7);
console.log(arr7.get(0));
console.log(arr7.includes(10));
console.log(arr7.last());
let val = arr7.reduce((val, item) => val + item, 0);
console.log(val);
console.log(arr7.count());

immer不可变数据结构

immer 是 mobx 的作者写的一个 immutable 库
核心实现是利用 ES6 的 proxy,几乎以最小的成本实现了 js 的不可变数据结构
对 draftState 的修改都会反应到 nextState 上
而 Immer 使用的结构是共享的,nextState 在结构上又与 currentState 共享未修改的部分

import { produce } from 'immer';
let baseState = {}
let nextState = produce(baseState, (draft) => {})
console.log(baseState===nextState);
import { produce } from 'immer';
let baseState = {
  ids: [1],
  pos: {
    x: 1,
    y: 1 
  }
}
let nextState = produce(baseState, (draft) => {
  draft.ids.push(2);
})
console.log(baseState.ids === nextState.ids);//false
console.log(baseState.pos === nextState.pos);//true

immer.js核心实现

  1. 兼容对象,函数和数组
// 创建草稿对象
function createDraftState(baseState){
    if(Array.isArray(baseState)){
        return [...baseState]
    }else if(Object.prototype.toString.call(baseState)){
        return Object.assign({}, baseState)
    }else{
        return baseState;
    }
}
// 生产方法
function produce(baseState, producer){
    const proxy = toProxy(baseState);
    producer(proxy);
    const internal = proxy[INTERNAL];
    internal.mutated?internal.draftStat:baseState
}
// 创建代理
const INTERNAL = Symbol('internal');
function toProxy(baseState, parentChange){
    let keyProxy = {};
    let internal = {
        draftState: createDraftState(),
        keyProxy,
        mutated: false
    }
    return new Proxy(baseState, {
        get(target, key){
            if(key === INTERNAL){
                return internal;
            }
            const value = target[key];
            if(Object.prototype.toString.call(value) === '[object Object]' 
            || Array.isArray(value)){
                if(key in keyProxy){
                    return keyProxy[key]
                }else{
                    keyProxy[key] = toProxy(value, () => {
                        internal.mutated = true;
                        const childProxy = keyProxy[key];
                        const { draftState } = childProxy[INTERNAL];
                        internal.draftState[key] = draftState;
                        parentChange&&parentChange()
                    })
                    return keyProxy[key]
                }
            }else if(typeof value === 'function'){
                internal.mutated = true;
                parentChange&&parentChange();
                return value.bind(internal.draftState)
            }
            return internal.mutated ? internal.draftState[key]: baseState[key]
        },
        set(target, key, value){
            internal.mutated = true;
            const { draftState } = internal;
            for(const key in target){
                draftState[key] = key in draftState ? draftState[key] : target[key];
            }
            draftState[key] = value;
            parentChange&&parentChange();
            return true;
        }
    })
}

hook版实现

  1. queueMicrotask用来帮我们执行微任务
  2. 当我们期望某段代码不阻塞当前执行的同步代码,同时又期望它尽可能快地执行时,我们可能会使用到queueMicrotask
import { useState, useRef } from 'react';
import { toProxy, INTERNAL } from './core';
import * as is from './is';
function useImmerState(baseState) {
    const [state, setState] = useState(baseState);
    const draftRef = useRef(toProxy(baseState, () => {
        queueMicrotask(()=>{
            const internalState = draftRef.current[INTERNAL];
            const newState = internalState.draftState;
            setState(() => {
                return (is.isArray(newState) ? [...newState] : Object.assign({}, newState));
            });
        })
    }));
    const updateDraft = (producer) => producer(draftRef.current);
    return [state, updateDraft];
}
export default useImmerState;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值