05月06, 2018

对象深拷贝&方法混合

Mixin模式可以实现多重继承,能够方便地复用代码、增强现有对象;文中特别针对对象方法的混合做了优化,使所有同名方法都能够执行。

深拷贝or浅拷贝?

数据类型

  1. 基本类型:Number、String、Boolean等;变量是直接按值存放的,存放在栈内存中的简单数据段,可以直接访问。
  2. 引用类型:Object、Array;变量保存的是一个指针,这个指针指向另一个位置。当需要访问引用类型(如对象,数组等)的值时,首先从栈中获得该对象的地址指针,然后再从堆内存中取得所需的数据。

拷贝仅针对引用类型数据

浅拷贝

const a = {
    name: 'Json',
    age: 18
}
const b = a;    // a仅将自己保存的内存地址给了b,因此b和a同时指向同一块内存空间

console.log(a.name);    // 'Json'
b.name = 'Table';
console.log(b.name);    // 'Table'
console.log(a.name);    // 'Table'
console.log(a === b);    // true

深拷贝?

const a = {
    name: 'Json',
    age: 18,
    children: {
        name: 'Hello',
        age: 1
    }
}
const b = {...a}    // 利用展开运算符将a展开,存入新内存中并将地址赋值给b

console.log(a.name);    // 'Json'
b.name = 'Table';
console.log(b.name);    // 'Table'
console.log(a.name);    // 'Json'
console.log(a === b);    // false
// 看起来很完美,修改b的属性并不会影响a,但是...

console.log(a.children.name);    // 'Hello'
b.children.name = 'Table';
console.log(b.children.name);    // 'Table'
console.log(a.children.name);    // 'Table'
console.log(a.children === b.children);    // true

类似对象展开和Object.assign()方法拷贝的只是属性值,假如源对象的属性值是一个指向对象的引用,它也只拷贝那个引用值。

深拷贝!

我们不仅需要将容器对象重新制定内存地址,如果其中含有引用类型的子属性我们也要对其进行处理

/**
 * 获取指定参数的原始类型
 * @param {Any} o 
 */
const getType = (o) => {
  let _t;

  return ((_t = typeof (o)) == "object" ? Object.prototype.toString.call(o).slice(8, -1) : _t).toLowerCase();
}

/**
 * 拷贝对象
 * @param {Object}
 */
const copyObj = (...arg) => {
  // 参数个数
  const length = arg.length;
  // 使用||运算符,排除隐式强制类型转换为false的数据类型
  // 如'', 0, undefined, null, false等
  // 如果target为以上的值,则设置target = {}
  let target = arg[0] || {};
  // 是否使用深拷贝
  let deep = false;
  let i = 1;
  let name;
  let options;
  let src;
  let copy;
  let copyIsArray;
  let clone;

  // 如果第一个参数的数据类型是Boolean类型,认为是设置拷贝模式
  // target往后取第二个参数
  if (getType(target) === 'boolean') {
    deep = target;

    target = arg[1] || {};
    i++;
  }

  // 如果target不是一个对象或数组或函数
  if (getType(target) !== 'object' && !(getType(target) === 'function')) {
    target = {};
  }

  // 如果arg.length === 1 或
  // typeof arg[0] === 'boolean',
  // 且存在arg[1],则直接返回target对象
  if (i === length) {
    return target;
  }

  // 循环每个源对象
  for (; i < length; i++) {
    // 如果传入的源对象是null或undefined
    // 则循环下一个源对象
    options = arg[i];
    if (typeof options != undefined) {
      // 遍历所有[[emuerable]] === true的源对象
      // 包括Object, Array, String
      // 如果遇到源对象的数据类型为Boolean, Number
      // for in循环会被跳过,不执行for in循环
      for (name in options) {
        // src用于判断target对象是否存在name属性
        src = target[name];
        // copy用于复制
        copy = options[name];
        // 判断copy是否是数组
        copyIsArray = Array.isArray(copy);
        if (deep && copy && (typeof copy === 'object' || copyIsArray)) {
          if (copyIsArray) {
            copyIsArray = false;
            // 如果目标对象存在name属性且是一个数组
            // 则使用目标对象的name属性,否则重新创建一个数组,用于复制
            clone = src && Array.isArray(src) ? src : [];
          } else {
            // 如果目标对象存在name属性且是一个对象
            // 则使用目标对象的name属性,否则重新创建一个对象,用于复制
            clone = src && typeof src === 'object' ? src : {};
          }
          // 深复制,所以递归调用copyObject函数
          // 返回值为target对象,即clone对象
          // copy是一个源对象
          target[name] = copyObj(deep, clone, copy);
        } else if (copy !== undefined) {
          // 浅复制,直接复制到target对象上
          target[name] = copy;
        }
      }
    }
  }
  // 返回目标对象
  return target;
}

混合

写过Vue的同学都知道其中有个比较高级的特性就是混入

但是有时我们可能想要将一些基础配置对象混入到其他地方,并且希望基础对象中的方法能够保留而不是覆盖。

方法混合

直接上优雅写法

Function.prototype.before = function (beforefn) {
  let _self = this;
  return function () {
    beforefn.apply(this, arguments);
    return _self.apply(this, arguments);
  }
}

Function.prototype.after = function (afterfn) {
  let _self = this;
  return function () {
    let ret = _self.apply(this, arguments);
    afterfn.apply(this, arguments);
    return ret;
  }
}

深拷贝 + 方法混合

有了方法混合,那么我们只需要在深拷贝中增加属性类型判断即可

const copyObjWithFunc = (...arg) => {
...
    if (deep && copy && (typeof copy === 'object' || copyIsArray)) {
        ...      
    } else if (getType(copy) === 'function' && getType(target[name]) === 'function') {
        // 函数复制,将copy方法追加在源方法后执行
        // 这里在执行或之后都可以
        target[name] = target[name].after(copy);
    } else if (copy !== undefined) {
        ...         
    }
...
}

结果

Jsfiddle

https://jsfiddle.net/guchong/jcwysoth/3/

参考

本文链接:http://guchongxi.com/post/merge-mixin.html

-- EOF --

Comments

评论加载中...

注:如果长时间无法加载,请针对 disq.us | disquscdn.com | disqus.com 启用代理。