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

js深拷贝 #30

Open
itboos opened this issue Aug 20, 2019 · 4 comments
Open

js深拷贝 #30

itboos opened this issue Aug 20, 2019 · 4 comments

Comments

@itboos
Copy link
Owner

itboos commented Aug 20, 2019

先附上结论:

 Object.assign(),  Array的 slice  concat 方法  对于引用类型数据都是浅拷贝
 JSON.strigify  JSON.parse 是深拷贝

此段代码来自

JSON.strigify和JSON.parse 🌰

    
    // 例1
    var source = { name:"source", child:{ name:"child" } } 
    var target = JSON.parse(JSON.stringify(source));
    target.name = "target";  //改变target的name属性
    console.log(source.name); //source 
    console.log(target.name); //target
    target.child.name = "target child"; //改变target的child 
    console.log(source.child.name); //child 
    console.log(target.child.name); //target child
    
    
    //例2
    var source = { name:function(){console.log(1);}, child:{ name:"child" } } 
    var target = JSON.parse(JSON.stringify(source));
    console.log(target.name); //undefined
    
    
    //例3
    var source = { name:function(){console.log(1);}, child:new RegExp("e") }
    var target = JSON.parse(JSON.stringify(source));
    console.log(target.name); //undefined
    console.log(target.child); //Object {}
    
    优点:
     /*
       * 1. 这两个方法是 ES5中引入的新的类型(支持的浏览器为IE8+) 兼容性较好
       * 2. 使用起来简单
       * 3. 可以满足基本的深拷贝需求,而且能够处理JSON格式能表示的所有数据类型
      */
    
     缺点:
      /*
       * 1. 不能拷贝方法,会直接忽略属性为方法的属性
       * 2. 不能拷贝正则,正则属性会变成一个空对象
       * 3. 如果元素是一个对象,则拷贝后此对象的__proto__ 上的constructor 属性会丢失,
       *  会变成 Object
       * 4. 无法处理对象存在循环引用的情况(JSON.parse方法对于存在循环引用的对象会报错,不能正确准换)
      */
@itboos
Copy link
Owner Author

itboos commented Aug 20, 2019

ramda 深拷贝的实现 方式

/**
 * Copies an object. 可以正确处理循环引用
 *
 * @param {*} value The value to be copied
 * @param {Array} refFrom Array containing the source references
 * @param {Array} refTo Array containing the copied source references
 * @param {Boolean} deep Whether or not to perform deep cloning.
 * @return {*} The copied value.
 */

// demo: 
// var obj1 = {
//   a: {
//     b: 1,
//   }
// }
// obj1.a.c = obj1
// const nobj = _clone(obj1, [], [], true)
// console.log(nobj)

// refFrom 的作用是 处理循环引用
// 具体来说,refFrom 会 保存每次_clone 传进来的 value 值,再克隆时,会先判断 属性值是否引用了之前的对象,如果是,则返回这个对象, 否则,进行下一步

export default function _clone(value, refFrom, refTo, deep) {
  var copy = function copy(copiedValue) {
    var len = refFrom.length;
    var idx = 0;
    while (idx < len) {
      if (value === refFrom[idx]) {
         return refTo[idx]; 
        // 这里需要注意下:返回的copyedValue 的引用, 而不是 refFrom[idx],不然就是旧对象的引用了
      }
      idx += 1;
    }
    // ramda 中写的的是 idx + 1 ,其实应该是 idx 才更合理
    refFrom[idx] = value;
    refTo[idx] = copiedValue;
    for (var key in value) {
      copiedValue[key] = deep ?
        _clone(value[key], refFrom, refTo, true) : value[key];
    }
    return copiedValue;
  };
  switch (type(value)) {
    case 'Object':  return copy({});
    case 'Array':   return copy([]);
    case 'Date':    return new Date(value.valueOf());
    case 'RegExp':  return _cloneRegExp(value);
    default:        return value;
  }
}


