Skip to content

Commit

Permalink
Merge branch 'feature/hdom-v5' into feature/hdom-canvas
Browse files Browse the repository at this point in the history
* feature/hdom-v5:
  perf(checks): update isPlainObject()
  refactor(hdom): use custom equiv also for diffAttributes()
  perf(diff): update diffObject() & add IOC support for custom equiv fn
  test(hdom): update tests
  refactor(hdom): trial run of custom equiv() impl for diffTree()
  perf(equiv): inline & refactor type checks, add IOC support for helpers
  • Loading branch information
postspectacular committed Sep 13, 2018
2 parents 360f15a + c285910 commit f7af43c
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 42 deletions.
4 changes: 3 additions & 1 deletion packages/checks/src/is-plain-object.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const OBJP = Object.getPrototypeOf({});

/**
* Similar to `isObject()`, but also checks if prototype is that of
* `Object` (or `null`).
Expand All @@ -7,5 +9,5 @@
export function isPlainObject(x: any): x is object {
let proto;
return Object.prototype.toString.call(x) === "[object Object]" &&
(proto = Object.getPrototypeOf(x), proto === null || proto === Object.getPrototypeOf({}));
(proto = Object.getPrototypeOf(x), proto === null || proto === OBJP);
}
7 changes: 3 additions & 4 deletions packages/diff/src/object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,20 @@ import { equiv } from "@thi.ng/equiv";

import { ObjectDiff } from "./api";

export function diffObject(a: any, b: any) {
export function diffObject(a: any, b: any, _equiv = equiv) {
const adds = [];
const dels = [];
const edits = [];
const keys = new Set(Object.keys(a).concat(Object.keys(b)));
const state = <ObjectDiff>{ distance: 0, adds, dels, edits };
if (a === b) {
return state;
}
for (let k of keys) {
for (let k of new Set(Object.keys(a).concat(Object.keys(b)))) {
const va = a[k];
const vb = b[k];
const hasA = va !== undefined;
if (hasA && vb !== undefined) {
if (!equiv(va, vb)) {
if (!_equiv(va, vb)) {
edits.push([k, vb]);
state.distance++;
}
Expand Down
7 changes: 3 additions & 4 deletions packages/equiv/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,13 @@
"devDependencies": {
"@types/mocha": "^5.2.5",
"@types/node": "^10.5.5",
"benchmark": "^2.1.4",
"mocha": "^5.2.0",
"nyc": "^12.0.2",
"typedoc": "^0.11.1",
"typescript": "^3.0.1"
},
"dependencies": {
"@thi.ng/checks": "^1.5.8"
},
"dependencies": {},
"keywords": [
"deep",
"equality",
Expand All @@ -39,4 +38,4 @@
"publishConfig": {
"access": "public"
}
}
}
48 changes: 23 additions & 25 deletions packages/equiv/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import { isArrayLike } from "@thi.ng/checks/is-arraylike";
import { isDate } from "@thi.ng/checks/is-date";
import { isMap } from "@thi.ng/checks/is-map";
import { isPlainObject } from "@thi.ng/checks/is-plain-object";
import { isRegExp } from "@thi.ng/checks/is-regexp";
import { isSet } from "@thi.ng/checks/is-set";
const OBJP = Object.getPrototypeOf({});

export const equiv = (a, b): boolean => {
let proto;
if (a === b) {
return true;
}
Expand All @@ -26,50 +22,52 @@ export const equiv = (a, b): boolean => {
if (typeof a === "string" || typeof b === "string") {
return false;
}
if (isPlainObject(a) && isPlainObject(b)) {
if ((proto = Object.getPrototypeOf(a), proto == null || proto === OBJP) &&
(proto = Object.getPrototypeOf(b), proto == null || proto === OBJP)) {
return equivObject(a, b);
}
if (isArrayLike(a) && isArrayLike(b)) {
if (typeof a !== "function" && a.length !== undefined &&
typeof b !== "function" && b.length !== undefined) {
return equivArrayLike(a, b);
}
if (isSet(a) && isSet(b)) {
if (a instanceof Set && b instanceof Set) {
return equivSet(a, b);
}
if (isMap(a) && isMap(b)) {
if (a instanceof Map && b instanceof Map) {
return equivMap(a, b);
}
if (isDate(a) && isDate(b)) {
if (a instanceof Date && b instanceof Date) {
return a.getTime() === b.getTime();
}
if (isRegExp(a) && isRegExp(b)) {
if (a instanceof RegExp && b instanceof RegExp) {
return a.toString() === b.toString();
}
// NaN
return (a !== a && b !== b);
};

const equivArrayLike = (a: ArrayLike<any>, b: ArrayLike<any>) => {
export const equivArrayLike = (a: ArrayLike<any>, b: ArrayLike<any>, _equiv = equiv) => {
let l = a.length;
if (b.length === l) {
while (--l >= 0 && equiv(a[l], b[l]));
if (l === b.length) {
while (--l >= 0 && _equiv(a[l], b[l]));
}
return l < 0;
};

const equivSet = (a: Set<any>, b: Set<any>) =>
export const equivSet = (a: Set<any>, b: Set<any>, _equiv = equiv) =>
(a.size === b.size) &&
equiv([...a.keys()].sort(), [...b.keys()].sort());
_equiv([...a.keys()].sort(), [...b.keys()].sort());

const equivMap = (a: Map<any, any>, b: Map<any, any>) =>
export const equivMap = (a: Map<any, any>, b: Map<any, any>, _equiv = equiv) =>
(a.size === b.size) &&
equiv([...a].sort(), [...b].sort());
_equiv([...a].sort(), [...b].sort());

const equivObject = (a: any, b: any) => {
const ka = Object.keys(a);
if (ka.length !== Object.keys(b).length) return false;
for (let i = ka.length, k; --i >= 0;) {
k = ka[i];
if (!b.hasOwnProperty(k) || !equiv(a[k], b[k])) {
export const equivObject = (a: any, b: any, _equiv = equiv) => {
if (Object.keys(a).length !== Object.keys(b).length) {
return false;
}
for (let k in a) {
if (!b.hasOwnProperty(k) || !_equiv(a[k], b[k])) {
return false;
}
}
Expand Down
55 changes: 53 additions & 2 deletions packages/hdom/src/diff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import * as iss from "@thi.ng/checks/is-string";
import { DiffLogEntry } from "@thi.ng/diff/api";
import { diffArray } from "@thi.ng/diff/array";
import { diffObject } from "@thi.ng/diff/object";
import { equiv } from "@thi.ng/equiv";
import { equivArrayLike, equivObject, equivMap, equivSet } from "@thi.ng/equiv";
import { HDOMImplementation, HDOMOpts } from "./api";

const isArray = isa.isArray;
const isString = iss.isString;

const max = Math.max;

/**
Expand Down Expand Up @@ -136,7 +137,7 @@ export const diffTree = <T>(

const diffAttributes = <T>(impl: HDOMImplementation<T>, el: T, prev: any, curr: any) => {
let i, e, edits;
const delta = diffObject(prev, curr);
const delta = diffObject(prev, curr, equiv);
impl.removeAttribs(el, delta.dels, prev);
let value = SEMAPHORE;
for (edits = delta.edits, i = edits.length; --i >= 0;) {
Expand Down Expand Up @@ -197,3 +198,53 @@ const extractEquivElements = (edits: DiffLogEntry<any>[]) => {
}
return equiv;
};

const OBJP = Object.getPrototypeOf({});

const equiv = (a: any, b: any): boolean => {
let proto;
if (a === b) {
return true;
}
if (a != null) {
if (typeof a.equiv === "function") {
return a.equiv(b);
}
} else {
return a == b;
}
if (b != null) {
if (typeof b.equiv === "function") {
return b.equiv(a);
}
} else {
return a == b;
}
if (typeof a === "string" || typeof b === "string") {
return false;
}
if ((proto = Object.getPrototypeOf(a), proto == null || proto === OBJP) &&
(proto = Object.getPrototypeOf(b), proto == null || proto === OBJP)) {
return ((<any>a).__diff !== false && (<any>b).__diff !== false) ?
equivObject(a, b, equiv) :
false;
}
if (typeof a !== "function" && a.length !== undefined &&
typeof b !== "function" && b.length !== undefined) {
return equivArrayLike(a, b, equiv);
}
if (a instanceof Set && b instanceof Set) {
return equivSet(a, b, equiv);
}
if (a instanceof Map && b instanceof Map) {
return equivMap(a, b, equiv);
}
if (a instanceof Date && b instanceof Date) {
return a.getTime() === b.getTime();
}
if (a instanceof RegExp && b instanceof RegExp) {
return a.toString() === b.toString();
}
// NaN
return (a !== a && b !== b);
};
12 changes: 6 additions & 6 deletions packages/hdom/test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { range } from "@thi.ng/iterators/range";
import { normalizeTree } from "../src/normalize";

function _check(a, b, ctx = null) {
assert.deepEqual(normalizeTree(a, ctx, [], false, false), b);
assert.deepEqual(normalizeTree({ ctx, keys: false, span: false }, a), b);
}

function check(id, a, b) {
Expand Down Expand Up @@ -111,21 +111,21 @@ describe("hdom", () => {
);

it("life cycle", () => {
let src: any = { render: () => ["div"] };
let res: any = ["div", {}];
let src: any = { render: () => ["div", "foo"] };
let res: any = ["div", {}, ["span", {}, "foo"]];
res.__this = src;
res.__init = res.__release = undefined;
res.__args = [null];
assert.deepEqual(
normalizeTree([src], null, [], false, false),
normalizeTree({ keys: false }, [src]),
res
);
res = ["div", { key: "0" }];
res = ["div", { key: "0" }, ["span", { key: "0-0" }, "foo"]];
res.__this = src;
res.__init = res.__release = undefined;
res.__args = [null];
assert.deepEqual(
normalizeTree([src], null, [0], true, false),
normalizeTree({}, [src]),
res
);
});
Expand Down

0 comments on commit f7af43c

Please sign in to comment.