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實(shí)際上是指向于同一塊內(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的修改。要解決這個(gè)問題拴孤,js中提供了另一種修改數(shù)據(jù)的方式口糕,要修改一個(gè)數(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
等方式進(jìn)行復(fù)制,但這些都是淺拷貝钾唬,就是只拷貝第一層數(shù)據(jù)万哪,更深層的數(shù)據(jù)還是同一個(gè)引用,比如:
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ù)的時(shí)候奕巍,還是會影響到state的值。如果要深層復(fù)制儒士,就得一層一層的做遞歸拷貝的止,這是一個(gè)復(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 對象的任何修改或添加刪除操作都會返回一個(gè)新的 Immutable 對象赂乐。Immutable 實(shí)現(xiàn)的原理是持久化數(shù)據(jù)結(jié)構(gòu)( Persistent Data Structure),也就是使用舊數(shù)據(jù)創(chuàng)建新數(shù)據(jù)時(shí)咖气,要保證舊數(shù)據(jù)同時(shí)可用且不變挨措。同時(shí)為了避免 deepCopy 把所有節(jié)點(diǎn)都復(fù)制一遍帶來的s性能損耗,Immutable 使用了 結(jié)構(gòu)共享(Structural Sharing)崩溪,即如果對象樹中一個(gè)節(jié)點(diǎn)發(fā)生變化浅役,只修改這個(gè)節(jié)點(diǎn)和受它影響的父節(jié)點(diǎn),其它節(jié)點(diǎn)則進(jìn)行共享伶唯。
immutable.js的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
降低mutable帶來的復(fù)雜度
節(jié)省內(nèi)存
歷史追溯性(時(shí)間旅行):時(shí)間旅行指的是觉既,每時(shí)每刻的值都被保留了,想回退到哪一步只要簡單的將數(shù)據(jù)取出就行抵怎,想一下如果現(xiàn)在頁面有個(gè)撤銷的操作奋救,撤銷前的數(shù)據(jù)被保留了,只需要取出就行反惕,這個(gè)特性在redux或者flux中特別有用
擁抱函數(shù)式編程:immutable本來就是函數(shù)式編程的概念尝艘,純函數(shù)式編程的特點(diǎn)就是,只要輸入一致姿染,輸出必然一致背亥,相比于面向?qū)ο螅@樣開發(fā)組件和調(diào)試更方便悬赏。推薦一本函數(shù)式編程的在線免費(fèi)書《JS 函數(shù)式編程指南》, 此書可以推薦給學(xué)生做為課外補(bǔ)充閱讀狡汉。
缺點(diǎn):
需要重新學(xué)習(xí)api
資源包大小增加(源碼5000行左右)
容易與原生對象混淆:由于api與原生不同,混用的話容易出錯(cuò)闽颇。
使用Immutable.js
參考官網(wǎng)重點(diǎn)講解數(shù)據(jù)不可變數(shù)據(jù)的創(chuàng)建盾戴、更新及比較方式 。對于就業(yè)班來說兵多,掌握以下知識點(diǎn)即可尖啡。
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進(jìn)行修改左冬,所以需要 重新賦值給一個(gè)新的變量
?
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)方法,可以看到诬像,兩個(gè)值不一樣
?
const obj = {
a: 1,
b: 2,
c: 3
}
?
console.log(Map.isMap(map), Map.isMap(obj)) // true false, 使用Map.isMap來判斷是否是一個(gè)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進(jìn)行比較 true
console.log(is(map, anotherMap)) // 使用is進(jìn)行比較 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進(jìn)行redux和immutable的集成怠褐。幾個(gè)注意點(diǎn):
redux
中畏梆,利用combineReducers
來合并多個(gè)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
類型, 比如一個(gè)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`方法奠涌,因?yàn)樗肋h(yuǎn)返回一個(gè)新的對象
})
在shouldComponentUpdate
里就可以使用immutable.is
或者instance.equals
來進(jìn)行數(shù)據(jù)的對比了宪巨。