/**
 * Gives a single-word string description of the (native) type of a value,
 * returning such answers as 'Object', 'Number', 'Array', or 'Null'. Does not
 * attempt to distinguish user Object types any further, reporting them all as
 * 'Object'.
 *
 * @func
 * @memberOf R
 * @since v0.8.0
 * @category Type
 * @sig (* -> {*}) -> String
 * @param {*} val The value to test
 * @return {String}
 * @example
 *
 *      R.type({}); //=> "Object"
 *      R.type(1); //=> "Number"  "Object.prototype.toString.call(1) => "[object Number]"
 *      R.type(false); //=> "Boolean"
 *      R.type('s'); //=> "String"
 *      R.type(null); //=> "Null"
 *      R.type([]); //=> "Array"
 *      R.type(/[A-z]/); //=> "RegExp"
 *      R.type(() => {}); //=> "Function"
 *      R.type(undefined); //=> "Undefined"
 */

const type = function type(val) {
  // 单独处理 null 和 undefined 向前兼容 ECMAScript 5.1 版本之前的。
  return val === null
    ? 'Null'
    : val === undefined
      ? 'Undefined'
      : Object.prototype.toString.call(val).slice(8, -1); // 从第八位开始截取,截取到倒数负一位
}

/**
 * @desc clone正则表达式
 * @createDate 2019/08/13
 *  https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/RegExp
 *  new RegExp(pattern [, flags]) -> new RegExp('abc', 'gi') = /abc/gi
 *  RegExp(pattern [, flags])
 *  RegExp.prototype.source ->正则对象的源模式文本。
 *  /\w+abc/.source -> 'w+abc'
 */
const _cloneRegExp = function _cloneRegExp(pattern) {
  return new RegExp(pattern.source, (pattern.global     ? 'g' : '') +
                                    (pattern.ignoreCase ? 'i' : '') +
                                    (pattern.multiline  ? 'm' : '') +
                                    (pattern.sticky     ? 'y' : '') +
                                    (pattern.unicode    ? 'u' : ''));
}
  • 缺点 由于内部使用的是 for in 遍历对象,导致无法遍历 Symbol 属性。
  • 对于 Set, 和一些 更多的类型都是返回了 引用

@itboos
Copy link
Owner Author

itboos commented Aug 20, 2019

别的深拷贝方法实现

来自上面写的那篇博文的底部实现方法:

var types = "Array,Object,String,Date,RegExp,Function,Boolean,Number,Null,Undefined".split(",");
var $ = {}
for (let i = types.length; i--; ) {
  $["is" + types[i]] = str =>
    Object.prototype.toString.call(str).slice(8, -1) === types[i];
}


function copy(obj, deep = false, hash = new WeakMap()) {
   // 利用WeakMap 来处理循环引用
    if (hash.has(obj)) {
      return hash.get(obj);
    }
    if ($.isFunction(obj)) {
      return new Function("return " + obj.toString())();
    } else if (obj === null || typeof obj !== "object") {
      return obj;
    } else {
      var name,
        target = $.isArray(obj) ? [] : {},
        value;
      hash.set(obj, target);

      for (name in obj) {
        value = obj[name];

        if (deep) {
          if ($.isArray(value) || $.isObject(value)) {
            target[name] = copy(value, deep, hash);
          } else if ($.isFunction(value)) {
            target[name] = new Function("return " + value.toString())();
          } else {
            target[name] = value;
          }
        } else {
          target[name] = value;
        }
      }
      return target;
    }
}

// 上面的代码可以正确处理循环引用,但是,对于方法,感觉不应该克隆,而是应该想ramda 那样,
// 返回函数的引用,原因是 那种克隆方法会丢失函数的属性,闭包变量等。

@itboos
Copy link
Owner Author

itboos commented Aug 20, 2019

拷贝一个方法

function fn(a, b,c) {
  return a + b + c;
}

new Function("return " + fn.toString()) // 返回如下的代码:

ƒ anonymous() {
  return function fn(a, b, c) {
     return a + b + c;
   }
  }

// 所以,得到真正的方法需要调用这个匿名函数
new Function("return " + fn.toString())()

@itboos
Copy link
Owner Author

itboos commented Aug 25, 2019

lodash 中拷贝 Symbol 的方法

/** Used to convert symbols to primitives and strings. */
const symbolValueOf = Symbol.prototype.valueOf

/**
 * Creates a clone of the `symbol` object.
 *
 * @private
 * @param {Object} symbol The symbol object to clone.
 * @returns {Object} Returns the cloned symbol object.
 */
function cloneSymbol(symbol) {
  return Object(symbolValueOf.call(symbol))
}

export default cloneSymbol

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

No branches or pull requests

1 participant