要進(jìn)行深度克隆床佳,首先就需要知道進(jìn)行克隆的這個(gè)變量是什么類型的值灾梦,知道了是什么類型的,我們才能分門別類的去根本不同的類型進(jìn)行克隆易迹。所以我們先介紹如何進(jìn)行準(zhǔn)確的類型判斷。
類型判斷
首先我們需要知道JavaScript都有哪些數(shù)據(jù)類型平道。
基本數(shù)據(jù)類型:Undefined睹欲、Null、Boolean一屋、Number窘疮、 String 和 Symbol。
引用數(shù)據(jù)類型:Object冀墨。
定義一些變量方便后面使用闸衫。
const str = ''
const _undefined = undefined
const _null = null
const num = 0
const bool = true
const sym = Symbol()
const obj = {}
const arr = []
const func = () => {}
const reg = /a/
const date = new Date()
const dom = document.getElementsByTagName('body')[0]
- 拓展 -- bigInt
BigInt 是一種內(nèi)置對(duì)象,它提供了一種方法來表示大于 253 - 1 的整數(shù)诽嘉。這原本是 Javascript中可以用 Number 表示的最大數(shù)字蔚出。BigInt 可以表示任意大的整數(shù)〕嬉福可以用在一個(gè)整數(shù)字面量后面加 n 的方式定義一個(gè) BigInt 身冬,如:10n,或者調(diào)用函數(shù)BigInt()岔乔。
這是一個(gè)新的基本數(shù)據(jù)類型酥筝。如下
const bigNum = 10n
const oldNum = 10
console.log(typeof bigNum) // 'bigint'
console.log(bigNum === oldNum) // false
console.log(bigNum == oldNum) // true
console.log(bigNum + bigNum) // 20n
console.log(bigNum + oldNum) // Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions
詳細(xì)可以看以下兩篇內(nèi)容:MDN - BigInt,JS最新基本數(shù)據(jù)類型:BigInt雏门。
然后我們看看有哪些方法可以去判斷一個(gè)變量的類型呢嘿歌?
typeof
typeof 操作符返回一個(gè)字符串,表示未經(jīng)計(jì)算的操作數(shù)的類型茁影。
我們看看上面定義的變量的表現(xiàn)宙帝。
typeof str // "string"
typeof _undefined // "undefined"
typeof _null // "object"
typeof num // "number"
typeof bool // "boolean"
typeof sym // "symbol"
typeof obj // "object"
typeof arr // "object"
typeof func // "function"
typeof reg // "object"
typeof date // "object"
typeof dom // "object"
可以看出來有些變量的表現(xiàn)讓人比較不滿意,所以在涉及到引用變量的時(shí)候募闲,使用typeof
時(shí)還是要注意的步脓。
typeof的迷惑行為
typeof new Number(1) === 'object'
什么?new Number(1)
不是個(gè)數(shù)字嗎?
這是因?yàn)槭褂?code>new操作符創(chuàng)建的變量都是這個(gè)構(gòu)造函數(shù)的實(shí)例靴患,被加在了原型鏈上仍侥,盡管他仍然等于 1。
let newNum = new Number(1)
console.log(newNum) // Number {1}
newNum === 1 // false
newNum + 1 // 2
因?yàn)檫@個(gè)迷惑行為鸳君,所以非必要時(shí)农渊,不要使用new
操作符去創(chuàng)建一個(gè)基本類型的變量,這可能會(huì)導(dǎo)致不必要的麻煩或颊。
typeof null === 'object'
什么砸紊?null
不是屬于基本數(shù)據(jù)類型null
嗎?
這個(gè)大家應(yīng)該都清楚囱挑,從 JavaScript 誕生到現(xiàn)在一直都是這樣醉顽。
在 JavaScript 最初的實(shí)現(xiàn)中,JavaScript 中的值是由一個(gè)表示類型的標(biāo)簽和實(shí)際數(shù)據(jù)值表示的平挑。對(duì)象的類型標(biāo)簽是 0徽鼎。由于
null
代表的是空指針(大多數(shù)平臺(tái)下值為0x00
),因此弹惦,null
的類型標(biāo)簽是 0否淤,typeof null
也因此返回"object"
。
typeof 9 + 'str'
什么棠隐?9 + 'str'
不是屬于字符串的拼接石抡,屬于string
類型嗎?
這是因?yàn)?strong>運(yùn)算符的優(yōu)先級(jí)助泽,typeof
的優(yōu)先級(jí)要高于+
啰扛,所以會(huì)先得到typeof 9
的值為'number'
,然后計(jì)算'number' + 'str'
嗡贺,得到最終結(jié)果為'numberstr'
隐解。
如果是這樣寫:typeof (9 + 'str')
。得到的結(jié)果就是'string'
诫睬。
- 拓展 -- 運(yùn)算符優(yōu)先級(jí)
下面列出了常用的運(yùn)算符的優(yōu)先級(jí)煞茫。
typeof newStr === 'undefined'
這個(gè)需要分情況討論,看下面一段代碼摄凡。
console.log(typeof varStr === 'undefined')
console.log(typeof letStr === 'undefined')
let letStr
執(zhí)行這段代碼的結(jié)果會(huì)是什么呢续徽?
首先有個(gè)前置知識(shí)。
在 ECMAScript 2015 之前亲澡,typeof 總能保證對(duì)任何所給的操作數(shù)返回一個(gè)字符串钦扭。即便是沒有聲明的標(biāo)識(shí)符,typeof 也能返回 'undefined'床绪。使用 typeof 永遠(yuǎn)不會(huì)拋出錯(cuò)誤客情。
所以第一行的答案必是true
其弊。那第二行呢?
在看正確答案之前我們先復(fù)習(xí)一下var let const
膀斋。
var
關(guān)聯(lián)詞用來聲明一個(gè)可變的本地變量梭伐,在全局范圍內(nèi)都有效,也就是說概页,當(dāng)你用var
聲明了一個(gè)變量的時(shí)候籽御,他就會(huì)在全局聲明時(shí)創(chuàng)建一個(gè)屬性练慕。
let
關(guān)聯(lián)詞用來聲明一個(gè)可變的本地變量惰匙,在其作用域或子塊內(nèi)都有效,也就是說铃将,當(dāng)你用let
聲明了一個(gè)變量的時(shí)候项鬼,在此之前或者作用域之外都是不能使用這個(gè)變量的。
const
關(guān)聯(lián)詞用來聲明一個(gè)不可變的本地變量劲阎,在其作用域或子塊內(nèi)都有效绘盟,也就是說,當(dāng)你用const
聲明了一個(gè)變量的時(shí)候悯仙,他和let
聲明的變量有著同樣的作用域龄毡,但無法更改。
我們通過一個(gè)簡(jiǎn)單的for循環(huán)理解一下var
和let
锡垄。
for (var i = 0; i < 10; i ++) {
setTimeout(() => console.log(i), 300) // 10 個(gè) 10
}
console.log(i) // 10
for (let j = 0; j < 10; j ++) {
setTimeout(() => console.log(j), 300) // 0 ~ 9
}
console.log(j) // Uncaught ReferenceError: j is not defined
對(duì)于第一個(gè)for循環(huán)沦零,var
定義的i
被提升到了代碼開頭,在全局范圍內(nèi)都有效货岭,所以全局只有一個(gè)變量i
路操,循環(huán)的每一層都是去改變這個(gè)i
的值;而循環(huán)內(nèi)部的setTimeout
都被加到了執(zhí)行隊(duì)列的尾端千贯,執(zhí)行其內(nèi)部的方法時(shí)就會(huì)去尋找全局的i
屯仗,這個(gè)時(shí)候的i就已經(jīng)時(shí)被改變成最后的值10
。同理在for循環(huán)外部的打印也是去找的全局變量i
搔谴。
對(duì)于第二個(gè)for循環(huán)魁袜。let
定義的j只在其當(dāng)輪的作用域下有效,所以每次循環(huán)其實(shí)都是一個(gè)新的變量j
敦第;而循環(huán)內(nèi)部的setTimeout
同樣都被加到了執(zhí)行隊(duì)列的尾端慌核,但是每個(gè)setTimeout
在執(zhí)行的時(shí)候都會(huì)去找其對(duì)應(yīng)的作用域下的值,也就是會(huì)輸出正確的0 - 9
申尼。在for循環(huán)外部的打印垮卓,因?yàn)槠洳辉诙xj
的作用域范圍內(nèi),所以會(huì)報(bào)錯(cuò)师幕。
看到這里粟按,最開始的那一段代碼的結(jié)果就顯而易見了诬滩。
console.log(typeof varStr === 'undefined') // true
console.log(typeof letStr === 'undefined') // Uncaught ReferenceError: letStr is not defined
let letStr
在加入了塊級(jí)作用域的 let 和 const 之后,在其被聲明之前對(duì)塊中的 let 和 const 變量使用 typeof 會(huì)拋出一個(gè) ReferenceError灭将。塊作用域變量在塊的頭部處于“暫存死區(qū)”疼鸟,直至其被初始化,在這期間庙曙,訪問變量將會(huì)引發(fā)錯(cuò)誤空镜。
typeof document.all === 'undefined'
什么?document.all
不是當(dāng)前頁面的標(biāo)簽的集合捌朴,屬于object
類型嗎吴攒?
這是一個(gè)例外,在MDN中是這樣說的砂蔽。
盡管規(guī)范允許為非標(biāo)準(zhǔn)的外來對(duì)象自定義類型標(biāo)簽洼怔,但它要求這些類型標(biāo)簽與已有的不同。document.all 的類型標(biāo)簽為 'undefined' 的例子在 Web 領(lǐng)域中被歸類為對(duì)原 ECMA JavaScript 標(biāo)準(zhǔn)的“故意侵犯”左驾。
總結(jié)
typeof
可以用來對(duì)基本數(shù)據(jù)類型變量(通過new
定義的除外)做類型校驗(yàn)镣隶,也可以用來區(qū)分是否是引用型變量;但是在用的時(shí)候需要注意上面所說的一些特殊情況诡右。
instanceof
instanceof
運(yùn)算符用于檢測(cè)構(gòu)造函數(shù)的prototype
屬性是否出現(xiàn)在某個(gè)實(shí)例對(duì)象的原型鏈上安岂。
我們看看上面定義的變量的表現(xiàn)。
str instanceof String // false
_undefined instanceof // ?
_null instanceof Object // false
num instanceof Number // false
bool instanceof Boolean // false
sym instanceof Symbol // false
obj instanceof Object // true
arr instanceof Array // true
func instanceof Function // true
reg instanceof RegExp // true
date instanceof Date // true
dom instanceof HTMLBodyElement // true
然后我們看看對(duì)于由構(gòu)造函數(shù)創(chuàng)建的基本類型的變量帆吻。
const conNum = new Number(10)
const conStr = new String('abc')
const conBoo = new Boolean(true)
conNum instanceof Number // true
conStr instanceof String // true
conBoo instanceof Boolean // true
以及當(dāng)右邊為Object
時(shí)域那。
conStr instanceof Object // true
_null instanceof Object // false
conNum instanceof Object // true
conBoo instanceof Object // true
obj instanceof Object // true
arr instanceof Object // true
func instanceof Object // true
reg instanceof Object // true
date instanceof Object // true
dom instanceof Object // true
這是因?yàn)?code>instanceof會(huì)在你的原型鏈上去找關(guān)聯(lián)關(guān)系,第一層就是每個(gè)變量所對(duì)應(yīng)的構(gòu)造函數(shù)桅锄,而Object
就是在原型鏈上能找到的終點(diǎn)了琉雳。
至于null
,雖然typeof null === 'object'
友瘤,而且Object.prototype.__proto__ === null
翠肘;但是實(shí)際上,null并不存在原型鏈辫秧,他就是一個(gè)簡(jiǎn)簡(jiǎn)單單的null束倍,沒有任何屬性。
下面這種神圖盟戏,相信大家都有看過绪妹,instanceof
就是在這個(gè)鏈上一層層去找的。
多全局對(duì)象
在瀏覽器中柿究,我們的腳本可能需要在多個(gè)窗口之間進(jìn)行交互邮旷。多個(gè)窗口意味著多個(gè)全局環(huán)境,不同的全局環(huán)境擁有不同的全局對(duì)象蝇摸,從而擁有不同的內(nèi)置類型構(gòu)造函數(shù)婶肩。這可能會(huì)引發(fā)一些問題办陷。比如,表達(dá)式
[] instanceof window.frames[0].Array
會(huì)返回false
律歼,因?yàn)?code>Array.prototype !== window.frames[0].Array.prototype民镜,并且數(shù)組從前者繼承。
總結(jié)
instanceof
可以用來判斷引用數(shù)據(jù)類型變量的具體類型以及由new
定義的基本數(shù)據(jù)類型變量的類型险毁。
constructor
constructor
是一種用于創(chuàng)建和初始化class
創(chuàng)建的對(duì)象的特殊方法制圈。
constructor
返回的是當(dāng)前變量的構(gòu)造器,和instanceof
一樣都是在原型鏈上操作畔况。先看下之前定義的變量的表現(xiàn)鲸鹦。
console.log(str.constructor) // String
console.log(_undefined.constructor) // Uncaught TypeError: Cannot read property 'constructor' of undefined
console.log(_null.constructor) // Uncaught TypeError: Cannot read property 'constructor' of null
console.log(num.constructor) // Number
console.log(bool.constructor) // Boolean
console.log(sym.constructor) // Symbol
console.log(obj.constructor) // Object
console.log(arr.constructor) // Array
console.log(func.constructor) // Function
console.log(reg.constructor) // RegExp
console.log(date.constructor) // Date
console.log(dom.constructor) // HTMLBodyElement
可以看出來除了null
和undefined
都可以使用constructor
屬性得到其的構(gòu)造函數(shù),也就是準(zhǔn)確的類型问窃。
- 注意:數(shù)值不能直接使用
constructor
console.log(1.constructor) // Uncaught SyntaxError: Invalid or unexpected token
console.log((1).constructor) // Number
總結(jié)
constructor
可以判斷除了null
和undefined
外的所有變量的類型亥鬓。
toString
toString()
方法返回一個(gè)表示該對(duì)象的字符串完沪。
toString()
是Object
原型鏈上的方法,如果直接調(diào)用返回的值是其轉(zhuǎn)換成字符串的值,我們可以通過call
方法來調(diào)用一忱。
const _toString = str => Object.prototype.toString.call(str)
console.log(_toString(str)) // [object String]
console.log(_toString(_undefined)) // [object Undefined]
console.log(_toString(_null)) // [object Null]
console.log(_toString(num)) // [object Number]
console.log(_toString(bool)) // [object Boolean]
console.log(_toString(sym)) // [object Symbol]
console.log(_toString(obj)) // [object Object]
console.log(_toString(arr)) // [object Array]
console.log(_toString(func)) // [object Function]
console.log(_toString(reg)) // [object RegExp]
console.log(_toString(date)) // [object Date]
console.log(_toString(dom)) // [object HTMLBodyElement]
那么徐紧,我們?yōu)槭裁匆ㄟ^call
來調(diào)用呢?
首先一點(diǎn)是宽档,Object
的toString
方法會(huì)返回一個(gè)[object ${calss}]
的形式的字符串尉姨,在ecma中是這樣說的:“Return the String value that is the result of concatenating the three Strings "[object ", class, and "]".”;翻譯過來就是“返回字符串值吗冤,該值是將三個(gè)字符串“[object”又厉,class和“]”連接在一起的結(jié)果”。所以我們可以使用這個(gè)方法去判斷變量類型椎瘟。
其次是覆致,大部分的類型都重寫了Object
的toString
方法,也就是每個(gè)類型的變量去直接調(diào)用結(jié)果的表現(xiàn)都是不一樣的肺蔚,所以我們需要通過call
去調(diào)用Object
的toString
方法煌妈。
總結(jié)
toString
可以用來準(zhǔn)確的判斷一個(gè)變量的類型。
其他
Array.isArray(arg)
對(duì)于數(shù)組宣羊,Array
給我們專門提供了Array.isArray(arg)
來判斷arg
是否是數(shù)組璧诵。
其實(shí)現(xiàn)方法很簡(jiǎn)單,就是封裝了toString
仇冯,如下
Array.isArray = function(arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
當(dāng)然其內(nèi)部的實(shí)現(xiàn)肯定不會(huì)這么簡(jiǎn)單之宿,至少也要加上類型判斷。
isNaN(arg) / Number.isNaN(arg)
isNaN
是提供給我們的一個(gè)全局方法苛坚,Number.isNaN
則是Number
的一個(gè)原型方法比被。二者均是用來判斷NaN
的坪创,后者比前者更加穩(wěn)妥。如下
console.log(isNaN(NaN)) // true
console.log(Number.isNaN(NaN)) // true
console.log(isNaN('NaN')) // true
console.log(Number.isNaN('NaN')) // false
console.log(isNaN(undefined)) // true
console.log(Number.isNaN(undefined)) // false
console.log(isNaN('23')) // false
console.log(Number.isNaN('23')) // false
console.log(isNaN('abc')) // true
console.log(Number.isNaN('abc')) // false
可以發(fā)現(xiàn)姐赡,當(dāng)參數(shù)為非數(shù)字類型時(shí)莱预,isNaN
會(huì)先嘗試將其轉(zhuǎn)換為數(shù)值,然后去判斷轉(zhuǎn)換后的值是否為NaN
项滑,所以會(huì)有很多的迷惑行為依沮。
而對(duì)于Number.isNaN
,其并不會(huì)去嘗試轉(zhuǎn)換參數(shù)枪狂,而是直接去判斷參數(shù)是否為NaN
危喉,哪怕是字符串的'NaN'
都不行,所以推薦使用Number.isNaN
州疾。
深度克隆
學(xué)習(xí)了幾種類型判斷的方法辜限,下面我們正式開始深度克隆。
我們?yōu)槭裁葱枰疃瓤寺严蓖。?/h3>
首先看下面這段代碼薄嫡。
const str = '123'
let newStr = str
newStr += '4'
const arr = [1, 2, 3, 4]
const newArr = arr
newArr.push(5)
console.log(newArr)
console.log(arr)
console.log(newStr)
console.log(str)
在這段代碼中,newArr是arr的復(fù)制颗胡,newStr是str的復(fù)制毫深;但是結(jié)果是arr跟著newArr改變了。
出現(xiàn)這種情況的原因是毒姨,在JavaScript中哑蔫,普通數(shù)據(jù)類型都存在棧(棧內(nèi)存)中,占用空間大小固定弧呐;引用數(shù)據(jù)類型都存在堆(堆內(nèi)存)中闸迷,通過指針建立聯(lián)系。而引用類型的值的復(fù)制俘枫,只是給新變量B添加了一個(gè)指針b腥沽,指向了被復(fù)制變量A的指針a所指向的內(nèi)存,也就是說崩哩,B雖然是經(jīng)過A復(fù)制得來巡球,但是他們指向的始終還是同一個(gè)值,所以會(huì)一個(gè)變邓嘹,另一個(gè)跟著變酣栈。
為了避免這種指向同一地址,聯(lián)動(dòng)更改的問題汹押,就需要用到深度克隆矿筝,使得復(fù)制出來的變量是一個(gè)全新的變量,不會(huì)對(duì)以前的變量產(chǎn)生其他影響棚贾。
實(shí)現(xiàn)過程
先搭個(gè)架子窖维。
const deepClone = arg => {
if (!arg) return arg
return arg
}
基本數(shù)據(jù)類型變量克隆
然后我們由上一節(jié)得知榆综,深度遍歷主要就是針對(duì)的引用型變量,所以第一步就是先區(qū)分出基本變量和引用型變量铸史。區(qū)分是否引用型變量可以使用typeof
鼻疮。
這一步有幾點(diǎn)需要注意一下, 一個(gè)是function
琳轿,typeof fun === 'function'
判沟,而對(duì)于function
來說,直接復(fù)制是沒有問題的崭篡,所以不需要考慮這個(gè)挪哄。
還有一個(gè)是document.all
,typeof document.all === 'undefined'
琉闪,document.all
并不被推薦使用迹炼,且其中的關(guān)鍵信息都是只讀的,所以在這里不考慮其的復(fù)制颠毙。
還有一個(gè)是通過new
定義的基本類型變量斯入,這些變量會(huì)通過第一步的判斷,所以我們留在下一步討論吟秩。
// 區(qū)分基本類型變量和引用類型變量
// 對(duì)于基本類型變量可以直接復(fù)制
if (typeof arg !== 'object) {
return arg
}
然后第二步咱扣,我們需要分類型考慮各類型的復(fù)制绽淘。不過在此之前涵防,需要先分辨出各類型。
在這里沪铭,用的是toString
壮池,當(dāng)然,其他的也可以杀怠。
const type = Object.prototype.toString.call(arg)
switch(type) {
case '[object RegExp]': break;
case '[object Date]': break;
case '[object Array]': break;
case '[object Object]': break;
default: return arg;
}
通過switch去處理不同類型的克隆椰憋,而default分支就用來處理通過了第一步判斷的基本類型的變量,可以直接返回赔退。
RegExp
克隆
我們需要先了解幾個(gè)RegExp
的內(nèi)置屬性橙依。
屬性名 | 含義 |
---|---|
source | source 屬性返回一個(gè)值為當(dāng)前正則表達(dá)式對(duì)象的模式文本的字符串,該字符串不會(huì)包含正則字面量?jī)蛇叺男备芤约叭魏蔚臉?biāo)志字符硕旗。 |
global | global 屬性表明正則表達(dá)式是否使用了 "g" 標(biāo)志窗骑。global 是一個(gè)正則表達(dá)式實(shí)例的只讀屬性。 |
ignoreCase | ignoreCase 屬性表明正則表達(dá)式是否使用了 "i" 標(biāo)志漆枚。ignoreCase 是正則表達(dá)式實(shí)例的只讀屬性创译。 |
multiline | multiline 屬性表明正則表達(dá)式是否使用了 "m" 標(biāo)志。multiline 是正則表達(dá)式實(shí)例的一個(gè)只讀屬性墙基。 |
我們需要獲取源正則對(duì)象的字符串和其標(biāo)志软族,然后生成一個(gè)新的正則刷喜。如下
switch(type) {
case '[object RegExp]':
let flag = ''
if (arg.global) flag.push('g')
if (arg.ignoreCase) flag.push('i')
if (arg.multiline) flag.push('m')
return new RegExp(arg.source, flag)
}
而在支持es6的環(huán)境中,我們可以不用這么麻煩立砸,直接生成即可掖疮。
switch(type) {
case '[object RegExp]':
return new RegExp(arg)
}
這是因?yàn)閺膃s6開始,當(dāng)?shù)谝粋€(gè)參數(shù)為正則表達(dá)式而第二個(gè)標(biāo)志參數(shù)存在時(shí)颗祝,new RegExp(/ab+c/, 'i') 不再拋出 TypeError ("從另一個(gè)RegExp構(gòu)造一個(gè)RegExp時(shí)無法提供標(biāo)志")的異常氮墨,取而代之,將使用這些參數(shù)創(chuàng)建一個(gè)新的正則表達(dá)式吐葵。
Date
克隆
對(duì)于時(shí)間规揪,我們可以直接獲取其時(shí)間戳,然后新生成一個(gè)即可温峭。
switch(type) {
case '[object Date]':
return new Date(obj.getTime())
}
Array
克隆
對(duì)于數(shù)組猛铅,可以通過遍歷去處理數(shù)組內(nèi)部每一項(xiàng)的值,而這些值又可能是任何屬性凤藏,所以這里需要用遞歸去處理這些值奸忽。
這里要注意,不能使用解構(gòu)揖庄,因?yàn)榻鈽?gòu)并不會(huì)改變數(shù)組內(nèi)部值的組成栗菜,所以對(duì)于內(nèi)部值,仍然是淺克隆蹄梢。
switch(type) {
case '[object Array]':
const result = []
for (let i = 0; i < arg.length; i ++) {
result[i] = deepClone(arg[i])
}
return result
}
Object
克隆
對(duì)于對(duì)象疙筹,和數(shù)組類似,都要使用遞歸去處理其內(nèi)部值禁炒,而不同點(diǎn)在于而咆,對(duì)象還需要處理其原型鏈、只讀屬性或者是修改過屬性的值等等幕袱。
首先是處理原型鏈暴备,可以用Object.getPrototypeOf()
和Object.create()
,前者作用是返回指定對(duì)象的原型们豌;后者作用是創(chuàng)建一個(gè)新對(duì)象涯捻,使用現(xiàn)有的對(duì)象來提供新創(chuàng)建的對(duì)象的proto。
const obj = {}
let proto = Object.getPrototypeOf(obj)
let newObj = Object.create(proto)
然后是只讀屬性或者是修改過屬性的值望迎,可以用Object.getOwnPropertyDescriptor()
和Object.defineProperty()
障癌,前者作用是返回指定對(duì)象上一個(gè)自有屬性對(duì)應(yīng)的屬性描述符;后者作用是直接在一個(gè)對(duì)象上定義一個(gè)新屬性擂煞,或者修改一個(gè)對(duì)象的現(xiàn)有屬性混弥,并返回此對(duì)象。
const obj = {
get foo() { return 2; }
}
const newObj = {}
let rule = Object.getOwnPropertyDescriptor(obj, 'foo')
Object.defineProperty(newObj, 'foo', rule)
然后合在一起處理就是
switch(type) {
case '[object Object]':
let proto = Object.getPrototypeOf(arg)
const result = Object.create(proto)
for (let item in arg) {
let rule = Object.getOwnPropertyDescriptor(arg, item)
rule.value = rule.value && deepClone(rule.value)
Object.defineProperty(result, item, rule)
}
return result
}
Dom
克隆
Dom可能是幾個(gè)節(jié)點(diǎn)的集合,也可能是單一的節(jié)點(diǎn)蝗拿。因?yàn)楣?jié)點(diǎn)的集合屬于HTMLCollection
接口晾捏,而這個(gè)接口是會(huì)自動(dòng)更新的,所以不考慮這個(gè)哀托。而對(duì)于單一節(jié)點(diǎn)惦辛,根據(jù)其節(jié)點(diǎn)名稱的不同,toString
返回的結(jié)果也不相同仓手,所以我們使用節(jié)點(diǎn)的一個(gè)屬性nodeTpy
來判斷其是否是dom節(jié)點(diǎn)胖齐。
這里還要用到一個(gè)內(nèi)置方法Node.cloneNode()
,其返回調(diào)用該方法的節(jié)點(diǎn)的一個(gè)副本嗽冒;接收一個(gè)參數(shù)deep
呀伙,參數(shù)如果為true,則該節(jié)點(diǎn)的所有后代節(jié)點(diǎn)也都會(huì)被克隆,如果為false,則只克隆該節(jié)點(diǎn)本身。
if (arg.nodeType && 'cloneNode' in arg) {
return arg.cloneNode(true)
}
總結(jié)
目前是做了基本的深度克隆添坊,但是還有很多缺陷剿另,比如沒有考慮過循環(huán)引用以及層數(shù)過多時(shí)遞歸會(huì)爆棧,后面會(huì)繼續(xù)做優(yōu)化贬蛙。
參考
JavaScript高級(jí)程序設(shè)計(jì)
MDN
JS最新基本數(shù)據(jù)類型:BigInt
ECMAScript (ECMA-262)
JavaScript中如何實(shí)現(xiàn)深度克隆