type函數
首先我們要實現一個getType函數對元素進行類型判斷,直接調用Object.prototype.toString
方法。
function getType(obj){ //tostring會返回對應不同的標簽的構造函數 var toString = Object.prototype.toString; var map = { '[object Boolean]' : 'boolean', '[object Number]' : 'number', '[object String]' : 'string', '[object Function]' : 'function', '[object Array]' : 'array', '[object Date]' : 'date', '[object RegExp]' : 'regExp', '[object Undefined]': 'undefined', '[object Null]' : 'null', '[object Object]' : 'object' }; if(obj instanceof Element) { return 'element'; } return map[toString.call(obj)]; }
深拷貝(deepClone)
對於一個引用類型,如果直接將它賦值給另一個變量,由於這兩個引用指向同一個地址,這時改變其中任何一個引用,另一個都會受到影響。當我們想復制一個對象並且切斷與這個對象的聯系,就要使用深拷貝。對於一個對象來說,由於可能有多層結構,所以我們可以使用遞歸來解決這個問題
function deepClone(data){ var type = getType(data); var obj; if(type === 'array'){ obj = []; } else if(type === 'object'){ obj = {}; } else { //不再具有下一層次 return data; } if(type === 'array'){ for(var i = 0, len = data.length; i < len; i++){ obj.push(deepClone(data[i])); } } else if(type === 'object'){ for(var key in data){ obj[key] = deepClone(data[key]); } } return obj; }
對於function類型,這里是直接賦值的,還是共享一個內存值。這是因為函數更多的是完成某些功能,有個輸入值和返回值,而且對於上層業務而言更多的是完成業務功能,並不需要真正將函數深拷貝。
廣度優先遍歷
上面是使用遞歸來進行深拷貝,顯然我們可以使用樹的廣度優先遍歷來實現
//這里為了閱讀方便,只深拷貝對象,關於數組的判斷參照上面的例子 function deepClone(data){ var obj = {}; var originQueue = [data]; var copyQueue = [obj]; //以下兩個隊列用來保存復制過程中訪問過的對象,以此來避免對象環的問題(對象的某個屬性值是對象本身) var visitQueue = []; var copyVisitQueue = []; while(originQueue.length > 0){ var _data = originQueue.shift(); var _obj = copyQueue.shift(); visitQueue.push(_data); copyVisitQueue.push(_obj); for(var key in _data){ var _value = _data[key] if(typeof _value !== 'object'){ _obj[key] = _value; } else { //使用indexOf可以發現數組中是否存在相同的對象(實現indexOf的難點就在於對象比較) var index = visitQueue.indexOf(_value); if(index >= 0){ // 出現環的情況不需要再取出遍歷 _obj[key] = copyVisitQueue[index]; } else { originQueue.push(_value); _obj[key] = {}; copyQueue.push(_obj[key]); } } } } return obj; }
JSON
深拷貝對象還有另一個解決方法,在對象中不含有函數的時候,使用JSON解析反解析就可以得到一個深拷貝對象