原文:你真的会克隆对象吗前端
在开始聊克隆以前,咱们仍是先来看看js数据类型。js的数据类型分为基本数据类型和复杂数据类型。git
克隆:基本数据 => 复制这个变量;复杂数据 => 拷贝引用(网上的介绍不少,不深刻了)github
对于对象的克隆,应该大多数人都能实现出来,可能深、浅拷贝都能想出好几种方式,咱们先来聊聊浅拷贝。数组
一个常见的浅拷贝通常是下面这样:函数
function shallowCopy (obj) {
if (typeof obj !== 'object') {
return
}
var newObj = obj instanceof Array ? [] : {}
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = obj[key]
}
}
return newObj
}
复制代码
或者更严谨一点的实现数组的判断:spa
Object.prototype.toString.call(arr) === '[object Array]'
复制代码
好像是没什么问题呢,毕竟通过了好多项目的检测,网上一搜就能出现一大堆。prototype
可是,咱们开头介绍数据类型的时候就已经说过了,ES6新增了Symbol
类型,状况好像就有点不同了code
Symbol
是ES6中引入的原始数据类型。Symbol
值经过Symbol
函数生成,是独一无二的。同时,ES6中规定了对象的属性名有两种类型,一种是字符串,另外一种就是 Symbol
类型。凡是属性名属于 Symbol 类型,就不会与其余属性名产生冲突。可是,随之而来的问题是,咱们的for...in
循环不能遍历出该属性对象
Symbol
做为属性名,该属性不会出如今for...in
、for...of
循环中,也不会被Object.keys()
、Object.getOwnPropertyNames()
、JSON.stringify()
返回。可是,它也不是私有属性,有一个Object.getOwnPropertySymbols
方法,能够获取指定对象的全部 Symbol 属性名。继承
有Symbol
类型,天然有遍历Symbol
类型的方法。Object.getOwnPropertySymbols
+ for...in
的组合起来好像是能知足咱们要求的了。嗯,看起来还不错,可是彷佛有点麻烦了,有没有更便捷一点的方式呢?或许新时代的男人---Reflect.ownKeys
,要闪亮登场了,这个既能遍历字符串,又能遍历Symbol
的死变态(请容许我这么夸他)。
Reflect.ownKeys
返回一个数组,包含对象自身的全部属性,不论是属性名是Symbol
或字符串,也不论是否可枚举
这个时候熟悉ES6的人或许开始有疑问了,咱们已经开始讨论Symbol
和Reflect.ownKeys
,为何浅克隆不直接用Object.assign
或者展开运算符(...
)呢?
嗯,待我吃根火腿冷静冷静,好像你说的很对!Object.assign
的确是能拷贝Symbol
类型的呢。可是呢,可是呢,咱们是一个有追求的猿类,多一种实现方式不是能让咱们多了解一些坑吗?并且这种方式不是能让咱们更灵活的实现不可预知的需求吗?对,没错,是这样子的...
Object.assign
这个更完美的男人出来以后,好像浅拷贝部分也该结束了,正常来讲,的确是这样。不过咱们再仔细想一想上面的两种方式,好像仍是有点区别的呢。咱们再来看看这两个男人:
Reflect.ownKeys
返回一个数组,包含对象自身的全部属性,不论是属性名是Symbol或字符串,也不论是否可枚举Object.assign
拷贝的属性是有限制的,只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性注意到了吗?这里面有一个是否可枚举的概念,这个时候是否是应该感慨咱们知道怎么实现不可预知的需求了呢。
咱们先看个例子:
var obj = Object.create({ foo: 1 }, {
[Symbol()]: {
value: 1,
enumerable: false
},
bar: {
value: 2,
enumerable: false
},
[Symbol()]: {
value: 3,
enumerable: true
},
baz: {
value: 4,
enumerable: true
}
})
Object.assign({}, obj) // {baz: 4, Symbol(): 3}
复制代码
唉,的确是这样呢!看来Object.assign
也不是咱们的理想归宿啊。咱们再回过头来看看Reflect.ownKeys
,上面挖的坑也该填了,咱们在讲Symbol
的时候,Object.getOwnPropertySymbols
+ for...in
直接用Reflect.ownKeys
替代了,在从可枚举的角度出发看看,好像哪里不对,for...in
只能循环遍历对象自身的和继承的可枚举的属性,且不含 Symbol
。头都大了吗?来来来,喝完这杯,还有一杯,继续接着来。这么多循环,咱们来缕缕头绪:
for...in
循环遍历对象自身的和继承的可枚举属性(不含 Symbol
属性)。Object.keys()
返回一个数组,包括对象自身的(不含继承的)全部可枚举属性(不含 Symbol
属性)的键名。Object.getOwnPropertyNames()
返回一个数组,包含对象自身的全部属性(不含 Symbol
属性,可是包括不可枚举属性)的键名。Object.getOwnPropertySymbols()
返回一个数组,包含对象自身的全部 Symbol
属性的键名。Reflect.ownKeys()
返回一个数组,包含对象自身的全部键名,无论键名是 Symbol
或字符串,也不论是否可枚举。终于清晰了,或许也该结束了吧。
慢着,好像上面的例子让我想到了什么!!!
咱们在来思考一个例子:
const source = {
get foo() { return 1 }
};
const target = {};
Object.assign(target, source) // { foo: 1 }
复制代码
好像并非咱们想要的呢,遍历的方式好像也不适用了,这可怎么办。别急,还有Object.getOwnPropertyDescriptors
能够用。
ES2017 引入了
Object.getOwnPropertyDescriptors
方法,返回指定对象全部自身属性(非继承属性)的描述对象
仔细阅读下文档,终于用Object.getOwnPropertyDescriptors
+Object.getPrototypeOf
成功了呢
Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
)
复制代码
写到这里,浅拷贝部分也该结束了
可能实际项目中并不须要处理的这么细致,可是但愿你们对各类遍历、实现一个浅拷贝以及ES6的一些知识有一个总结和一点新的认识吧,原本想继续写深拷贝的,无赖篇幅已经不短,加上长夜漫漫,我想睡觉,深拷贝的问题更复杂,我先放放,往后再说。
最后的最后,做为一个前端界的小学生,第一次用掘金写文章,虽然使用掘金挺久了,发表文章仍是有点慌,文章有什么错误之处,还请指正。同时对这篇文章有兴趣的朋友,能够继续关注下一篇的深克隆,会更新的会更新的...