前端基础 JS篇05

352 阅读4分钟

这是我参与11月更文挑战的第25天,活动详情查看:2021最后一次更文挑战

前言

之前都是忙于业务开发,对前端的基本知识都是用到什么查什么,很少有时间沉下心来自己归纳,总结下基础,有时候,用到什么,觉得值得留意一下的东西,也都是自己笔记里记录下,也不怎么及时整理,然后随着时间的推移,陆陆续续记录的东西都是乱七八糟的,都没整理过,这次,写这个前端的基础系列,整理一下

打算以轻松,闲谈的调调来聊聊,文笔不好,大家将就的看

快速回顾

上文讲了

  1. == 和 === 差别
  2. 讲解闭包

深拷贝和浅拷贝

在讲对象的时候,我们说过在,对象类型在赋值的过程中其实是复制了地址,从而会导致改变了一方其他也都被改变的情况。通常在开发中我们不希望出现这样的问题,我们可以使用浅拷贝来解决这个情况。

let a = { 
    age: 1
} 

let b = a 
a.age = 2 
console.log(b.age) // 2

浅拷贝

首先可以通过 Object.assign 来解决这个问题,很多人认为这个函数是用来深拷贝的。其实并不是,Object.assign 只会拷贝所有的属性值到新的对象中,如果属性值是对象的话,拷贝的是地址,所以并不是深拷贝。

let a = { 
    age: 1 
} 

let b = Object.assign({}, a) 
a.age = 2 
console.log(b.age) // 1

另外我们还可以通过展开运算符 ... 来实现浅拷贝

let a = { 
    age: 1
} 

let b = { ...a } 
a.age = 2 
console.log(b.age) // 1

通常浅拷贝就能解决大部分问题了,但是当我们遇到如下情况就可能需要使用到深拷贝了

but, 事情到这里还没结束,我们来看下面的代码:

let a = { 
    age: 1, 
    jobs: { 
        first: 'FE'
    }
}

let b = { ...a } 
a.jobs.first = 'native' 
console.log(b.jobs.first) // native

解释上面的代码:

浅拷贝只解决了第一层的问题,如果接下去的值中还有对象的话,那么就又回到最开始的话题了,两者享有相同的地址。要解决这个问题,我们就得使用深拷贝了。

深拷贝

这个问题通常可以通过 JSON.parse(JSON.stringify(object)) 来解决。

let a = { 
    age: 1, 
    jobs: { 
        first: 'FE'
    }
} 

let b = JSON.parse(JSON.stringify(a)) 
a.jobs.first = 'native' 
console.log(b.jobs.first) // FE

但是该方法也是有局限性的:

  • 会忽略 undefined
  • 会忽略 symbol
  • 不能序列化函数
  • 不能解决循环引用的对象
let obj = { 
    a: 1, 
    b: { 
        c: 2,
        d: 3,
    },
} 

obj.c = obj.b 
obj.e = obj.a 
obj.b.c = obj.c 
obj.b.d = obj.b 
obj.b.e = obj.b.c 

let newObj = JSON.parse(JSON.stringify(obj)) 
console.log(newObj)

如果你有这么一个循环引用对象,你会发现并不能通过该方法实现深拷贝

image.png

在遇到函数、 undefined 或者 symbol 的时候,该对象也不能正常的序列化

let a = { 
    age: undefined, 
    sex: Symbol('male'), 
    jobs: function() {}, 
    name: 'aaa' 
}

let b = JSON.parse(JSON.stringify(a)) 
console.log(b) // {name: "aaa"}

你会发现在上述情况中,该方法会忽略掉函数和 undefined 。

但是在通常情况下,复杂数据都是可以序列化的,所以这个函数可以解决大部分问题。

如果你所需拷贝的对象含有内置类型并且不包含函数,可以使用 MessageChannel

function structuralClone(obj) {
    return new Promise(resolve => { 
        const { port1, port2 } = new MessageChannel()
        port2.onmessage = ev => resolve(ev.data)
        port1.postMessage(obj)
    })
}

var obj = { 
    a: 1,
    b: { c: 2 } 
}
obj.b.d = obj.b 

// 注意该方法是异步的 
// 可以处理 undefined 和循环引用对象 

const test = async () => { 
    const clone = await structuralClone(obj) 
    console.log(clone) 
} 

test()

当然你可能想自己来实现一个深拷贝,但是其实实现一个深拷贝是很困难的,需要我们考虑好多种边界情况,比如原型链如何处理、DOM 如何处理等等,所以这里我们实现的深拷贝只是简易版

ps.如果想要深入了解深靠谱,推荐大家去看下lodash的深拷贝

简易版深拷贝

function deepClone(obj) { 
    function isObject(o) { 
        return (typeof o === 'object' || typeof o === 'function') && o !== null 
    }
    
    if (!isObject(obj)) {
        throw new Error('非对象') 
    } 
    
    let isArray = Array.isArray(obj) 
    let newObj = isArray ? [...obj] : { ...obj } 
    Reflect.ownKeys(newObj).forEach(key => { 
        newObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key] 
     })
     
     return newObj 
 }
 
 let obj = { 
     a: [1, 2, 3], 
     b: { c: 2, d: 3 } 
 } 
 
 let newObj = deepClone(obj) 
 newObj.b.c = 1 
 console.log(obj.b.c) // 2

总结

  1. 浅拷贝
  2. 深拷贝

ps: 干巴巴的文字讲了还是比较枯燥,并且难以印象深刻的,大家还是日常使用中,多多体会