纯函数(Pure Function)
简单说,一个函数的返回结果只依赖于他的参数,并且执行过程中没有副作用,就是纯函数
从上面看,纯函数需要两个条件:
- 函数的返回结果只依赖于它的参数
 
- 函数的执行过程没有副作用
 
现在分别看一下两个条件
函数的返回结果只依赖于它的参数
1 2 3
   | const a = 1 const foo = (b) => a + b foo(2) 
 
  | 
 
foo就不是一个纯函数,因为他的返回值要依赖外部变量a,我们不知道a的情况下,不能保证foo(2)的返回值是3,如果a变化,那返回值是不可预料的。
1 2 3
   | const a = 1 const foo = (x, b) => x + b foo(1, 2) 
 
  | 
 
修改一下foo,现在函数的返回结果只依赖于他的参数,所以是纯函数
 
函数执行过程没有副作用
 
再修改一下foo
   1 2 3 4 5 6 7
   | const a = 1 const foo = (obj, b) => {   return obj.x + b } const counter = { x: 1 } foo(counter, 2)  counter.x 
 
  | 
 
   把原来的x变成了一个对象,可以往里面传一个对象进行计算,过程中不会对传入的对象进行更改,counter不会发生变化,所以是纯函数,但是如果我们稍微修改它一下:
   1 2 3 4 5 6 7 8
   | const a = 1 const foo = (obj, b) => {   obj.x = 2   return obj.x + b } const counter = { x: 1 } foo(counter, 2)  counter.x 
 
  | 
 
   在foo内部添加了一句obj.x = 2,计算前counter.x是1,但是计算以后的counter.x是2。函数内的执行对外部产生了影响,也叫副作用,所以不是纯函数
   只要函数执行产生了外部可以观察到的变化,就是副作用,像调用DOM API修改页面,发送AJAX请求,甚至console.log也是副作用
优化共享结构的对象
其实之前的部分已经可以算是简单实现了一个redux,但是如果细心就会发现,之前的版本中有很严重的性能问题,如果我们试着在每个渲染函数开头输出一些日志看一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
   | function renderApp (appState) {   console.log('render app...')   renderTitle(appState.title)   renderContent(appState.content) }
  function renderTitle (title) {   console.log('render title...')   const titleDOM = document.getElementById('title')   titleDOM.innerHTML = title.text   titleDOM.style.color = title.color }
  function renderContent (content) {   console.log('render content...')   const contentDOM = document.getElementById('content')   contentDOM.innerHTML = content.text   contentDOM.style.color = content.color }
 
  | 
 
执行一次初始化渲染和两次更新
1 2 3 4 5 6
   | const store = createStore(appState, stateChanger) store.subscribe(() => renderApp(store.getState())) 
  renderApp(store.getState())  store.dispatch({ type: 'UPDATE_TITLE_TEXT', text: 'React.js' })  store.dispatch({ type: 'UPDATE_TITLE_COLOR', color: 'blue' }) 
 
  | 
 
在控制塔瞄一眼呗:

看得出来一组三个render函数输出了三次,第一次肯定是首次渲染,后边两次分别是两次store.dispatch导致的。可以看出每次更新数据都要重新渲染整个App,但是我们两次更新都只是更新了title字段,并不需要重新调用renderContent,它是一个冗余的操作,现在需要优化它
提出一种解决方法(但不限于一种),在每个渲染函数调用之前判断一下传入的数据和旧数据是否相同,相同就不调用了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
   | function renderApp (newAppState, oldAppState = {}) {    if (newAppState === oldAppState) return    console.log('render app...')   renderTitle(newAppState.title, oldAppState.title)   renderContent(newAppState.content, oldAppState.content) }
  function renderTitle (newTitle, oldTitle = {}) {   if (newTitle === oldTitle) return    console.log('render title...')   const titleDOM = document.getElementById('title')   titleDOM.innerHTML = newTitle.text   titleDOM.style.color = newTitle.color }
  function renderContent (newContent, oldContent = {}) {   if (newContent === oldContent) return    console.log('render content...')   const contentDOM = document.getElementById('content')   contentDOM.innerHTML = newContent.text   contentDOM.style.color = newContent.color }
 
  | 
 
然后用一个oldState 来保存旧的应用状态,在需要重新渲染的地方把新旧数据传进去:
1 2 3 4 5 6 7 8
   | const store = createStore(appState, stateChanger) let oldState = store.getState()  store.subscribe(() => {   const newState = store.getState()    renderApp(newState, oldState)    oldState = newState  }) ...
 
  | 
 
可别以为这样就好了……,看看stateChanger:
1 2 3 4 5 6 7 8 9 10 11 12
   | function stateChanger (state, action) {   switch (action.type) {     case 'UPDATE_TITLE_TEXT':       state.title.text = action.text       break     case 'UPDATE_TITLE_COLOR':       state.title.color = action.color       break     default:       break   } }
 
  | 
 
即使修改了state.title.text,但是state还是原来那个state,一切都是以前的一切,这些引用指向的还是于谦的对象,只是内容改变了而已,就像下边这个语句:
1 2 3 4 5 6
   | let obj = { 	x: 0 } let obj2 = obj obj2.x = 1 obj !== obj2 
 
  | 
 
想让两者成为两个完全不同的对象,就得使用ES6的语法
1 2
   | const obj = {a:1} const obj2 = {...obj}
 
  | 
 
放到例子中就是
1 2 3 4 5 6 7
   | let newAppState = {    ...appState,    title: {      ...appState.title,      text: 'new text'    } }
 
  | 
 
用一个图来表示对象结构

appState和newAppState是两个不同的对象,因为浅复制所以两个对象的content指向同一对象,但是title被一个新的对象覆盖,所以指向不同,同样的可修改appState.title.color
1 2 3 4 5 6 7
   | let newAppState1 = {    ...newAppState,    title: {      ...newAppState.title,      color: "blue"    } }
 
  | 
 

这样每次我们修改某些数据的时候,都不会碰原来的数据,每次都是copy的一个新对象。现在我们可以修改stateChanger中的代码,让他产生上述共享结构的对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
   | function stateChanger (state, action) {   switch (action.type) {     case 'UPDATE_TITLE_TEXT':       return {          ...state,         title: {           ...state.title,           text: action.text         }       }     case 'UPDATE_TITLE_COLOR':       return {          ...state,         title: {           ...state.title,           color: action.color         }       }     default:       return state    } }
 
  | 
 
1 2 3 4 5 6 7 8 9 10
   | function createStore (state, stateChanger) {   const listeners = []   const subscribe = (listener) => listeners.push(listener)   const getState = () => state   const dispatch = (action) => {     state = stateChanger(state, action)      listeners.forEach((listener) => listener())   }   return { getState, dispatch, subscribe } }
 
  | 
 
现在再看一眼控制台:

这样我们就能避免不需要的渲染了!过两天再往下走……