Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented toJS in such a way that it only recurses into observables #589

Merged
merged 4 commits into from
Oct 3, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 56 additions & 5 deletions src/api/tojs.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,63 @@
import {isObservableArray} from "../types/observablearray";
import {isObservableObject} from "../types/observableobject";
import {isObservableMap} from "../types/observablemap";
import {isObservableValue} from "../types/observablevalue";
import {isObservable} from "../api/isobservable";
import {deprecated} from "../utils/utils";

/**
* Basically, a deep clone, so that no reactive property will exist anymore.
*/
export function toJS(source, detectCycles: boolean = true, __alreadySeen: [any, any][] = null) {
// TODO: in 3.0, detectCycles should default to false?
// optimization: using ES6 map would be more efficient!
// optimization: lift this function outside toJS, this makes recursion expensive
function cache(value) {
if (detectCycles)
__alreadySeen.push([source, value]);
return value;
}
if (isObservable(source)) {
if (detectCycles && __alreadySeen === null)
__alreadySeen = [];
if (detectCycles && source !== null && typeof source === "object") {
for (let i = 0, l = __alreadySeen.length; i < l; i++)
if (__alreadySeen[i][0] === source)
return __alreadySeen[i][1];
}

if (isObservableArray(source)) {
const res = cache([]);
const toAdd = source.map(value => toJS(value, detectCycles, __alreadySeen));
res.length = toAdd.length;
for (let i = 0, l = toAdd.length; i < l; i++)
res[i] = toAdd[i];
return res;
}
if (isObservableObject(source)) {
const res = cache({});
for (let key in source)
res[key] = toJS(source[key], detectCycles, __alreadySeen);
return res;
}
if (isObservableMap(source)) {
const res = cache({});
source.forEach(
(value, key) => res[key] = toJS(value, detectCycles, __alreadySeen)
);
return res;
}
if (isObservableValue(source))
return toJS(source.get(), detectCycles, __alreadySeen);
}
return source;
}

/**
* Basically, a deep clone, so that no reactive property will exist anymore.
*/
export function toJSlegacy(source, detectCycles: boolean = true, __alreadySeen: [any, any][] = null) {
deprecated("toJSlegacy is deprecated and will be removed in the next major. Use `toJS` instead. See #566");
// optimization: using ES6 map would be more efficient!
function cache(value) {
if (detectCycles)
Expand All @@ -28,7 +79,7 @@ export function toJS(source, detectCycles: boolean = true, __alreadySeen: [any,
return source;
if (Array.isArray(source) || isObservableArray(source)) {
const res = cache([]);
const toAdd = source.map(value => toJS(value, detectCycles, __alreadySeen));
const toAdd = source.map(value => toJSlegacy(value, detectCycles, __alreadySeen));
res.length = toAdd.length;
for (let i = 0, l = toAdd.length; i < l; i++)
res[i] = toAdd[i];
Expand All @@ -37,22 +88,22 @@ export function toJS(source, detectCycles: boolean = true, __alreadySeen: [any,
if (isObservableMap(source)) {
const res = cache({});
source.forEach(
(value, key) => res[key] = toJS(value, detectCycles, __alreadySeen)
(value, key) => res[key] = toJSlegacy(value, detectCycles, __alreadySeen)
);
return res;
}
if (isObservableValue(source))
return toJS(source.get(), detectCycles, __alreadySeen);
return toJSlegacy(source.get(), detectCycles, __alreadySeen);
if (typeof source === "object") {
const res = cache({});
for (let key in source)
res[key] = toJS(source[key], detectCycles, __alreadySeen);
res[key] = toJSlegacy(source[key], detectCycles, __alreadySeen);
return res;
}
return source;
}

export function toJSON(source, detectCycles: boolean = true, __alreadySeen: [any, any][] = null) {
deprecated("toJSON is deprecated. Use toJS instead");
return toJS.apply(null, arguments);
return toJSlegacy.apply(null, arguments);
}
2 changes: 1 addition & 1 deletion src/mobx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export { autorun, autorunAsync, autorunUntil, when, reaction } from "./api/auto
export { action, isAction, runInAction } from "./api/action";

export { expr } from "./api/expr";
export { toJSON, toJS } from "./api/tojs";
export { toJSON, toJS, toJSlegacy } from "./api/tojs";
export { ITransformer, createTransformer } from "./api/createtransformer";
export { whyRun } from "./api/whyrun";

Expand Down
3 changes: 2 additions & 1 deletion test/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,14 @@ test('correct api should be exposed', function(t) {
'runInAction',
'spy',
'toJS',
'toJSlegacy',
'toJSON',
'transaction',
'untracked',
'useStrict',
'when',
'whyRun'
]);
].sort());
t.equals(Object.keys(mobx).filter(function(key) {
return mobx[key] == undefined;
}).length, 0);
Expand Down
Loading