在学习对js的array、number、objects、string的操作时,我们可以对比loadash工具库的实现,在实际开发过程中进行使用。
在开始之前我们先明确一个概念,js的栈与堆。栈与堆都是js的数据结构,栈的空间较小,用来存储基础类型的数据,例如string,number;堆空间较大,用来存储大的数据,例如function、object等。
在执行js的时候,会先创建一个上下文,这里就是一个栈结构
var a = 1
var b = {}
// 上下文环境存储的是
// 键 a : 值 1
// 键 b : 值 对象的引用地址
一、浅合并
浅合并通俗来理解就是只合并第一层,不对相同属性做递归合并处理,而是直接替换,比如以下示例
let obj1 = {
a: 'a1',
b: 'b1',
c: {
d: 'c1',
e: 'c2'
}
}
let obj2 = {
a: 'a2',
c: {
d: 'c3'
}
}
console.log(shallowMerge(obj1, obj2))
// 输出
{
a: 'a2',
b: 'b1',
c: {
d: 'c3'
}
}
实现浅合并思想如下:
- 边界值判定,如果object1不是对象,使用object2替换object1
- 如果object1是对象,object2不是对象,返回object1
- 如果object1是对象且object2是对象,遍历object2进行合并,遇到相同属性直接进行替换
js已经替我们实现了Object.assign,但是会有一些边界值问题,我们可以进行自定义实现
// 是否是引用类型,具体指使用typeof类型为object,并且不是null的值
function isObjectLike(value) {
return typeof value === 'object' && value !== null
}
// 使用Object.prototype.toString获取表示该对象的字符串,例如[object Array]
// 在es5之后,toString方法已经可以返回正确的类型,null对应[object Null]
const toString = Object.prototype.toString
function getTag(value) {
return toString.call(value)
}
// 判断是否是普通对象,typeof性能会更好
function isPlainObject(value) {
if (!isObjectLike(value) || getTag(value) != '[object Object]') {
return false
}
// 例如:Object.create(null)
if (Object.getPrototypeOf(value) === null) {
return true
}
// 循环遍历对象,如果是自定义构造器实例化的object则返回false
let proto = value
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto)
}
return Object.getPrototypeOf(value) === proto
}
// 浅合并
shallowMerge (obj1, obj2) {
let isPlain1 = isPlainObject(obj1)
let isPlain2 = isPlainObject(obj2)
// 1.边界值判定,如果object1不是对象,使用object2替换object1
if(!isPlain1){
return obj2
}
// 2.如果object1是对象,object2不是对象,返回object1
if(!isPlain2){
return obj1
}
// 3.如果object1是对象且object2是对象,遍历object2进行合并
// Object.keys 获取可枚举普通键
// Object.getOwnPropertyNames 获取除symbol以外的所有键
// Object.getOwnPropertySymbols 获取symbol键
// 根据Object.assign定义,它会合并Object.keys与Object.getOwnPropertySymbols的所有值
[...Object.keys(obj2),...Object.getOwnPropertySymbols(obj2)].forEach((key)=>{
obj1[key] = obj2[key]
})
return obj1
}
二、深合并
深合并也就是要进行递归合并,将对象的所有子属性也进行合并,代码演示如下:
let obj1 = {
a: 'a1',
b: 'b1',
c: {
d: 'c1',
e: 'c2'
}
}
let obj2 = {
a: 'a2',
c: {
d: 'c3'
}
}
console.log(deepMerge(obj1, obj2))
// 输出
{
a: 'a2',
b: 'b1',
c: {
d: 'c3',
e: 'c2'
}
}
实现深合并思想如下:
- 边界值判定,如果object1不是对象,使用object2替换object1
- 如果object1是对象,object2不是对象,返回object1
- 如果object1是对象且object2是对象,遍历object2进行合并,遇到键相同的普通对象值,递归合并,其他直接进行替换 与浅拷贝唯一的区别就是赋值的时候判断一下值类型,进行递归调用
deepMerge (obj1, obj2) {
let isPlain1 = isPlainObject(obj1)
let isPlain2 = isPlainObject(obj2)
if(!isPlain1){
return obj2
}
if(!isPlain2){
return obj1
}
[...Object.keys(obj2),...Object.getOwnPropertySymbols(obj2)].forEach((key)=>{
//与浅拷贝区别之处
obj1[key] = deepMerge(obj1[key],obj2[key])
})
return obj1
}
三、浅拷贝
浅拷贝也就是只拷贝一层,对于非基础类型直接引用,其中任一对象属性的改变会影响到另一对象,示例如下:
let obj1 = {
a: 'a1',
c: {
d: 'c1'
}
}
let obj2 = shallowClone(obj1)
obj1.a = 'a2'
obj1.c.d = 'c2'
console.log(obj2)
// 输出
{
a: 'a1',
c: {
d: 'c2'
}
}
浅拷贝实现思想
- 对obj1进行遍历,然后将基础类型值直接赋值给obj2,将引用类型的引用地址赋值给obj2
shallowClone(obj){
let result = {}
// for...in以任意顺序遍历一个对象的除[Symbol]以外的[可枚举]属性,包括继承的可枚举属性
for (const key in obj){
// hasOwnProperty会返回一个布尔值,指示对象自身属性中是否具有指定的属性
if(obj.hasOwnProperty(key)){
result[key] = obj[key]
}
}
return result
}
三、深拷贝
深拷贝就是对对象所有属性进行递归遍历,且拷贝对象之间的值不会有相互影响。 示例如下
let obj1 = {
a: 'a1',
c: {
d: 'c1'
}
}
let obj2 = deepClone(obj1)
obj1.a = 'a2'
obj1.c.d = 'c2'
console.log(obj2)
// 输出
{
a: 'a1',
c: {
d: 'c1'
}
}
最简单的深拷贝使用Object.parse(Object.stringfy()),缺点是不能处理函数与正则,拷贝出来的值会变成null与空对象。
深拷贝实现思想
- 要对子元素的类型先进行分类
- 如果是基础类型则直接赋值
- 如果是引用类型则根据不同情况做处理
- 对于普通对象做递归拷贝
// 将所有类型罗列
// 基础类型
const stringTag = '[object String]'
const symbolTag = '[object Symbol]'
const numberTag = '[object Number]'
const boolTag = '[object Boolean]'
const nullTag = '[object Null]'
const undefinedTag = '[object Undefined]'
// 内置对象
const argsTag = '[object Arguments]'
const arrayTag = '[object Array]'
const dateTag = '[object Date]'
const errorTag = '[object Error]'
const mapTag = '[object Map]'
const objectTag = '[object Object]'
const regexpTag = '[object RegExp]'
const setTag = '[object Set]'
const weakMapTag = '[object WeakMap]'
...
function deepClone(obj, hash = new WeakMap()) {
// 防止循环引用,缓存引用地址,如果出现过则直接返回结果
if (hash.get(obj)) return hash.get(obj);
// 基础类型与function直接返回值,function的拷贝没有考虑this指向问题
if(typeof obj !== 'object' || obj === null){
return obj
}
// 对日期和正则做特殊处理,如果数据复杂,可以针对需要支持的内置对象做响应处理
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
// 对其他引用对象做处理
let cloneObj = new obj.constructor()
hash.set(obj, cloneObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 实现一个递归拷贝
cloneObj[key] = deepClone(obj[key])
}
}
return cloneObj;
}
没有尽善尽美的深拷贝,知晓其支持程度及相应优缺点用来支持业务功能。