JavaScript的變量類型正則表達式
五種基本變量類型Null, Undefined, Boolean, Number, String,變量都是按值存放的,存放在棧內存中的簡單數據段,能夠直接訪問。對於引用類型,是存放在棧中的對象,變量保存的是一個指針,這個指針指向另外一個位置。當須要訪問引用類型(如對象,數組等)的值時,首先從棧中獲取該對象的地址指針,而後再從堆內存中取得所需的數據。JavaScript存儲對象都是存地址的,因此淺拷貝會致使obj1和obj2只想同一塊內存地址。改變了其中一方的內容,都是在原來的內存上作修改會致使拷貝對象和原對象都發生改變,而深拷貝是開闢一塊新的內存地址,將原對象的各個屬性逐個複製進去。對拷貝對象和原對象各自的操做各不影響。數組
eg1:緩存
//數組拷貝 var arr1 = [1,2,3]; var arr2 = arr1; arr1[0] = "e"; console.log(arr1);//"e,2,3" console.log(arr2);//"e,2,3"
淺拷貝的實現函數
function shallowClone(copyObj) { var obj = {}; for(var i in copyObj) { obj[i] = copyObj[i]; } } var x = { a:1, b:{d:{e:5}}, c:[1,2,3] }; var y = shallowClone(x); console.log(y.b.d===x.b.d);//true;
2. Object.assign()oop
Object.assign方法用於對象的合併,並將源對象(source)的全部可枚舉屬性,複製到目標對象(target)。當目標對象和源對象有同名屬性時,或多個源對象有同名屬性,則後面的屬性會覆蓋前面的屬性。測試
const target = {a: 1,b: 1}; const source1 = {b: 2}; const source2 = {c: 3}; Object.assign(target, source1, source2); console.log(target) //{a: 1, b: 2, c: 3}
Object.assign()拷貝屬性是有限制的,只拷貝原對象的自身屬性(不拷貝繼承屬性),也不拷貝不可枚舉的屬性(enumerable: false)this
Object.assign({b: 'c'}, Object.defineProperty({}, 'invisible', { enumerable: false, value: 'hello' }) ) //{b: 'c'}
Object.assign
方法實行的是淺拷貝,而不是深拷貝。也就是說,若是源對象某個屬性的值是對象,那麼目標對象拷貝獲得的是這個對象的引用。spa
const obj1 = {a: {b: 5}}; const obj2 = Object.assign({}, x); obj1.a.b=2;
console.log(obj2.a.b) //2
上面代碼中,源對象obj1
的a
屬性的值是一個對象,Object.assign
拷貝獲得的是這個對象的引用。這個對象的任何變化,都會反映到目標對象上面。prototype
//採用這種方法能夠克隆它繼承的值 function clone(origin){ let originProto = Object.getPrototypeOf(origin); return Object.assign(Object.create(originProto), origin); }
深拷貝的實現指針
1.Array的slice和concat方法
Array的slice和concat方法不修改原數組,只會返回一個淺複製了原數組中的元素的一個新數組。之因此把它放在深拷貝里是由於它表現得像個深拷貝其實是淺拷貝。原數組的元素會按照下述規則拷貝:
var arr = [1, 2, 3]; var arr_shallow = arr; var arr_concat = arr.concat(); var arr_slice = arr.slice(0); console.log(arr===arr_shallow);//true; console.log(arr==arr_slice);//false; console.log(arr===arr_concat);//false;
能夠看到,concat和slice返回不一樣的數組實例,這與直接引用複製是不一樣的。而從另外一個例子能夠看出Array的concat和slice並非真正的深拷貝,數組中的對象元素(Object, Array)只是複製了引用。以下:
var arr = [1, [1,2,3], {name: "knight"}]; var arr_concat = arr.concat(); var arr_slice = arr.slice(0); arr_concat[1][0] = 5;//改變arr_concat中數組元素的值 console.log(arr[1]);//[5,2,3]; console.log(arr_slice[1]);//[5,2,3]; arr_slice[2].name = "knightboy";//改變arr_slice中對象元素的值 console.log(arr[2].name);//knightboy console.log(arr_concat[2].name);//knightboy
1.1 遞歸實現對象的深拷貝
function deepCopy(obj) { var target = Array.isArray(obj) ? [] : {}; for(var key in obj){ if(typeof(obj[key]) == 'object' || Array.isArray(obj[key])) target[key] = deepCopy(obj[key]); else target[key] = obj[key]; } return target; }
2. JSON對象的parse和stringify
JSON對象parse方法能夠將JSON字符串反序列化成JS對象,stringify方法能夠將JS對象序列化成JSON字符串,藉助這兩個方法,能夠實現對象的深拷貝
//eg1
var source = {name: "source", child: {name: "child"}}; var target = JSON.parse(JSON.stringify(source)); target.name = "target";//改變target的name屬性 console.log(source.name); //source console.log(target.name);//target target.child.name = "target child";//改變target的child console.log(source.child.name);//child console.log(target.child.name);//target child
//eg2
var source = {name: function(){console.log(1);}, child:{name: "child"}}; var target = JSON.parse(JSON.stringify(source)); console.log(target.name);//undefined;
//eg3
var source = {name: function() {console.log(1);}, child:new RegExp("e")}; var target = JSON.parse(JSON.stringify(source)); console.log(target.name);//undefined console.log(target.child);//Object {}
這種方法使用較爲簡單,能夠知足基本的深拷貝需求,並且可以處理JSON格式能表示的全部數據類型,可是對於正則表達式類型、函數類型等沒法進行深拷貝(並且會直接丟失相應的值)。還有一點很差的地方是它會拋棄對象的constructor。也就是深拷貝以後,無論這個對象原來的構造函數是什麼,在深拷貝以後都會變成Object。同時若是對象中存在循環引用的狀況也沒法正確處理。
3. jQuery.extend()方法源碼實現
/**
*$.extend([deep], clone, copy)
*[options]用來緩存arguments[i]
*[name]用來接收將要被擴展對象的key
*[src]表示改變以前,target對象上每一個key所對應的value,即target[name]
*[copy]表示傳入對象上每一個key所對應的value, 即options[name]
*[copyIsArray]斷定copy是否爲一個數組
*[clone]深拷貝中用來臨時存儲對象或數組的src
*/
jQuery.extend = jQuery.fn.extend = function( ) { var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {}; i=1, length = arguments.length, deep = false; //Handle a deep copy situation if(typeof target === "boolean") { deep = target; //Skip the boolean and target target = arguments[i] || {}; i++; } //Handle case when target is a string or something(possible in deep copy) if(typeof target!="object" && !isFunction(target)) { target = {}; } //Extend jQuery itself if only one argument is passed if(i === length) { target = this; i--; } for(; i< length; i++) { //Only deal with non-null/undefined values if((options = arguments[i])!=null) { //Extend the base object for(name in options) { src = target[name]; copy = options[name]; //Prevent never-ending loop such as var i = {}; i.a = i; $.extend(true, {}, i); if(target === copy) { continue; } //Recurse if we're merging plain objects or arrays if(deep && copy && (jQuery.isPlainObject(copy) || (copyIsArray = Array.isArray(copy)))) { if(copyIsArray) { copyIsArray = false; clone = src && Array.isArray(src) ? src : []; else { clone = src && jQuery.isPlainObject(src) ? src : {}; } //Never move original objects, clone them target[name] = jQuery.extend(deep, clone, copy); //Don't bring in undefined values } else if(copy !== undefined) { target[name] = copy;//if find name, cover it,or make it; } } } } //Return the modified object return target; };
這個方法到目前仍是不能處理原對象內部的循環引用,
var a = {name: "knight"}, b = {name: "knightboy"}; a.child = b; b.parent = a; $.extend(true, {}, a); //Uncaught RangeError: Maximum call stack size exceeded
4. 自定義實現深拷貝
// 偏函數
//判斷類型 boolean var $ = (function() { var $={}; var types = "Array Object String Date RegExp Function Boolean Number Null Undefined".split(" "); function type() { return Object.prototype.toString.call(this).slice(8, -1); } for(var i=types.length;i--;) { $['is' + types[i]] = (function(self) { return function(obj) { return type.call(obj) === self; }; })(types[i]); } return $; })(); function copy(obj, key, deep) { var index = 0; if (obj === null || typeof obj !== "object") { return obj; } var name, value, target = $.isArray(obj) ? [] : {}; for (name in obj) { value = obj[name]; if (value === obj) { continue; } if(index===0) value.copykey = 1; if (deep && ($.isArray(value) || $.isObject(value))) { if (!value.copykey && key!==name) { target[name] = copy(value, name, deep); delete target[name].copykey; delete value.copykey; } else { target[name] = value; delete target[name].copykey; delete value.copykey; } } else { target[name] = value; } index=1; } return target; } //嵌套 const obj1 = { x: { m: 1 }, y: undefined, z: function add(z1, z2) { return z1 + z2; }, a: Symbol("foo") }; const obj2 = copy(obj1,{},true); obj2.x.m = 2; //原型鏈 function SupType(){ this.SupProperty = "knight"; } function SubType(){ this.SubProperty = 19; } SupType.prototype.getSupValue = function(){ return this.SupProperty; }; SubType.prototype = new SupType(); var instance = new SubType(); let origin = instance.getSupValue(); //循環引用test1 var a = {name: "knight"}, b = {name: "knightboy"}; a.child = b; b.parent = a; //循環引用test2 function P(obj){ } P.prototype.test = function(){console.log(1)}; var p =new RegExp('/[.]/'); var obj3,obj4; //var obj5=new P(); obj3={a:1,b:2,c:3,d:{a:1,b:2,c:3,d:{a:1,b:2,c:3,d:{a:1,b:2,c:3,d:Date.now(),e:p}}}}; obj4={a:1,b:2,c:3,d:Date.now(),e:p}; obj3.a = obj4; obj4.a = obj3; obj3.f = obj4; obj4.f = obj3; var res = copy(obj3,{},true); console.log("源對象(copy):\n",obj3); console.log("目標對象(clone):\n",res); res.b=5; console.log(res.b=obj3.b); console.log("二次環引用測試():\n",obj4);
//數據類型判斷還有一種更加簡潔的方法: var Type = {}; for(var i=0,type;type=['String','Array','Number','Object','Boolean','Undefined','Null','Symbol','Date','RegExp','Function'][i++];){ (function(type){ Type['is'+ type] = function(obj){ return Object.prototype.toString.call(obj) === '[object ' + type + ']'; } })(type); } Type.isArray([]);//true