一、数据类型
js中的数据类型:number、string、boolean、undefined、object、function、Symbol
根据各个类型的特性,我们会将这些类型分为两大类:基本类型、引用类型
基本类型:number、string、boolean、undefined、Symbol、null
引用类型:Array、Object、Function
二、基本类型和引用类型的区别
1、存储方式不同
存储数据的内存空间有两个:栈、堆
基本类型:将值直接存储在栈内存中
例:
let a = 1 // 基本类型的定义
在内存中:
引用类型:将值存储在堆中,将堆中存储数据的内存地址,存储在栈中
例:
let arr = [1, 2, 3] // 定义了引用类型
在内存中:
2、赋值时的操作不同
基本类型:将变量a赋值给变量b,是将变量a的值,复制一份,放在b栈空间中
例:
let a = 1 let b = a // 基本类型的赋值 a = 2 console.log(b) // 1
在内存中:
基本类型赋值后,改变其中一个的值,另一个的值没有被重新复制,所以另一个的值是不变的。
引用类型:将变量a的值赋值给变量b,将变量a在栈中存储的内存地址,复制了一份放在变量b的栈内存中了
例:
let arr = [1, 2, 3] let brr = arr
在内存中:
两个变来那个共享同一个堆内存,只要一个变量将其中的数据改变,另一个变量也就改变了。
3、比较方式不同
基本类型:
- 等于判断值是否相同,不比较类型
- 全等先判断类型是否相同,如果类型相同,才会判断其中的值是否相等
引用类型:
- 等于判断栈中存储的内存地址是否相同
- 全等先判断类型,类型相同再判断栈中内存地址的值是否相等
例:
基本类型:
let a = 1 let b = 1 console.log(a == b) // true console.log(a === b) // true
引用类型:
let arr = [1, 2, 3] let brr = [1, 2, 3] console.log(arr == brr) // false
三、深拷贝浅拷贝
基本类型太简单,没有深和浅的区别。所以深拷贝和浅拷贝针对的都是引用类型。
拷贝的原因:在某些操作中,我们需要一个跟原数据一模一样的数据,但希望原数据和新数据完全没有关联。
1、浅拷贝
原数据和新数据不共用同一个堆地址,但其中包含的引用类型数据还会共享同一个地址。
1.1、数组浅拷贝的方式
1.1.1、扩展运算
let arr = [ 11, { name: '张三', age: 12 }, 33 ] let brr = [...arr] console.log(arr === brr) // false ==> arr和brr没有共用同一个堆地址 console.log(arr[1] === brr[1]) // true ==> 其中包含的引用类型还共用同一个地址
浅拷贝只是将外层数据拷贝了一份新的,深层次数据还在关联,没有拷贝。
1.1.2、手动进行遍历
let arr = [ 1, [ 2, 3 ], 4 ] let brr = [] for (let a in arr) { brr.push(arr[a]) } console.log(arr === brr);// false console.log(arr, brr); console.log(arr[1] === brr[1]); // true
1.1.3、slice方法
let arr = [ 1, [ 2, 3 ], 4 ] let brr = arr.slice() console.log(arr === brr);// false console.log(arr, brr); console.log(arr[1] === brr[1]); // true
1.1.4、concat方法
let arr = [ 1, [ 2, 3 ], 4 ] let brr = arr.concat() console.log(arr === brr);// false console.log(arr, brr); console.log(arr[1] === brr[1]); // true
1.1.5、assign方法
let arr = [ 1, [ 2, 3 ], 4 ] let brr = Object.assign([], arr) console.log(arr === brr);// false console.log(arr, brr); console.log(arr[1] === brr[1]); // true
1.2、对象的浅拷贝
1.2.1、手动遍历放在新对象中
let obj = { name: '张三', age: 12, children: ['大毛', '二毛', '小明'] } // 手动遍历 let pbj = {} for(let key in obj) { pbj[key] = obj[key] } console.log(pbj, obj); console.log(pbj === obj); // false console.log( obj.children === pbj.children );
1.2.2、扩展运算
let obj = { name: '张三', age: 12, children: ['大毛', '二毛', '小明'] } // 扩展运算 let pbj = {...obj} console.log(pbj, obj); console.log(pbj === obj); console.log(pbj.children === obj.children);
1.2.3、assign方法
let obj = { name: '张三', age: 12, children: ['大毛', '二毛', '小明'] } // assign方法 let pbj = Object.assign({}, obj) console.log(pbj, obj); console.log(pbj === obj); console.log(pbj.children === obj.children);
2、深拷贝
2.1、利用json转换
let arr = [ 1, [ 2, 3 ], 4 ] let brr = JSON.parse(JSON.stringify(arr)) console.log(arr === brr) // false console.log(arr[1] === brr[1]) // false
注意:利用json转成进行深拷贝,如果原数据中包含函数,转换的时候会被忽略,函数是无法拷贝过来的。
2.2、手动封装递归函数实现深拷贝
function deepCopy(data) { // 定义最终返回的数据,初始值为null(后面需要根据原数据的类型,决定这个数据的类型) let newData = null // 判断如果原数据是数组 if (Object.prototype.toString.call(data) === '[object Array]') { newData = [] // 最终返回的数据也应该是数组 } else if (Object.prototype.toString.call(data) === '[object Object]') { // 原数据是对象 newData = {} // 最终返回的数据也应该是对象 } else { // 原数据既不是数组也不是对象 ===> 将原数据返回出来就是一种数据的拷贝 return data } // 遍历原数据 for (let key in data) { if (typeof data[key] === 'object') { // 判断其中包含的数据的类型是否是数组或对象 // 调用递归函数,处理包含的数组或对象,得到一个新的数据,放在最终返回的数据中 newData[key] = deepCopy(data[key]) } else { // 遍历出来的是一个基本数据,就直接放在最终返回的数据中 newData[key] = data[key] } } // 将最终返回的数据返回出去 return newData }