JS類型判斷與深度克隆

要進(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 - BigIntJS最新基本數(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í)煞茫。

運(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)理解一下varlet锡垄。

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ù)組從前者繼承。

instanceof

總結(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

可以看出來除了nullundefined都可以使用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可以判斷除了nullundefined外的所有變量的類型亥鬓。

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)是宽档,ObjecttoString方法會(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è)方法去判斷變量類型椎瘟。

其次是覆致,大部分的類型都重寫了ObjecttoString方法,也就是每個(gè)類型的變量去直接調(diào)用結(jié)果的表現(xiàn)都是不一樣的肺蔚,所以我們需要通過call去調(diào)用ObjecttoString方法煌妈。

總結(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.alltypeof 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)深度克隆

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末雨女,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子阳准,更是在濱河造成了極大的恐慌氛堕,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,222評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件野蝇,死亡現(xiàn)場(chǎng)離奇詭異讼稚,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)浪耘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,455評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門乱灵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人七冲,你說我怎么就攤上這事」嫫牛” “怎么了澜躺?”我有些...
    開封第一講書人閱讀 157,720評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)抒蚜。 經(jīng)常有香客問我掘鄙,道長(zhǎng),這世上最難降的妖魔是什么嗡髓? 我笑而不...
    開封第一講書人閱讀 56,568評(píng)論 1 284
  • 正文 為了忘掉前任操漠,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘浊伙。我一直安慰自己撞秋,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,696評(píng)論 6 386
  • 文/花漫 我一把揭開白布嚣鄙。 她就那樣靜靜地躺著吻贿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪哑子。 梳的紋絲不亂的頭發(fā)上舅列,一...
    開封第一講書人閱讀 49,879評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音卧蜓,去河邊找鬼帐要。 笑死,一個(gè)胖子當(dāng)著我的面吹牛弥奸,可吹牛的內(nèi)容都是我干的宠叼。 我是一名探鬼主播,決...
    沈念sama閱讀 39,028評(píng)論 3 409
  • 文/蒼蘭香墨 我猛地睜開眼其爵,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼冒冬!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起摩渺,我...
    開封第一講書人閱讀 37,773評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤简烤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后摇幻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體横侦,經(jīng)...
    沈念sama閱讀 44,220評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,550評(píng)論 2 327
  • 正文 我和宋清朗相戀三年绰姻,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了枉侧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,697評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡狂芋,死狀恐怖榨馁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情帜矾,我是刑警寧澤翼虫,帶...
    沈念sama閱讀 34,360評(píng)論 4 332
  • 正文 年R本政府宣布,位于F島的核電站屡萤,受9級(jí)特大地震影響珍剑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜死陆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,002評(píng)論 3 315
  • 文/蒙蒙 一招拙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦别凤、人聲如沸饰序。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,782評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽菌羽。三九已至,卻和暖如春由缆,著一層夾襖步出監(jiān)牢的瞬間注祖,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,010評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工均唉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留是晨,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,433評(píng)論 2 360
  • 正文 我出身青樓舔箭,卻偏偏與公主長(zhǎng)得像罩缴,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子层扶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,587評(píng)論 2 350