Skip to content

Commit 4193244

Browse files
feat(common): Perf improvements in hot functions:
- extend: Use native ES6 `Object.assign` if available - extend: Use nested for loop for non-native polyfill - defaults: Simplify logic - pick / omit: avoid v8 deopt. simplify. remove varargs support - arrayTuples: unroll for arguments.length === 1, 2, 3, 4 Related to #27 Related to angular-ui/ui-router#3274
1 parent 027c995 commit 4193244

File tree

4 files changed

+65
-81
lines changed

4 files changed

+65
-81
lines changed

src/common/common.ts

+61-77
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
*
66
* @preferred
77
* @module common
8-
*/ /** for typedoc */
9-
10-
import { isFunction, isString, isArray, isRegExp, isDate } from "./predicates";
11-
import { all, any, prop, curry, val } from "./hof";
8+
*/
9+
/** for typedoc */
10+
import { isFunction, isString, isArray, isRegExp, isDate, isDefined } from "./predicates";
11+
import { all, any, prop, curry, val, not } from "./hof";
1212
import { services } from "./coreservices";
1313
import { State } from "../state/stateObject";
1414

@@ -18,10 +18,10 @@ export const fromJson = angular.fromJson || JSON.parse.bind(JSON);
1818
export const toJson = angular.toJson || JSON.stringify.bind(JSON);
1919
export const copy = angular.copy || _copy;
2020
export const forEach = angular.forEach || _forEach;
21-
export const extend = angular.extend || _extend;
21+
export const extend = Object.assign || _extend;
2222
export const equals = angular.equals || _equals;
23-
export const identity = (x: any) => x;
24-
export const noop = () => <any> undefined;
23+
export function identity(x: any) { return x; }
24+
export function noop(): any {}
2525

