js 对象深复制,创建对象和继承。主要参考高级编程第三版,总结网上部分资料和自己的代码测试心得。每走一小步,就做一个小结。

1.对象/数组深复制

  一般的=号传递的都是对象/数组的引用,如在控制台输入

  1. var a=[1,2,3],
  2. b=a;
  3. b[0]=0;
  4. a[0]

  此时显示的结果为0,也就是说a和b指向的是同一个数组,只是名字不一样罢了。

  单层深复制:

1.js的slice函数:

  返回一个新的数组,包含下标从 start 到 end (不包括该元素,此参数可选)的元素。

  控制台输入:

  1. var a=[1,2,3],
  2. b=a.slice(0);
  3. b[0]=5;
  4. a

  返回的a并没有变,说明b是a的副本,修改副本对a没有影响。

  然后输入一下代码:

  1. var a=[[1,4],2,3],
  2. b=a.slice(0);
  3. b[0][1]=5;
  4. a

  可以看到a的值变了。说明slice函数只是单层复制。类似原型继承(见下文),基本类型的属性复制了(有自己的副本),引用类型的属性指向了同一个引用。

2.concat函数

  用于连接两个或多个数组。不会改变现有的数组,而仅仅会返回被连接数组的一个副本。

  同样用上面的例子测试,只是改动第二句

  1. b=a.concat([]);

  可以看到一样的结果。

3.c=$.extend({}, {}, b)  (jquery的extend方法)(此处不完整,见最后)

  1和2两个是百度上搜索的,自己验证了一下。第三个是看js OOP的时候忽然想到的,jq的extend是多层深复制么?

  首先是jquery.1.11.0的extend源码

  1. jQuery.extend = jQuery.fn.extend = function() {
  2. var src, copyIsArray, copy, name, options, clone,
  3. target = arguments[0] || {},
  4. i = 1,
  5. length = arguments.length,
  6. deep = false;
  7.  
  8. // Handle a deep copy situation
  9. if ( typeof target === "boolean" ) {
  10. deep = target;
  11.  
  12. // skip the boolean and the target
  13. target = arguments[ i ] || {};
  14. i++;
  15. }
  16.  
  17. // Handle case when target is a string or something (possible in deep copy)
  18. if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
  19. target = {};
  20. }
  21.  
  22. // extend jQuery itself if only one argument is passed
  23. if ( i === length ) {
  24. target = this;
  25. i--;
  26. }
  27.  
  28. for ( ; i < length; i++ ) {
  29. // Only deal with non-null/undefined values
  30. if ( (options = arguments[ i ]) != null ) {
  31. // Extend the base object
  32. for ( name in options ) {
  33. src = target[ name ];
  34. copy = options[ name ];
  35.  
  36. // Prevent never-ending loop
  37. if ( target === copy ) {
  38. continue;
  39. }
  40.  
  41. // Recurse if we're merging plain objects or arrays
  42. if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
  43. if ( copyIsArray ) {
  44. copyIsArray = false;
  45. clone = src && jQuery.isArray(src) ? src : [];
  46.  
  47. } else {
  48. clone = src && jQuery.isPlainObject(src) ? src : {};
  49. }
  50.  
  51. // Never move original objects, clone them
  52. target[ name ] = jQuery.extend( deep, clone, copy );
  53.  
  54. // Don't bring in undefined values
  55. } else if ( copy !== undefined ) {
  56. target[ name ] = copy;
  57. }
  58. }
  59. }
  60. }
  61.  
  62. // Return the modified object
  63. return target;
  64. };

  注意标红的那一句,继承基本对象,里面的实现是用in遍历属性,很明显如果是引用对象肯定也是复制引用了,并非深层对象的副本。我们来测试一下:

  1. var a=[1,2,3],
  2. b=[2,3,4],
  3. d=$.extend( a, b);
  4. d[0]=5;
  5. a

  其实此时返回的d就是a的别名,指向同一个数组。这句话只是让a去继承b的属性。于是我们可以变一下

  1. var a=[1,2,3],
  2. b=[2,3,4],
  3. e=$.extend({}, b);

  这时候的e也就是b的一个副本了,相当于用b的属性扩充了{},然后e指向扩充后了的{}。这样的话,一般插件里面用传递的参数覆盖默认的参数的写法

  1. c=$.extend({}, a, b);

  也就不难理解了,毕竟只是改了{},再次调用插件的时候里面的默认参数a还是没有变滴!

  接下来是重点,用二维数组测试

  1. var a=[[1,2],2,3],
  2. f=$.extend({}, a);
  3. f[0][0]=5;
  4. a[0]

  发现改变f[0][0],a[0][0]也跟着变了!如果extend有多个参数的时候,如

  1. var a=[[1,2],2,3],
  2. b=[[2,3,4],4,6],
  3. g=$.extend({}, a, b);
  4. g[0][0]=5;
  5. b[0]

  可以发现b跟着变了,而测试a可以看到a并没有变化。因此,这种方法写插件参数的时候,在插件里面对引用型参数的改变会反馈到传入的相应参数上,小伙伴们注意咯!(不过一般貌似也不会在里面改参数吧?)

  多层深复制

  1.网上摘录的代码,用递归实现层层基本类型属性的复制。

  1. function getType(o)
  2. {
  3. var _t;
  4. return ((_t = typeof(o)) == "object" ? o==null && "null" || Object.prototype.toString.call(o).slice(8,-1):_t).toLowerCase();
  5. }
  6. function extend(destination,source)
  7. {
  8. for(var p in source)
  9. {
  10. if(getType(source[p])=="array"||getType(source[p])=="object")
  11. {
  12. destination[p]=getType(source[p])=="array"?[]:{};
  13. arguments.callee(destination[p],source[p]); //递归调用在这里
  14. }
  15. else
  16. {
  17. destination[p]=source[p];
  18. }
  19. }
  20. }

  这个我在前面的AntSystem里面用过,确实写得简单易懂。

  2.使用new操作符,以构造函数的形式实现多层深复制

  不得不承认,new是一个很神奇的操作符,虽然这样做可能有些繁琐。

  1. function newF(){
  2. var a=0,
    b=[5,[4,5]];
  3. this.name="codetker";
  4. this.a=a;
  5. this.b=b;
  6. }
  7. var temp=new newF();
  8. temp.a=5;
  9. temp.b[1][0]=6;
  10. var temp2=new newF();
  11. temp2.a
  12. temp2.b[1][0]

  可以看到temp2的a和b[1][0]都没有被temp影响。好吧,我承认,其实这就是构造函数模式而已。管他呢,理解了,能用就行!

2.创建对象

  讨厌的设计模式来了。。。说不定什么时候能喜欢上这些呢?毕竟是前辈们的结晶。

  (摘自高级编程第三版)

  1.确定原型和实例的关系

  1. //b是a的原型
  2. a instanceof b
  3. b.prototype.isPrototypeOf(a)

  注意construtor针对的是构造函数。

  2.工厂模式:在内部创建对象

  1. function createPerson(name) {
  2. var o = new Object();
  3. o.name = name;
  4. o.sayName = function() {
  5. alert(this.name);
  6. };
  7. return o;
  8. }
  9. var person = createPerson('codetker');
  10. person.sayName();

  缺点:无法知道对象的类型。也就是1里面的判断为false

  3.构造函数模式:

  1. function Person(name) {
  2. this.name = name;
  3. this.sayName = function() {
  4. alert(this.name);
  5. };
  6. }
  7. var person = new Person('codetker'); //能判断类型
  8. person.sayName();

  缺点:实例会拥有多余的属性(每个实例均新创建一次所有方法)

  4.原型模式:

  1. function Person() {
  2.  
  3. }
  4. Person.prototype.name = 'codetker'; //将属性和方法都写在了构造函数的原型上
  5. Person.prototype.sayName = function() {
  6. alert(this.name);
  7. };
  8. var person = new Person(); //建立了实例和Person.prototype之间的连接(person._proto_ FF/Chrome/Safari or person.[[prototype]] in ES5)
  9. person.sayName();

  在这里面,可以用Person.prototype.isPrototypeOf(person) or Object.getPrototypeOf(person)==Person.prototype来确认是否为原型。用hasOwnProterty()判断对象实例属性和原型属性。

  in操作符可以在通过对象能够访问属性时返回true,因此结合property in object与hasOwnProperty(object,property)可以判断属性到底是存在于对象中,还是存在于原型中。如

  1. function hasPrototypeProperty(object,name){
  2. return !object.hasOwnProperty(name) && (name in object);
  3. }

  另外,对象的原型可以用对象字面量简写,如

  1. Person.prototype = {
  2. constructor: Person, //如果想用constructor的话
  3. name: 'codetker',
  4. sayName: function() {
  5. alert(this.name);
  6. }
  7. }; //相当于创建新对象,因此constructor不指向Person。如果在之前new一个实例,则实例取不到Person.prototype修改后的内容

  问题也来了,这样相对于重写了默认的prototype对象,因此constructor属性也变了。如果需要constructor,可以像上面手动设置一下,不过这样的constructor属性就会被默认为可枚举的。要改成一模一样,可以用Object.defineProperty方法。

  原型模式缺点:
  实例和构造函数没关系,而和原型有松散关系。但是前面的实例可能修改了原型导致后面的实例不好受。实例应该有属于自己的全部属性。

  5.组合使用构造函数模式和原型模式:分开写

  1. function Person(name, age) { //每个实例都有自己的属性
  2. this.name = name;
  3. this.age = age;
  4. this.friends = ['a', 'b'];
  5. }
  6. person.prototype = { //所有的实例共用原型的方法
  7. constructor: Person,
  8. sayName: function() {
  9. alert(this.name);
  10. }
  11. };
  12. var person = new Person('codetker', 21);
  13. //一般插件的形式

  6.动态原型模式:将所有信息封装在构造函数中,在构造函数中初始化原型

  1. function Person(name, age) { //每个实例都有自己的属性
  2. this.name = name;
  3. this.age = age;
  4. this.friends = ['a', 'b'];
  5. //方法
  6. if (typeof this.sayName != 'function') {
  7. Person.prototype.sayName = function() {
  8. alert(this.name);
  9. };
  10. }
  11. }
  12. var person = new Person('codetker', 21);

  7.寄生构造函数模式:在工厂模式的基础之上使用new,返回的对象在构造函数和构造函数的原型属性之间没有任何关系

  1. function createPerson(name) {
  2. var o = new Object();
  3. o.name = name;
  4. o.sayName = function() {
  5. alert(this.name);
  6. };
  7. return o;
  8. }
  9. var person =new createPerson('codetker');
  10. person.sayName();
  11. //person与Person以及Person.prototype之间没有联系,不能用instanceof判断对象类型

  8.稳妥构造函数模式:不使用new,不引用this,私有变量外部无法访问,仅暴露方法

  1. //应用于安全的环境中
  2. function createPerson(name) {
  3. var o = new Object();
  4.  
  5. //这儿可以定义私有变量
  6.  
  7. o.sayName = function() {
  8. alert(name);
  9. };
  10. return o;
  11. }
  12. var person =createPerson('codetker');
  13. person.sayName();
  14. //仅能通过sayName()方法访问
  15. //person与Person以及Person.prototype之间没有联系,不能用instanceof判断对象类型

3.继承

  听起来很高大上的样子!其实,,,还是挺高大上的。。。

  1.原型链继承

  1. function Super() {
  2. this.property = true;
  3. }
  4. Super.prototype.getValue = function() {
  5. return this.property;
  6. };
  7.  
  8. function Sub() {
  9. this.sub = false;
  10. }
  11. //继承,创建Super的实例,并将实例的原型赋给Sub的原型。即用Super的实例重写了Sub的原型对象
  12. Sub.prototype = new Super();
  13. //原型上添加方法(一定要放在替换原型的语句之后,不然就miss)
  14. Sub.prototype.getSub = function() {
  15. return this.sub;
  16. };
  17. //实例
  18. var instance = new Sub();
  19. console.log(instance.getValue());

  缺点:
  1.通过原型实现继承的时候,原型实际上会变成另一个类型的实例,于是原来的实例属性就变成了现在的原型属性了。即第二个实例会受到第一个实例的影响
  2.没有办法在不影响所有对象的情况下,给超类的构造函数传递参数

  2.借用构造函数实现继承(伪造对象/经典继承)

  1. function Super(name) {
  2. this.color = ['red', 'blue'];
  3. this.name = name;
  4. this.sayName = function() {
  5. console.log(this.name);
  6. };
  7. }
  8. Super.prototype.say = function() {
  9. console.log('not seen');
  10. }
  11.  
  12. function Sub(name2) {
  13. //继承了Super,在子类型构造函数的内部调用超类型的构造函数,从而执行了Super()中定义的初始化代码
  14. Super.call(this, name2); //可以在子类型的构造函数里面给超类传递参数
  15. }
  16.  
  17. var instance = new Sub('codetker'); //实例之间不冲突,拥有自己的属性
  18. console.log(instance.color);
  19. console.log(instance.name);
  20. instance.sayName();
  21. instance.say(); //not a function

  缺点:

  类似构造函数的问题,方法都在构造函数中定义,外面无法定义
  超类中原型定义的方法,对子类型都不可见

  3.组合继承

  1. function Super(name) {
  2. this.color = ['red', 'blue'];
  3. this.name = name;
  4. this.sayName = function() {
  5. console.log(this.name);
  6. };
  7. }
  8. Super.prototype.say = function() {
  9. console.log(this.name);
  10. }
  11.  
  12. function Sub(name2, age) {
  13. //继承属性
  14. Super.call(this, name2); //调用一次
  15. this.age = age;
  16. }
  17. Sub.prototype = new Super(); //调用一次,继承方法
  18. Sub.prototype.constructor = Super;
  19.  
  20. var instance = new Sub('codetker', 21); //实例之间不冲突,拥有自己的属性
  21. instance.say(); //OK now

  缺点:无论什么情况下,都会调用两次超类型构造函数

  4.原型式继承

  1. function object(Super) { //浅复制了Super
  2. function F() {} //临时性构造函数
  3. F.prototype = Super;
  4. return new F();
  5. }
  6. var person = {
  7. name: 'codetker',
  8. friends: ['a', 'b']
  9. };
  10. var person2 = object(person);
  11. person2.name = 'code';
  12. person2.friends.push('c');
  13. console.log(person2.name);
  14. console.log(person.name); //name没变(基本类型),用于创建类似对象
  15. console.log(person2.friends);
  16. console.log(person.friends); //friends变了(引用类型)

  ES5用Object.create()方法规范化了原型继承,只有一个参数的时候同object(),而两个参数的时候后面的参数为传入的属性,如Object.create(person,{name:{value:'TK'}});

  5.寄生式继承

  1. function object(Super) { //浅复制了Super
  2. function F() {} //临时性构造函数
  3. F.prototype = Super;
  4. return new F();
  5. }
  6.  
  7. function create(o) {
  8. var clone = object(o); //通过调用函数创建一个对象
  9. clone.sayHi = function() { //以某种方式来增强这个对象
  10. alert('Hi!');
  11. };
  12. return clone;
  13. }
  14. var person = {
  15. name: 'codetker',
  16. friends: ['a', 'b']
  17. };
  18. var another = create(person);
  19. another.sayHi();

  6.寄生组合式继承

  1. //不必为了指定子类型的原型而调用超类型的构造函数(YUI.lang.extend()采用寄生组合继承)
  2. function object(Super) { //浅复制了Super
  3. function F() {} //临时性构造函数
  4. F.prototype = Super;
  5. return new F();
  6. }
  7.  
  8. function inheritPrototype(sub, super) {
  9. var prototype = object(super.prototype); //创建对象,超类型原类型的副本
  10. prototype.constructor = sub(); //增强对象,为副本添加constructor属性
  11. sub.prototype = prototype; //指定对象,赋值
  12. }
  13.  
  14. function Super(name) {
  15. this.color = ['red', 'blue'];
  16. this.name = name;
  17. this.sayName = function() {
  18. console.log(this.name);
  19. };
  20. }
  21. Super.prototype.say = function() {
  22. console.log(this.name);
  23. }
  24.  
  25. function Sub(name2, age) {
  26. //继承属性
  27. Super.call(this, name2); //调用一次
  28. this.age = age;
  29. }
  30. inheritPrototype(Sub, Super);
  31. Sub.prototype.sayAge = function() {
  32. alert(this.age);
  33. };

  感觉内容不少,完全属于自己的却不多。。。不过高级编程第三版确实讲得很详细,且做分享吧~

-------------------------------------------------  补充  -------------------------------------------------------

  刚刚看了评论,发现自己着实太粗心了。。。看代码看一半就自以为是了,对不住大家哈。按照评论里的内容,重新解读一下源码:

  1. jQuery.extend = jQuery.fn.extend = function() {
  2. var src, copyIsArray, copy, name, options, clone,
  3. target = arguments[0] || {}, //这里说明第一个参数其实空着也是可以的
  4. i = 1,
  5. length = arguments.length,
  6. deep = false; //多层深复制的参数在这里
  7.  
  8. // Handle a deep copy situation
  9. if ( typeof target === "boolean" ) {
  10. deep = target;
  11.  
  12. // skip the boolean and the target
  13. target = arguments[ i ] || {};
  14. i++;
  15. }
  16.  
  17. // Handle case when target is a string or something (possible in deep copy)
  18. if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
  19. target = {};
  20. }
  21.  
  22. // extend jQuery itself if only one argument is passed
  23. if ( i === length ) {
  24. target = this;
  25. i--;
  26. }
  27.  
  28. for ( ; i < length; i++ ) {
  29. // Only deal with non-null/undefined values
  30. if ( (options = arguments[ i ]) != null ) {
  31. // Extend the base object
  32. for ( name in options ) {
  33. src = target[ name ];
  34. copy = options[ name ];
  35.  
  36. // Prevent never-ending loop
  37. if ( target === copy ) {
  38. continue;
  39. }
  40.  
  41. // Recurse if we're merging plain objects or arrays
  42. if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
  43. if ( copyIsArray ) {
  44. copyIsArray = false;
  45. clone = src && jQuery.isArray(src) ? src : [];
  46.  
  47. } else {
  48. clone = src && jQuery.isPlainObject(src) ? src : {};
  49. }
  50.  
  51. // Never move original objects, clone them
  52. target[ name ] = jQuery.extend( deep, clone, copy ); //多层深复制的递归调用在这里
  53.  
  54. // Don't bring in undefined values
  55. } else if ( copy !== undefined ) {
  56. target[ name ] = copy;
  57. }
  58. }
  59. }
  60. }
  61.  
  62. // Return the modified object
  63. return target;
  64. };

  仔细看jquery.extend()方法的源码,会看到我们这些使用者和库创建者的差距。首先代码的注释已经相当完整了,然后对object,array引用类型的处理,对string等基本类型的处理,对多参数的处理,用isPlainObject()判断是否为纯粹的对象,用isArray()判断是否为数组,涵盖了所有能想到的情况,最后deep参数可选的设置,判断要复制的属性是否为undefined未定义类型,无论是在设计上,代码排版上都可圈可点,值得借鉴。山高人为峰,学习之路漫漫~

  最后来看一下我的错误,注意红色的标记,是判断是否多层深复制的关键。在判断条件中,首先是可选参数deep的设置是否为true,然后是要复制的属性是否存在,最后是属性的类型是否是对象或者数组,满足三者才能递归调用以实现多层深复制。再来改一下上面的例子,可见如下

  当$.extend()的agruments[0]为true时,会实现对象的多层深复制!

  最后谢谢@笨雷雷的评论,帮我找出了自己的粗心坏毛病。欢迎大家对我的部落格提出问题,一起分享,共同进步~

js 对象深复制,创建对象和继承的更多相关文章

  1. JavaScript对象深复制

    1.原理 使用JSON,当然需要JSON安全的格式,JSON安全请参考:http://www.cnblogs.com/mengfangui/p/8257269.html 2.示例 <!DOCTY ...

  2. java对象深复制、浅复制(深拷贝、浅拷贝)的理解

    先看一个例子 User user1 = new User(); user1.setId("111"); Map<String, User> map1 = new Has ...

  3. js的深复制与浅复制

    什么是深复制和浅复制? 深复制和浅复制的概念只存在于对象array和数组obj上. 浅复制是:模糊复制,就是不管对方是字符串类型还是引用类型都通通复制过来.结果两个变量的内容会同时变化. 深复制是:有 ...

  4. js对象的复制,传递,新增,删除和比较

    当我们把一个某个对象拷贝或者传递给某个函数时,往往传递的是该对象的引用. 因此我们在引用上做的任何改动,都将会影响到它所引用的原对象.  复制,拷贝  var o = { add: 'Changdao ...

  5. js对象深潜拷贝(从requirejs中抠出来的)

    var op = Object.prototype, ostring = op.toString, hasOwn = op.hasOwnProperty; function isFunction(it ...

  6. js 对象属性复制到另一个对象

    var obj={a:1,b:2,c:3} var newObj={};for(var i in obj){newObj[i]=obj[i];}console.log(newObj);

  7. C# 对象深复制

    Mark: //实现IClonable接口并重写Clone方法就可以实现深克隆了 #region ICloneable 成员 public object Clone() { MemoryStream ...

  8. c#对象深复制demo

    public class Person : ICloneable { public string Name; object ICloneable.Clone() { return this.Clone ...

  9. 原生js实现深复制

    function deepClone (obj) { if (obj === null) { // 如果是null则直接返回 return obj; } let copy = Array.isArra ...

随机推荐

  1. 不同系统下的回车\r和换行\n,及其历史

    我们平时按下键盘上的‘回车键’,就能实现回车换行[我们在屏幕上所看到的就是光标移到了下一行的开头位置!!ps:不讨论软件实现的特殊功能,如word里的回车智能缩进].因此对这个按键更准确说应该叫做‘回 ...

  2. python学习之while语句

    while循环 1.简单的while循环while True: ")#这是一个简单的while循环,当等于True时会一直打印1 2.while执行多少次后退出 coun=0while Tr ...

  3. Shell基本功能&配置&Oh-My-Zsh

    http://blog.csdn.net/yangcs2009/article/details/45720193

  4. Android自定义View自定义属性

    1.引言 对于自定义属性,大家肯定都不陌生,遵循以下几步,就可以实现: 自定义一个CustomView(extends View )类 编写values/attrs.xml,在其中编写styleabl ...

  5. 車(rook)

    [题目分析] JustPenz:我们假设n>m(不大于就交换),那最多能摆m个,所以会有(n-m)个空白,我们把这些空白插入到n中,答案就是C(n,n-m)=C(n,m);数据范围比较大,我们就 ...

  6. [问题2015S04] 复旦高等代数 II(14级)每周一题(第五教学周)

    [问题2015S04] 设 \(A\) 为 \(n\) 阶方阵, \(C\) 为 \(k\times n\) 矩阵, 且对任意的 \(\lambda\in\mathbb{C}\), \(\begin{ ...

  7. [问题2014S01] 复旦高等代数II(13级)每周一题(第一教学周)

    问题2014S01  设 \(f(x_1,x_2,\cdots,x_n)\) 是次数等于 2 的 \(n\) 元实系数多项式, \(S\) 是使得 \(f(x_1,x_2,\cdots,x_n)\) ...

  8. Oracle介绍(初学者必须知道的)

    1.为什么学习数据库?(两个概念) 数据库的概念: 数据库是按照数据结构组织,存储和管理数据的仓库. 数据库,简单来说是本身可视为电子化的文件柜——存储电子文件的处所,用户可以对文件中的数据进行新增. ...

  9. linux显示-bash-4.2# 问题

    今天,安装配置完mysql后,重新连接的shell的时候显示的不是root@localhost # 了,而是显示的-bash-4.2# 提示信息: Last login: Tue Apr 5 00:3 ...

  10. pycharm的一些设置和快捷键

    最近在搞python的开发,用上了pycharm,所以记录一些pycharm的设置 1. pycharm默认是自动保存的,但我个人不太习惯,习惯自己按ctrl + s  所以进行如下设置: 1. Se ...