一、JS的基本数据类型
- 基本数据类型:String,Boolean,Number,Undefined,Null;
- 引用数据类型:Object(Array,Date,RegExp,Function);
- 基本数据类型和引用数据类型的区别:
1、保存位置不同:基本数据类型保存在栈内存中,引用数据类型保存在堆内存中,然后在栈内存中保存了一个对堆内存中实际对象的引用,即数据在堆内存中的地址,JS对引用数据类型的操作都是操作对象的引用而不是实际的对象,例如复制的实质是复制了地址,因而它们指向了同一个堆内存对象;
为什么基本数据类型保存在栈中,而引用数据类型保存在堆中?
1)堆比栈大,栈比堆速度快;
2)基本数据类型比较稳定,而且相对来说占用的内存小;
3)引用数据类型大小是动态的,而且是无限的,引用值的大小会改变,不能把它放在栈中,否则会降低变量查找的速度,因此放在变量栈空间的值是该对象存储在堆中的地址,地址的大小是固定的,所以把它存储在栈中对变量性能无任何负面影响;
4)堆内存是无序存储,可以根据引用直接获取;
按引用访问:js不允许直接访问保存在堆内存中的对象,所以在访问一个对象时,首先得到的是这个对象在堆内存中的地址,然后再按照这个地址去获得这个对象中的值;
2、使用typeof
可以返回基本数据类型,但是NULL类型会返回object,因此null值表示一个空对象指针;引用数据类型使用typeof会返回object,此时需要使用instanceof
来检测
typeof 1 //number
typeof new Number(1) //object
1 instanceof Number //false
new Number(1) instanceof Number //true
其中1是基本数据类型,不是引用数据类型,因此instanceof
总为false
通过new Number(1)
包装以后成为引用数据类型,可以判断为Number
3、引用数据类型 (注意,例如new Boolean(false)和false是不===的,但是经过JSON转换后,丢失了constructor,变为普通的false)
- ES6新增数据类型:Map,Set,Generator,Symbol
- 本地对象:ECMA-262 把本地对象(native object)定义为“独立于宿主环境的 ECMAScript 实现提供的对象”,即本地对象就是 ECMA-262 定义的类(引用类型);
- 宿主对象:宿主”就是我们网页的运行环境,即“操作系统”和“浏览器”,所有非本地对象都是宿主对象(host object),即由 ECMAScript 实现的宿主环境提供的对象,所有的BOM和DOM对象都是宿主对象,因为其对于不同的“宿主”环境所展示的内容不同,即ECMAScript官方未定义的对象都属于宿主对象,因为其未定义的对象大多数是自己通过ECMAScript程序创建的对象;
- JS内置对象:是指JS语言自带的一些对象,供开发者使用,这些对象提供了一些常用的或是最基本而必要的功能;
1、Arguments:函数参数集合;
2、Array对象:length,instanceof,isArray(),toString()返回字符串,valueOf()返回数组的值,join()可以将数组转为字符串,push(),pop(),shift(),unshift(),reverse(),sort(),
slice(),splice(),indexOf(),lastIndexOf(),迭every(),filter(),forEach(),map(),some(),
归并方法reduce(),reduceRight();
3、Boolean:布尔对象;
4、Error:异常对象;
5、Number:数值对象;
6、String对象:length,charAt()返回指定位置的字符,concat(),slice(),subString(),
subStr(),indexOf(),lastIndexOf(),trim(),toLowerCase(),toUpperCase(),split(),
text.match(),text.splice();
7、Date对象:toUTCstring(),getTime();
8、RegExp对象:test();
9、Function对象:arguments,this,apply(this,arguments),call(this,num1,num2);
10、Math对象:min(),max(),ceil(),floor(),round(),random();
11、Global对象:encodeURI,encodeURIComponent,parseInt(),eval();
12、Object对象:prototype,constructor; - 基本包装类型:Boolean,Number,String
为了便于操作“基本类型值”,JS 提供了 三个 特殊的引用类型:Boolean、Number、String。这些类型和其他引用类型相似,但同时 也具备与各自基本类型相应的特殊行为。实际上:每当读取一个基本类型值的时候, “后台就会创建一个对应的基本包装类型的对象”,从而能够调用一些方法来操作这些数据。
当通过直接赋值方式创建时,具有基本类型的性质,无法动态添加属性和方法。
当通过New修饰符创建时,属于引用类型,可以动态添加属性和方法。
二、赋值、浅拷贝与深拷贝
赋值
赋的其实是该对象的在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,两个对象是完全联动的。
浅拷贝
按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。
Object.assign()
和数组的slice()
、concat()
仅对基本类型数据进行了深拷贝扩展运算符
let bar = {...baz};
- 遍历赋值
function shallowCopy(src) {
var dst = {};
for (var prop in src) {
if (src.hasOwnProperty(prop)) {
dst[prop] = src[prop];
}
}
return dst;
}
深拷贝
创造一个一模一样的对象,新对象跟原对象不共享内存,修改不会互相影响。
-
JSON.parse(JSON.stringify())
可以部分实现深拷贝,但会失去constructor,凡是undefined、function、symbol、Map, Set, RegExp, Date, ArrayBuffer 等内置类型在进行序列化时会丢失,Boolean、Number、String等基本包装类型会变成普通的基本数据类型。此外不能处理多重嵌套。
let a = {};
let b = {a};
a.b = b;
let copy = JSON.parse(JSON.stringify(a));//报错
- 采用函数库lodash提供的
_.cloneDeep
进行深拷贝
var obj2 = _.cloneDeep(obj1);
- 采用jquery提供的
$.extend
进行深拷贝
var obj2 = $.extend(true, {}, obj1);
- 利用
MessageChannel
发送并接收消息,作为复制结果。缺点是该操作为异步,且依然不支持function。
function structuralClone(obj) {
return new Promise(resolve =>{
const {port1, port2} = new MessageChannel();
//两个port可以互相通过onmessage ,监听到对方postMessage的内容
port2.onmessage = ev => resolve(ev.data);
port1.postMessage(obj);
})
}
const obj = /* ... */;
structuralClone(obj).then(res=>{
console.log(res);
})
- 利用history api中的
history.state
传递并接收数据,作为复制结果。该方法是同步的,但有些浏览器对调用频率有限制。 - 遍历对象、数组直到都是基本数据类型,然后再进行复制
// 定义检测数据类型的功能函数
function checkedType(target) {
return Object.prototype.toString.call(target).slice(8, -1)
}
// 实现深度克隆---对象/数组
function clone(target) {
// 判断拷贝的数据类型
// 初始化变量result 成为最终克隆的数据
let result, targetType = checkedType(target)
if (targetType === 'object') {
result = {}
} else if (targetType === 'Array') {
result = []
} else {
return target
}
// 遍历目标数据
for (let i in target) {
// 获取遍历数据结构的每一项值。
let value = target[i]
// 判断目标结构里的每一值是否存在对象/数组
if (checkedType(value) === 'Object' ||
checkedType(value) === 'Array') { //对象/数组里嵌套了对象/数组
// 继续遍历获取到value值
result[i] = clone(value)
} else {
// 获取到value值是基本的数据类型或者是函数。
result[i] = value;
}
}
return result
}
// 定义检测数据类型的功能函数
function checkedType(target) {
return Object.prototype.toString.call(target).slice(8, -1)
}
// 实现深度克隆---对象/数组
function clone(target) {
// 判断拷贝的数据类型
// 初始化变量result 成为最终克隆的数据
let result, targetType = checkedType(target)
if (targetType === 'object') {
result = {}
} else if (targetType === 'Array') {
result = []
} else {
return target
}
// 遍历目标数据
for (let i in target) {
// 获取遍历数据结构的每一项值。
let value = target[i]
// 判断目标结构里的每一值是否存在对象/数组
if (checkedType(value) === 'Object' ||
checkedType(value) === 'Array') {
// 对象/数组里嵌套了对象/数组
// 继续遍历获取到value值
result[i] = clone(value)
} else {
// 获取到value值是基本的数据类型或者是函数。
result[i] = value;
}
}
return result
}