2626
export type Mapper<X, T> = (x: X, key?: (string|number)) => T;
2727
export interface TypedMap<T> { [key: string]: T; }
@@ -206,22 +206,10 @@ export const deregAll = (functions: Function[]) =>
206206
* to only those properties of the objects in the defaultsList.
207207
* Earlier objects in the defaultsList take precedence when applying defaults.
208208
*/
209-
export function defaults(opts = {}, ...defaultsList: Obj[]) {
210-
let defaults = merge.apply(null, [{}].concat(defaultsList));
211-
return extend({}, defaults, pick(opts || {}, Object.keys(defaults)));
212-
}
213-
214-
/**
215-
* Merges properties from the list of objects to the destination object.
216-
* If a property already exists in the destination object, then it is not overwritten.
217-
*/
218-
export function merge(dst: Obj, ...objs: Obj[]) {
219-
forEach(objs, function(obj: Obj) {
220-
forEach(obj, function(value: any, key: string) {
221-
if (!dst.hasOwnProperty(key)) dst[key] = value;
222-
});
223-
});
224-
return dst;
209+
export function defaults(opts, ...defaultsList: Obj[]) {
210+
let _defaultsList = defaultsList.concat({}).reverse();
211+
let defaultVals = extend.apply(null, _defaultsList);
212+
return extend({}, defaultVals, pick(opts || {}, Object.keys(defaultVals)));
225213
}
226214

227215
/** Reduce function that merges each element of the list into a single object, using extend */
@@ -244,16 +232,9 @@ export function ancestors(first: State, second: State) {
244232
return path;
245233
}
246234

247-
type PickOmitPredicate = (keys: string[], key: string) => boolean;
248-
function pickOmitImpl(predicate: PickOmitPredicate, obj: Obj, ...keys: string[]) {
249-
let objCopy = {};
250-
for (let key in obj) {
251-
if (predicate(keys, key)) objCopy[key] = obj[key];
252-
}
253-
return objCopy;
254-
}
255-
256235
/**
236+
* Return a copy of the object only containing the whitelisted properties.
237+
257238
* @example
258239
* ```
259240
*
@@ -263,24 +244,16 @@ function pickOmitImpl(predicate: PickOmitPredicate, obj: Obj, ...keys: string[])
263244
* @param obj the source object
264245
* @param propNames an Array of strings, which are the whitelisted property names
265246
*/
266-
export function pick(obj: Obj, propNames: string[]): Obj;
267-
/**
268-
* @example
269-
* ```
270-
*
271-
* var foo = { a: 1, b: 2, c: 3 };
272-
* var ab = pick(foo, 'a', 'b'); // { a: 1, b: 2 }
273-
* ```
274-
* @param obj the source object
275-
* @param propNames 1..n strings, which are the whitelisted property names
276-
*/
277-
export function pick(obj: Obj, ...propNames: string[]): Obj;
278-
/** Return a copy of the object only containing the whitelisted properties. */
279-
export function pick(obj: Obj) {
280-
return pickOmitImpl.apply(null, [inArray].concat(restArgs(arguments)));
247+
export function pick(obj: Obj, propNames: string[]): Obj {
248+
let copy = {};
249+
// propNames.forEach(prop => { if (obj.hasOwnProperty(prop)) copy[prop] = obj[prop] });
250+
propNames.forEach(prop => { if (isDefined(obj[prop])) copy[prop] = obj[prop] });
251+
return copy;
281252
}
282253

283254
/**
255+
* Return a copy of the object omitting the blacklisted properties.
256+
*
284257
* @example
285258
* ```
286259
*
@@ -290,22 +263,10 @@ export function pick(obj: Obj) {
290263
* @param obj the source object
291264
* @param propNames an Array of strings, which are the blacklisted property names
292265
*/
293-
export function omit(obj: Obj, propNames: string[]): Obj;
294-
/**
295-
* @example
296-
* ```
297-
*
298-
* var foo = { a: 1, b: 2, c: 3 };
299-
* var ab = omit(foo, 'a', 'b'); // { c: 3 }
300-
* ```
301-
* @param obj the source object
302-
* @param propNames 1..n strings, which are the blacklisted property names
303-
*/
304-
export function omit(obj: Obj, ...propNames: string[]): Obj;
305-
/** Return a copy of the object omitting the blacklisted properties. */
306-
export function omit(obj: Obj) {
307-
let notInArray = (array, item) => !inArray(array, item);
308-
return pickOmitImpl.apply(null, [notInArray].concat(restArgs(arguments)));
266+
export function omit(obj: Obj, propNames: string[]): Obj {
267+
return Object.keys(obj)
268+
.filter(not(inArray(propNames)))
269+
.reduce((acc, key) => (acc[key] = obj[key], acc), {});
309270
}
310271

311272

@@ -534,10 +495,26 @@ export const pairs = (obj: Obj) =>
534495
* arrayTuples(foo, bar, baz); // [ [0, 1, 10], [2, 3, 30], [4, 5, 50], [6, 7, 70] ]
535496
* ```
536497
*/
537-
export function arrayTuples(...arrayArgs: any[]): any[] {
538-
if (arrayArgs.length === 0) return [];
539-
let length = arrayArgs.reduce((min, arr) => Math.min(arr.length, min), 9007199254740991); // aka 2^53 − 1 aka Number.MAX_SAFE_INTEGER
540-
return Array.apply(null, Array(length)).map((ignored, idx) => arrayArgs.map(arr => arr[idx]));
498+
export function arrayTuples(...args: any[]): any[] {
499+
if (args.length === 0) return [];
500+
let maxArrayLen = args.reduce((min, arr) => Math.min(arr.length, min), 9007199254740991); // aka 2^53 − 1 aka Number.MAX_SAFE_INTEGER
501+
502+
let i, result = [];
503+
504+
for (i = 0; i < maxArrayLen; i++) {
505+
// This is a hot function
506+
// Unroll when there are 1-4 arguments
507+
switch (args.length) {
508+
case 1: result.push([args[0][i]]); break;
509+
case 2: result.push([args[0][i], args[1][i]]); break;
510+
case 3: result.push([args[0][i], args[1][i], args[2][i]]); break;
511+
case 4: result.push([args[0][i], args[1][i], args[2][i], args[3][i]]); break;
512+
default:
513+
result.push(args.map(array => array[i])); break;
514+
}
515+
}
516+
517+
return result;
541518
}
542519

543520
/**
@@ -591,14 +568,20 @@ function _forEach(obj: (any[]|any), cb: (el, idx?) => void, _this: Obj) {
591568
Object.keys(obj).forEach(key => cb(obj[key], key));
592569
}
593570

594-
function _copyProps(to: Obj, from: Obj) {
595-
Object.keys(from).forEach(key => to[key] = from[key]);
596-
return to;
597-
}
598-
function _extend(toObj: Obj, fromObj: Obj): Obj;
599-
function _extend(toObj: Obj, ...fromObj: Obj[]): Obj;
600-
function _extend(toObj: Obj) {
601-
return restArgs(arguments, 1).filter(identity).reduce(_copyProps, toObj);
571+
/** Like Object.assign() */
572+
export function _extend(toObj: Obj, ...fromObjs: Obj[]): any;
573+
export function _extend(toObj: Obj): any {
574+
for (let i = 1; i < arguments.length; i++) {
575+
let obj = arguments[i];
576+
if (!obj) continue;
577+
let keys = Object.keys(obj);
578+
579+
for (let j = 0; j < keys.length; j++) {
580+
toObj[keys[j]] = obj[keys[j]];
581+
}
582+
}
583+
584+
return toObj;
602585
}
603586

604587
function _equals(o1: any, o2: any): boolean {
@@ -684,8 +667,9 @@ export const sortBy = (propFn: (a) => number, checkFn: Predicate<any> = val(true
684667
* @param sortFns list of sort functions
685668
*/
686669
export const composeSort = (...sortFns: sortfn[]): sortfn =>
687-
(a, b) =>
688-
sortFns.reduce((prev, fn) => prev || fn(a, b), 0);
670+
function composedSort(a, b) {
671+
return sortFns.reduce((prev, fn) => prev || fn(a, b), 0);
672+
};
689673

690674
// issue #2676
691675
export const silenceUncaughtInPromise = (promise: Promise<any>) =>

src/params/paramTypes.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ export class ParamTypes {
198198

199199

200200
/** @internalapi */
201-
private defaultTypes: any = pick(ParamTypes.prototype, "hash", "string", "query", "path", "int", "bool", "date", "json", "any");
201+
private defaultTypes: any = pick(ParamTypes.prototype, ["hash", "string", "query", "path", "int", "bool", "date", "json", "any"]);
202202

203203
/** @internalapi */
204204
constructor() {

src/transition/transition.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -728,7 +728,7 @@ export class Transition implements IHookRegistry {
728728
let toStateOrName = this.to();
729729

730730
const avoidEmptyHash = (params: RawParams) =>
731-
(params["#"] !== null && params["#"] !== undefined) ? params : omit(params, "#");
731+
(params["#"] !== null && params["#"] !== undefined) ? params : omit(params, ["#"]);
732732

733733
// (X) means the to state is invalid.
734734
let id = this.$id,

test/_testUtils.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ let stateProps = ["resolve", "resolvePolicy", "data", "template", "templateUrl",
66
export function tree2Array(tree, inheritName) {
77

88
function processState(parent, state, name) {
9-
let substates = omit.apply(null, [state].concat(stateProps));
10-
let thisState = pick.apply(null, [state].concat(stateProps));
9+
let substates: any = omit(state, stateProps);
10+
let thisState: any = pick(state, stateProps);
1111
thisState.name = name;
1212
if (!inheritName) thisState.parent = parent;
1313

0 commit comments

Comments
 (0)