原文:你真的會克隆對象嗎git
在開始聊克隆以前,咱們仍是先來看看js數據類型。js的數據類型分爲基本數據類型和複雜數據類型。github
克隆:基本數據 => 複製這個變量;複雜數據 => 拷貝引用(網上的介紹不少,不深刻了)數組
對於對象的克隆,應該大多數人都能實現出來,可能深、淺拷貝都能想出好幾種方式,咱們先來聊聊淺拷貝。函數
一個常見的淺拷貝通常是下面這樣:prototype
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 }
或者更嚴謹一點的實現數組的判斷:code
Object.prototype.toString.call(arr) === '[object Array]'
好像是沒什麼問題呢,畢竟通過了好多項目的檢測,網上一搜就能出現一大堆。對象
可是,咱們開頭介紹數據類型的時候就已經說過了,ES6新增了Symbol
類型,狀況好像就有點不同了繼承
Symbol
是ES6中引入的原始數據類型。Symbol
值經過Symbol
函數生成,是獨一無二的。同時,ES6中規定了對象的屬性名有兩種類型,一種是字符串,另外一種就是 Symbol
類型。凡是屬性名屬於 Symbol 類型,就不會與其餘屬性名產生衝突。可是,隨之而來的問題是,咱們的for...in
循環不能遍歷出該屬性ip
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的一些知識有一個總結和一點新的認識吧,原本想繼續寫深拷貝的,無賴篇幅已經不短,加上長夜漫漫,我想睡覺,深拷貝的問題更復雜,我先放放,往後再說。
最後的最後,對這篇文章有興趣的朋友,能夠繼續關注下一篇的深克隆,會更新的會更新的...