也说Javascript对象拷贝及疑问

也说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 < 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方法的源码分析,并没有看到这个疑问,也挺奇怪的。

时间: 2024-12-29 11:19:47

也说Javascript对象拷贝及疑问的相关文章

Javascript对象拷贝(clone)

1. [代码]方法代码     function cp(source, target) {    function isBaseType(v) {        var type = typeof v;        var basetype = {            "string": true,            "number": true,            "boolean": true,            "

JavaScript 对象拷贝

JavaScript 对象拷贝 JavaScript 如何复制一个对象?浅拷贝可以复制出原始值属性,但是对于引用值属性仅仅复制了一份引用.利用递归对每个引用值属性的属性进行复制,这种方式称之为深拷贝 问题引入 var person1 = { name: '张三', age: "22" } var person2 = person1; 我们希望拷贝一份 person1 的属性给 person2,赋值是最简单的做法,但是这并不是我们想要的结果. 因为这仅仅是将 person1.person

javaScript| 对象的拷贝

上一遍是我们基本素组的拷贝,当然少不了我们对象的拷贝,当然也有我们的浅拷贝和我们的深拷贝滴啦: 然后,深拷贝,从某个角度来说就是我们对象的继承: 对象拷贝分为浅拷贝(shallow)和深拷贝(deep)两种.浅拷贝只复制一层对象的属性,并不会进行递归复制,而javascript存储对象都是存地址的,所以浅拷贝会导致对象中的子对象指向同一块内存地址:而深拷贝则不同,它不仅将原对象的各个属性逐个复制出去,而且将原对象各个属性所包含的对象也依次采用深拷贝的方法递归复制到新对象上 先补充一点基础知识,然

谈论JavaScript对象——个人总结

前言 疑惑.怀疑与思考 JavaScript到底是面向对象还是基于对象? 与其它的语言相比,JavaScript总是显得不那么合群.比如: 不同于其它的面向对象语言,JavaScript一直没有类的概念(ES6之前),ES6的到来也并没有改变它是基于原型的本质,这点是最让开发人员困惑的地方 _proto_ 和 prototype 傻傻分不清 对象可以是由 new 关键字实例化,也可以直接由花括号定义 JavaScript对象可以自由添加属性,而其他的语言不行 在被诟病和争论中,有人喊出JavaS

JavaScript大杂烩5 - JavaScript对象的若干问题

1. 类型检查:instanceof与typeof 这是两个相似的操作符,instanceof用于检测函数的实例类型,主要是在面向对象编程中检查new出来的对象类型,需要注意instanceof是检查function对象的,前面实现的复制继承中的例子就不适用于使用instanceof来检查继承关系了.typeof,它用于检测变量的类型,在实际情况中应用的不是很多,稍微了解一下就可以了. 在使用typeof之前,有一点需要确认,那就是string与String不是同一个类型,这个不用多说了,我们前

JavaScript大杂烩4 - 理解JavaScript对象的继承机制

面向对象之继承 JavaScript是单根的面向对象语言,它只有单一的根Object,所有的其他对象都是直接或者间接的从Object对象继承(没有指定父类的对象,都被认为是从Object继承的). 在前面我们讨论了面向对象的封装性,在最后的地方也谈到了JavaScript的继承是通过原型和原型链实现的,下面我们就详细的展开这个问题:JavaScript到底是如何实现继承的? 继承的本质 继承的本质是重用,从语法上来讲,继承就是"D是B"的描述,其中B是基类,描述共性,D是子类,描述特性

深入浅出 JavaScript 对象 v0.5

JavaScript 没有类的概念,因此它的对象与基于类的语言中的对象有所不同.笔者主要参考<JS 高级程序设计>.<JS 权威指南>和<JS 精粹> 本文由浅入深的讲解了对象的概念,特性,和使用,由于笔者水平的确有限,有些观点也是边理解,边查证,边分享. 希望大家都能感受到分享的乐趣,祝我们共同进步,请大家不吝交流. 目录 对象是什么? 对象有什么特性? 对象有什么用? 如何创建对象? 对象直接量 工厂方法创建对象 通过 new 创建对象 对象属性的查询与设置(检索与

JavaScript语言精粹读书笔记- JavaScript对象

JavaScript 对象 除了数字.字符串.布尔值.null.undefined(都不可变)这5种简单类型,其他都是对象. JavaScript中的对象是可变的键控集合(keyed collections). 对象是属性的容器,其中每个属性都拥有名字和值. JavaScript中的对象是无类别的(class-free)的.它对新属性的名字和值没有约束. JavaScript包括一个原型链特性,允许对象继承另一对象的属性. 对象的检索: stooge[“first-name”]或者stooge.

JSON不是JavaScript对象

一篇短小精悍的文章,我觉得不错就翻译了. 原文作者 Fizer Khan 原文地址 http://www.fizerkhan.com/blog/posts/JSON-is-not-Javascript-Object.html 很多人都把JSON当作JavaScript对象,但它根本不是.JSON只是一种灵感来自JavaScript对象结构的字符串表示法.JSON被设计得简单松散,用来在服务器和浏览器之间传输数据.因为它的简洁性,也在浏览器和服务器之外的其他应用中被使用. 我尝试拷贝一些JavaS