util.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676
  1. /**
  2. * @module zrender/core/util
  3. */
  4. // 用于处理merge时无法遍历Date等对象的问题
  5. var BUILTIN_OBJECT = {
  6. '[object Function]': 1,
  7. '[object RegExp]': 1,
  8. '[object Date]': 1,
  9. '[object Error]': 1,
  10. '[object CanvasGradient]': 1,
  11. '[object CanvasPattern]': 1,
  12. // For node-canvas
  13. '[object Image]': 1,
  14. '[object Canvas]': 1
  15. };
  16. var TYPED_ARRAY = {
  17. '[object Int8Array]': 1,
  18. '[object Uint8Array]': 1,
  19. '[object Uint8ClampedArray]': 1,
  20. '[object Int16Array]': 1,
  21. '[object Uint16Array]': 1,
  22. '[object Int32Array]': 1,
  23. '[object Uint32Array]': 1,
  24. '[object Float32Array]': 1,
  25. '[object Float64Array]': 1
  26. };
  27. var objToString = Object.prototype.toString;
  28. var arrayProto = Array.prototype;
  29. var nativeForEach = arrayProto.forEach;
  30. var nativeFilter = arrayProto.filter;
  31. var nativeSlice = arrayProto.slice;
  32. var nativeMap = arrayProto.map;
  33. var nativeReduce = arrayProto.reduce;
  34. // Avoid assign to an exported variable, for transforming to cjs.
  35. var methods = {};
  36. export function $override(name, fn) {
  37. // Clear ctx instance for different environment
  38. if (name === 'createCanvas') {
  39. _ctx = null;
  40. }
  41. methods[name] = fn;
  42. }
  43. /**
  44. * Those data types can be cloned:
  45. * Plain object, Array, TypedArray, number, string, null, undefined.
  46. * Those data types will be assgined using the orginal data:
  47. * BUILTIN_OBJECT
  48. * Instance of user defined class will be cloned to a plain object, without
  49. * properties in prototype.
  50. * Other data types is not supported (not sure what will happen).
  51. *
  52. * Caution: do not support clone Date, for performance consideration.
  53. * (There might be a large number of date in `series.data`).
  54. * So date should not be modified in and out of echarts.
  55. *
  56. * @param {*} source
  57. * @return {*} new
  58. */
  59. export function clone(source) {
  60. if (source == null || typeof source !== 'object') {
  61. return source;
  62. }
  63. var result = source;
  64. var typeStr = objToString.call(source);
  65. if (typeStr === '[object Array]') {
  66. if (!isPrimitive(source)) {
  67. result = [];
  68. for (var i = 0, len = source.length; i < len; i++) {
  69. result[i] = clone(source[i]);
  70. }
  71. }
  72. }
  73. else if (TYPED_ARRAY[typeStr]) {
  74. if (!isPrimitive(source)) {
  75. var Ctor = source.constructor;
  76. if (source.constructor.from) {
  77. result = Ctor.from(source);
  78. }
  79. else {
  80. result = new Ctor(source.length);
  81. for (var i = 0, len = source.length; i < len; i++) {
  82. result[i] = clone(source[i]);
  83. }
  84. }
  85. }
  86. }
  87. else if (!BUILTIN_OBJECT[typeStr] && !isPrimitive(source) && !isDom(source)) {
  88. result = {};
  89. for (var key in source) {
  90. if (source.hasOwnProperty(key)) {
  91. result[key] = clone(source[key]);
  92. }
  93. }
  94. }
  95. return result;
  96. }
  97. /**
  98. * @memberOf module:zrender/core/util
  99. * @param {*} target
  100. * @param {*} source
  101. * @param {boolean} [overwrite=false]
  102. */
  103. export function merge(target, source, overwrite) {
  104. // We should escapse that source is string
  105. // and enter for ... in ...
  106. if (!isObject(source) || !isObject(target)) {
  107. return overwrite ? clone(source) : target;
  108. }
  109. for (var key in source) {
  110. if (source.hasOwnProperty(key)) {
  111. var targetProp = target[key];
  112. var sourceProp = source[key];
  113. if (isObject(sourceProp)
  114. && isObject(targetProp)
  115. && !isArray(sourceProp)
  116. && !isArray(targetProp)
  117. && !isDom(sourceProp)
  118. && !isDom(targetProp)
  119. && !isBuiltInObject(sourceProp)
  120. && !isBuiltInObject(targetProp)
  121. && !isPrimitive(sourceProp)
  122. && !isPrimitive(targetProp)
  123. ) {
  124. // 如果需要递归覆盖,就递归调用merge
  125. merge(targetProp, sourceProp, overwrite);
  126. }
  127. else if (overwrite || !(key in target)) {
  128. // 否则只处理overwrite为true,或者在目标对象中没有此属性的情况
  129. // NOTE,在 target[key] 不存在的时候也是直接覆盖
  130. target[key] = clone(source[key], true);
  131. }
  132. }
  133. }
  134. return target;
  135. }
  136. /**
  137. * @param {Array} targetAndSources The first item is target, and the rests are source.
  138. * @param {boolean} [overwrite=false]
  139. * @return {*} target
  140. */
  141. export function mergeAll(targetAndSources, overwrite) {
  142. var result = targetAndSources[0];
  143. for (var i = 1, len = targetAndSources.length; i < len; i++) {
  144. result = merge(result, targetAndSources[i], overwrite);
  145. }
  146. return result;
  147. }
  148. /**
  149. * @param {*} target
  150. * @param {*} source
  151. * @memberOf module:zrender/core/util
  152. */
  153. export function extend(target, source) {
  154. for (var key in source) {
  155. if (source.hasOwnProperty(key)) {
  156. target[key] = source[key];
  157. }
  158. }
  159. return target;
  160. }
  161. /**
  162. * @param {*} target
  163. * @param {*} source
  164. * @param {boolean} [overlay=false]
  165. * @memberOf module:zrender/core/util
  166. */
  167. export function defaults(target, source, overlay) {
  168. for (var key in source) {
  169. if (source.hasOwnProperty(key)
  170. && (overlay ? source[key] != null : target[key] == null)
  171. ) {
  172. target[key] = source[key];
  173. }
  174. }
  175. return target;
  176. }
  177. export var createCanvas = function () {
  178. return methods.createCanvas();
  179. };
  180. methods.createCanvas = function () {
  181. return document.createElement('canvas');
  182. };
  183. // FIXME
  184. var _ctx;
  185. export function getContext() {
  186. if (!_ctx) {
  187. // Use util.createCanvas instead of createCanvas
  188. // because createCanvas may be overwritten in different environment
  189. _ctx = createCanvas().getContext('2d');
  190. }
  191. return _ctx;
  192. }
  193. /**
  194. * 查询数组中元素的index
  195. * @memberOf module:zrender/core/util
  196. */
  197. export function indexOf(array, value) {
  198. if (array) {
  199. if (array.indexOf) {
  200. return array.indexOf(value);
  201. }
  202. for (var i = 0, len = array.length; i < len; i++) {
  203. if (array[i] === value) {
  204. return i;
  205. }
  206. }
  207. }
  208. return -1;
  209. }
  210. /**
  211. * 构造类继承关系
  212. *
  213. * @memberOf module:zrender/core/util
  214. * @param {Function} clazz 源类
  215. * @param {Function} baseClazz 基类
  216. */
  217. export function inherits(clazz, baseClazz) {
  218. var clazzPrototype = clazz.prototype;
  219. function F() {}
  220. F.prototype = baseClazz.prototype;
  221. clazz.prototype = new F();
  222. for (var prop in clazzPrototype) {
  223. if (clazzPrototype.hasOwnProperty(prop)) {
  224. clazz.prototype[prop] = clazzPrototype[prop];
  225. }
  226. }
  227. clazz.prototype.constructor = clazz;
  228. clazz.superClass = baseClazz;
  229. }
  230. /**
  231. * @memberOf module:zrender/core/util
  232. * @param {Object|Function} target
  233. * @param {Object|Function} sorce
  234. * @param {boolean} overlay
  235. */
  236. export function mixin(target, source, overlay) {
  237. target = 'prototype' in target ? target.prototype : target;
  238. source = 'prototype' in source ? source.prototype : source;
  239. defaults(target, source, overlay);
  240. }
  241. /**
  242. * Consider typed array.
  243. * @param {Array|TypedArray} data
  244. */
  245. export function isArrayLike(data) {
  246. if (!data) {
  247. return;
  248. }
  249. if (typeof data === 'string') {
  250. return false;
  251. }
  252. return typeof data.length === 'number';
  253. }
  254. /**
  255. * 数组或对象遍历
  256. * @memberOf module:zrender/core/util
  257. * @param {Object|Array} obj
  258. * @param {Function} cb
  259. * @param {*} [context]
  260. */
  261. export function each(obj, cb, context) {
  262. if (!(obj && cb)) {
  263. return;
  264. }
  265. if (obj.forEach && obj.forEach === nativeForEach) {
  266. obj.forEach(cb, context);
  267. }
  268. else if (obj.length === +obj.length) {
  269. for (var i = 0, len = obj.length; i < len; i++) {
  270. cb.call(context, obj[i], i, obj);
  271. }
  272. }
  273. else {
  274. for (var key in obj) {
  275. if (obj.hasOwnProperty(key)) {
  276. cb.call(context, obj[key], key, obj);
  277. }
  278. }
  279. }
  280. }
  281. /**
  282. * 数组映射
  283. * @memberOf module:zrender/core/util
  284. * @param {Array} obj
  285. * @param {Function} cb
  286. * @param {*} [context]
  287. * @return {Array}
  288. */
  289. export function map(obj, cb, context) {
  290. if (!(obj && cb)) {
  291. return;
  292. }
  293. if (obj.map && obj.map === nativeMap) {
  294. return obj.map(cb, context);
  295. }
  296. else {
  297. var result = [];
  298. for (var i = 0, len = obj.length; i < len; i++) {
  299. result.push(cb.call(context, obj[i], i, obj));
  300. }
  301. return result;
  302. }
  303. }
  304. /**
  305. * @memberOf module:zrender/core/util
  306. * @param {Array} obj
  307. * @param {Function} cb
  308. * @param {Object} [memo]
  309. * @param {*} [context]
  310. * @return {Array}
  311. */
  312. export function reduce(obj, cb, memo, context) {
  313. if (!(obj && cb)) {
  314. return;
  315. }
  316. if (obj.reduce && obj.reduce === nativeReduce) {
  317. return obj.reduce(cb, memo, context);
  318. }
  319. else {
  320. for (var i = 0, len = obj.length; i < len; i++) {
  321. memo = cb.call(context, memo, obj[i], i, obj);
  322. }
  323. return memo;
  324. }
  325. }
  326. /**
  327. * 数组过滤
  328. * @memberOf module:zrender/core/util
  329. * @param {Array} obj
  330. * @param {Function} cb
  331. * @param {*} [context]
  332. * @return {Array}
  333. */
  334. export function filter(obj, cb, context) {
  335. if (!(obj && cb)) {
  336. return;
  337. }
  338. if (obj.filter && obj.filter === nativeFilter) {
  339. return obj.filter(cb, context);
  340. }
  341. else {
  342. var result = [];
  343. for (var i = 0, len = obj.length; i < len; i++) {
  344. if (cb.call(context, obj[i], i, obj)) {
  345. result.push(obj[i]);
  346. }
  347. }
  348. return result;
  349. }
  350. }
  351. /**
  352. * 数组项查找
  353. * @memberOf module:zrender/core/util
  354. * @param {Array} obj
  355. * @param {Function} cb
  356. * @param {*} [context]
  357. * @return {*}
  358. */
  359. export function find(obj, cb, context) {
  360. if (!(obj && cb)) {
  361. return;
  362. }
  363. for (var i = 0, len = obj.length; i < len; i++) {
  364. if (cb.call(context, obj[i], i, obj)) {
  365. return obj[i];
  366. }
  367. }
  368. }
  369. /**
  370. * @memberOf module:zrender/core/util
  371. * @param {Function} func
  372. * @param {*} context
  373. * @return {Function}
  374. */
  375. export function bind(func, context) {
  376. var args = nativeSlice.call(arguments, 2);
  377. return function () {
  378. return func.apply(context, args.concat(nativeSlice.call(arguments)));
  379. };
  380. }
  381. /**
  382. * @memberOf module:zrender/core/util
  383. * @param {Function} func
  384. * @return {Function}
  385. */
  386. export function curry(func) {
  387. var args = nativeSlice.call(arguments, 1);
  388. return function () {
  389. return func.apply(this, args.concat(nativeSlice.call(arguments)));
  390. };
  391. }
  392. /**
  393. * @memberOf module:zrender/core/util
  394. * @param {*} value
  395. * @return {boolean}
  396. */
  397. export function isArray(value) {
  398. return objToString.call(value) === '[object Array]';
  399. }
  400. /**
  401. * @memberOf module:zrender/core/util
  402. * @param {*} value
  403. * @return {boolean}
  404. */
  405. export function isFunction(value) {
  406. return typeof value === 'function';
  407. }
  408. /**
  409. * @memberOf module:zrender/core/util
  410. * @param {*} value
  411. * @return {boolean}
  412. */
  413. export function isString(value) {
  414. return objToString.call(value) === '[object String]';
  415. }
  416. /**
  417. * @memberOf module:zrender/core/util
  418. * @param {*} value
  419. * @return {boolean}
  420. */
  421. export function isObject(value) {
  422. // Avoid a V8 JIT bug in Chrome 19-20.
  423. // See https://code.google.com/p/v8/issues/detail?id=2291 for more details.
  424. var type = typeof value;
  425. return type === 'function' || (!!value && type === 'object');
  426. }
  427. /**
  428. * @memberOf module:zrender/core/util
  429. * @param {*} value
  430. * @return {boolean}
  431. */
  432. export function isBuiltInObject(value) {
  433. return !!BUILTIN_OBJECT[objToString.call(value)];
  434. }
  435. /**
  436. * @memberOf module:zrender/core/util
  437. * @param {*} value
  438. * @return {boolean}
  439. */
  440. export function isTypedArray(value) {
  441. return !!TYPED_ARRAY[objToString.call(value)];
  442. }
  443. /**
  444. * @memberOf module:zrender/core/util
  445. * @param {*} value
  446. * @return {boolean}
  447. */
  448. export function isDom(value) {
  449. return typeof value === 'object'
  450. && typeof value.nodeType === 'number'
  451. && typeof value.ownerDocument === 'object';
  452. }
  453. /**
  454. * Whether is exactly NaN. Notice isNaN('a') returns true.
  455. * @param {*} value
  456. * @return {boolean}
  457. */
  458. export function eqNaN(value) {
  459. /* eslint-disable-next-line no-self-compare */
  460. return value !== value;
  461. }
  462. /**
  463. * If value1 is not null, then return value1, otherwise judget rest of values.
  464. * Low performance.
  465. * @memberOf module:zrender/core/util
  466. * @return {*} Final value
  467. */
  468. export function retrieve(values) {
  469. for (var i = 0, len = arguments.length; i < len; i++) {
  470. if (arguments[i] != null) {
  471. return arguments[i];
  472. }
  473. }
  474. }
  475. export function retrieve2(value0, value1) {
  476. return value0 != null
  477. ? value0
  478. : value1;
  479. }
  480. export function retrieve3(value0, value1, value2) {
  481. return value0 != null
  482. ? value0
  483. : value1 != null
  484. ? value1
  485. : value2;
  486. }
  487. /**
  488. * @memberOf module:zrender/core/util
  489. * @param {Array} arr
  490. * @param {number} startIndex
  491. * @param {number} endIndex
  492. * @return {Array}
  493. */
  494. export function slice() {
  495. return Function.call.apply(nativeSlice, arguments);
  496. }
  497. /**
  498. * Normalize css liked array configuration
  499. * e.g.
  500. * 3 => [3, 3, 3, 3]
  501. * [4, 2] => [4, 2, 4, 2]
  502. * [4, 3, 2] => [4, 3, 2, 3]
  503. * @param {number|Array.<number>} val
  504. * @return {Array.<number>}
  505. */
  506. export function normalizeCssArray(val) {
  507. if (typeof (val) === 'number') {
  508. return [val, val, val, val];
  509. }
  510. var len = val.length;
  511. if (len === 2) {
  512. // vertical | horizontal
  513. return [val[0], val[1], val[0], val[1]];
  514. }
  515. else if (len === 3) {
  516. // top | horizontal | bottom
  517. return [val[0], val[1], val[2], val[1]];
  518. }
  519. return val;
  520. }
  521. /**
  522. * @memberOf module:zrender/core/util
  523. * @param {boolean} condition
  524. * @param {string} message
  525. */
  526. export function assert(condition, message) {
  527. if (!condition) {
  528. throw new Error(message);
  529. }
  530. }
  531. /**
  532. * @memberOf module:zrender/core/util
  533. * @param {string} str string to be trimed
  534. * @return {string} trimed string
  535. */
  536. export function trim(str) {
  537. if (str == null) {
  538. return null;
  539. }
  540. else if (typeof str.trim === 'function') {
  541. return str.trim();
  542. }
  543. else {
  544. return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
  545. }
  546. }
  547. var primitiveKey = '__ec_primitive__';
  548. /**
  549. * Set an object as primitive to be ignored traversing children in clone or merge
  550. */
  551. export function setAsPrimitive(obj) {
  552. obj[primitiveKey] = true;
  553. }
  554. export function isPrimitive(obj) {
  555. return obj[primitiveKey];
  556. }
  557. /**
  558. * @constructor
  559. * @param {Object} obj Only apply `ownProperty`.
  560. */
  561. function HashMap(obj) {
  562. var isArr = isArray(obj);
  563. // Key should not be set on this, otherwise
  564. // methods get/set/... may be overrided.
  565. this.data = {};
  566. var thisMap = this;
  567. (obj instanceof HashMap)
  568. ? obj.each(visit)
  569. : (obj && each(obj, visit));
  570. function visit(value, key) {
  571. isArr ? thisMap.set(value, key) : thisMap.set(key, value);
  572. }
  573. }
  574. HashMap.prototype = {
  575. constructor: HashMap,
  576. // Do not provide `has` method to avoid defining what is `has`.
  577. // (We usually treat `null` and `undefined` as the same, different
  578. // from ES6 Map).
  579. get: function (key) {
  580. return this.data.hasOwnProperty(key) ? this.data[key] : null;
  581. },
  582. set: function (key, value) {
  583. // Comparing with invocation chaining, `return value` is more commonly
  584. // used in this case: `var someVal = map.set('a', genVal());`
  585. return (this.data[key] = value);
  586. },
  587. // Although util.each can be performed on this hashMap directly, user
  588. // should not use the exposed keys, who are prefixed.
  589. each: function (cb, context) {
  590. context !== void 0 && (cb = bind(cb, context));
  591. /* eslint-disable guard-for-in */
  592. for (var key in this.data) {
  593. this.data.hasOwnProperty(key) && cb(this.data[key], key);
  594. }
  595. /* eslint-enable guard-for-in */
  596. },
  597. // Do not use this method if performance sensitive.
  598. removeKey: function (key) {
  599. delete this.data[key];
  600. }
  601. };
  602. export function createHashMap(obj) {
  603. return new HashMap(obj);
  604. }
  605. export function concatArray(a, b) {
  606. var newArray = new a.constructor(a.length + b.length);
  607. for (var i = 0; i < a.length; i++) {
  608. newArray[i] = a[i];
  609. }
  610. var offset = a.length;
  611. for (i = 0; i < b.length; i++) {
  612. newArray[i + offset] = b[i];
  613. }
  614. return newArray;
  615. }
  616. export function noop() {}