之前学编译原理课的时候要做课设,老师说"不限制语言"。
虽然我知道他的意思是可以用 python/Java/C
但是还是头铁的用了 JavaScript,尽管知道这个是一门弱定义语言以及 JavaScript 不能严格的算是面向对象语言以及知道 JavaScript 独特的内存管理方式和其他的也不太一样。
所以最后还是用了 python 。尽管为了避免弱定义这个特点带来的副作用产生了很多额外的代码,但是还是放弃了,因为实在不知道该怎么在好几层对象嵌套里解决拷贝的问题。T^T
但是工作还是要找的学习还是不能停的,来补一补当时没有跳过去的坑。
shallow copy
当然是先容简单的来啦!
数组
直接赋值
第一个方式很简单,直接赋值就好了。
1 | let foo = [1,2,3] |
Array.prototpye.slice()
slice() 方法返回一个新的数组对象,这一对象是一个由 begin 和 end 决定的原数组的浅拷贝(包括 begin,不包括end)。原始数组不会被改变。 – MDN
1 | const foo = [{key:1,},{key,2}] |
slice() 只在第一层复制出新的 Array , 但是仅停留在第一层。没有递归每一个元素,所以依旧只能算浅拷贝。
slice([begin[, end]]) 是截取一个数组的一部分返回一个浅拷贝的新数组的函数(包括 begin 不包括 end),有两个可选参数
- begin。
省略时默认为 0;
负数时同 python 里一样,表示从倒数第几个开始提取;
大于原数组长度时,返回空数组 - end。
省略时提取到原数组末尾;
大于原数组长度时也会提取到末尾;
负数时表示提取到倒数第几个为止
Array.prototype.concat()
和 slice() 相反,concat() 用于合并两个数组,返回一个新的数组。
var new_array = old_array.concat(value1[, value2[, …[, valueN]]])
1 | let a = [1, 2, 3] |
concat() 也是浅拷贝。
1 | let foo = [1, 2, [3, 4]] |
Array.from()
from() 方法与前面提到的 slice() 和 concat() 不同,是 Array 构造函数的属性方法,而不是 Array 构造函数的原型上的方法。
不被 IE 支持
Array.from() 方法从一个类似数组或可迭代对象中创建一个新的,浅拷贝的数组实例。
1 | console.log(Array.from('foo')); |
Array.from(arrayLike[, mapFn[, thisArg]])
- arrayLike
转成数组的伪数组对象or可迭代对象。 - mapFn
新数组中的每个元素会执行该回调函数 - 执行回调时的 this 对象。
1 | let a = [1, 2, [2, 3]] |
对象
直接赋值
1 | let obj = {one:1,tow:2} |
Object.assign()
Object.assign(target, …sources)
把源对象(sources)复制到目标对象(target)
1 | let target = {} |
tips
- 继承属性和不可枚举属性是不能拷贝的
- null 和 undefined 不是对象,作为 sources 会被忽略,可作为 target
- 原始类型会被包装成对象。
拓展运算符
其实一直有一段时间以为拓展运算符是deep copy 来着,流下了无知的泪水
拓展运算符也可以操作 Object 也可以操作 Array,所以单独拿出来了。
1 | let a = [1, 2, [2, 3]] |
1 | let bar = {one:1,tow:{key:'123'}} |
其实都是差不多,只进行了一层拷贝。
deep copy
MDN 推荐使用 JSON.parse(JSON.stringify(object)) 这一套 combo 。
JSON.parse(JSON.stringify(object))
常用, 但是也有缺陷。
1 | // Deep Clone |
1 | let arr = [1, 3, { |
最明显的缺陷是会忽视值为 undefined 的属性 / 值为函数的属性
1 | let foo = {one:1,two:undefined, three: function(){console.log(3)}} |
其他缺陷: 参考搞不懂JS中赋值·浅拷贝·深拷贝的请看这里
拷贝的对象的值中如果有symbol则经过JSON.stringify()序列化后的JSON字符串中这个键值对会消失
无法拷贝不可枚举的属性和拷贝对象的原型链
拷贝Date引用类型会变成字符串
拷贝RegExp引用类型会变成空对象
对象中含有NaN、Infinity和-Infinity,则序列化的结果会变成null
无法拷贝对象的循环应用(即obj[key] = obj)
递归函数
手撕一个递归函数来实现每一层的拷贝来实现 deep copy。
其实也很简单啦,就一个递归函数,遇到是对象的属性就进入递归。
1 | //定义检测数据类型的功能函数 |
使用内置 MessageChannel 对象
缺点:不能处理函数属性
1 | function clone(obj) { |