對象的拷貝在js中比較重要,因爲js不同與C++等,沒有指針操作,基本類型都是沒有引用的,只有對象和函數纔有引用,這就造成當我們在拷貝一個對象時,可能會有深淺拷貝之分。
淺拷貝的意思就是隻複製引用(指針),而未複製真正的值。而深拷貝則是改變對象的引用,深拷貝形成的新對象和原來的對象有不一樣的地址。
比如一個對象的定義如下:
var a = {
b: 3
}
這時這個對象只有一層,我們可以用很多中方法都可以實現對象的拷貝,比如Object.assign({}, a)來實現對象的拷貝,因爲對象第一層中的值沒有引用。
Object.assign()
方法用於將所有可枚舉屬性的值從一個或多個源對象複製到目標對象。它將返回目標對象。
但是如果遇到複雜對象,則必須要用到深拷貝,比如我們在react中的setState中的新狀態必須要重新生成一個新的對象(地址),不能有副作用(不能改變原來對象的值)。
此時有很多種方法都可以實現對象的拷貝:
1. JSON.parse(JSON.stringify(obj))
序列化能解決大部分對象拷貝的問題,但是有很多缺點:
(1)會忽略undefined
(2)不能序列化函數
(3)不能解決循環引用的對象
2.MessageChannel
Channel Messaging API的MessageChannel
接口允許我們創建一個新的消息通道,並通過它的兩個MessagePort
屬性發送數據。
我們可以根據這個新的消息通道拷貝一個新的對象,能解決序列化不能忽略undefined和循環引用的問題。
唯一缺點是:不能序列化函數。
function cloneDeep(obj){
retrun Promise(resolve => {
const {port1, port2} = new MessageChannel();
port2.onmessage = ev => resolve)(ev.data);
prot1.postMessage(obj);
})
}
//注意這是異步函數
var obj = {
a: 1,
b: {
c: b
}
}
const cloneObject = await cloneDeep(obj)
3.使用lodash庫中的cloneDeep函數
loadsh cloneDeep,在webpack中需要按需引入,否則打包過大,
使用方式有三種:
(1)使用時按需引入
import { deepClone } from 'lodash/deepClone';
(2)使用插件優化
安裝後在webpack中配置
var LodashModuleReplacementPlugin = require('lodash-webpack-plugin');
var webpack = require('webpack');
module.exports = {
module: {
rules: [
{
use: 'babel-loader',
test: /\.js$/,
exclude: /node_modules/,
options: {
plugins: ['lodash'],
presets: [['env', {modules: false, targets: {node: 4}}]]
}
}
]
},
plugins: [new LodashModuleReplacementPlugin(), new webpack.optimize.UglifyJsPlugin()]
};
(3)使用 lodash-es
tree-shaking 作爲 rollup 的一個殺手級特性,能夠利用 ES6 的靜態引入規範,減少包的體積,避免不必要的代碼引入,webpack2 也很快引入了這個特性。
要用到 tree-shaking,必然要保證引用的模塊都是 ES6 規範的。lodash-es 是着具備 ES6 模塊化的版本,只需要直接引入就可以。
import {isEmpty, isObject, cloneDeep} from 'lodash-es';
4.jQuery的extend深拷貝和淺拷貝
淺層複製(只複製頂層的非 object 元素)
var newObject = jQuery.extend({}, oldObject);
深層複製(一層一層往下複製直到最底層)
var newObject = jQuery.extend(true, {}, oldObject);
除此以外,還有很多其他優秀的庫也實現了深拷貝,這裏就不一一舉例。
在gitHub上有人分析了lodash的源碼中關於deepClone的實現,有興趣的可以再深入瞭解。