immutable详解

391 阅读3分钟

这是我参与8月更文挑战的第11天,活动详情查看:8月更文挑战

  • JavaScript中的对象是可变的,因为使用了引用赋值,新的对象简单的引用了原始对象,改变新的对象会影响到原始对象。为了解决这个问题,一般就是使用浅拷贝或者深拷贝来避免被修改,但是这样会造成cpu和内存的浪费

传统处理复杂对象

浅拷贝

简单的复制拷贝,其实地址不变

深拷贝

JSON.parse

利用jaon.pase将对象转换为其JSON字符串形式,然后将其解析回对象

const deepClone(obj)=>JSON.parse(JSON.stringify(obj))

⚠️注意:不能处理循环对象,子父节点互相引用的情况,无法处理对象中有function,正则等情况

messageChannel

  • messageChannel接口是信道通信api的一个接口,允许我们创建一个新的信道并通过信道的两个MessageProt属性传递数据\
  • 类似的还有Histor API,Notification API.都是用了结构化克隆算法实现传输值的
function structuralClone(obj) {
  return new Promise(resolve => {
    const { port1, port2 } = new MessageChannel();
    port2.onmessage = function (e) {
      resolve(e.data)
      console.log("MessageEvent输出:", e)
    }
    port1.postMessage(obj);
  });
}


const originObj = { name: "一缕清风" }
structuralClone(originObj).then((data) => {
  console.log('clone', data)  //clone { name: '一缕清风' }
  data.age = 18
})
console.log(originObj)  //{ name: '一缕清风' }

originObj.name = "Kenguba"
console.log('change origin data-->', originObj) //change origin data--> { name: 'Kenguba' }

/*
{ name: '一缕清风' }
change origin data--> { name: 'Kenguba' }
MessageEvent输出: MessageEvent {
  type: 'message',
  defaultPrevented: false,
  cancelable: false,
  timeStamp: 46.602142999880016
}
clone { name: '一缕清风' }
*/

什么是 immutable Data

  • immutable(不可改变的)
  • immutable是一种持久化数据。一旦创建,就不能更改的数据,对immutable对象的任何修改或删除添加都会返回一个新的immutable对象,但是原数据不会改变

    • 实现原理就是持久化数据结构,在使用旧数据创建新数据的时候,会保证旧数据同时可用且不变,同时为了避免深度复制复制所有节点的带来的性能损耗,immutable使用了结构共享,即如果对象树种的一个节点发生变化,只修改这个节点和受他影响的父节点,其他节点则共享。

为什么要使用 immutable

  • 如果需要频繁的操作一个复杂对象,每次完全拷贝一次的效率太低了。大部分场景下都只是更新了对象的一两个字段,其他字段都不变,对于这些不变的字段的拷贝都是多于的

  • 核心思路 创建持久化的数据结构,在操作对象的时候值clone变化的节点和其祖先节点,其他的不变,实现结构贡献

总结 immutable的不可变性让纯函数更强大,每次都返回新的immutable的特性让程序员可以对其进行链式操作,用起来更方便

类似 immutable 的库

  • Immutable.js
  • Immer.js
  • 自己写的全部深度克隆代码
function type(obj) {
  var type = Object.prototype.toString.call(obj)

  var map = {
    '[object Boolean]': 'boolean',
    '[object Number]': 'number',
    '[object String]': 'string',
    '[object Function]': 'function',
    '[object Array]': 'array',
    '[object Date]': 'date',
    '[object RegExp]': 'regExp',
    '[object Undefined]': 'undefined',
    '[object Null]': 'null',
    '[object Object]': 'object'
  }
  if (obj instanceof Element) {
    return 'element'
  }

  return map[type]
}

function deepClone(item) {
  if (!item) return item

  var types = ['number', 'string', 'boolean'], result
  if (types.includes(type(item))) {
    result = item
  }

  if (result === undefined) {
    if (type(item) === 'array') {
      console.log('arr')
      result = []
      item.forEach((child, index, array) => {
        result[index] = deepClone(child)
      })
    } else if (type(item) === 'object') {
      if (item.nodeType && typeof item.cloneNode == 'function') {
        retult = item.cloneNode(true)
        //真实的复制函数并没有走到这里
      } else if (!item.prototype) {
        // 检查是否是可循环的
        if (item instanceof Date) {
          result = new Date(item)
        } else {
          result = {}
          for (var i in item) {
            result[i] = deepClone(item[i])
          }
        }
      } else {
        console.log('发现了其他类型')
      }
    } else {
      result = item
    }
  } else {
    console.log('123', result)
  }
  return result
}

let arr = [1, 2, 3]

var arr2 = deepClone(arr)
console.log(arr2, 'arr2')
arr.push(1)
console.log(arr2, 'arr2-after')