知识点总结
写一个深克隆的方法,我们需要掌握的知识点
- js的数据类型:原始类型,引用类型。
- js对象的理解。Date,ExpRep等内置对象的使用。
- 使用typeof、Object.prototype.toString进行类型判断。
- 闭包的应用。
- for in 语句。
js的数据类型
js的类型分两种
- 原始类型:Undefined、Null、Boolean、Number、String、Symbol
- 引用类型:Object 在引用类型中,有内置类型Array、Date、RegExp这样的内置类型,也有用户自己用构造函数创建的类型。
深克隆 vs 浅克隆
因为有了引用类型,才有了深克隆和浅克隆的区别。
浅克隆会对于原始类型,可以完全复制,对于引用类型,只会复制它的引用,对其内部的值,不会复制。
深克隆不论对于原始类型,还是引用类型,都会完全复制。生成一个跟原来的对象,完全没有关联的新对象。
浅克隆的实现
写一个最简单的实现,只考虑简单对象的浅克隆
function clone(value) {
var cloneValue = {};
for (var prop in value) {
cloneValue[prop] = value[prop];
}
return cloneValue;
}
深克隆的实现
写一个简单的实现,只考虑简单对象的深克隆
function clone(value) {
var cloneValue = {};
for (var key in value) {
if(typeof value[key] === 'object') {
cloneValue[key] = clone(value[key]);
} else {
cloneValue[key] = value[key];
}
}
return cloneValue;
}
在浅克隆的基础上,判断对象的属性值,是否还是个对象,如果是,调用clone,形成递归,我们的深克隆方法就实现啦。
目前这个方法还不完善,增加我们克隆对象的复杂度,不止是简单对象了,还要能克隆数组。
function type(value) {
return Object.prototype.toString.call(value).match(/\[object (.*?)\]/)[1];
}
function clone(value) {
var copyFunc = function(copiedValue){
for(var key in value) {
if(typeof value[key] === 'object') {
copiedValue[key] = clone(value[key]);
} else {
copiedValue[key] = value[key];
}
}
return copiedValue;
}
switch (type(value)) {
case 'Object': return copyFunc({});
case 'Array': return copyFunc([]);
default: return value;
}
}
这次我们除了clone方法,先写了一个type方法,利用Object.prototype.toString判断入参的类型。在clone方法里,我们将上次的clone方法变成一个闭包函数copyFunc,现在的clone方法,先判断入参是对象还是数组,再传入{}或者[]给copyFunc,利用for in语句既可以遍历对象,又可以遍历数组,完成克隆。
现在,我们考虑更多的可克隆对象,除了对象、数组,还有Date、RegExp、这样的内置对象。
function _cloneRegExp(pattern){
return new RegExp(pattern.source, (
(pattern.global ? 'g' : '') +
(pattern.ignoreCase ? 'i' : '') +
(pattern.multiline ? 'm' : '') +
(pattern.sticky ? 'y' : '') +
(pattern.unicode ? 'u' : '') +
));
}
function clone(value) {
var copyFunc = function(copiedValue){
for(var key in value) {
if(typeof value[key] === 'object') {
copiedValue[key] = clone(value[key]);
} else {
copiedValue[key] = value[key];
}
}
return copiedValue;
}
switch (type(value)) {
case 'Object': return copyFunc({});
case 'Array': return copyFunc([]);
case 'Date': return new Date(value.valueOf());
case 'RegExp': return _cloneRegExp(value);
default: return value;
}
}
只需在swith下添加两个case即可。写到这里,我们的克隆方法基本完毕了,可以说这个实现,是我目前看过最清晰的深克隆实现了。
再考虑一种情况,就是对象的多个属性,引用同一个对象多情况。我们希望保证克隆的对象,同样能保持多个属性引用的是同一个克隆出来的对象。
var childObj = { key: 1 };
var obj = {
key1:childObj,
key2:childObj,
}
比如上面这个obj对象,我们要克隆它,可以用下面的深克隆实现。
function clone(value, refFrom = [], refTo = []) {
var copyFunc = function(copiedValue){
var len = refFrom.length;
var idx = 0;
while (idx < len) {
if (value === refFrom[idx]) {
return refTo[idx];
}
idx += 1;
}
refFrom[idx + 1] = value;
refTo[idx + 1] = copiedValue;
for(var key in value) {
if(typeof value[key] === 'object') {
copiedValue[key] = clone(value[key], refFrom, refTo);
} else {
copiedValue[key] = value[key];
}
}
return copiedValue;
}
switch (type(value)) {
case 'Object': return copyFunc({});
case 'Array': return copyFunc([]);
case 'Date': return new Date(value.valueOf());
case 'RegExp': return _cloneRegExp(value);
default: return value;
}
}
这个版本的深克隆,就是ramda源码中对于克隆的实现,优雅又简洁。