纯函数
纯函数是函数式编程的基础,纯函数定义:
- 给定相同的输入,始终返回相同的输出
- 无副作用,不会修改作用域之外的状态
- 不依赖于外部的可变状态
一个简单的纯函数
function incrementByOne(num) { return ++num;}
对于上面的函数,无论执行多少次 increatementByOne(4),结果都返回 5,而且不会修改到作用于外的状态,不依赖于外部变量
函数副作用
函数副作用指当调用函数时,除了返回函数值之外,还对主调用函数产生附加的影响。例如修改全局变量(函数外的变量)或修改参数。函数副作用会给程序设计带来不必要的麻烦,给程序带来十分难以查找的错误,并且降低程序的可读性。严格的函数式语言要求函数必须无副作用。有副作用的函数就是非纯函数
var user = { name: 'kee', age: 24,};// 修改了作用域之外的状态function grow(u) { var nuser = u; nuser.age += 1; return nuser;}var nUser = grow(user); // 此时原来的 user.age = 25;
避免函数副作用
- 函数入口使用参数运算,而不修改它
- 函数内不修改函数外的变量,如全局变量
- 运算结果通过函数返回给外部(出口)
上面的 grow 函数在编码中不注意通常会容易造成错误,修改到了输入的参数,当然解决的方法很简单,只需要拷贝一个新的对象
Object.assign()
Object.assign()方法用于将所有可枚举的属性的值从一个或多个源对象复制到目标对象,返回新的对象。
注意:Object.assign()只是浅拷贝
修改上面的 grow 方法
function grow(u) { var nuser = Object.assign({}, u); nuser.age += 1; return nuser;}
es6 扩展语法
注意:这个也只是浅拷贝
function grow(u) { return { ...user, age: user.age + 1 };}
当然还有很多方法可以实现,例如自己封装对象深拷贝方法
immutable.js
immutable.js 是 facebook 提供一个操作复杂数据类型的库,不可变的数据指的是一旦创建了就不修改,提供了很多方法供我们调用,在不修改原来的数据基础上返回新的数据,保证旧的数据可用
可操作的数据类型包括 List, Stack , Map , OrderedMap , Set , OrderedSet and Record.
immutable.js 利用 javascript vm 通过共享哈希映射和向量来最小化复制和缓存数据,即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。(听起来很厉害的样子,我也不懂什么意思哈),可以高效操作上面的数据类型
在上面的 grow 方法,由于只是简单的浅拷贝,如果是嵌套太多层,深拷贝将会带来性能问题,immutable 就可以解决这个问题
const { Map } = require('immutable');const user = Map({ name: 'kee', age: 24,});// 修改了作用域之外的状态function grow(u) { return user.set('age', u.get('age') + 1);}const nUser = grow(user); // 此时原来的 user.get('age')= 25;
react 和 immutable
可变数据在 react 通常会导致一些不可预制的 bug
class ListOfWords extends React.PureComponent { render() { return <div>{this.props.words.join(',')}</div>; }}class WordAdder extends React.Component { constructor(props) { super(props); this.state = { words: ['marklar'], }; this.handleClick = this.handleClick.bind(this); } handleClick() { // This section is bad style and causes a bug const words = this.state.words; words.push('marklar'); this.setState({ words: words }); } render() { return ( <div> <button onClick={this.handleClick} /> <ListOfWords words={this.state.words} /> </div> ); }}
上面的例子,点击按钮时新增一个单词的功能,但是发现点击时候视图却没有更新,因为 pureComponent 只是做浅比较,同样是数组,pureComponent 认为没改变,所以视图没更新
解决的方法如下
使用数组的 concat 返回一个新的数组
// 使用原生数组方法 contat返回一个新的数组handleClick() { this.setState(prevState => ({ words: prevState.words.concat(['marklar']) }));}
es6 对象扩展语法
// es6的对象扩展语法handleClick() { this.setState(prevState => ({ words: [...prevState.words, 'marklar'], }));};
react 中使用 immutable.js
写法还是一样,只是用 immutable 来操作复杂的数组和对象
var React = require('react');var { Map, List } = require('immutable');var Component = React.createClass({ getInitialState() { return { data: Map({ count: 0, items: List() }), }; }, handleCountClick() { this.setState(({ data }) => ({ data: data.update('count', (v) => v + 1), })); }, handleAddItemClick() { this.setState(({ data }) => ({ data: data.update('items', (list) => list.push(data.get('count'))), })); }, render() { var data = this.state.data; return ( <div> <button onClick={this.handleCountClick}>Add to count</button> <button onClick={this.handleAddItemClick}>Save count</button> <div>Count: {data.get('count')}</div> Saved counts: <ul> {data.get('items').map((item) => ( <li>Saved: {item}</li> ))} </ul> </div> ); },});React.render(<Component />, document.body);