JavaScript數(shù)據(jù)修改的問題
看一段大家熟悉的代碼
const state = {
str: '教育',
obj: {
y: 1
},
arr: [1, 2, 3]
}
const newState = state
?
console.log(newState === state) // true
由于js的對象和數(shù)組都是引用類型。所以newState的state實際上是指向于同一塊內(nèi)存地址的, 所以結(jié)果是newState和state是相等的。
嘗試修改一下數(shù)據(jù)
const state = {
str: '教育',
obj: {
y: 1
},
arr: [1, 2, 3]
}
const newState = state
?
newState.str = '教育H5學(xué)院'
?
console.log(state.str, newState.str)
可以看到,newState的修改也會引起state的修改。要解決這個問題,js中提供了另一種修改數(shù)據(jù)的方式,要修改一個數(shù)據(jù)之前先制作一份數(shù)據(jù)的拷貝,像這樣
const state = {
str: '教育',
obj: {
y: 1
},
arr: [1, 2, 3]
}
const newState = Object.assign({}, state)
?
newState.str = '教育H5學(xué)院'
?
console.log(state.str, newState.str)
我們可以使用很多方式在js中復(fù)制數(shù)據(jù),比如…, Object.assign, Object.freeze, slice, concat, map, filter, reduce等方式進行復(fù)制,但這些都是淺拷貝,就是只拷貝第一層數(shù)據(jù),更深層的數(shù)據(jù)還是同一個引用,比如:
const state = {
str: '教育',
obj: {
y: 1
},
arr: [1, 2, 3]
}
const newState = Object.assign({}, state)
?
newState.obj.y = 2
newState.arr.push(4)
?
console.log(state, newState)
可以看到,當(dāng)在更改newState更深層次的數(shù)據(jù)的時候,還是會影響到state的值。如果要深層復(fù)制,就得一層一層的做遞歸拷貝,這是一個復(fù)雜的問題。雖然有些第三方的庫已經(jīng)幫我們做好了,比如lodash的cloneDeep方法。深拷貝是非常消耗性能的。
import { cloneDeep } from 'lodash'
?
const state = {
str: '教育',
obj: {
y: 1
},
arr: [1, 2, 3]
}
const newState = cloneDeep(state)
?
newState.obj.y = 2
newState.arr.push(4)
?
console.log(state, newState)
什么是不可變數(shù)據(jù)
不可變數(shù)據(jù) (Immutable Data )就是一旦創(chuàng)建,就不能再被更改的數(shù)據(jù)。對 Immutable 對象的任何修改或添加刪除操作都會返回一個新的 Immutable 對象。Immutable 實現(xiàn)的原理是持久化數(shù)據(jù)結(jié)構(gòu)( Persistent Data Structure),也就是使用舊數(shù)據(jù)創(chuàng)建新數(shù)據(jù)時,要保證舊數(shù)據(jù)同時可用且不變。同時為了避免 deepCopy 把所有節(jié)點都復(fù)制一遍帶來的s性能損耗,Immutable 使用了 結(jié)構(gòu)共享(Structural Sharing),即如果對象樹中一個節(jié)點發(fā)生變化,只修改這個節(jié)點和受它影響的父節(jié)點,其它節(jié)點則進行共享。

