There is a type of code reuse pattern called "copying properties pattern". When we think of code reuse, we most likely think of code inheritance, but it's important to remember the ultimate goal - we want to reuse code. Inheritance is only a means to achieve code reuse, not the only way. Copying properties is also a reuse pattern, which is different from inheritance. In this pattern, an object will obtain members from another object by simply copying them. Anyone who has used jQuery knows that it has a $.extend() method. In addition to extending third-party plug-ins, it can also be used to copy attributes. Let’s take a look at the implementation code of an extend() function (note that this is not the source code of jQuery, just a simple example):
function extend(parent, child) { var i; //如果不传入第二参数child //那么就创建一个新的对象 child = child || {}; //遍历parent对象的所有属性 //并且过滤原型上的属性 //然后将自身属性复制到child对象上 for(i in parent) { if(parent.hasOwnProperty(i)) { child[i] = parent[i]; } } //返回目标对象child return child; }
The above code is a simple implementation, it only traverses the members of the parent object and copies them to the child object. Let’s test it using the extend() method above:
var dad = {name: "Adam"}; var kid = extend(dad); console.log(kid.name); //Adam
We found that the extend() method can already work normally. But there is a problem. What is given above is a so-called shallow clone. When using shallow copy, if the properties of the child object are changed, and the property happens to be an object, then this operation will also modify the parent object. In many cases, this is not the result we want. Consider the following:
var dad = { counts: [1, 2, 3], reads: {paper: true} }; var kid = extend(dad) //调用extend()方法将dad的属性复制到kid上面 kid.counts.push(4); //把4追加到kid.counts数组里面 console.log(dad.counts); //[1, 2, 3, 4]
Through the above example, we will find that after modifying the kid.counts attribute (adding element 4 to it), dad.counts will also be affected. This is because when using shallow copy, since the object is passed by reference, that is, kid.counts and dad.counts point to the same array (or in memory, they point to the same heap address).
Next, let us modify the extend() function to implement deep copying. What we need to do is to check each attribute of the parent object, and if the attribute happens to be an object, then recursively copy the attributes of the object. In addition, you also need to detect whether the object is an array. This is because the literal creation method of arrays is different from the literal creation method of objects. The former is [] and the latter is {}. To detect an array, you can use the Object.prototype.toString() method. If it is an array, it will return "[object Array]". Let’s take a look at the extend() function of the deep copy version:
function extendDeep(parent, child) { child = child || {}; for(var i in parent) { if(parent.hasOwnProperty(i)) { //检测当前属性是否为对象 if(typeof parent[i] === "object") { //如果当前属性为对象,还要检测它是否为数组 //这是因为数组的字面量表示和对象的字面量表示不同 //前者是[],而后者是{} child[i] = (Object.prototype.toString.call(parent[i]) === "[object Array]") ? [] : {}; //递归调用extend extendDeep(parent[i], child[i]); } else { child[i] = parent[i]; } } } return child; }
Okay, the deep copy function has been written. Let’s test it to see if it can work as expected, that is, whether deep copy can be achieved:
var dad = { counts: [1, 2, 3], reads: {paper: true} }; var kid = extendDeep(dad); //修改kid的counts属性和reads属性 kid.counts.push(4); kid.reads.paper = false; console.log(kid.counts); //[1, 2, 3, 4] console.log(kid.reads.paper); //false console.log(dad.counts); //[1, 2, 3] console.log(dad.reads.paper); //true
Through the above example, we can find that even if the child object's kid.counts and kid.reads are modified, the parent object's dad.counts and kid.reads have not changed, so our purpose has been achieved.
The following is a summary of the basic ideas for implementing deep copy:
1. Check whether the current attribute is an object
2. Because arrays are special objects, it is necessary to detect whether the attribute is an array if it is an object.
3. If it is an array, create an [] empty array, otherwise, create a {} empty object and assign it to the current property of the child object. Then, the extendDeep function is called recursively.
The above example uses a deep copy method implemented by ourselves using a recursive algorithm. In fact, the two methods provided by the new JSON object in ES5 can also achieve deep copying, namely JSON.stringify() and JSON.parse(); the former is used to convert the object into a string, and the latter is used to convert the string Convert to object. Below we use this method to implement a deep copy function:
function extendDeep(parent, child) { var i, proxy; proxy = JSON.stringify(parent); //把parent对象转换成字符串 proxy = JSON.parse(proxy) //把字符串转换成对象,这是parent的一个副本 child = child || {}; for(i in proxy) { if(proxy.hasOwnProperty(i)) { child[i] = proxy[i]; } } proxy = null; //因为proxy是中间对象,可以将它回收掉 return child; }
The following is a test example:
var dad = { counts: [1, 2, 3], reads: {paper: true} }; var kid = extendDeep(dad); //修改kid的counts属性和reads属性 kid.counts.push(4); kid.reads.paper = false; console.log(kid.counts); //[1, 2, 3, 4] console.log(kid.reads.paper); //false console.log(dad.counts); //[1, 2, 3] console.log(dad.reads.paper); //true
The test found that it also achieved deep copying. It is generally recommended to use the latter method, because JSON.parse and JSON.stringify are built-in functions and will be processed faster. In addition, the previous method uses recursive calls. We all know that recursion is a relatively inefficient algorithm.
This is all about the implementation method of JavaScript deep clone. I hope it will be helpful to you!