diff --git a/src/index.js b/src/index.js index e6ef2bff1..167277615 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,7 @@ import devtoolPlugin from './plugins/devtool' import applyMixin from './mixin' import { mapState, mapMutations, mapGetters, mapActions } from './helpers' +import { isObject, isPromise, assert } from './util' let Vue // bind on install @@ -157,10 +158,6 @@ class Store { } } -function assert (condition, msg) { - if (!condition) throw new Error(`[vuex] ${msg}`) -} - function updateModule (targetModule, newModule) { if (newModule.actions) { targetModule.actions = newModule.actions @@ -339,14 +336,6 @@ function enableStrictMode (store) { }, { deep: true, sync: true }) } -function isObject (obj) { - return obj !== null && typeof obj === 'object' -} - -function isPromise (val) { - return val && typeof val.then === 'function' -} - function getNestedState (state, path) { return path.length ? path.reduce((state, key) => state[key], state) diff --git a/src/plugins/logger.js b/src/plugins/logger.js index e1d27f7a0..1bef55cc7 100644 --- a/src/plugins/logger.js +++ b/src/plugins/logger.js @@ -1,18 +1,20 @@ // Credits: borrowed code from fcomb/redux-logger +import { deepCopy } from '../util' + export default function createLogger ({ collapsed = true, transformer = state => state, mutationTransformer = mut => mut } = {}) { return store => { - let prevState = JSON.parse(JSON.stringify(store.state)) + let prevState = deepCopy(store.state) store.subscribe((mutation, state) => { if (typeof console === 'undefined') { return } - const nextState = JSON.parse(JSON.stringify(state)) + const nextState = deepCopy(state) const time = new Date() const formattedTime = ` @ ${pad(time.getHours(), 2)}:${pad(time.getMinutes(), 2)}:${pad(time.getSeconds(), 2)}.${pad(time.getMilliseconds(), 3)}` const formattedMutation = mutationTransformer(mutation) diff --git a/src/util.js b/src/util.js new file mode 100644 index 000000000..fe7db6365 --- /dev/null +++ b/src/util.js @@ -0,0 +1,59 @@ +/** + * Get the first item that pass the test + * by second argument function + * + * @param {Array} list + * @param {Function} f + * @return {*} + */ +function find (list, f) { + return list.filter(f)[0] +} + +/** + * Deep copy the given object considering circular structure. + * This function caches all nested objects and its copies. + * If it detects circular structure, use cached copy to avoid infinite loop. + * + * @param {*} obj + * @param {Array} cache + * @return {*} + */ +export function deepCopy (obj, cache = []) { + // just return if obj is immutable value + if (obj === null || typeof obj !== 'object') { + return obj + } + + // if obj is hit, it is in circular structure + const hit = find(cache, c => c.original === obj) + if (hit) { + return hit.copy + } + + const copy = Array.isArray(obj) ? [] : {} + // put the copy into cache at first + // because we want to refer it in recursive deepCopy + cache.push({ + original: obj, + copy + }) + + Object.keys(obj).forEach(key => { + copy[key] = deepCopy(obj[key], cache) + }) + + return copy +} + +export function isObject (obj) { + return obj !== null && typeof obj === 'object' +} + +export function isPromise (val) { + return val && typeof val.then === 'function' +} + +export function assert (condition, msg) { + if (!condition) throw new Error(`[vuex] ${msg}`) +} diff --git a/test/unit/jasmine.json b/test/unit/jasmine.json index 264370cfa..fec87e7c5 100644 --- a/test/unit/jasmine.json +++ b/test/unit/jasmine.json @@ -1,7 +1,8 @@ { "spec_dir": "test/unit", "spec_files": [ - "test.js" + "test.js", + "util.js" ], "helpers": [ "../../node_modules/babel-register/lib/node.js" diff --git a/test/unit/util.js b/test/unit/util.js new file mode 100644 index 000000000..8f17cbc60 --- /dev/null +++ b/test/unit/util.js @@ -0,0 +1,41 @@ +import { deepCopy } from '../../src/util' + +describe('util', () => { + it('deepCopy: nornal structure', () => { + const original = { + a: 1, + b: 'string', + c: true, + d: null, + e: undefined + } + const copy = deepCopy(original) + + expect(copy).toEqual(original) + }) + + it('deepCopy: nested structure', () => { + const original = { + a: { + b: 1, + c: [2, 3, { + d: 4 + }] + } + } + const copy = deepCopy(original) + + expect(copy).toEqual(original) + }) + + it('deepCopy: circular structure', () => { + const original = { + a: 1 + } + original.circular = original + + const copy = deepCopy(original) + + expect(copy).toEqual(original) + }) +})