immutable.js的優(yōu)缺點
優(yōu)點:
- 降低mutable帶來的復(fù)雜度
- 節(jié)省內(nèi)存
- 歷史追溯性(時間旅行):時間旅行指的是,每時每刻的值都被保留了,想回退到哪一步只要簡單的將數(shù)據(jù)取出就行,想一下如果現(xiàn)在頁面有個撤銷的操作,撤銷前的數(shù)據(jù)被保留了,只需要取出就行,這個特性在redux或者flux中特別有用
- 擁抱函數(shù)式編程:immutable本來就是函數(shù)式編程的概念,純函數(shù)式編程的特點就是,只要輸入一致,輸出必然一致,相比于面向?qū)ο螅@樣開發(fā)組件和調(diào)試更方便。推薦一本函數(shù)式編程的在線免費書《JS 函數(shù)式編程指南》, 此書可以推薦給學(xué)生做為課外補充閱讀。
缺點:
- 需要重新學(xué)習(xí)api
- 資源包大小增加(源碼5000行左右)
- 容易與原生對象混淆:由于api與原生不同,混用的話容易出錯。
使用Immutable.js
參考官網(wǎng)重點講解數(shù)據(jù)不可變數(shù)據(jù)的創(chuàng)建、更新及比較方式 。對于就業(yè)班來說,掌握以下知識點即可。
Map
import { Map } from 'immutable'
?
const map = Map({
a: 1,
b: 2,
c: 3
})
?
const newMap = map.set('b', 20) // immutable數(shù)據(jù)每次都是生成新的再重新調(diào)用set進行修改,所以需要 重新賦值給一個新的變量
?
console.log(map, newMap) // immutable.Map不是原生的對象
console.log(map.b, newMap.b) // immutable.Map不是原生的對象, 所以是undefined
console.log(map.get('b'), newMap.get('b')) // 要取值,需要調(diào)用get(key)方法,可以看到,兩個值不一樣
?
const obj = {
a: 1,
b: 2,
c: 3
}
?
console.log(Map.isMap(map), Map.isMap(obj)) // true false, 使用Map.isMap來判斷是否是一個immutable.Map類型
List
import { List } from 'immutable'
?
const list = List([1, 2, 3, 4])
const newList = list.push(5)
console.log(list, newList)
console.log(list[4], newList[4]) // undefined undefined
console.log(list.get(4), newList.get(4)) // undefined 5
console.log(list.size, newList.size) // 4 5
?
const arr = [1, 2, 3, 4]
?
console.log(List.isList(list), List.isList(arr)) // true false
equals & is
import { Map, is } from 'immutable'
?
const map = Map({
a: 1,
b: 2,
c: 3
})
?
const anotherMap = Map({
a: 1,
b: 2,
c: 3
})
?
console.log(map == anotherMap) // false
console.log(map === anotherMap) // false
console.log(map.equals(anotherMap)) // 使用equals進行比較 true
console.log(is(map, anotherMap)) // 使用is進行比較 true
List常用api
import { List } from 'immutable'
?
const list = List([1, 2, 3, 4])
const list1 = list.push(5)
const list2 = list1.unshift(0)
const list3 = list.concat(list1, list2)
const list4 = list.map(v => v * 2)
?
console.log(list.size, list1.size, list2.size, list3.size, list4.toJS()) // 4 5 6 15, [2, 4, 6, 8]
Map常用api
import { Map } from 'immutable'
?
const alpha = Map({
a: 1,
b: 2,
c: 3
})
const objKeys = alpha.map((v, k) => k)
console.log(objKeys.join()) // a, b, c
?
?
const map1 = Map({
a: 1,
b: 2
})
const map2 = Map({
c: 3,
d: 4
})
const obj = {
d: 400,
e: 50
}
?
const mergedMap = map1.merge(map2, obj)
?
console.log(mergedMap.toObject())
console.log(mergedMap.toJS())
嵌套數(shù)據(jù)結(jié)構(gòu)
const { fromJS } = require('immutable');
const nested = fromJS({ a: { b: { c: [ 3, 4, 5 ] } } });
?
const nested2 = nested.mergeDeep({ a: { b: { d: 6 } } });
// Map { a: Map { b: Map { c: List [ 3, 4, 5 ], d: 6 } } }
?
console.log(nested2.getIn([ 'a', 'b', 'd' ])); // 6
?
const nested3 = nested2.updateIn([ 'a', 'b', 'd' ], value => value + 1);
console.log(nested3);
// Map { a: Map { b: Map { c: List [ 3, 4, 5 ], d: 7 } } }
?
const nested4 = nested3.updateIn([ 'a', 'b', 'c' ], list => list.push(6));
// Map { a: Map { b: Map { c: List [ 3, 4, 5, 6 ], d: 7 } } }
在redux中使用immutable.js
redux官網(wǎng)推薦使用redux-immutable進行redux和immutable的集成。幾個注意點:
redux中,利用combineReducers來合并多個reduce, redux自帶的combineReducers只支持原生js形式的,所以需要使用redux-immutable提供的combineReducers來代替
// 使用redux-immutable提供的combineReducers方法替換redux里的combineReducers
import {combineReducers} from 'redux-immutable'
import reducerOne from './reducerOne'
import reducerTwo from './reducerTwo'
const rootReducer = combineReducers({
reducerOne,
reducerTwo
});
export default rootReducer;
reducer中的initialState也需要初始化成immutable類型, 比如一個counter的reducer
import { Map } from 'immutable'
?
import ActionTypes from '../actions'
?
const initialState = Map({
count: 0
})
?
export default (state = initialState, action) => {
switch (action.type) {
case ActionTypes.INCREAMENT:
return state.set('count', state.get('count') + 1) // 使用set或setIn來更改值, get或者getIn來取值
case ActionTypes.DECREAMENT:
return state.set('count', state.get('count') - 1)
default:
return state
}
}
state成為了immutable類型,connect的mapStateToProp也需要相應(yīng)的改變
const mapStateToProps = state => ({
count: state.getIn(['counter', 'count']) // 永遠(yuǎn)不要在mapStateToProps里使用`toJS`方法,因為它永遠(yuǎn)返回一個新的對象
})
在shouldComponentUpdate里就可以使用immutable.is或者instance.equals來進行數(shù)據(jù)的對比了。