深浅拷贝(deep copy, shallow copy)
深浅拷贝的基础知识是关于值类型和引用类型的区分,可参考《JavaScript 数据类型(值类型/引用类型)》一文
(1)什么是拷贝?
注意:一定在内存中有两个数据副本才是拷贝
问题:
var num1 = 123;
var num2 = num1; // 这里内存中数据123有两个副本,因此是拷贝
var obj1 = { name: 'jim' };
var obj2 = obj1; //这里内存中数据只有一个,没有拷贝,因此这不是拷贝
(2)深拷贝
拷贝对象的所有数据,两个数据副本在内存中完全独立,就是深拷贝
(3)浅拷贝(相对复杂些)
拷贝的对象不完全,数据副本在内存中还有关联,就是浅拷贝
(4)理解深浅拷贝
深浅拷贝只有在对象含有引用类型的成员时才考虑。
function Person( name ) {
this.name = name;
this.copy = function () {
var tmp = new Person( this.name );
return tmp;
}
}
var p1 = new Person( '张三' );
var p2 = p1.copy();
// 这里没有深浅拷贝之分
浅拷贝:
// 浅拷贝
function Person( name ) {
this.name = name;
this.car = null;
this.shallowCopy = function () {
var tmp = new Person(); //拷贝出来的副本
for( var k in this ) {
tmp[ k ] = this[ k ]; // 如果是值类型, 就直接拷贝了, 如果是引用类型, 就没有拷贝
}
return tmp;
};
}
function Car ( name ) {
this.name = name;
}
var p1 = new Person( '李四' );
p1.car = new Car( '劳斯莱斯' );
var p2 = p1.shallowCopy();
深拷贝:
// 深拷贝
// 1> 首先有一个函数, 该函数的特点是 深拷贝一个对象, 并将对象的拷贝结果返回
// 2> 在函数内部实现算法, 首先准备一个对象
// 3> 遍历目标对象中的所有属性
// 4> 判断属性是否为值类型, 如果是值类型直接赋值
// 5> 如果是引用类型:
// -> 再准备一个对象
// -> 再遍历这个属性
// -> ...
// => 如果是引用类型的对象, 就调用一次自己这个方法
function deepCopyHandler( obj ) {
// 深拷贝 obj 返回新对象
var tmp = {};
for ( var k in obj ) {
if ( typeof obj[ k ] == 'object' ) {
// 深拷贝 obj[ k ],递归
tmp[ k ] = deepCopyHandler( obj[ k ] );
} else {
tmp[ k ] = obj[ k ];
}
}
return tmp;
}
function Person( name ) {
this.name = name;
this.car = null;
this.deepCopy = function () {
return deepCopyHandler( this );
};
}
function Car ( name ) {
this.name = name;
}
var p1 = new Person( '李四' );
p1.car = new Car( '劳斯莱斯' );
var p2 = p1.deepCopy();
(5)例:为了兼容ie8,使用递归的方法实现 getElementsByClassName方法
/**
* 递归查找有指定类名的元素(进阶方法)
*
* 可以不新建空数组,也不返回,而将空数组作为参数传入函数
* 且在一开始就限定必须传数组,否则就抛出异常
* 这样的好处是,省去了拼接数组的过程,因为传入的一直是同一个数组
*
* @param className 指定类名
* @param tag 总元素
* @returns {Array}
*/
function getByClass(className,arr,tag){
if(typeof arr == 'undefined' || typeof arr.push != 'function'){
throw new Error('传入参数不正确!');
}
tag = tag || document;//没传tag就用document元素
var nodes = tag.childNodes;//总元素下的所有子节点
/*遍历*/
for(var i=0; i<nodes.length; i++){
//找到元素节点
if(nodes[i].nodeType == 1){
//找到有类名且包含指定类名的元素
if(nodes[i].className && (' '+nodes[i].className+' ').indexOf(' '+className+' ') > -1){
arr.push(nodes[i]);//推进数组里
}
//递归,接着查下一层子元素,找到就推进数组中
getByClass(className,arr,nodes[i]);
}
}
}
//使用该方法
var divs = [];
getByClass('c',divs);//该函数没有返回值,只能执行后从参数中得到结果
alert(divs.length);
for(var i=0; i<divs.length; i++){
divs[i].style.borderColor = 'green';
}
(6)扩展:
深度拷贝对象时,类型不重要,方法是重要的
函数,就是应该共享
遍历的时候应该只考虑当前对象的成员,不考虑原型中的成员(用object.prototype中提供的hasOwnProperty来判断)
如果是数组或伪数组,最好不要一开始就创建{},而是创建[]较好