util.ts 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822
  1. /* global: defineProperty */
  2. import { Dictionary, ArrayLike, KeyOfDistributive } from './types';
  3. import { GradientObject } from '../graphic/Gradient';
  4. import { ImagePatternObject } from '../graphic/Pattern';
  5. import { platformApi } from './platform';
  6. // 用于处理merge时无法遍历Date等对象的问题
  7. const BUILTIN_OBJECT: Record<string, boolean> = reduce([
  8. 'Function',
  9. 'RegExp',
  10. 'Date',
  11. 'Error',
  12. 'CanvasGradient',
  13. 'CanvasPattern',
  14. // For node-canvas
  15. 'Image',
  16. 'Canvas'
  17. ], (obj, val) => {
  18. obj['[object ' + val + ']'] = true;
  19. return obj;
  20. }, {} as Record<string, boolean>);
  21. const TYPED_ARRAY: Record<string, boolean> = reduce([
  22. 'Int8',
  23. 'Uint8',
  24. 'Uint8Clamped',
  25. 'Int16',
  26. 'Uint16',
  27. 'Int32',
  28. 'Uint32',
  29. 'Float32',
  30. 'Float64'
  31. ], (obj, val) => {
  32. obj['[object ' + val + 'Array]'] = true;
  33. return obj;
  34. }, {} as Record<string, boolean>);
  35. const objToString = Object.prototype.toString;
  36. const arrayProto = Array.prototype;
  37. const nativeForEach = arrayProto.forEach;
  38. const nativeFilter = arrayProto.filter;
  39. const nativeSlice = arrayProto.slice;
  40. const nativeMap = arrayProto.map;
  41. // In case some env may redefine the global variable `Function`.
  42. const ctorFunction = function () {}.constructor;
  43. const protoFunction = ctorFunction ? ctorFunction.prototype : null;
  44. const protoKey = '__proto__';
  45. let idStart = 0x0907;
  46. /**
  47. * Generate unique id
  48. */
  49. export function guid(): number {
  50. return idStart++;
  51. }
  52. export function logError(...args: any[]) {
  53. if (typeof console !== 'undefined') {
  54. console.error.apply(console, args);
  55. }
  56. }
  57. /**
  58. * Those data types can be cloned:
  59. * Plain object, Array, TypedArray, number, string, null, undefined.
  60. * Those data types will be assigned using the original data:
  61. * BUILTIN_OBJECT
  62. * Instance of user defined class will be cloned to a plain object, without
  63. * properties in prototype.
  64. * Other data types is not supported (not sure what will happen).
  65. *
  66. * Caution: do not support clone Date, for performance consideration.
  67. * (There might be a large number of date in `series.data`).
  68. * So date should not be modified in and out of echarts.
  69. */
  70. export function clone<T extends any>(source: T): T {
  71. if (source == null || typeof source !== 'object') {
  72. return source;
  73. }
  74. let result = source as any;
  75. const typeStr = <string>objToString.call(source);
  76. if (typeStr === '[object Array]') {
  77. if (!isPrimitive(source)) {
  78. result = [] as any;
  79. for (let i = 0, len = (source as any[]).length; i < len; i++) {
  80. result[i] = clone((source as any[])[i]);
  81. }
  82. }
  83. }
  84. else if (TYPED_ARRAY[typeStr]) {
  85. if (!isPrimitive(source)) {
  86. /* eslint-disable-next-line */
  87. const Ctor = source.constructor as typeof Float32Array;
  88. if (Ctor.from) {
  89. result = Ctor.from(source as Float32Array);
  90. }
  91. else {
  92. result = new Ctor((source as Float32Array).length);
  93. for (let i = 0, len = (source as Float32Array).length; i < len; i++) {
  94. result[i] = (source as Float32Array)[i];
  95. }
  96. }
  97. }
  98. }
  99. else if (!BUILTIN_OBJECT[typeStr] && !isPrimitive(source) && !isDom(source)) {
  100. result = {} as any;
  101. for (let key in source) {
  102. // Check if key is __proto__ to avoid prototype pollution
  103. if (source.hasOwnProperty(key) && key !== protoKey) {
  104. result[key] = clone(source[key]);
  105. }
  106. }
  107. }
  108. return result;
  109. }
  110. export function merge<
  111. T extends Dictionary<any>,
  112. S extends Dictionary<any>
  113. >(target: T, source: S, overwrite?: boolean): T & S;
  114. export function merge<
  115. T extends any,
  116. S extends any
  117. >(target: T, source: S, overwrite?: boolean): T | S;
  118. export function merge(target: any, source: any, overwrite?: boolean): any {
  119. // We should escapse that source is string
  120. // and enter for ... in ...
  121. if (!isObject(source) || !isObject(target)) {
  122. return overwrite ? clone(source) : target;
  123. }
  124. for (let key in source) {
  125. // Check if key is __proto__ to avoid prototype pollution
  126. if (source.hasOwnProperty(key) && key !== protoKey) {
  127. const targetProp = target[key];
  128. const sourceProp = source[key];
  129. if (isObject(sourceProp)
  130. && isObject(targetProp)
  131. && !isArray(sourceProp)
  132. && !isArray(targetProp)
  133. && !isDom(sourceProp)
  134. && !isDom(targetProp)
  135. && !isBuiltInObject(sourceProp)
  136. && !isBuiltInObject(targetProp)
  137. && !isPrimitive(sourceProp)
  138. && !isPrimitive(targetProp)
  139. ) {
  140. // 如果需要递归覆盖,就递归调用merge
  141. merge(targetProp, sourceProp, overwrite);
  142. }
  143. else if (overwrite || !(key in target)) {
  144. // 否则只处理overwrite为true,或者在目标对象中没有此属性的情况
  145. // NOTE,在 target[key] 不存在的时候也是直接覆盖
  146. target[key] = clone(source[key]);
  147. }
  148. }
  149. }
  150. return target;
  151. }
  152. /**
  153. * @param targetAndSources The first item is target, and the rests are source.
  154. * @param overwrite
  155. * @return Merged result
  156. */
  157. export function mergeAll(targetAndSources: any[], overwrite?: boolean): any {
  158. let result = targetAndSources[0];
  159. for (let i = 1, len = targetAndSources.length; i < len; i++) {
  160. result = merge(result, targetAndSources[i], overwrite);
  161. }
  162. return result;
  163. }
  164. export function extend<
  165. T extends Dictionary<any>,
  166. S extends Dictionary<any>
  167. >(target: T, source: S): T & S {
  168. // @ts-ignore
  169. if (Object.assign) {
  170. // @ts-ignore
  171. Object.assign(target, source);
  172. }
  173. else {
  174. for (let key in source) {
  175. // Check if key is __proto__ to avoid prototype pollution
  176. if (source.hasOwnProperty(key) && key !== protoKey) {
  177. (target as S & T)[key] = (source as T & S)[key];
  178. }
  179. }
  180. }
  181. return target as T & S;
  182. }
  183. export function defaults<
  184. T extends Dictionary<any>,
  185. S extends Dictionary<any>
  186. >(target: T, source: S, overlay?: boolean): T & S {
  187. const keysArr = keys(source);
  188. for (let i = 0; i < keysArr.length; i++) {
  189. let key = keysArr[i];
  190. if ((overlay ? source[key] != null : (target as T & S)[key] == null)) {
  191. (target as S & T)[key] = (source as T & S)[key];
  192. }
  193. }
  194. return target as T & S;
  195. }
  196. // Expose createCanvas in util for compatibility
  197. export const createCanvas = platformApi.createCanvas;
  198. /**
  199. * 查询数组中元素的index
  200. */
  201. export function indexOf<T>(array: T[] | readonly T[] | ArrayLike<T>, value: T): number {
  202. if (array) {
  203. if ((array as T[]).indexOf) {
  204. return (array as T[]).indexOf(value);
  205. }
  206. for (let i = 0, len = array.length; i < len; i++) {
  207. if (array[i] === value) {
  208. return i;
  209. }
  210. }
  211. }
  212. return -1;
  213. }
  214. /**
  215. * 构造类继承关系
  216. *
  217. * @param clazz 源类
  218. * @param baseClazz 基类
  219. */
  220. export function inherits(clazz: Function, baseClazz: Function) {
  221. const clazzPrototype = clazz.prototype;
  222. function F() {}
  223. F.prototype = baseClazz.prototype;
  224. clazz.prototype = new (F as any)();
  225. for (let prop in clazzPrototype) {
  226. if (clazzPrototype.hasOwnProperty(prop)) {
  227. clazz.prototype[prop] = clazzPrototype[prop];
  228. }
  229. }
  230. clazz.prototype.constructor = clazz;
  231. (clazz as any).superClass = baseClazz;
  232. }
  233. export function mixin<T, S>(target: T | Function, source: S | Function, override?: boolean) {
  234. target = 'prototype' in target ? target.prototype : target;
  235. source = 'prototype' in source ? source.prototype : source;
  236. // If build target is ES6 class. prototype methods is not enumerable. Use getOwnPropertyNames instead
  237. // TODO: Determine if source is ES6 class?
  238. if (Object.getOwnPropertyNames) {
  239. const keyList = Object.getOwnPropertyNames(source);
  240. for (let i = 0; i < keyList.length; i++) {
  241. const key = keyList[i];
  242. if (key !== 'constructor') {
  243. if ((override ? (source as any)[key] != null : (target as any)[key] == null)) {
  244. (target as any)[key] = (source as any)[key];
  245. }
  246. }
  247. }
  248. }
  249. else {
  250. defaults(target, source, override);
  251. }
  252. }
  253. /**
  254. * Consider typed array.
  255. * @param data
  256. */
  257. export function isArrayLike(data: any): data is ArrayLike<any> {
  258. if (!data) {
  259. return false;
  260. }
  261. if (typeof data === 'string') {
  262. return false;
  263. }
  264. return typeof data.length === 'number';
  265. }
  266. /**
  267. * 数组或对象遍历
  268. */
  269. export function each<I extends Dictionary<any> | any[] | readonly any[] | ArrayLike<any>, Context>(
  270. arr: I,
  271. cb: (
  272. this: Context,
  273. // Use unknown to avoid to infer to "any", which may disable typo check.
  274. value: I extends (infer T)[] | readonly (infer T)[] | ArrayLike<infer T> ? T
  275. // Use Dictionary<infer T> may cause infer fail when I is an interface.
  276. // So here use a Record to infer type.
  277. : I extends Dictionary<any> ? I extends Record<infer K, infer T> ? T : unknown : unknown,
  278. index?: I extends any[] | readonly any[] | ArrayLike<any> ? number : keyof I & string, // keyof Dictionary will return number | string
  279. arr?: I
  280. ) => void,
  281. context?: Context
  282. ) {
  283. if (!(arr && cb)) {
  284. return;
  285. }
  286. if ((arr as any).forEach && (arr as any).forEach === nativeForEach) {
  287. (arr as any).forEach(cb, context);
  288. }
  289. else if (arr.length === +arr.length) {
  290. for (let i = 0, len = arr.length; i < len; i++) {
  291. // FIXME: should the elided item be travelled? like `[33,,55]`.
  292. cb.call(context, (arr as any[])[i], i as any, arr);
  293. }
  294. }
  295. else {
  296. for (let key in arr) {
  297. if (arr.hasOwnProperty(key)) {
  298. cb.call(context, (arr as Dictionary<any>)[key], key as any, arr);
  299. }
  300. }
  301. }
  302. }
  303. /**
  304. * Array mapping.
  305. * @typeparam T Type in Array
  306. * @typeparam R Type Returned
  307. * @return Must be an array.
  308. */
  309. export function map<T, R, Context>(
  310. arr: readonly T[],
  311. cb: (this: Context, val: T, index?: number, arr?: readonly T[]) => R,
  312. context?: Context
  313. ): R[] {
  314. // Take the same behavior with lodash when !arr and !cb,
  315. // which might be some common sense.
  316. if (!arr) {
  317. return [];
  318. }
  319. if (!cb) {
  320. return slice(arr) as unknown[] as R[];
  321. }
  322. if (arr.map && arr.map === nativeMap) {
  323. return arr.map(cb, context);
  324. }
  325. else {
  326. const result = [];
  327. for (let i = 0, len = arr.length; i < len; i++) {
  328. // FIXME: should the elided item be travelled, like `[33,,55]`.
  329. result.push(cb.call(context, arr[i], i, arr));
  330. }
  331. return result;
  332. }
  333. }
  334. export function reduce<T, S, Context>(
  335. arr: readonly T[],
  336. cb: (this: Context, previousValue: S, currentValue: T, currentIndex?: number, arr?: readonly T[]) => S,
  337. memo?: S,
  338. context?: Context
  339. ): S {
  340. if (!(arr && cb)) {
  341. return;
  342. }
  343. for (let i = 0, len = arr.length; i < len; i++) {
  344. memo = cb.call(context, memo, arr[i], i, arr);
  345. }
  346. return memo;
  347. }
  348. /**
  349. * Array filtering.
  350. * @return Must be an array.
  351. */
  352. export function filter<T, Context>(
  353. arr: readonly T[],
  354. cb: (this: Context, value: T, index: number, arr: readonly T[]) => boolean,
  355. context?: Context
  356. ): T[] {
  357. // Take the same behavior with lodash when !arr and !cb,
  358. // which might be some common sense.
  359. if (!arr) {
  360. return [];
  361. }
  362. if (!cb) {
  363. return slice(arr);
  364. }
  365. if (arr.filter && arr.filter === nativeFilter) {
  366. return arr.filter(cb, context);
  367. }
  368. else {
  369. const result = [];
  370. for (let i = 0, len = arr.length; i < len; i++) {
  371. // FIXME: should the elided items be travelled? like `[33,,55]`.
  372. if (cb.call(context, arr[i], i, arr)) {
  373. result.push(arr[i]);
  374. }
  375. }
  376. return result;
  377. }
  378. }
  379. /**
  380. * 数组项查找
  381. */
  382. export function find<T, Context>(
  383. arr: readonly T[],
  384. cb: (this: Context, value: T, index?: number, arr?: readonly T[]) => boolean,
  385. context?: Context
  386. ): T {
  387. if (!(arr && cb)) {
  388. return;
  389. }
  390. for (let i = 0, len = arr.length; i < len; i++) {
  391. if (cb.call(context, arr[i], i, arr)) {
  392. return arr[i];
  393. }
  394. }
  395. }
  396. /**
  397. * Get all object keys
  398. *
  399. * Will return an empty array if obj is null/undefined
  400. */
  401. export function keys<T extends object>(obj: T): (KeyOfDistributive<T> & string)[] {
  402. if (!obj) {
  403. return [];
  404. }
  405. // Return type should be `keyof T` but exclude `number`, becuase
  406. // `Object.keys` only return string rather than `number | string`.
  407. type TKeys = KeyOfDistributive<T> & string;
  408. if (Object.keys) {
  409. return Object.keys(obj) as TKeys[];
  410. }
  411. let keyList: TKeys[] = [];
  412. for (let key in obj) {
  413. if (obj.hasOwnProperty(key)) {
  414. keyList.push(key as any);
  415. }
  416. }
  417. return keyList;
  418. }
  419. // Remove this type in returned function. Or it will conflicts wicth callback with given context. Like Eventful.
  420. // According to lib.es5.d.ts
  421. /* eslint-disable max-len*/
  422. export type Bind1<F, Ctx> = F extends (this: Ctx, ...args: infer A) => infer R ? (...args: A) => R : unknown;
  423. export type Bind2<F, Ctx, T1> = F extends (this: Ctx, a: T1, ...args: infer A) => infer R ? (...args: A) => R : unknown;
  424. export type Bind3<F, Ctx, T1, T2> = F extends (this: Ctx, a: T1, b: T2, ...args: infer A) => infer R ? (...args: A) => R : unknown;
  425. export type Bind4<F, Ctx, T1, T2, T3> = F extends (this: Ctx, a: T1, b: T2, c: T3, ...args: infer A) => infer R ? (...args: A) => R : unknown;
  426. export type Bind5<F, Ctx, T1, T2, T3, T4> = F extends (this: Ctx, a: T1, b: T2, c: T3, d: T4, ...args: infer A) => infer R ? (...args: A) => R : unknown;
  427. type BindFunc<Ctx> = (this: Ctx, ...arg: any[]) => any
  428. interface FunctionBind {
  429. <F extends BindFunc<Ctx>, Ctx>(func: F, ctx: Ctx): Bind1<F, Ctx>
  430. <F extends BindFunc<Ctx>, Ctx, T1 extends Parameters<F>[0]>(func: F, ctx: Ctx, a: T1): Bind2<F, Ctx, T1>
  431. <F extends BindFunc<Ctx>, Ctx, T1 extends Parameters<F>[0], T2 extends Parameters<F>[1]>(func: F, ctx: Ctx, a: T1, b: T2): Bind3<F, Ctx, T1, T2>
  432. <F extends BindFunc<Ctx>, Ctx, T1 extends Parameters<F>[0], T2 extends Parameters<F>[1], T3 extends Parameters<F>[2]>(func: F, ctx: Ctx, a: T1, b: T2, c: T3): Bind4<F, Ctx, T1, T2, T3>
  433. <F extends BindFunc<Ctx>, Ctx, T1 extends Parameters<F>[0], T2 extends Parameters<F>[1], T3 extends Parameters<F>[2], T4 extends Parameters<F>[3]>(func: F, ctx: Ctx, a: T1, b: T2, c: T3, d: T4): Bind5<F, Ctx, T1, T2, T3, T4>
  434. }
  435. function bindPolyfill<Ctx, Fn extends(...args: any) => any>(
  436. func: Fn, context: Ctx, ...args: any[]
  437. ): (...args: Parameters<Fn>) => ReturnType<Fn> {
  438. return function (this: Ctx) {
  439. return func.apply(context, args.concat(nativeSlice.call(arguments)));
  440. };
  441. }
  442. export const bind: FunctionBind = (protoFunction && isFunction(protoFunction.bind))
  443. ? protoFunction.call.bind(protoFunction.bind)
  444. : bindPolyfill;
  445. export type Curry1<F, T1> = F extends (a: T1, ...args: infer A) => infer R ? (...args: A) => R : unknown;
  446. export type Curry2<F, T1, T2> = F extends (a: T1, b: T2, ...args: infer A) => infer R ? (...args: A) => R : unknown;
  447. export type Curry3<F, T1, T2, T3> = F extends (a: T1, b: T2, c: T3, ...args: infer A) => infer R ? (...args: A) => R : unknown;
  448. export type Curry4<F, T1, T2, T3, T4> = F extends (a: T1, b: T2, c: T3, d: T4, ...args: infer A) => infer R ? (...args: A) => R : unknown;
  449. type CurryFunc = (...arg: any[]) => any
  450. function curry<F extends CurryFunc, T1 extends Parameters<F>[0]>(func: F, a: T1): Curry1<F, T1>
  451. function curry<F extends CurryFunc, T1 extends Parameters<F>[0], T2 extends Parameters<F>[1]>(func: F, a: T1, b: T2): Curry2<F, T1, T2>
  452. function curry<F extends CurryFunc, T1 extends Parameters<F>[0], T2 extends Parameters<F>[1], T3 extends Parameters<F>[2]>(func: F, a: T1, b: T2, c: T3): Curry3<F, T1, T2, T3>
  453. function curry<F extends CurryFunc, T1 extends Parameters<F>[0], T2 extends Parameters<F>[1], T3 extends Parameters<F>[2], T4 extends Parameters<F>[3]>(func: F, a: T1, b: T2, c: T3, d: T4): Curry4<F, T1, T2, T3, T4>
  454. function curry(func: Function, ...args: any[]): Function {
  455. return function (this: any) {
  456. return func.apply(this, args.concat(nativeSlice.call(arguments)));
  457. };
  458. }
  459. export {curry};
  460. /* eslint-enable max-len*/
  461. export function isArray(value: any): value is any[] {
  462. if (Array.isArray) {
  463. return Array.isArray(value);
  464. }
  465. return objToString.call(value) === '[object Array]';
  466. }
  467. export function isFunction(value: any): value is Function {
  468. return typeof value === 'function';
  469. }
  470. export function isString(value: any): value is string {
  471. // Faster than `objToString.call` several times in chromium and webkit.
  472. // And `new String()` is rarely used.
  473. return typeof value === 'string';
  474. }
  475. export function isStringSafe(value: any): value is string {
  476. return objToString.call(value) === '[object String]';
  477. }
  478. export function isNumber(value: any): value is number {
  479. // Faster than `objToString.call` several times in chromium and webkit.
  480. // And `new Number()` is rarely used.
  481. return typeof value === 'number';
  482. }
  483. // Usage: `isObject(xxx)` or `isObject(SomeType)(xxx)`
  484. // Generic T can be used to avoid "ts type gruards" casting the `value` from its original
  485. // type `Object` implicitly so that loose its original type info in the subsequent code.
  486. export function isObject<T = unknown>(value: T): value is (object & T) {
  487. // Avoid a V8 JIT bug in Chrome 19-20.
  488. // See https://code.google.com/p/v8/issues/detail?id=2291 for more details.
  489. const type = typeof value;
  490. return type === 'function' || (!!value && type === 'object');
  491. }
  492. export function isBuiltInObject(value: any): boolean {
  493. return !!BUILTIN_OBJECT[objToString.call(value)];
  494. }
  495. export function isTypedArray(value: any): boolean {
  496. return !!TYPED_ARRAY[objToString.call(value)];
  497. }
  498. export function isDom(value: any): value is HTMLElement {
  499. return typeof value === 'object'
  500. && typeof value.nodeType === 'number'
  501. && typeof value.ownerDocument === 'object';
  502. }
  503. export function isGradientObject(value: any): value is GradientObject {
  504. return (value as GradientObject).colorStops != null;
  505. }
  506. export function isImagePatternObject(value: any): value is ImagePatternObject {
  507. return (value as ImagePatternObject).image != null;
  508. }
  509. export function isRegExp(value: unknown): value is RegExp {
  510. return objToString.call(value) === '[object RegExp]';
  511. }
  512. /**
  513. * Whether is exactly NaN. Notice isNaN('a') returns true.
  514. */
  515. export function eqNaN(value: any): boolean {
  516. /* eslint-disable-next-line no-self-compare */
  517. return value !== value;
  518. }
  519. /**
  520. * If value1 is not null, then return value1, otherwise judget rest of values.
  521. * Low performance.
  522. * @return Final value
  523. */
  524. export function retrieve<T>(...args: T[]): T {
  525. for (let i = 0, len = args.length; i < len; i++) {
  526. if (args[i] != null) {
  527. return args[i];
  528. }
  529. }
  530. }
  531. export function retrieve2<T, R>(value0: T, value1: R): T | R {
  532. return value0 != null
  533. ? value0
  534. : value1;
  535. }
  536. export function retrieve3<T, R, W>(value0: T, value1: R, value2: W): T | R | W {
  537. return value0 != null
  538. ? value0
  539. : value1 != null
  540. ? value1
  541. : value2;
  542. }
  543. type SliceParams = Parameters<typeof nativeSlice>;
  544. export function slice<T>(arr: ArrayLike<T>, ...args: SliceParams): T[] {
  545. return nativeSlice.apply(arr, args as any[]);
  546. }
  547. /**
  548. * Normalize css liked array configuration
  549. * e.g.
  550. * 3 => [3, 3, 3, 3]
  551. * [4, 2] => [4, 2, 4, 2]
  552. * [4, 3, 2] => [4, 3, 2, 3]
  553. */
  554. export function normalizeCssArray(val: number | number[]) {
  555. if (typeof (val) === 'number') {
  556. return [val, val, val, val];
  557. }
  558. const len = val.length;
  559. if (len === 2) {
  560. // vertical | horizontal
  561. return [val[0], val[1], val[0], val[1]];
  562. }
  563. else if (len === 3) {
  564. // top | horizontal | bottom
  565. return [val[0], val[1], val[2], val[1]];
  566. }
  567. return val;
  568. }
  569. export function assert(condition: any, message?: string) {
  570. if (!condition) {
  571. throw new Error(message);
  572. }
  573. }
  574. /**
  575. * @param str string to be trimmed
  576. * @return trimmed string
  577. */
  578. export function trim(str: string): string {
  579. if (str == null) {
  580. return null;
  581. }
  582. else if (typeof str.trim === 'function') {
  583. return str.trim();
  584. }
  585. else {
  586. return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
  587. }
  588. }
  589. const primitiveKey = '__ec_primitive__';
  590. /**
  591. * Set an object as primitive to be ignored traversing children in clone or merge
  592. */
  593. export function setAsPrimitive(obj: any) {
  594. obj[primitiveKey] = true;
  595. }
  596. export function isPrimitive(obj: any): boolean {
  597. return obj[primitiveKey];
  598. }
  599. interface MapInterface<T, KEY extends string | number = string | number> {
  600. delete(key: KEY): boolean;
  601. has(key: KEY): boolean;
  602. get(key: KEY): T | undefined;
  603. set(key: KEY, value: T): this;
  604. keys(): KEY[];
  605. forEach(callback: (value: T, key: KEY) => void): void;
  606. }
  607. class MapPolyfill<T, KEY extends string | number = string | number> implements MapInterface<T, KEY> {
  608. private data: Record<KEY, T> = {} as Record<KEY, T>;
  609. delete(key: KEY): boolean {
  610. const existed = this.has(key);
  611. if (existed) {
  612. delete this.data[key];
  613. }
  614. return existed;
  615. }
  616. has(key: KEY): boolean {
  617. return this.data.hasOwnProperty(key);
  618. }
  619. get(key: KEY): T | undefined {
  620. return this.data[key];
  621. }
  622. set(key: KEY, value: T): this {
  623. this.data[key] = value;
  624. return this;
  625. }
  626. keys(): KEY[] {
  627. return keys(this.data);
  628. }
  629. forEach(callback: (value: T, key: KEY) => void): void {
  630. // This is a potential performance bottleneck, see details in
  631. // https://github.com/ecomfe/zrender/issues/965, however it is now
  632. // less likely to occur as we default to native maps when possible.
  633. const data = this.data;
  634. for (const key in data) {
  635. if (data.hasOwnProperty(key)) {
  636. callback(data[key], key);
  637. }
  638. }
  639. }
  640. }
  641. // We want to use native Map if it is available, but we do not want to polyfill the global scope
  642. // in case users ship their own polyfills or patch the native map object in any way.
  643. const isNativeMapSupported = typeof Map === 'function';
  644. function maybeNativeMap<T, KEY extends string | number = string | number>(): MapInterface<T, KEY> {
  645. // Map may be a native class if we are running in an ES6 compatible environment.
  646. // eslint-disable-next-line
  647. return (isNativeMapSupported ? new Map<KEY, T>() : new MapPolyfill<T, KEY>()) as MapInterface<T, KEY>;
  648. }
  649. /**
  650. * @constructor
  651. * @param {Object} obj
  652. */
  653. export class HashMap<T, KEY extends string | number = string | number> {
  654. data: MapInterface<T, KEY>
  655. constructor(obj?: HashMap<T, KEY> | { [key in KEY]?: T } | KEY[]) {
  656. const isArr = isArray(obj);
  657. // Key should not be set on this, otherwise
  658. // methods get/set/... may be overridden.
  659. this.data = maybeNativeMap<T, KEY>();
  660. const thisMap = this;
  661. (obj instanceof HashMap)
  662. ? obj.each(visit)
  663. : (obj && each(obj, visit));
  664. function visit(value: any, key: any) {
  665. isArr ? thisMap.set(value, key) : thisMap.set(key, value);
  666. }
  667. }
  668. // `hasKey` instead of `has` for potential misleading.
  669. hasKey(key: KEY): boolean {
  670. return this.data.has(key);
  671. }
  672. get(key: KEY): T {
  673. return this.data.get(key);
  674. }
  675. set(key: KEY, value: T): T {
  676. // Comparing with invocation chaining, `return value` is more commonly
  677. // used in this case: `const someVal = map.set('a', genVal());`
  678. this.data.set(key, value);
  679. return value;
  680. }
  681. // Although util.each can be performed on this hashMap directly, user
  682. // should not use the exposed keys, who are prefixed.
  683. each<Context>(
  684. cb: (this: Context, value?: T, key?: KEY) => void,
  685. context?: Context
  686. ) {
  687. this.data.forEach((value, key) => {
  688. cb.call(context, value, key);
  689. });
  690. }
  691. keys(): KEY[] {
  692. const keys = this.data.keys();
  693. return isNativeMapSupported
  694. // Native map returns an iterator so we need to convert it to an array
  695. ? Array.from(keys)
  696. : keys;
  697. }
  698. // Do not use this method if performance sensitive.
  699. removeKey(key: KEY): void {
  700. this.data.delete(key);
  701. }
  702. }
  703. export function createHashMap<T, KEY extends string | number = string | number>(
  704. obj?: HashMap<T, KEY> | { [key in KEY]?: T } | KEY[]
  705. ) {
  706. return new HashMap<T, KEY>(obj);
  707. }
  708. export function concatArray<T, R>(a: ArrayLike<T>, b: ArrayLike<R>): ArrayLike<T | R> {
  709. const newArray = new (a as any).constructor(a.length + b.length);
  710. for (let i = 0; i < a.length; i++) {
  711. newArray[i] = a[i];
  712. }
  713. const offset = a.length;
  714. for (let i = 0; i < b.length; i++) {
  715. newArray[i + offset] = b[i];
  716. }
  717. return newArray;
  718. }
  719. export function createObject<T>(proto?: object, properties?: T): T {
  720. // Performance of Object.create
  721. // https://jsperf.com/style-strategy-proto-or-others
  722. let obj: T;
  723. if (Object.create) {
  724. obj = Object.create(proto);
  725. }
  726. else {
  727. const StyleCtor = function () {};
  728. StyleCtor.prototype = proto;
  729. obj = new (StyleCtor as any)();
  730. }
  731. if (properties) {
  732. extend(obj, properties);
  733. }
  734. return obj;
  735. }
  736. export function disableUserSelect(dom: HTMLElement) {
  737. const domStyle = dom.style;
  738. domStyle.webkitUserSelect = 'none';
  739. domStyle.userSelect = 'none';
  740. // @ts-ignore
  741. domStyle.webkitTapHighlightColor = 'rgba(0,0,0,0)';
  742. (domStyle as any)['-webkit-touch-callout'] = 'none';
  743. }
  744. export function hasOwn(own: object, prop: string): boolean {
  745. return own.hasOwnProperty(prop);
  746. }
  747. export function noop() {}
  748. export const RADIAN_TO_DEGREE = 180 / Math.PI;