Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

深浅拷贝 #12

Open
Genluo opened this issue Aug 31, 2019 · 0 comments
Open

深浅拷贝 #12

Genluo opened this issue Aug 31, 2019 · 0 comments

Comments

@Genluo
Copy link
Owner

Genluo commented Aug 31, 2019

基本类型: undefined, boolean,number,string,null,symbol

  • 基本类型存放在栈中
  • 基本数据类型值不可变(所以理解了所有的字符串方法都是返回一个新的字符串,并不会改变原有字符串)

引用类型:Object

  • 引用类型存放在堆内存中
  • 变量实际上存放的是一个指针
  • 传值和传址的区别

实现浅拷贝

  • 使用函数进行实现
function shadowClone(target) {
  const obj = {};
  for(var prop of target) {
    if (target.hasOwnProperty(prop)) {
      obj[prop] = target[prop];
    }
  }
  return obj;
}
  • 通过Object.assign实现上述问题

实现深拷贝:

  • 递归➕浅拷贝
function isRef(target) {
  const name = Object.prototype.toString.call(target);
  //  '[object Function]','[object Symbol]',
  const refName = [
    '[object Object]',
    '[object Array]',
  ]
  return refName.includes(name) && name;
}
function getRefName(target) {
  return Object.prototype.toString.call(target);
}

function deepClone(target) {
  let obj;
  const name = getRefName(target);
  switch(name) {
    case '[object Object]' : obj = {}; break;
    case '[object Array]' : obj = [];break;
    default : throw new Error();
  }
  for(var prop in target) {
    // 仅仅复制可枚举的自身属性
    if (target.hasOwnProperty(prop)) {
      if (isRef(target[prop])) {
        obj[prop] = deepClone(target[prop]);
      } else {
        obj[prop] = target[prop];
      }
    }
  }
  // 另一种复制递归方法
  // for (let prop of Object.keys(target)) {
  //   if (isRef(target[prop])) {
  //     obj[prop] = deepClone(target[prop]);
  //   } else {
  //     obj[prop] = target[prop];
  //   }
  // }
  return obj;
}
  • 使用JSON进行处理(内部使用原理同上)
// JSON实质上迭代和浅拷贝, 循环引用不会出问题,json做了检测
function jsonClone(target) {
  return JSON.parse(JSON.stringify(target));
}
  • Reflect方法(将一些明显属于语言内部的方法存放在Reflect对象上,现阶段,某些方法同时部署在Object和Reflect,未来新方法只部署在Reflect对象上)

问题:

  • 如何解决爆栈问题?

对于这个问题,一种方法是通过语言上的尾调用优化来解决,另一种就是迭代变为循环,对于这种方法存在两种不同的解决思路,一种就是写一个通用函数,可以将所有尾递归函数变为循环,另一种方式直接改动语言来实现,这种可以更加灵活的去解决遇见的其他问题。

通用递归变为循环的函数:

尾递归优化只在严格模式下生效,那么在正常模式下,或者在那些不支持该功能,就只有自己实现尾递归优化了。

// 注意理解思想
function tco(f) {
  var value;
  var active = false;
  var accumlated = [];

  return function accumlator() {
    accumlated.push(arguments);
    if (!active) {
      active = true;
      while (accumlated.length) {
        value = f.apply(this, accumlated.shift());
      }
      active = false;
      return value;
    }
  }
}
const sum  = tco(function(x, y) {
  if (y > 0) {
    return sum(x + 2, y - 1);
  } else {
    return x;
  }
})

将递归变为循环

function isRef(target) {
  const name = Object.prototype.toString.call(target);
  //  '[object Function]','[object Symbol]',
  const refName = [
    '[object Object]',
    '[object Array]',
  ]
  return refName.includes(name) && name;
}

function cloneLoop(target) {
  const result = {};
  const nodeList = [{
    parent: result,
    data: target,
  }]

  while(nodeList.length) {
    const { parent, data } = nodeList.pop();
    for(let prop of Object.keys(data)) {
      let name = isRef(data[prop]);
      // 如果不是引用类型
      if (!name) {
        parent[prop] = data[prop];
        continue;
      }
      // 如果是引用类型,初始化引用类型
      switch(name) {
        case '[object Object]' : parent[prop] = {};break;
        case '[object Array]' : parent[prop] = []; break;
        default : throw new Error('node');
      }
      // 生成下一个遍历的节点
      nodeList.push({
        parent: parent[prop],
        data: data[prop],
      })
    }
  }
  return result;
}
  • 如何解决引用丢失的问题?
  • 如何解决出现的循环引用问题?

