js中的对象一般是可变的(mutable),因为使用了引用赋值,新的对象简单引用了原始对象,改变新的对象将影响到原始对象
var obj1 = {name: 'sysuzhyupeng'};
var obj2 = obj1;
obj2.name = 'zhyupeng';
我们给obj2的name属性赋值后,会发现obj1的name属性也改变了,虽然这样做可以节约内存,但是带来了很多问题。为了解决这个问题,一般的做法是使用深拷贝来避免被修改,但这样做又造成了CPU和内存的浪费。
Immutable.js 所创建的数据有一个特性:数据创建后不会被改变(尤其是引用类型数据)。我们使用 Immutable.js 的示例来解释这一特性:
var Immutable = require('immutable');
var map1 = Immutable.Map({a:1, b:2, c:3});
var map2 = map1.set('b', 50);
map1.get('b'); //2
map2.get('b'); //50
map1 === map2 // false
map1.get('a') === map2.get('a') // true
map1 使用 set 方法更新数据,结果返回一个新的 Map 类型数据 map2,map2 包含了更新后的数据,但是 map1 没有发生变化。这种特性让我们在引用数据的时候毫无后顾之忧,因为任何对数据的修改都不会影响最原始的数据。Immutable.js 相比深拷贝的优势在于区分发生变化的数据和未变化的数据,对于上面的 map1 和 map2,b 是变化的数据,所以 map1 和 map2 各保存一份 b 数据,而 a 和 c 是未变化的数据,所以 map1 和 map2 仍然共享 a 和 c 的数据。(深拷贝没有做到这点)
Immutable.js 仍然提供了类似原生 JavaScript Array、Map 和 Set 中的方法,并且提供了在原生 JavasScript 数据和 Immutable 数据之间快速转换的机制。
List
List<T>(): List<T>
List<T>(iter: Iterable.Indexed<T>): List<T>
List<T>(iter: Iterable.Set<T>): List<T>
List<K, V>(iter: Iterable.Keyed<K, V>): List<any>
List<T>(array: Array<T>): List<T>
List<T>(iterator: Iterator<T>): List<T>
List<T>(iterable: Object): List<T>
下面演示几个 List 常用的操作,更详细的 API 说明请参考官方文档
// 1.查看 List 长度
const $arr1 = List([1, 2, 3]);
$arr1.size // 3
// 2. 添加或替换 List 实例中的元素
// set(index: number, value: T)
// 将 index 位置的元素替换为 value,即使索引越界也是安全的
const $arr2 = $arr1.set(-1, 0);
// => [1, 2, 0]
const $arr3 = $arr1.set(4, 0);
// => [ 1, 2, 3, undefined, 0 ]
// 3. 删除 List 实例中的元素
// delete(index: number)
// 删除 index 位置的元素
const $arr4 = $arr1.delete(1);
// => [ 1, 3 ]
// 4. 向 List 插入元素
// insert(index: number, value: T)
// 向 index 位置插入 value
const $arr5 = $arr1.insert(1, 1.5);
// => [ 1, 1.5, 2, 3 ]
// 5. 清空 List
// clear()
const $arr6 = $arr1.clear();
List() 是一个构造方法,可以用于创建新的 List 数据类型,上面代码演示了该构造方法接收的参数类型,此外 List 拥有两个静态方法:
- List.isList(value),判断 value 是否是 List 类型
- List.of(…values),创建包含 …values 的列表
注意:为了避免和原生对象混淆,我们需要约定变量命名规则,如所有immutable类型对象以$开头。
Map
Map 可以使用任何类型的数据作为 Key 值,并使用 Immutable.is() 方法来比较两个 Key 值是否相等。
Map() 是 Map 类型的构造方法,行为类似于 List(),用于创建新的 Map 实例,此外,还包含两个静态方法:Map.isMap() 和 Map.of()。下面介绍几个 Map 实例的常用操作,更详细的 API 使用说明请参考官方文档:
// 1. Map 实例的大小
const $map1 = Map({ a: 1 });
$map1.size
// => 1
// 2. 添加或替换 Map 实例中的元素
// set(key: K, value: V)
const $map2 = $map1.set('a', 2);
// => Map { "a": 2 }
// 3. 删除元素
// delete(key: K)
const $map3 = $map1.delete('a');
// => Map {}
// 4. 清空 Map 实例
const $map4 = $map1.clear();
// => Map {}
// 5. 更新 Map 元素
// update(updater: (value: Map<K, V>) => Map<K, V>)
// update(key: K, updater: (value: V) => V)
// update(key: K, notSetValue: V, updater: (value: V) => V)
const $map5 = $map1.update('a', () => (2))
// => Map { "a": 2 }
// 6. 合并 Map 实例
const $map6 = Map({ b: 2 });
$map1.merge($map6);
// => Map { "a": 1, "b": 2 }
React
在 React 官方文档的《Advanced Performance》 一节中,专门对 React 的性能瓶颈、优化方式做了详细的解析。当一个 React 组件的 props 和 state 发生变化时,React 会根据变化后的 props 和 state 创建一个新的 virtual DOM,然后比较新旧两个 vritual DOM 是否一致,只有当两者不同时,React 才会将 virtual DOM 渲染真实的 DOM 结点,而对 React 进行性能优化的核心就是减少渲染真实 DOM 结点的频率,间接地指出开发者应该准确判断 props 和 state 是否真正发生了变化。
在比对新旧 virtual DOM 和渲染真实 DOM 前,React 为开发者提供了 shouldComponentUpdate() 方法中断接下来的比对和渲染操作,默认情况下,该方法总会返回 true,如果它返回 false,则不执行比对和渲染操作:
// 最简单的实现:
shouldComponentUpdate (nextProps) {
return this.props.value !== nextProps.value;
}
当我们需要比对的值是对象、数组等引用值时,就会出现问题。
如果数据是 Immutable Data 的话,那么数据发生变化就会生成新的对象,开发者只需要检查对象应用是否发生变化即可:
var SomeRecord = Immutable.Record({ foo: null });
var x = new SomeRecord({ foo: 'bar' });
var y = x.set('foo', 'bar');
x === y; // true