浅谈JavaScript中对象的复制和疑问

发布于 2019-04-22 08:52:02 字数 7420 浏览 1550 评论 0

有时候我们可能需要复制JavaScript中的对象,而复制对象这个操作并不难,但是其中却有很多却别,了解 C 语言的指针的朋友应该明白,一个对象可以有其它引用,而内存地址只有一个。

浅谈JavaScript中对象的复制和疑问

浅拷贝

当我们需要将一个对象拷贝至另一个对象时,我们一般会这么实现

function shadowCopy(source,target){
  var target=target||{};
  for(var i in source){
    target[i]=source[i];
  }
  return target;
}
var a={name:'Lily',age:19};
var b=shadowCopy(a);//b={name:'Lily',age:19}

浅拷贝的问题是,如果父对象的属性等于数组或另一个对象,实际上子对象获得的只是一个内存地址,而不是真正拷贝,父对象的数组或对象属性发生变化时,子对象对应属性也发生变化

function shadowCopy(source,target){
  var target=target || {};
  for(var i in source){
    target[i]=source[i];
  }
  return target;
}
var a={name:'Lily',Hobbies:['Music','Sport']};
var b=shadowCopy(a);//b={name:'Lily',Hobbies:['Music','Sport']}
a.Hobbies.push('Read');//b={name:'Lily',Hobbies:['Music','Sport','Read']}

深拷贝

为了解决上述问题,需要对对象的数组和对象属性进行深拷贝。它的实现并不难,只要递归调用"浅拷贝"就行了

function deepCopy(source,target){
  var target=target||{};
  for(var i in source){
    if(typeof source[i] === 'object'){
      target[i] = (source[i].constructor === Array ) ? [] : {} ;
      deepCopy(source[i],target[i]);
    }else{
      target[i]=source[i];
    }
  }
  return target;
}			 
var a={name:'Lily',Hobbies:['Music','Sport']};
var b=deepCopy(a);//b={name:'Lily',Hobbies:['Music','Sport']}
a.Hobbies.push('Read');//b={name:'Lily',Hobbies:['Music','Sport','Read']},b={name:'Lily',Hobbies:['Music','Sport']}

上述代码中有一个问题,当待拷贝对象中存在自引用时,程序会陷入无限循环

var a={name:'lily'};
a.obj=a;
deepCopy(a);

在 Chome Console 运行时,如下提示

RangeError: Maximum call stack size exceeded

为了解决自引用问题,拷贝时加入判断逻辑

function deepCopy(source,target){
  var target=target||{};
  for(var i in source){
        //防止自引用
    if(source[i] === source )
      continue;
    if(typeof source[i] === 'object'){
      target[i] = (source[i].constructor === Array ) ? [] : {} ;
      deepCopy(source[i],target[i]);
    }else{
      target[i]=source[i];
    }
  }
  return target;
}			 
var a={name:'lily'};
a.obj=a;
var b=deepCopy(a);//b={name:'lily'}

jQuery 拷贝实现

网上有很多对 jQuery extend 方法的分析,有不了解的可以去搜索阅读

贴一处被分析的源码

jQuery.extend = jQuery.fn.extend = function() {
  var src, copyIsArray, copy, name, options, clone,
    target = arguments[0] || {},
    i = 1,
    length = arguments.length,
    deep = false;
  // Handle a deep copy situation
  if ( typeof target === "boolean" ) {
    deep = target;
    target = arguments[1] || {};
    // skip the boolean and the target
    i = 2;
  }
  // 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 ( length === i ) {
    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 = jQuery.isArray(copy)) ) ) {
          if ( copyIsArray ) {
            copyIsArray = false;
            clone = src && jQuery.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;
};

jQuery 实现疑问

在阅读上述 jQuery 代码时,有个地方有疑问,疑问代码如下

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

注意到在 extend 方法中,为了防止无限循环,这里有一个逻辑,在 target 对象等于 copy 对象时,调过这次复制操作。其中 copy 对象为 options 对象的属性对象。

  • 这里为什么要拿 target 对象与 copy 对象比较呢?
  • 难道不应该是比较 copy 对象和 options 对象吗?

带着这个疑问,在一个已经引入了 jQuery 库的页面 Console 中执行下

var a={name:'lily'};
a.obj=a;
var b={};
$.extend(true,b,a);
RangeError: Maximum call stack size exceeded

可以看到,当 a 对象中存在自引用属性时,extend 方法并不能防止无限循环的发生

那么判断 target === copy 能起到什么作用呢?

var a={name:'lily'};
var b={age:19};
a.obj=b;
$.extend(true,b,a);
//此时b={age: 19, name: "lily"}

去掉判断 target === copy 会陷入无限循环吗?实际上是不会的

var a={name:'lili'};
var b={age:19};
a.obj=b;
deepCopy(true,b,a);
//b=Object {age: 19, name: "lili", obj: Object}
//其中Object为b

这里的 deepCopy 是我将 jQuery 的 extend 方法,去掉上述判断逻辑,自己实现了一份

function deepCopy() {
  var src, copyIsArray, copy, name, options, clone,
    target = arguments[0] || {},
    i = 1,
    length = arguments.length,
    deep = false;
  // Handle a deep copy situation
  if ( typeof target === "boolean" ) {
    deep = target;
    target = arguments[1] || {};
    // skip the boolean and the target
    i = 2;
  }
  // 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 ( length === i ) {
    target = this;
    --i;
  }
  for ( i = 0; 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 ( options === copy ) {
          continue;
        }
        */
        // Recurse if we're merging plain objects or arrays
        if ( deep && copy && ( isPlainObject(copy) || (copyIsArray =isArray(copy)) ) ) {
          if ( copyIsArray ) {
            copyIsArray = false;
            clone = src && isArray(src) ? src : [];
          } else {
            clone = src && isPlainObject(src) ? src : {};
          }
          // Never move original objects, clone them
          target[ name ] =deepCopy( deep, clone, copy );
        // Don't bring in undefined values
        } else if ( copy !== undefined ) {
          target[ name ] = copy;
        }
      }
    }
  }
  // Return the modified object
  return target;
};
var isString=function(obj){
  return Object.prototype.toString.call(obj) === '[object String]';
};
var isArray=function(obj){
  return Object.prototype.toString.call(obj) === '[object Array]';
};
var isPlainObject=function(obj){
  return Object.prototype.toString.call(obj) === '[object Object]';
}
var a={name:'lili'};
var b={age:19};
a.obj=b;
deepCopy(true,b,a);

所以这里是 jQuery extend 方法的实现 bug,还是我的理解有误呢?

搜到的一些对 jQuery extend 方法的源码分析,并没有看到这个疑问,也挺奇怪的。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

JSmiles

生命进入颠沛而奔忙的本质状态,并将以不断告别和相遇的陈旧方式继续下去。

文章
评论
84963 人气
更多

推荐作者

夢野间

文章 0 评论 0

doggiejohn

文章 0 评论 0

就此别过

文章 0 评论 0

初见终念

文章 0 评论 0

qq_rvKjBH

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文