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