一、js的数据类型
1.堆和栈
stack为自动分配的内存空间,它由系统自动释放;而heap则是动态分配的内存,大小也不一定会自动释放
2.基本数据类型
存放在栈中的简单数据段,数据大小确定,内存可分配,直接按值存放,所以可以直接按值访问。
js中的基本数据类型有:Number、String、Undefined、Null、Boolean、Symbol
var a = 10;
var b = a;
b = 20;
console.log(a); // 10值
console.log(b); // 20值
下图演示了基本数据类型赋值的过程:
3.引用数据类型
引用数据类型大体分为Object和Function
存在堆内存中的对象,每个空间大小不一样,要根据情况进行特定的配置。
变量其实是保存在栈内存中的一个指针(保存的是堆内存中的引用地址),这个指针指向堆内存,通过这个指针可以快速的访问堆内存中的对象。
var a = new Object();
var b = a;
b.name = "我有名字了";
console.log(a.name); // 我有名字了
从这个例子可以看到,a和b显示出一个共用某个东西的状态,其实它们共用的就是堆内存中的对象,在给a这个变量赋值时,a其实就是一个在栈中指向堆内存对象的指针(保存的是堆内存中对象的引用地址),然后将a赋值给b,其实就相当于将引用地址赋值给了b,相当于告诉b对象在堆中的位置,于是a和b都指向了堆内存中的对象,也就是说他们俩共用了堆内存中的那个对象,这样只要它们之间任意一个对堆内存中的对象进行修改,在另一个上也会显示已修改的状态。
二、检测变量类型
1.检查变量是基本数据类型还是引用类型
typeof():
2.检测是否是数组
instanceof:变量 intanceof 类型; 返回的是布尔值,true 变量是该类型 否则不是。
prototype+toString+call():
var a=[];
Object.prototype.toString.call(a); //返回字符串 "[Object Array]"
//toString() 方法可把一个逻辑值转换为字符串,并返回结果。
Object.prototype.toString.call(a).indexOf("Array"); //如果变量是纯数组,返回的数字不是-1
Object.prototype.toString.call(a).indexOf("Array")!==-1; //true
Array.prototype.isPrototypeOf(variable):用于指示一个对象是否存在于某个对象的原型链中,如果是返回true,不是返回false,该方法属于Object对象,因为所有对象都继承了Object对象的实例,所以几乎所有的实例对象都可以使用该方法,如果variable的原型链中存在Array对象,就会返回true,也就说明variable是数组类型。
构造函数constructor:动手试试,一个数组a,a.constructor返回的是[Function:Array],也就是说再调用相应的方法验证一下a.constructor中是否有Array就行了,自然而然想到了indexOf()方法,但要注意的是,a.constructor返回的并不是字符串,所以需要用到toString,整理一下,最终的写法是a.constructor.toString.indexOf("Array");如果是数组则返回下标,如果不是则返回-1。
这里还要特别注意一下constructor,因为我原先了解的constructor是在原型链里面,即构造函数.prototype.constructor===构造函数,在这里我们是对一个数组对象直接constructor,效果和原型链里的不一样,需要做一下区分,W3school对constructor属性的定义是返回对创建此对象的数组函数的引用。
另外,前面的方法是将a.constructor的结果转换成字符串再判断,其实可以不转换,直接a.constructor===Array,看返回结果是否是true就可以了。
数组方法isArray():这个很简单 Array.isArray(variable)。
3.检查是否是对象
Object.prototype.toString.call(a)://[Object Object] 与数组判断相同,这里不再详述。
constructor:也与数组判断相同
instanceof:要注意arr instanceof Object也是true
typeof:typeof obj===Object
$.isPlainObject():这是jquery的一个方法,判断指定参数是否是一个纯粹的对象,所谓纯粹对象就是用var a={}或者var a=new Object()两种方式之一创建的对象。
三、浅拷贝和深拷贝
前面我们提到了引用类型时举了一个a和b的例子,当对b赋值a时,b相当于保存了一个引用指针,这个指针指向a所指向的那个对象,这样一来,a和b对那个对象的修改就会相互影响,这就是浅拷贝,即只拷贝了指向对象的指针,却没有在堆内存之开辟一个新的空间复制对象。与之相对的,我如果在堆内存中开辟一个新的空间用来存储与原对象有相同属性内容的对象,再让b指向它,这样我们就做到了将一个对象完完全全的复制了一份,这就是我们说的深拷贝。
深拷贝的几种方法:
数组深拷贝:
1.直接遍历拷贝
var a=[1,2,3,4,5]
function deepClone(arr){
var b=[];
for(var i=0;i<arr.length;i++){
b.push(arr[i]);
}
return b;
}
deepClone(a);
这种方法简单直接,就不多讲了。只需要注意遍历不止可以用常规的for循环,用for...of也可以。
2.slice方法
slice方法返回一个从已有数组中截取一段下来的新数组,而且不改变原数组。当没有设置参数时默认返回原整个原数组。
var a=[1,2,3,4,5];
var b=a.slice();
b.[0]=100;
console.log(a); //[1,2,3,4,5]
console.log(b); //[100,2,3,4,5]
3.concat方法
concat方法用于数组拼接,而且也返回一个新数组,并不会改变原数组。没设置参数时返回原数组+空数组,也就是相当于整个复制了原数组。
var a=[1,2,3,4,5];
var b=a.concat();
b.[0]=100;
console.log(a); //[1,2,3,4,5]
console.log(b); //[100,2,3,4,5]
var a=[
{name:"john"},
{name:"green"},
{name:"mary"},
{name:"lucy"}
];
var b=a.slice();
b[0].name="brank";
console.log(a); //[{name:"brank"},{name:"green"},{name:"mary"},{name:"lucy"}];
console.log(b); //[{name:"brank"},{name:"green"},{name:"mary"},{name:"lucy"}];
这样就不得不提到对象的深拷贝了。
对象深拷贝:
1.直接遍历
2.Object.assign()
Object.assign()用于对象的拼接,它会把参数中指定对象的属性拷贝到目标对象,然后返回目标对象。Object.assign(目标对象,源对象1,源对象2...);
var c={
name:"lily",
age:20,
height:1.67
}
var d=Object.assign({},c);
d.name="white";
console.log(d); //{name:"white",age:20,height:1.67}
console.log(c); //{name:"lily",age:20,height:1.67}
3.ES6扩展运算符
...用于取出参数中所有可遍历属性,拷贝到当前对象中。
var c={
name:"lily",
age:20,
height:1.67
}
var d={...c};
d.name="white";
console.log(d); //{ name: 'white', age: 20, height: 1.67 }
console.log(c); //{ name: 'lily', age: 20, height: 1.67 }
①JSON.parse()和JSON.stringify();
JSON.stringify();可以将对象转化成JSON字符串,而JSON.parse()可以将字符串转化成JS对象。我们可以利用这两个方法的特性进行转换。
var c={
name:"lily",
age:20,
height:1.67
}
var d=JSON.parse(JSON.stringify(c));
d.name="white";
console.log(d); //{ name: 'white', age: 20, height: 1.67 }
console.log(c); //{ name: 'lily', age: 20, height: 1.67 }
要注意的是,这种方法无法处理Function和RegExp,也就是说对象中如果有某个属性值为函数或正则表达式,那么复制的新对象相对应的那个属性不存在或者为空对象。如下:
var a={
name:"lily",
age:function(){
console.log("18");
},
height:new RegExp("text")
};
var b=JSON.parse(JSON.stringify(a));
console.log(b); //{ name: 'lily', height: {} }
此外,这种序列化和反序列化的方式还不支持存在循环引用的对象,如果对存在循环引用的对象进行序列化和反序列化会报出TypeError错误,然后一个Date对象在序列化和反序列化后并不会转化为js对象,而是一个字符串。
②手写递归
var c={
name:"lily",
age:20,
height:1.67
}
function deepClone(obj){
var d=obj.constructor===Array?[]:{};
if(typeof obj!=="object"){
return;
}
for(var i in obj){
typeof obj[i]==="object"?deepClone(obj[i]):d[i]=obj[i];
}
return d;
}
console.log(deepClone(c)); //{ name: 'lily', age: 20, height: 1.67 }
四、深拷贝的相关问题
显而易见,深拷贝可将一个引用类型的数据深度复制,在一些业务中这是很常见的需求,即新对象完全继承旧对象的属性同时新对象的操作不影响旧对象。但是这种操作肯定是有成本的,深拷贝其实很消耗性能
(我们可能只是希望改变新数组里的其中一个元素的时候不影响原数组,但却被迫要把整个原数组都拷贝一遍,这不是一种浪费吗?)
所以,在当业务中深拷贝的需求越来越多时,性能问题就会形成新的瓶颈,我们将不得不考虑性能问题。怎样才能真正实现需求而又尽量不用深拷贝呢?immutable为我们提供了新的解决方案。
通过immutable引入的一套api,我们将达到以下两个目的:
·在改变新数组或对象时不改变原数组对象。
·在大量深拷贝操作中显著地减少性能消耗。
示例:
const { Map } = require('immutable')
const map1 = Map({ a: 1, b: 2, c: 3 })
const map2 = map1.set('b', 50)
map1.get('b') // 2
map2.get('b') // 50