上面这两个问题可以通过缓存来实现,我们将我们已经克隆的对象都缓存下来,并且和原对象连接起来,代码如下:

function find(list, node) {
  return list.filter(({ target, source}) => {
    if(node === source) {
      return true
    }
    return false;
  })
}
// 也可以使用hash(weakMap)进行替换链表
function cloneLoop(target) {
  const result = {};
  const uniqueList = [];
  const nodeList = [{
    parent: result,
    data: target,
  }];

  while(nodeList.length) {
    const { parent, data } = nodeList.pop();
    for(let prop of Object.keys(data)) {
      let name = isRef(data[prop]); // 属性名称
      // 如果不是引用类型
      if (!name) {
        parent[prop] = data[prop];
        continue;
      }
      let uniqueData = find(uniqueList, data[prop]);
      if (uniqueData.length) {
        parent[prop] = uniqueData[0].target;
        continue;
      }
      // 如果是引用类型,初始化引用类型
      switch(name) {
        case '[object Object]' : parent[prop] = {};break;
        case '[object Array]' : parent[prop] = []; break;
        default : throw new Error('node');
      }
      // 将新克隆的节点,传入uniqueList列表中
      uniqueList.push({
        source: data[prop],
        target: parent[prop],
      })
      // 生成下一个遍历的节点
      nodeList.push({
        parent: parent[prop],
        data: data[prop],
      })
    }
  }
  return result;
}
  • get,set方法的深度拷贝问题?

使用对象描述符进行进行深拷贝

  • 如果出现Promise、函数,Set类型、Map类型、WeakSet、WeakMap,RegExp等特殊对象应该怎么处理?
  • 属性为Symbol应该怎样进行复制
  • 拷贝一个自身可枚举、自身不可枚举、自身Symbol类型键、原型可以枚举、循环引用可以深拷贝的函数
  • 如何判断对象自身存循环引用

结构化克隆算法

结构化克隆算法是HTML5用于复制复杂javascript对象的算法,通过来workers的postMessage()或者使用IndexedDB存储对象时在内部使用,他通过递归输入对象来构造克隆,同时保证先前访问过引用的访问,以避免无限便利循环。

  • Error 以及 Function 对象是不能被结构化克隆算法复制的;如果你尝试这样子去做,这会导致抛出 DATA_CLONE_ERR 的异常。
  • 企图去克隆 DOM 节点同样会抛出 DATA_CLONE_ERROR 异常。
  • 对象的某些特定参数也不会被保留
  • RegExp 对象的 lastIndex 字段不会被保留
  • 属性描述符,setters 以及 getters(以及其他类似元数据的功能)同样不会被复制。例如,如果一个对象用属性描述符标记为 read-only,它将会被复制为 read-write,因为这是默认的情况下。
  • 原形链上的属性也不会被追踪以及复制。

开源库实现方式

  • Zepto实现如下
// 方法:用户合并一个或多个对象到第一个对象
// 参数:
// target 目标对象  对象都合并到target里
// source 合并对象
// deep 是否执行深度合并
function extend(target, source, deep) {
    for (key in source)
        if (deep && (isPlainObject(source[key]) || isArray(source[key]))) {
            if (isPlainObject(source[key]) && !isPlainObject(target[key])) {
                target[key] = {};
            }
            if (isArray(source[key]) && !isArray(target[key])){
                target[key] = [];
            }
            // 执行递归
            extend(target[key], source[key], deep)
        } else if (source[key] !== undefined) {
            target[key] = source[key];
        }
}

// Copy all but undefined properties from one or more
// objects to the `target` object.
$.extend = function(target){
    var deep, args = slice.call(arguments, 1);
    //第一个参数为boolean值时,表示是否深度合并
    if (typeof target == 'boolean') {
        deep = target;
        //target取第二个参数
        target = args.shift()
    }
    // 遍历后面的参数,都合并到target上
    args.forEach(function(arg){ extend(target, arg, deep) })
    return target
}
  • lodash 中的深拷贝

[源码地址](

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant