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

1.对象/数组深复制

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

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

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

  单层深复制:

1.js的slice函数:

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

  控制台输入:

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

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

  然后输入一下代码:

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

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

2.concat函数

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

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

b=a.concat([]);

  可以看到一样的结果。

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

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

  首先是jquery.1.11.0的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; // 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 = 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;
};

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

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

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

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

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

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

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

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

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

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

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

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

  多层深复制

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

function getType(o)
{
var _t;
return ((_t = typeof(o)) == "object" ? o==null && "null" || Object.prototype.toString.call(o).slice(8,-1):_t).toLowerCase();
}
function extend(destination,source)
{
for(var p in source)
{
if(getType(source[p])=="array"||getType(source[p])=="object")
{
destination[p]=getType(source[p])=="array"?[]:{};
arguments.callee(destination[p],source[p]); //递归调用在这里
}
else
{
destination[p]=source[p];
}
}
}

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

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

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

function newF(){
var a=0,
b=[5,[4,5]];
this.name="codetker";
this.a=a;
this.b=b;
}
var temp=new newF();
temp.a=5;
temp.b[1][0]=6;
var temp2=new newF();
temp2.a
temp2.b[1][0]

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

2.创建对象

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

  (摘自高级编程第三版)

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

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

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

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

function createPerson(name) {
var o = new Object();
o.name = name;
o.sayName = function() {
alert(this.name);
};
return o;
}
var person = createPerson('codetker');
person.sayName();

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

  3.构造函数模式:

function Person(name) {
this.name = name;
this.sayName = function() {
alert(this.name);
};
}
var person = new Person('codetker'); //能判断类型
person.sayName();

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

  4.原型模式:

function Person() {

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

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

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

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

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

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

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

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

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

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

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

function Person(name, age) { //每个实例都有自己的属性
this.name = name;
this.age = age;
this.friends = ['a', 'b'];
//方法
if (typeof this.sayName != 'function') {
Person.prototype.sayName = function() {
alert(this.name);
};
}
}
var person = new Person('codetker', 21);

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

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

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

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

3.继承

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

  1.原型链继承

function Super() {
this.property = true;
}
Super.prototype.getValue = function() {
return this.property;
}; function Sub() {
this.sub = false;
}
//继承,创建Super的实例,并将实例的原型赋给Sub的原型。即用Super的实例重写了Sub的原型对象
Sub.prototype = new Super();
//原型上添加方法(一定要放在替换原型的语句之后,不然就miss)
Sub.prototype.getSub = function() {
return this.sub;
};
//实例
var instance = new Sub();
console.log(instance.getValue());

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

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

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

  缺点:

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

  3.组合继承

function Super(name) {
this.color = ['red', 'blue'];
this.name = name;
this.sayName = function() {
console.log(this.name);
};
}
Super.prototype.say = function() {
console.log(this.name);
} function Sub(name2, age) {
//继承属性
Super.call(this, name2); //调用一次
this.age = age;
}
Sub.prototype = new Super(); //调用一次,继承方法
Sub.prototype.constructor = Super; var instance = new Sub('codetker', 21); //实例之间不冲突,拥有自己的属性
instance.say(); //OK now

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

  4.原型式继承

function object(Super) { //浅复制了Super
function F() {} //临时性构造函数
F.prototype = Super;
return new F();
}
var person = {
name: 'codetker',
friends: ['a', 'b']
};
var person2 = object(person);
person2.name = 'code';
person2.friends.push('c');
console.log(person2.name);
console.log(person.name); //name没变(基本类型),用于创建类似对象
console.log(person2.friends);
console.log(person.friends); //friends变了(引用类型)

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

  5.寄生式继承

function object(Super) { //浅复制了Super
function F() {} //临时性构造函数
F.prototype = Super;
return new F();
} function create(o) {
var clone = object(o); //通过调用函数创建一个对象
clone.sayHi = function() { //以某种方式来增强这个对象
alert('Hi!');
};
return clone;
}
var person = {
name: 'codetker',
friends: ['a', 'b']
};
var another = create(person);
another.sayHi();

  6.寄生组合式继承

//不必为了指定子类型的原型而调用超类型的构造函数(YUI.lang.extend()采用寄生组合继承)
function object(Super) { //浅复制了Super
function F() {} //临时性构造函数
F.prototype = Super;
return new F();
} function inheritPrototype(sub, super) {
var prototype = object(super.prototype); //创建对象,超类型原类型的副本
prototype.constructor = sub(); //增强对象,为副本添加constructor属性
sub.prototype = prototype; //指定对象,赋值
} function Super(name) {
this.color = ['red', 'blue'];
this.name = name;
this.sayName = function() {
console.log(this.name);
};
}
Super.prototype.say = function() {
console.log(this.name);
} function Sub(name2, age) {
//继承属性
Super.call(this, name2); //调用一次
this.age = age;
}
inheritPrototype(Sub, Super);
Sub.prototype.sayAge = function() {
alert(this.age);
};

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

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

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

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; // 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 = 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.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. <转>关闭 程序崩溃时 windows 正在检查该问题的解决方案

    本文转自:http://www.cnblogs.com/dabaopku/archive/2011/07/04/2097029.html 尤其是使用visual studio开发程序 ,自己特意thr ...

  2. Web Service 通过BinaryFormatter序列化和反序列化泛型List

    1.序列化和反序列化的扩展方法如下: using System; using System.Collections.Generic; using System.Linq; using System.T ...

  3. 【树莓派】基于TinyProxy搭建HTTP代理服务器

    一.前言 关于为什么要玩玩HTTP代理就不用我多说了.  二.搭建环境 * Linux laptop 2.6.32-45-generic #100-Ubuntu SMP Wed Nov 14 10:4 ...

  4. 学习mongo系列(十)MongoDB 备份(mongodump)与恢复(mongorerstore) 监控(mongostat mongotop)

    一.备份 在Mongodb中我们使用mongodump命令来备份MongoDB数据.该命令可以导出所有数据到指定目录中. mongodump命令可以通过参数指定导出的数据量级转存的服务器. mongo ...

  5. LINUX CP 跳过询问是否覆盖

    有两个方法可以解决此问题: 1..bashrc里面注释掉 Alias cp='cp -i' 2.使用 \cp 命令(在cp前加一个'\')

  6. D3.js 做一个简单的图表(条形图)

    柱形图是一种最简单的可视化图标,主要有矩形.文字标签.坐标轴组成. 本文为简单起见,只绘制矩形的部分,用以讲解如何使用 D3 在 SVG 画布中绘图. 一. 画布是什么 前几章的处理对象都是 HTML ...

  7. 0030 Linux 网络操作命令

    1. 主机是否可达 ping IP 2. 服务是否在运行 telnet IP port 3. 网络配置 ifconfig ip route arp 4. 网络访问 curl wget 5. 网络追踪 ...

  8. css学习笔记 1

    对于一个页面,如何控制页面的结构就看如何去理解css的各个属性了,只有了解了css的各个属性后才能更有效的让css控制页面的任何一个结构. css的结构:选择符:{属性名1:属性值; 属性名2:属性值 ...

  9. JavaWeb基础: ServletConfig

    基本概念 ServletConfig用于配置Servlet的参数:在Servlet的配置文件中,可以使用一个或者是多个<init-param> 标签为Servlet配置一些初始化参数.当有 ...

  10. SQLServer更新语句要注意

    在SQLServer中 update语句中对于表不能使用别名 eg:update table a set a.column="" where ... 这样在SQLServer中是不 ...