在javascript,python这种带引用类型的高级程序设计语言中,很容易出现浅拷贝而导致错误。
本篇文章,我浅述一下我对js中深拷贝函数封装的思考。
思路很简单,封装一个递归函数,终止条件是‘不是引用类型’(不是对象)
如下所示
function deepCopy(src) {
if (!(src instanceof Object)) {
return src;
}
var dst = new Object();
for (let k in src) {
dst[k] = deepCopy(src[k]);
}
return dst;
}
其中在选取递归形式上也有一些考量。如果利用引用类型的特点不用返回值,直接改变,首先函数形式上,不太优雅,且到了非引用类型那一层的时候,又无法得到改变,形式不统一。故采用接收返回值的递归形式。
但有这样一个问题,因为数组是object,看似兼容了数组,其实由于数组的特殊性,这样做,返回的dst没有length属性,因为for...in数组不会得到'k',实验一下就知道了
var arr = [1,2,3];
var copy = deepCopy(arr);
console.log(arr);
console.log(copy);
浏览器中是这样的
如果你调试的话,你就能发现for...in并不想所想的那样,而是略过了length属性
这属于是array的特性了,但如果你dst那里new的是一个Array而非Object,for...in在读完属性名'0','1','2'后,同样略过length,但dst里会自动生成一个正确的length
那怎么办?...就在添加一个判断好了(这里我是很疑惑的,我不知道是否还有其它特殊的存在,这里我光发现了array这个特例)
function deepCopy(src) {
if (!(src instanceof Object)) {
return src;
}
else if (src instanceof Array) {
let dst = new Array();
for (let i = 0; i < src.length; i++) {
dst[i] = deepCopy(src[i]);
}
return dst;
}
else {
let dst = new Object();
for (let k in src) {
dst[k] = deepCopy(src[k]);
}
return dst;
}
}
其实还有这样么一个问题,或者说倾向于强迫症了,我举一个例子。
class Position {
constructor() {
this.x = x;
this.y = y;
}
}
var a = new Position(1, 2);
var b = deepCopy(a);
console.log(a);
console.log(b);
在浏览器上,结果是这样的。
问题在哪?b没有Position,确实,b的属性跟方法都跟a一样,但严格来说,b并不是Position类的对象,用js里的理论,就是b的构造函数的名字不叫Position。
这两个问题其实是一体的,可能有些类像array一样有特殊的行为,如果不是相同的类而泛指object,很可能一些属性是无法得到的,比如array的length,这种角度来说可能并不是'强迫症',而是避免某种'糟糕的未知的情况'
可能会想到修改函数名,但很遗憾,函数的name属性是read only(如果可能的话,很大可能上仅仅是解决强迫症罢了)
于是我这样修改这个deepCopy函数
function deepCopy(src) {
if (!(src instanceof Object)) {
return src;
}
var dst = new (eval(src.constructor.name))();
for (let k in src) {
dst[k] = deepCopy(src[k]);
}
return dst;
}
这样的确可以实现b是Position类的对象,并且数组的特殊情况也解决了,然而,这里也出现了一个问题。
拿刚才那个Position类继续说明,这要求Position类的构造函数是‘无参‘的......
然而很多情况下都不是无参,我继续思考,能否像c++一样,有default constructor?
很遗憾,js不支持函数重载,并且直截了当地告诉我,只能有一个constructor函数
为什么js这样设计?我想到一个可能的原因,js的函数是可变参,还记得arguments吗?
我可以这样写一个Position类
class Position {
constructor() {
this.x = arguments[0];
this.y = arguments[1];
}
}
var a = new Position();
console.log(a);
这样写也没问题
class Position {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
var a = new Position();
console.log(a);
它们都不会报错,x,y值是undefined,perfect!也就意味着构造函数'无参'并不重要,但你最好不要传数组等一些类型,并索引访问,be like
class Position {
constructor(pos) {
this.x = pos[0];
this.y = pos[1];
}
}
var a = new Position();
console.log(a);
这毫无疑问是会报错的
有了这种特性就很好办了,上面封装的函数应当是没有问题的
临近结尾,再贴一遍
function deepCopy(src) {
if (!(src instanceof Object)) {
return src;
}
var dst = new (eval(src.constructor.name))();
for (let k in src) {
dst[k] = deepCopy(src[k]);
}
return dst;
}
以上是我个人对JavaScript中深拷贝的一些思考,仅供个人记录,如有错误,还请斧正,也欢迎交流。