不会封装函数?深拷贝不会写?来读读vuex源码里面的工具函数并完善吧

源码里面的文件位置:/src/util.js
英文注释是源码里面本来有的,英语不太好,也没咋看,直接上自己的中文注释

/**
 * Get the first item that pass the test
 * by second argument function
 *
 * @param {Array} list数组
 * @param {Function} f 函数
 * @return {*}
 */
// 找到数组中第一个符合条件的元素
export function find (list, f) {
  return list.filter(f)[0]
}

/**
 * Deep copy the given object considering circular structure.
 * This function caches all nested objects and its copies.
 * If it detects circular structure, use cached copy to avoid infinite loop.
 *
 * @param {*} obj
 * @param {Array<Object>} cache
 * @return {*}
 */
// 深拷贝
// 整体思路-递归
export function deepCopy (obj, cache = []) {
  // just return if obj is immutable value
  if (obj === null || typeof obj !== 'object') {
    return obj
  }

  // if obj is hit, it is in circular structure
  // 这里其实是通过下面的cache缓存当前嵌套的对象
  // 然后通过find方法判断是否存在循环引用,
  const hit = find(cache, c => c.original === obj)
  if (hit) {
    return hit.copy
  }

  const copy = Array.isArray(obj) ? [] : {}
  // put the copy into cache at first
  // because we want to refer it in recursive deepCopy
  // 缓存副本
  cache.push({
    original: obj,
    copy
  })

  // 递归调用深拷贝函数
  Object.keys(obj).forEach(key => {
    copy[key] = deepCopy(obj[key], cache)
  })

  return copy
}

/**
 * forEach for object
 */
// 遍历对象的属性和值
export function forEachValue (obj, fn) {
  Object.keys(obj).forEach(key => fn(obj[key], key))
}

// 判断是否为除了null之外的对象
export function isObject (obj) {
  return obj !== null && typeof obj === 'object'
}

// 判断promise对象
export function isPromise (val) {
  return val && typeof val.then === 'function'
}

// assert node中的断言
// 断言抛出异常
export function assert (condition, msg) {
  if (!condition) throw new Error(`[vuex] ${msg}`)
}

// 保留原始参数的闭包函数
export function partial (fn, arg) {
  return function () {
    return fn(arg)
  }
}

这次我们重点理理它这个深拷贝
在这之前,废话不多说直接打开谷歌控制台使用下面的对象来测试一下vuex的深拷贝,自己先去发现一下有没有什么问题

let obj = {
  num: 0,
  str: '',
  boolean: true,  
  unf: undefined,
  nul: null,
  obj: { name: '我是一个对象', id: 1 },
  arr: [0, 1, 2],
  func: function () { console.log('我是一个函数') },
  date: new Date(0),
  reg: new RegExp('/我是一个正则/ig'),
  [Symbol('1')]: 1,
};
Object.defineProperty(obj, 'innumerable', {
  enumerable: false, value: '不可枚举属性' }
);
obj.loop = obj    // 设置loop成循环引用的属性

这里我就不贴结果了,自己运行的话,很快就能发现

  • Date和RegExp类型的直接被实例化为一个空对象
  • 拷贝得到的对象中并没有Symbol和不可枚举的属性

Symbol是es6新出的一种基本数据类型(不知道的去恶补吧)
通过Object.defineProperty设置的不可枚举属性,不了解的,可以去了解下js对象属性的描述符(如果了解vue双向绑定原理的一定知道这个方法和对象属性的描述符get和set,这里的enumerable是一类东西)

抛出了问题,试着改进下吧,注意看注释

循环引用的问题,还是沿用它自己的,有兴趣的可以去试着用WeakMap处理一下循环引用的问题

// 改进版的深拷贝
function deepClone (obj, cache = []) {
  // just return if obj is immutable value
  if (obj === null || typeof obj !== 'object') {
    return obj
  }
  // 手动处理Date和正则类型会返回空对象的问题
  // 这里我们直接自己返回一个new的实例
  if (obj.constructor === Date) return new Date(obj)
  if (obj.constructor === RegExp) return new RegExp(obj)

  // 循环引用这里还是保留之前的处理方式
  // find(上面代码第一个方法)
  const hit = find(cache, c => c.original === obj)
  if (hit) {
    return hit.copy
  }
  const copy = Array.isArray(obj) ? [] : {}
  cache.push({
    original: obj,
    copy
  })

  // 递归调用深拷贝函数
  // Reflect.ownKeys除了可枚举属性还可以返回不可枚举的属性和es6的symbol属性
  Reflect.ownKeys(obj).forEach(key => {
    copy[key] = deepClone(obj[key], cache)
  })
  return copy
}

说到深拷贝,不得不提一下乞丐版JSON.stringfy

深拷贝
将一个对象从内存中完整地拷贝出来一份给目标对象,并从堆内存中开辟一个全新的空间存放新对象,且新对象的修改并不会改变原对象,二者实现真正的分离。

JSON.stringfy是目前开发过程中最简单的深拷贝方法,其实就是把一个对象序列化成为 JSON 的字符串,并将对象里面的内容转换成字符串,最后再用JSON.parse() 的方法将JSON 字符串生成一个新的对象。示例代码如下所示。

let obj1 = { a:1, b:[1,2,3] }
let str = JSON.stringify(obj1)let obj2 = JSON.parse(str);
console.log(obj2);   //{a:1,b:[1,2,3]}
obj1.a = 2;
obj1.b.push(4);
console.log(obj1);   //{a:2,b:[1,2,3,4]}
console.log(obj2);   //{a:1,b:[1,2,3]}

从上面的代码可以看到,通过 JSON.stringfy 可以初步实现一个对象的深拷贝,通过改变 obj1 的 b 属性,其实可以看出 obj2 这个对象也不受影响。
但是使用 JSON.stringfy 实现深拷贝还是有一些地方值得注意,如果你此时用上面代码示例的那个比较复杂的对象来测试,你就会发现问题,总结下来主要有这几点:

  • 拷贝的对象的值中如果有函数、undefined、symbol 这几种类型,经过 JSON.stringify 序列化之后的字符串中这个键值对会消失;
  • 拷贝 Date 引用类型会变成字符串;
  • 无法拷贝不可枚举的属性;
  • 无法拷贝对象的原型链;
  • 拷贝 RegExp 引用类型会变成空对象;
  • 对象中含有 NaN、Infinity 以及 -Infinity,JSON 序列化的结果会变成 null;
  • 无法拷贝对象的循环应用,即对象成环 (obj[key] = obj)。

所以在开发中,JSON.stringfy只能解决普通对象的深拷贝,如果对象中还包括更复杂的类型,这种方法是有缺陷的,这时候就需要自己去手写一个符合实际情况的深拷贝方法

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值