摘要:對(duì)象克隆我們經(jīng)常會(huì)用到一個(gè)對(duì)象去做一些事情,可能有時(shí)候我們不想改變?cè)械臄?shù)據(jù)。如果是對(duì)象接著遞歸復(fù)制判斷是對(duì)象還是數(shù)組其實(shí)這還不是最終的深克隆,因?yàn)檫@一個(gè)也有它自己的問題,但是面對(duì)一般的情況應(yīng)該沒問題,跟高級(jí)的用法請(qǐng)自行學(xué)習(xí)。
類型判斷
我們先說一下JS的數(shù)據(jù)類型,我們一般說JS有六大數(shù)據(jù)類型(ES6以前)分別是:
基本數(shù)據(jù)類型
Number
String
Boolean
null
undefined
引用數(shù)據(jù)類型
object
在ES6中新增了Symbol數(shù)據(jù)類型。
有時(shí)我們需要知道數(shù)據(jù)的類型其判斷一些事情,我們經(jīng)常會(huì)用typeof去判斷數(shù)據(jù)類型。
那么typeof能判斷什么類型呢?
Number
String
Boolean
object
undefined
function
就這幾個(gè)數(shù)據(jù)類型,但這些我們夠用嗎?或者說準(zhǔn)確嗎?
我們沒有看到null那么他是什么類型呢?我們用typeof null發(fā)現(xiàn)它是object,是不是很奇怪,其實(shí)這是一個(gè)bug,但是這個(gè)bug是能修復(fù)的但是不能修復(fù),因?yàn)閚ull一般是用來表示一個(gè)對(duì)象是空的,有時(shí)我們用null來取消事件,我理解的它好像是一個(gè)占位符,表示這個(gè)對(duì)象是空的。那為什么不能修復(fù)呢?因?yàn)樾迯?fù)它好說,但是修復(fù)了它會(huì)帶來許多麻煩。
專業(yè)點(diǎn)說就是:不同的對(duì)象在底層都是用二進(jìn)制來表示,在JS中二進(jìn)制前三位都是0的就會(huì)判斷為object類型,因?yàn)閚ull全是0所以會(huì)判斷null也是object類型。
我們來看一下typeof判斷的情況。
console.log(typeof(1)); //number console.log(typeof("1")); // string console.log(typeof(true)); // boolean console.log(typeof({})); // object console.log(typeof(function (){})); // function console.log(typeof(null)); // object console.log(typeof(undefined)); // undefined
當(dāng)我們想判斷一個(gè)對(duì)象那個(gè)是不是null或者是不是Date、RegExp等類型時(shí)會(huì)怎么樣呢?我們發(fā)現(xiàn)都是object,那我們有沒有辦法區(qū)分他們呢?
在這之前我先介紹一下Object.prototype.toString這個(gè)方法,我相信大家不陌生吧。它也能判斷數(shù)據(jù)類型,但是是這樣的。
console.log(Object.prototype.toString.call(1)); console.log(Object.prototype.toString.call("1")); console.log(Object.prototype.toString.call(true)); console.log(Object.prototype.toString.call({})); console.log(Object.prototype.toString.call(function (){})); console.log(Object.prototype.toString.call(null)); console.log(Object.prototype.toString.call(undefined)); console.log(Object.prototype.toString.call(new RegExp())); console.log(Object.prototype.toString.call(new Date())); [object Number] [object String] [object Boolean] [object Object] [object Function] [object Null] [object Undefined] [object RegExp] [object Date]
我們發(fā)現(xiàn)它比typeof高級(jí)點(diǎn),能分辨的更準(zhǔn)確,但是格式好像不是我們要的。
接下來進(jìn)入主題,直接上代碼。
//首先定義好數(shù)據(jù)類型。 let types = { "[object Object]": "Object", "[object RegExp]": "RegExp", "[object Date]": "Date" }; function type(regs) { let result = typeof(regs); // 先獲取傳過來的參數(shù) // 如果是對(duì)象在進(jìn)行判斷,不是則返回。 if(result === "object"){ if(regs === null){ return "null"; }else{ return types[Object.prototype.toString.call(regs)]; } }else{ return result; } } console.log(type(1)); //number console.log(type("1")); // string console.log(type(true)); // boolean console.log(type({})); // object console.log(type(function (){})); // function console.log(type(null)); // null console.log(type(undefined)); // undefined console.log(type(new RegExp())); //RegExp console.log(type(new Date())); //Date
對(duì)象克隆
我們經(jīng)常會(huì)用到一個(gè)對(duì)象去做一些事情,可能有時(shí)候我們不想改變?cè)械臄?shù)據(jù)。,時(shí)候我們就需要對(duì)象克隆了,你可能簡(jiǎn)單的以為就是 = 就行了,那我們來看一看。
let obj = { a: 1 } let obj2 = obj; console.log(obj); //{a: 1} console.log(obj2); //{a: 1}
我們看到復(fù)制過來了,這樣我們就可以隨便使用了。那我們來修改一下obj看看。
obj.a = 2; console.log(obj); //{a: 2} console.log(obj2); //{a: 2}
發(fā)現(xiàn)都變成了{a: 2},是不是很奇怪。因?yàn)閷?duì)象是引用類型的,他們賦值其實(shí)是賦的地址值,就是他們指向同一個(gè)地方,那么我們應(yīng)該怎么做呢?你應(yīng)該知道怎么遍歷對(duì)象,我們就把它遍歷一遍再?gòu)?fù)制??创a
let obj = { a: 1 } function clone(obj) { let a = {}; for(let o in obj){ a[o] = obj[o] } return a; } let obj2 = clone(obj); console.log(obj); //{a: 1} console.log(obj2); //{a: 1} obj.a = 2; console.log(obj); //{a: 2} console.log(obj2); //{a: 1}
沒有改變,看來我們成功了,那這篇文章就到這了。呵呵,其實(shí)遠(yuǎn)沒有,我們來看一下有沒有什么問題。
當(dāng)里面的數(shù)據(jù)為引用類型時(shí):
let obj = { a: { b: 1 }, c: 3 } function clone(obj) { let a = {}; for(let o in obj){ a[o] = obj[o] } return a; } let obj2 = clone(obj); console.log(obj); console.log(obj2); obj.a .b = 2; console.log(obj); console.log(obj2);
我們發(fā)現(xiàn)
又出問題了。
如果你知道for...in你就會(huì)知道它的另一個(gè)錯(cuò)誤。就是它會(huì)遍歷它原型上的可枚舉屬性和非Symbol的屬性。那么我們?cè)趺锤纳埔幌履兀楷F(xiàn)在介紹一下hasOwnProperty這個(gè)屬性,它就是判斷自身有沒有這個(gè)屬性,而不會(huì)去原型上找。
function clone(obj) { let a = {}; for(let o in obj){ if(obj.hasOwnProperty(o)){ a[o] = obj[o]; } } return a; }
這個(gè)問題解決了,就差上一個(gè)了,我們接著用判斷數(shù)據(jù)的類型來判斷是否還需要復(fù)制的方法解決上一個(gè)問題。
let obj = { a: { b: 1, d: { e:[{f: 2}], g: { h:{ l: 5 } } } }, c: 3 } function deepClone(origin, target) { let tar = target || {}, arr = "[object Array]", str = Object.prototype.toString; for(let o in origin){ if(origin.hasOwnProperty(o)){ // 如果是對(duì)象接著遞歸復(fù)制 if(typeof origin[o] === "object"){ // 判斷是對(duì)象還是數(shù)組 tar[o] = str.call(origin[o]) === arr ? [] : {}; deepClone(origin[o], tar[o]); }else{ tar[o] = origin[o]; } } } return tar; } let obj2 = deepClone(obj, {}); console.log(obj); console.log(obj2); obj.a.d.g.h.l = 6; console.log(obj.a.d.g.h.l); //6 console.log(obj2.a.d.g.h.l); //5
其實(shí)這還不是最終的深克隆,因?yàn)檫@一個(gè)也有它自己的問題,但是面對(duì)一般的情況應(yīng)該沒問題,跟高級(jí)的用法請(qǐng)自行學(xué)習(xí)。
模擬實(shí)現(xiàn)JQ的$.extend()方法(只是粗略的寫了一下,如有錯(cuò)誤歡迎指出):
function extend() { let origin, // 要拷貝的源 target = arguments[0], // 獲取第一個(gè)參數(shù) isDeepClone = false; // 是否深拷貝 length = arguments.length, //拷貝的個(gè)數(shù) arr = "[object Array]", str = Object.prototype.toString, i = 0; if(typeof target === "boolean"){ isDeepClone = target; i ++; target = arguments[i]; //獲取目標(biāo)元素 } //防止循環(huán)引用 if(origin === target){ return; } // 兼容function if(typeof target !== "object" && typeof target !== "function" ){ target = {}; } for ( ; i < length; i++) { origin = arguments[i]; for(let o in origin){ if(origin.hasOwnProperty(o)){ if(origin[o] === "object"){ if(isDeepClone){ target[o] = str.call(origin[o]) === arr ? [] : {}; extend(true, target[o], origin[o]); } }else{ target[o] = origin[o]; } } } } return target; }
補(bǔ)充:其實(shí)不止這一種深克隆的方法,不如我們處理數(shù)據(jù)最常使用的JSON
let obj = { a: { b: function (argument) { }, d: { e:[{f: 2}], g: { h:{ l: 5 } } } }, c: 3 } let r = JSON.stringify(obj); r = JSON.parse(r); obj.a.d.g.h.l = 6; console.log(r.a.d.g.h.l); // 5
也是可以的,我們輸出一下r看看。
有沒有發(fā)現(xiàn)少了什么?對(duì),就是function,它不僅不能復(fù)制function還有undefined也不行,還有別的自己查一下吧。
3.數(shù)組克隆
有了上面的鋪墊,我們知道數(shù)組也是引用類型,就不能簡(jiǎn)單的等于來復(fù)制。
concat
let arr = [8,5,6,6,8]; let arr2 = arr.concat(); arr2[3] = 1; console.log(arr); //[8, 5, 6, 6, 8] console.log(arr2); //[8, 5, 6, 1, 8]
可以復(fù)制成功,那么引用類型呢?
let arr = [8,{a: 1},6,6,8]; let arr2 = arr.concat(); arr2[1].a = 2; console.log(arr); console.log(arr2);
還有我們常用的slice也是一樣
let arr = [8,{a: 1},6,6,8]; let arr2 = arr.slice(); arr2[1].a = 2; arr2[2] = 2; console.log(arr); console.log(arr2);
還有一些別的方法,我就不一一列舉了,這些都是淺復(fù)制。
如果想深度克隆數(shù)組,也可以使用上面介紹的使用JSON也是可以的。
let arr = [8,{a: 1},6,6,8]; let arr2 = JSON.parse( JSON.stringify(arr) ); arr2[1].a = 2; arr2[2] = 2; console.log(arr); console.log(arr2);
目前想到的就這些,總感覺拉下了什么,如果我想起來了我會(huì)繼續(xù)補(bǔ)充的。
4.閑聊
上面寫的我意猶未盡,可能是自己知識(shí)的局限性暫時(shí)只能想到那些,上面說到了for...in,那么我們來簡(jiǎn)單的說一下for...of和它的區(qū)別。
他們都是遍歷用的,每次遍歷數(shù)組和對(duì)象都會(huì)想起它們,那么你會(huì)不會(huì)弄混呢。
那我們直接遍歷一次,看看有什么區(qū)別。
let arr = [8,{a: 1},6,6,8]; let a = { b:1, r: 8, h:{ e:6 } } console.log("for...of"); for(let i of arr){ console.log(i); } console.log("for...in"); for(let i in a){ console.log("key:" + i); }
是不是感覺挺好的,我們?cè)賮砜纯础?/p>
let arr = [8,{a: 1},6,6,8]; let a = { b:1, r: 8, h:{ e:6 } } console.log("for...in遍歷數(shù)組"); for(let i in arr){ console.log(i); } console.log("for...of遍歷對(duì)象"); for(let i of a){ console.log("key:" + i); }
for...of遍歷對(duì)象直接報(bào)錯(cuò)了,你有沒有注意到報(bào)錯(cuò)的信息。就是不可遍歷,因?yàn)椴皇莍terable。數(shù)組、字符串、Set、Map,內(nèi)置好了Iterator(迭代器),它們的原型中都有一個(gè)Symbol.iterator方法,而Object對(duì)象并沒有實(shí)現(xiàn)這個(gè)接口,使得它無法被for...of遍歷。
至于for...of遍歷對(duì)象就需要實(shí)現(xiàn)Iterator,這里我就不寫了,百度好多。
for...in遍歷數(shù)組遍歷出來的是索引。不過我們也可以得到數(shù)組的值。
for(let i in arr){ console.log(arr[i]); //[8,{a: 1},6,6,8] }
我覺得你看到這里應(yīng)該知道遍歷什么用哪個(gè)更合適了。
補(bǔ)充
__proto__的實(shí)現(xiàn)
Object.defineProperty(Object.prototype, __proto__, { get: function(){ return Object.getPrototypeOf(this); }, set: function(ob){ Object.setPrototypeOf(this, ob); return ob;
}) ```
下一篇文章我想說一下數(shù)組去重好像不是最全的數(shù)組去重方法,因?yàn)閮?nèi)容挺多,我就不一起寫了,喜歡的可以點(diǎn)一個(gè)贊,或者關(guān)注一下。鼓勵(lì)一下一名自學(xué)前端的大學(xué)生。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/100699.html