JS中的对象一般是可变的(Mutable),因为使用了引用赋值,新的对象简单的引用了原始对象,改变新的对象将影响到原始对象。如 foo={a: 1}; bar=foo; bar.a=2
你会发现此时 foo.a
也被改成了 2
。虽然这样做可以节约内存,但当应用复杂后,这就造成了非常大的隐患,突变带来的优点变得得不偿失。为了解决这个问题,一般的做法是使用 shallowCopy(浅拷贝)或 deepCopy(深拷贝)来避免被修改,但缺点是造成了 CPU 和内存的浪费。
如深拷贝的代码如下:
var cloneDeep = require('lodash.clonedeep'); // npm install lodash.clonedeep
var data = {
id: '1',
author: {
name: 'Lee'
}
};
var dataDeepCopy = cloneDeep(data);
console.log(dataDeepCopy === data); // 打印 false
dataDeepCopy.id = '2';
dataDeepCopy.author.name = 'Huang';
console.log(data.id); // 打印 1
console.log(dataDeepCopy.id); // 打印 2
什么是 IMMUTABLE DATA?
Immutable Data 就是一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改操作都会返回一个新的 Immutable 对象。Immutable 实现的原理是 Persistent Data Structure(持久化数据结构),也就是使用旧数据创建新数据时,要保证旧数据同时可用且没有发生变化。同时为了避免 deepCopy 把所有节点都复制一遍带来的性能损耗,Immutable 使用了 Structural Sharing(结构共享),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。
react目前最常用的immutable的方法是Immutable.fromJS() 和 Immutable.Record(),本篇主要通过这两种用法来认识immutable。
Immutable.fromJS(Object) 入参数据格式为对象
这个函数的主要功能是将js的object对象格式{ }转换为Map格式,该Map是独一无二的,不可改变的。换种说法,等同于深度拷贝。什么是Map格式?它跟Java中的Map是一样的,数据表现形式为键值模式Map(key, value)。在同一个Map中每个key都是唯一的,通过key可以获取一个唯一的值。先看个简单的例子:
let student = Immutable.fromJS({
id: 1,
msg: {
name: "John",
age: 30
},
school: {
sid: "1",
sname: "Harvard"
}
});
console.log(student.get('id')); // 打印 1
console.log(student.get('msg')); // 打印Map对象
console.log(student.get('msg').toJS()); // 打印 {name: "John", age: 30}
Map格式通过get(attr)来获取数据,这里要注意,当该属性如上例中的msg也为对象时,此时get获得的还是一个Map对象,Map对象可以通过toJS() 转换为 react可用的 Object对象格式。那么怎么去设置(或说深拷贝)一个新的Map对象呢?可以通过set设置:
let studentImmuCopy = student.set('id', 2).set('msg', Immutable.Map({
name: "Lee",
age: 29
}));
console.log(studentImmuCopy.get('id')); // 打印 2
console.log(studentImmuCopy.get('msg')); // 打印 Map对象
console.log(studentImmuCopy.get('msg').toJS()); // 打印 {name: "Lee", age: 29}
console.log(student.get('school') === studentImmuCopy.get('school')); // 打印 true
通过打印结果,你会发现student和 studentImmuCopy是两个完全不同的Map对象,studentImmuCopy 继承了student的数据结构并修改了部分数据,如id和msg,但是school并没有修改,所以在最后一个比较打印出来的结果是true,表示两者的school属性指向的是同一个内存位置。这正是Immutable的强大之处,深度拷贝,只改变了set的重新设置的值,没有被改变的值依然共享(指向)同一个内存位置。当set的值在原对象中没有该属性时,则新增这个属性值。
使用delete来删除某个属性值,如删除msg脚本如下:
console.log(student.delete('msg').toJS()); // 打印 {id:1, school:{sid:1,sname:"Harvard"}}
Immutable.fromJS(Array) 入参数据格式为数组
这个函数的主要功能是将js的Array数组格式[ ]转换为List格式,与Map一样,得到的List 是独一无二的。请看下面用法:
let arr = Immutable.fromJS([1, 2, 3, 4, 5]);
console.log(arr); // List
console.log(arr.toJS()); // [1, 2, 3, 4, 5]
console.log(arr.size); // List 长度
console.log(arr.count()); // List 长度
console.log(arr.set(1, 0).toJS()); // 索引值从零开始计算,将索引值为1的值换为0, 打印[1, 0, 3, 4, 5]
console.log(arr.set(-1, 0).toJS()); // 将索引值为-1的值换为 0,此处为倒数第一个 [1, 2, 3, 4, 0]
console.log(arr.insert(1, 0).toJS()); // 将索引值为1的位置插入新的值0 [1, 0, 2, 3, 4, 5]
console.log(arr.push(6).toJS()); // 在列表最后插入数值为6的值
console.log(arr.delete(1).toJS()); // 删除索引值为1 的值 [1, 3, 4, 5]
console.log(arr.clear().toJS()); // 清空列表 []
List 数据类型也拥有 pop 、 push 、 shift 、 unshift 这四种操作方法,和原生 Array的四种方法使用方式一致,但唯一区别就是返回新的 List ,并且不改变原来的数组本身,而原生则是会改变元素本身。
Immutable.Record()
Immutable.Record()指定义一个实例结构模板,通过new方法创建与该模板相同结构的实例。
// record 模板实例
const studentRecord = Immutable.Record({
id: "",
msg: {
name: "",
age: ""
},
school: {
sid: "1",
sname: "Harvard"
}
});
const x = new studentRecord({
id: '1',
msg: {
name: "Lee",
age: "29"
}
});
const y = x.set('id', '2').set('msg', {
name: 'Huang',
age: '30'
});
console.log(x); // 打印 Record 对象
console.log(x.id); // 打印 1
console.log(x.msg); // {name: "Lee", age: "29"}
console.log(x.msg.name); // 打印 Lee
console.log(y.msg.name); // 打印 Huang
console.log(x.toJS()); // 打印 {id:'1',msg:{name:'Lee',age:"29"},school:{sid:"1",sname:"Harvard"}}
console.log(y.toJS()); // 打印 {id:'2',msg:{name:'Huang',age:"30"},school:{sid:"1",sname:"Harvard"}}
console.log(x.scholl === y.scholl); // 打印 true
Record对象与Map对象不同,无需通过get()来获取属性。
总的来说,在react开发,当需要用到深度拷贝时,可以使用Immutable来替代 require('lodash.clonedeep'),当然Immutable 还有很多很强大的功能,可以到官网查看哦。