你的浏览器不支持canvas

Enjoy life!

javascript - 深拷贝和浅拷贝

Date: Author: JM

本文章采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可。

一、什么是浅拷贝和深拷贝

  • 浅拷贝:
    • 浅拷贝就是指对象地址的复制,并没有开辟新的栈,即:复制的结果是原对象与浅拷贝的对象指向同一个地址。
    • 修改其中一个对象的属性,另外一个对象的这个属性也会跟随着改变
  • 深拷贝:
    • 深拷贝就是值开辟了新的栈,原对象与深拷贝的对象不是指向同一个地址。
    • 修改一个对象的属性,另外一个对象并不会随之发生改变

二、浅拷贝的实现

2.1 直接赋值

    let oObj = {
      name: 'jm',
      age: 18,
      hobbies: ['打篮球', '游泳']
    }

    // 直接赋值,浅拷贝
    let copy = oObj
    copy.hobbies.push('跳舞')
    console.log(oObj)
    console.log(copy)

2.2 Object.assign()

  • Object.assign() :实现的只是浅拷贝
let oObj = {
  name: 'jm',
  age: 18,
  hobbies: ['打篮球', '游泳']
}

// Object.assign() 浅拷贝
let copy = Object.assign({}, oObj)
copy.hobbies.push('跳舞')
console.log(oObj)
console.log(copy)

三、深拷贝的实现

3.1 递归实现

    let oObj = {
      name: 'jm',
      age: 18,
      hobbies: ['打篮球', '游泳'],
      friend: {
        name: 'lili',
        age: 18
      }
    }

    let cObj = {
      workPlace: ['佛山', '广州']
    }

    // 测试是否已成功深拷贝
    const result = deepCopy(oObj, cObj)
    result.hobbies.push('学习')
    console.log(result)
    console.log(oObj)

    /**
     * 深拷贝(递归实现)- 将原对象复制到目标对象上
     * @param originObj 原对象
     * @param copyObj 目标对象
     * @return {*|{}}
     */
    function deepCopy (originObj, targetObj) {
        let cObj = targetObj || {}
        for (var i in originObj) {
          const item = originObj[i]
          // 引用类型
          if (typeof item === 'object') {
            // 对象
            if (Object.prototype.toString.call(item) === '[object Object]') {
              cObj[i] = {}
            }
            // 数组
            else if (Object.prototype.toString.call(item) === '[object Array]') {
              cObj[i] = []
            }
            // 递归
            deepCopy(originObj[i], cObj[i])
          }
          // 基本类型
          else {
            cObj[i] = item
          }
        }
        return cObj
    }

copy

3.1 通过JSON解析解决

    let oObj = {
      name: 'jm',
      age: 18,
      hobbies: ['打篮球', '游泳'],
      friend: {
        name: 'lili',
        age: 18
      }
    }

    // 通过JSON解析,也可以实现深拷贝
    let copy = JSON.parse(JSON.stringify(oObj))
    copy.hobbies.push('跳舞')
    console.log(oObj)
    console.log(copy)

copy


  • 但是,如果值中出现undefined,那么将会解析“失败”
  • demo
    let oObj = {
      name: 'jm',
      age: 18,
      hobbies: ['打篮球', '游泳'],
      friend: undefined
    }

    // 通过JSON解析,也可以实现深拷贝
    let copy = JSON.parse(JSON.stringify(oObj))
    copy.hobbies.push('跳舞')
    console.log(oObj)
    console.log(copy)

copy

  • 从上图结果可知道,原对象中的friend设置为undefined,经过一系列的JSON解析后,你会发现深拷贝后的结果中friend这个属性没了!

四、jQuery.extend()

4.1 源码

jQuery.extend = jQuery.fn.extend = function() {
    var options, name, src, copy, copyIsArray, clone,
      target = arguments[ 0 ] || {},
      i = 1,
      length = arguments.length,
      deep = false;

    // Handle a deep copy situation
    if ( typeof target === "boolean" ) {
      deep = target;

      // Skip the boolean and the target
      target = arguments[ i ] || {};
      i++;
    }

    // Handle case when target is a string or something (possible in deep copy)
    if ( typeof target !== "object" && !jQuery.isFunction( target ) ) {
      target = {};
    }

    // Extend jQuery itself if only one argument is passed
    if ( i === length ) {
      target = this;
      i--;
    }

    for ( ; i < length; i++ ) {

      // Only deal with non-null/undefined values
      if ( ( options = arguments[ i ] ) != null ) {

        // Extend the base object
        for ( name in options ) {
          src = target[ name ];
          copy = options[ name ];

          // Prevent never-ending loop
          if ( target === copy ) {
            continue;
          }

          // Recurse if we're merging plain objects or arrays
          if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
              ( copyIsArray = Array.isArray( copy ) ) ) ) {

            if ( copyIsArray ) {
              copyIsArray = false;
              clone = src && Array.isArray( src ) ? src : [];

            } else {
              clone = src && jQuery.isPlainObject( src ) ? src : {};
            }

            // Never move original objects, clone them
            target[ name ] = jQuery.extend( deep, clone, copy );

            // Don't bring in undefined values
          } else if ( copy !== undefined ) {
            target[ name ] = copy;
          }
        }
      }
    }

    // Return the modified object
    return target;
  };

4.2 只传入一个对象

  • 只传入一个对象,本实例返回的是 window 对象,但也包含了fruits1 对象里有的属性和值
  • demo
var fruits1 = {
    apple: 0,
    banana: { weight: 52, price: 100 },
    cherry: 97
};

const fruits = extend(fruits1)
console.log(fruits)

copy

4.3 浅拷贝

  let fruits1 = {
    apple: 0,
    banana: { weight: 52, price: 100 },
    cherry: 97
  };

  let fruits2 = {
    orange: {},
    watermelon: {
      weight: 100,
      price: 10
    }
  }

 // 浅拷贝 
  extend(fruits1, fruits2)
  fruits1.orange.place = 'blue'
  console.log(fruits1, fruits2)

copy

4.4 深拷贝

  let fruits1 = {
    apple: 0,
    banana: { weight: 52, price: 100 },
    cherry: 97
  };

  let fruits2 = {
    orange: {},
    watermelon: {
      weight: 100,
      price: 10
    }
  }

  // 深拷贝
  extend(true, fruits1, fruits2)
  fruits1.orange.place = 'blue'
  console.log(fruits1, fruits2)

copy

4.4 深拷贝 - 有相同属性

  let fruits1 = {
    apple: 0,
    banana: { weight: 52, price: 100 },
    cherry: 97
  };

  let fruits2 = {
    apple: {
      weight: 100,
      price: 10
    },
    banana: {
      weight: 80
    }
  }

  // 深拷贝
  extend(true, fruits1, fruits2)
  console.log(fruits1)
  console.log(fruits2)

copy

4.6 分析

  • jQuery 的深拷贝和浅拷贝的封装的函数有以下几大特点(考虑十分全面):
    • 开启一个变量:用来规定是深拷贝还是浅拷贝
    • 在开始深拷贝或者浅拷贝前,都会做一大堆的判断,比如:判断这个是不是对象(非null),判断这个对象是不是数组等等
    • 整个深拷贝主要用了递归的思想,实在妙!

对于本文内容有问题或建议的小伙伴,欢迎在文章底部留言交流讨论。