JavaScript 克隆
克隆
浅层克隆
深层克隆
一个已有的对象obj,现在把对象obj克隆给一个空对象obj1
var obj = {
name : 'abc',
age : '123,',
sex : 'female'
}
var obj1 = {}
/**
* 写一个克隆方法clone(origin, target)
* origin Object 原始的对象
* target Object 模板对象
* */
function clone(origin ,target){
for(var prop in origin){ // 循环把origin上面所有的属性都拿出来
target[prop] = origin[prop]; // 每一次循环拿出的属性名origin[prop],让"target[prop] = origin[prop]"。
}
}
clone(obj ,obj1); // 克隆obj
console.log(obj1); // 打印obj1 {name: "abc", age: "123,", sex: "female"}这个方法再兼容一下(容个错)防止用户不传target。
如果用户传一个空对象就用户传的,如果没传就新建一个空对象,换句话说不传第二个参数也行。
function clone(origin ,target){
var target = target || {}; // 判断target如果传第二个参数就用传进来的,如果没传就自动生成一个空对象。
for(var prop in origin){
target[prop] = origin[prop];
}
return target; // 最后把克隆完的反回去
}
var obj = {
name : 'abc',
age : '123,',
sex : 'female'
}
var obj1 = clone(obj);
console.log(obj1); // {name: "abc", age: "123,", sex: "female"}有一个问题,这个克隆是浅层克隆,浅层克隆原始值克隆完后是没问题的。
浅层克隆
function clone(origin ,target){
var target = target || {};
for(var prop in origin){
target[prop] = origin[prop];
}
return target;
}
var obj = {
name : 'abc',
age : '123,',
sex : 'female',
}
var obj1 = clone(obj)
console.log(obj); // obj {name: "abc", age: "123,", sex: "female"}
console.log(obj1); // obj1 {name: "abc", age: "123,", sex: "female"}
obj.name = 'bcd'; // 现在让obj的name值改成bcd
console.log(obj1.name); // obj1的name没有变化还是abc,因为是原始值,原始值就是值的拷贝原始值就是值的拷贝,现在也让值拷贝,但是让引用值拷贝。
function clone(origin ,target){
var target = target || {};
for(var prop in origin){
target[prop] = origin[prop];
}
return target;
}
var obj = {
name : 'abc',
age : '123,',
sex : 'female',
car : ['visa','unionpay'] // 里面加一个数组
}
var obj1 = clone(obj);
console.log(obj1); // car1 ["visa", "unionpay"]
obj1.car.push('master'); // obj1.car是引用值,push进去一个'master'进去
console.log(obj1.car); // obj1.car肯定的会多一个 ["visa", "unionpay", "master"]
console.log(obj.car); // 但是obj.car也被变了 ["visa", "unionpay", "master"]因为是引用值,拷贝过去的是一个引用,两个人指向的是同一个房间,你改我也改。
现在我们想实现"深度克隆","深度拷贝"的意思是,我拿的和你的属性是一模一样的,但是就不是一个人。
你有一个引用值car里面放的是数组,拷贝过来之后我也有car数组,但是拷贝到我这之后,我改你是不会被更改的,两个人用的是两个房间。
深层克隆
浅度克隆非常简单,就是把A拷贝给B。深度克隆和浅度克隆的区别,深度克隆克隆后,不论是引用值还是原始值,都是各自独立的。
深度克隆和浅度克隆一样,也需要两个参数origin, targe
function deepClone(origin, target){
}对象obj作为原始对象,深度克隆,把obj拷贝给obj1。
var obj = {
name : 'abc',
age : 123,
card : ['vise', 'master'],
wife : {
name : "lili",
son : {
}
}
}先考虑一下深度克隆的步骤:
1. 遍历对象,判断是不是原始值
// 1). 首先把obj所有的属性值都挨个遍历一遍。
// 2). 每次遍历的时候判断一下,当前值是原始值还是引用值。
// 3). 是原值直接拷贝,原始值之间到拷贝是栈内存的拷贝,直接复制值过去。
// PS:引用值也是栈内存赋值,只不过复制的是一个地址,所以不行。
var obj1 = {
name : 'abc', // 原值直接拷贝
age : 123, // 原值直接拷贝
}2. card是引用值怎么办?
// 1). 判断card是不是原始值,不是原始值是引用值。
// 2). 引用值有两种,判断是数组还是对象
// 3). 如果是数组建立新数组,如果是对象建立新对象。
var obj1 = {
name : 'abc',
age : 123,
card : [], // car不原始值是引用值怎么办?判断是数组还是对象,是数组建立新数组、空数组
}3. 建立完相应的数组或对象后怎么办?
// 1). 然后,建立一个新的引用值之后,把原始的引用值当做被拷贝对象,把新的空数组当做拷贝对象,再一次进行深度拷贝。
// 2). 这样判断的过程就循环了,重新从开始的第一部开始走了。
// 3). 先判断数组里面第一位是不是引用值,如果是原始值直接拷贝,如果是引用值看看是那种类型,是数组还是对象,到这就开始循环了。
// 4). 数组里面都是原始值,直接拷贝
var obj1 = {
name : 'abc',
age : 123,
card : [obj.car[0], obj.car[1]], // 把原始的引用值当做被拷贝对象,把新的空数组当做拷贝对象,再一次进行深度拷贝。
} // 数组里面都是原始值,直接拷贝然后,看下一个wife是对象
// 1). 先判定是引用值,然后判定是对象,判定后建立一个空对象{}
// 2). 完后,做类似的wife对象到{}空对象的拷贝。
// 3). 紧接着,判定过程又循环了,看看wife对象里面的每一个值是不是原始值,
// 4). 第一个name是不是原始值,是原始值直接拷贝。
// 5). 第二个son不是原始值,建立新的对象,
// 6). 再把对象obj.wife.son到obj1.wife.son新建空对象的拷贝过程循环一次。
var obj1 = {
name : 'abc',
age : 123,
card : [obj.card[0], obj.card[1]],
wife : {} // 然后,看下一个wife是对象,建立一个空对象
}
var obj1 = {
name : 'abc',
age : 123,
card : [obj.card[0], obj.card[1]]],
wife : {
name : "bcd", // 第一个name是原始值直接拷贝。
}
}
var obj1 = {
name : 'abc',
age : 123,
card : [obj.card[0], obj.card[1]]],
wife : {
name : "bcd",
son : {} // 第二个son不是原始值,建立新的对象
}
}
var obj1 = {
name : 'abc',
age : 123,
card : [obj.card[0], obj.card[1]]],
wife : {
name : "bcd",
son : {
name : "lili", // 再把对象obj.wife.son到这个{}对象的拷贝过程循环一次。
}
}
}开始写代码:
遍历对象 for(var prop in obj)
1. 判断是不是原始值 用typeof()判断,如果结果是object,是引用值,如果结果不是,基本上是原始值
2. 判定是数组还是对象 instanceof、toString、constructor
3. 建立相应的数组或对象
主要来说就三步,三步之外就开始循环重复这三步了。
首先要便历对象,遍历对象用"for in"循环,"for in"除了遍历也能遍历数组。
// 数组没有属性名也能"for in"循环
var arr = ['a', 'b', 'c'];
for(var prop in arr){
console.log(prop); // prop代表索引位,依次代表0,1,2...
}数组也能"for in",因为数组也算特殊类型的对象。
原始值怎么判断?用typeof()判断,如果结果是object是引用值,如果结果不是,基本上是原始值。
console.log(typeof({})); // object
console.log(typeof([])); // object
var a = 'abc';
console.log(typeof(a)); // string数组还是对象怎办判断?有三种方法instanceof、toString、constructor。
Object.prototype.toString.call({}); // [object Object]
Object.prototype.toString.call([]); // [object Array]
/***
判断是数组还是对象的三种方法instanceof、toString、constructor,用的时候这三种都行,建议大家用toString。因为instanceof、constructor有个小问题,这个问题基本上一辈子也不会遇到但是确实有。
这个问题是,js学到后期会有父子域的问题,也就是说一个页面里未必只有一个页面,可能还有个子页面,比如"<iframe src="http://baidu.com"></iframe>"有一个父子域。
子域里面的一个数组,放到父域里面的Array,"[] instanceof Array",会打印false,正常来说是true,但父子域里面是false。
只有toString没有父子域的问题。
***/做预备工作
1). 先做一个容错"target = traget || {}",后期"或运算符"对值的应用基本上是这样。
2). 会用到"Objecte.prototype.toString()",每次写这么长太麻烦,把它引用出来,这个"toStr.call()"就代表引用了。用的时候toStr.call()就行了。
3). "Object.prototype.toString.call([])"得出的结果要经历比对,比如跟数组比对还是跟对象比对。跟数组比对出来的是"[object Array]",把"[object Array]"弄能字符串储存起来。
function deepClone(origin, target){
var target = traget || {},
toStr = Object.prototype.toString,
arrStr = "[object Array]";
}紧接着开始遍历
// 遍历对象origin
function deepClone(origin, target){
var target = traget || {},
toStr = Object.prototype.toString,
arrStr = "[object Array]";
for(var prop in origin){
if(origin.hasOwnProperty(prop)){
}
}
}第一步,判断是不是原始值
判断每一个值是不是原始值,判断之前但凡是"for in"循环,要跟一个"hasOwnProperty(prop)"判断一下,别把原型链上的东西拿下来。如果是true再进行下一步判断,如果不是true代表是原型上到属性,原型上的属性不拷贝。
判断是不是原始值,判断"typeof origin[prop]"(用方括号的形式)的结果,如果结果是"object"代表引用值,不是基本上是原始值,
是原始值直接赋值拷贝"target[prop] = origin[prop]",这一下就把原始值搞定了。null先不考虑最后再补充。
function deepClone(origin, target){
var target = traget || {},
toStr = Object.prototype.toString,
arrStr = "[object Array]";
for(var prop in origin){
if(origin.hasOwnProperty(prop)){
if(typeof origin[prop] == 'Object'){ // 判定是引用值
// 引用值...
}else{
target[prop] = origin[prop]; // 原始值直接搞定
}
}
}
}
// PS: hasOwnProperty()尽量写上,因为deepClone写的是一个方法的抽象,因为不知道未来传进来的对象是什么样的,所以尽量把方法写全。这是克隆方法的一个抽象,方法都是抽象来的,方法就是要应用于任何的情况。
// 未来传进来的对象有可能就有原型,所以hasOwnProperty()尽量得写(为了避免拿对象原型链上的属性)。
// 原始值上还有包装类,在包装类上建东西也能拿下来。接下来到引用值,第二步,第三步都是给引用值准备的。第二步判断是数组还是对象,第三步判断完建立相应的数组或对象。
已知"origin[prop]"已经是引用值了,判断是数组的引用值还是对象的引用值,怎么判断?用Object.prototype.toString一下已经写好了引用"toStr.call(origin[prop])"。
当返回值等于"[object Array]",是数组就建立一个"[]"空数组,是对象就建立一个空对象。在哪建立数组/对象?在"target[prop]"身上建立数组或对象。
function deepClone(origin, target){
var target = traget || {},
toStr = Object.prototype.toString,
arrStr = "[object Array]";
for(var prop in origin){
if(origin.hasOwnProperty(prop)){
if(typeof origin[prop] == 'object'){
if(toStr.call(origin[prop]) == arrStr){ // "origin[prop]"已经是引用值了,判断是数组的引用值,还是对象的引用值
target[prop] = []; // 建立数组
}else{
target[prop] = {}; // 建立对象
}
}else{
target[prop] = origin[prop];
}
}
}
}第三部完事了,然后递归。
递归的过程,是把已有的原始数组当做拷贝对象,把新建立的空数组当做被拷贝对象。origin[prop],target[prop]再来一遍这样的循环
function deepClone(origin, target){
var target = target || {},
toStr = Object.prototype.toString,
arrStr = "[object Array]";
for(var prop in origin){
if(origin.hasOwnProperty(prop)){
if(typeof origin[prop] == 'object'){
if(toStr.call(origin[prop]) == arrStr){
target[prop] = [];
}else{
target[prop] = {};
}
deepClone(origin[prop], target[prop]); // 递归,origin[prop],target[prop]再来一遍这样的循环。
}else{
target[prop] = origin[prop]; // 递归出口,当origin[prop]是原始值,就是出口了。
}
}
}
}试一下深层拷贝
function deepClone(origin, target){
var target = target || {},
toStr = Object.prototype.toString,
arrStr = "[object Array]";
for(var prop in origin){
if(origin.hasOwnProperty(prop)){
if(typeof origin[prop] == 'object'){
if(toStr.call(origin[prop]) == arrStr){
target[prop] = [];
}else{
target[prop] = {};
}
deepClone(origin[prop], target[prop]);
}else{
target[prop] = origin[prop];
}
}
}
}
var obj = {
name : 'abc',
age : 123,
card : ['vise', 'master'],
wife : {
name : "bcd",
son : {
name : "aaa"
}
}
}
var obj1 = {}
deepClone(obj, obj1);
console.log(obj1); // {name: "abc", age: 123, card: Array(2), wife: {…}}
console.log(obj); // {name: "abc", age: 123, card: Array(2), wife: {…}}
obj.card.push('abc'); // obj.card有两个值,再push一个abc进去
console.log(obj1.card); // obj1.card还是两个["vise", "master"],它两个互没影响,深层拷贝实现了。我的就是我的,你的就是你的,引用值也是你的就是你的,我的就是我的,完全分开了。这就是深度拷贝的一个过程。
再完稍微完善一下,如果没传target,要把target返回出去。还有null的问题,在那判断?
function deepClone(origin, target){
var target = target || {},
toStr = Object.prototype.toString,
arrStr = "[object Array]";
for(var prop in origin){
if(origin.hasOwnProperty(prop)){
// null的问题在这判断。
// 什么是引用值?不是null而且typof结果还是object。所以前面还要加一条typeof origin[prop] !== "null"绝对不等于"null",绝对不等于有隐式类型转换都不行。把后面两个条件叠加在一起。
if(typeof origin[prop] !== "null" && typeof origin[prop] == 'object'){
if(toStr.call(origin[prop]) == arrStr){
target[prop] = [];
}else{
target[prop] = {};
}
deepClone(origin[prop], target[prop]);
}else{
target[prop] = origin[prop];
}
}
}
return target; // 如果没传target,要把target返回出去。
}
var obj = {
name : 'abc',
age : 123,
card : ['vise', 'master'],
wife : {
name : "bcd",
son : {
name : "aaa"
}
}
}
var objCopy = deepClone(obj);
console.log(objCopy); // {name: "abc", age: 123, card: Array(2), wife: {…}}
console.log(obj); // {name: "abc", age: 123, card: Array(2), wife: {…}}
obj.card.push('abc'); // obj.card有两个值,再push一个abc进去
console.log(objCopy.card); // objCopy.card还是两个["vise", "master"]