diff --git a/.eustia.js b/.eustia.js index 54418ab..d76bbdf 100644 --- a/.eustia.js +++ b/.eustia.js @@ -13,7 +13,7 @@ module.exports = { format: 'es' }, stringify: { - files: 'src/lib/stringify.js', + files: 'src/lib/stringifyWorker.js', output: 'src/lib/stringifyUtil.js', format: 'es' }, diff --git a/doc/UTIL_API.md b/doc/UTIL_API.md index 1d25fc8..57b7c29 100644 --- a/doc/UTIL_API.md +++ b/doc/UTIL_API.md @@ -1076,6 +1076,20 @@ if (detectOs() === 'ios') { } ``` +## difference + +Create an array of unique array values not included in the other given array. + +|Name |Type |Desc | +|---------|-----|----------------------------| +|arr |array|Array to inspect | +|[...rest]|array|Values to exclude | +|return |array|New array of filtered values| + +```javascript +difference([3, 2, 1], [4, 2]); // -> [3, 1] +``` + ## each Iterate over elements of collection and invokes iterator for each element. @@ -1215,6 +1229,19 @@ filter([1, 2, 3, 4, 5], function (val) { }); // -> [2, 4] ``` +## flatten + +Recursively flatten an array. + +|Name |Type |Desc | +|------|-----|-------------------| +|arr |array|Array to flatten | +|return|array|New flattened array| + +```javascript +flatten(['a', ['b', ['c']], 'd', ['e']]); // -> ['a', 'b', 'c', 'd', 'e'] +``` + ## freeze Shortcut for Object.freeze. @@ -1602,6 +1629,20 @@ isObj({}); // -> true isObj([]); // -> true ``` +## isPromise + +Check if value looks like a promise. + +|Name |Type |Desc | +|------|-------|----------------------------------| +|val |* |Value to check | +|return|boolean|True if value looks like a promise| + +```javascript +isPromise(new Promise(function () {})); // -> true +isPromise({}); // -> false +``` + ## isRegExp Check if value is a regular expression. @@ -2219,6 +2260,32 @@ obj.b = obj; stringify(obj); // -> '{"a":1,"b":"[Circular ~]"}' ``` +## stringifyAll + +Stringify object into json with types. + +|Name |Type |Desc | +|---------|------|-------------------| +|obj |* |Object to stringify| +|[options]|object|Stringify options | +|return |string|Stringified object | + +Available options: + +|Name |Type |Desc | +|------------------|-------|-------------------------| +|unenumerable=false|boolean|Include unenumerable keys| +|symbol=false |boolean|Include symbol keys | +|accessGetter=false|boolean|Access getter value | +|timeout=0 |number |Timeout of stringify | +|depth=0 |number |Max depth of recursion | + +When time is out, all remaining values will all be "Timeout". + +```javascript +stringifyAll(function test() {}); // -> '{"value":"function test() {}","type":"Function",...}' +``` + ## stripHtmlTag Strip html tags from a string. diff --git a/src/Console/Console.js b/src/Console/Console.js index a80b0f1..935f792 100644 --- a/src/Console/Console.js +++ b/src/Console/Console.js @@ -4,7 +4,6 @@ import { noop, evalCss, $, Emitter } from '../lib/util' import emitter from '../lib/emitter' import Settings from '../Settings/Settings' import stringify from './stringify' -import libStringify from '../lib/stringify' export default class Console extends Tool { constructor() { @@ -314,8 +313,6 @@ export default class Console extends Tool { } } -Console.stringify = libStringify - const CONSOLE_METHOD = [ 'log', 'error', diff --git a/src/Console/Log.js b/src/Console/Log.js index b8d808d..6108619 100644 --- a/src/Console/Log.js +++ b/src/Console/Log.js @@ -425,8 +425,10 @@ let render = data => tpl(data) function extractObj(obj, options = {}, cb) { defaults(options, { - getterVal: Log.showGetterVal, - unenumerable: Log.showUnenumerable + accessGetter: Log.showGetterVal, + unenumerable: Log.showUnenumerable, + symbol: Log.showUnenumerable, + timeout: 1000 }) stringify(obj, options, result => cb(JSON.parse(result))) diff --git a/src/Console/stringify.js b/src/Console/stringify.js index e24a407..fd57e52 100644 --- a/src/Console/stringify.js +++ b/src/Console/stringify.js @@ -1,6 +1,5 @@ -import stringify from '../lib/stringify' import StringifyWorker from '../lib/stringifyWorker' -import { nextTick, uniqId, tryIt } from '../lib/util' +import { nextTick, uniqId, tryIt, stringifyAll } from '../lib/util' let isWorkerSupported = !!window.Worker @@ -38,7 +37,7 @@ function exports(obj, options, cb) { } } - let result = stringify(obj, options) + let result = stringifyAll(obj, options) nextTick(() => cb(result)) } diff --git a/src/Elements/Elements.js b/src/Elements/Elements.js index d8e16f3..75f43f0 100644 --- a/src/Elements/Elements.js +++ b/src/Elements/Elements.js @@ -1,6 +1,5 @@ import Tool from '../DevTools/Tool' import CssStore from './CssStore' -import stringify from '../lib/stringify' import Highlight from './Highlight' import Select from './Select' import Settings from '../Settings/Settings' @@ -22,7 +21,8 @@ import { safeGet, pxToNum, isNaN, - isNum + isNum, + stringifyAll } from '../lib/util' export default class Elements extends Tool { @@ -171,7 +171,12 @@ export default class Elements extends Tool { let data = this._elData if (!data) { - data = stringify(this._curEl, { getterVal: true }) + data = stringifyAll(this._curEl, { + unenumerable: true, + symbol: true, + accessGetter: true, + timeout: 1000 + }) data = JSON.parse(data) } let sources = container.get('sources') diff --git a/src/lib/JsonViewer.js b/src/lib/JsonViewer.js index 52f858a..db8fe92 100644 --- a/src/lib/JsonViewer.js +++ b/src/lib/JsonViewer.js @@ -1,31 +1,33 @@ import { evalCss, $, - isStr, startWith, - trim, isArr, - contain, isObj, uniqId, upperFirst, - isNum, toNum, - isBool, escape, toStr, chunk, each, - map, - isNaN + isNaN, + isNum, + isBool, + keys, + trim } from './util' export default class JsonViewer { constructor(data, $el) { evalCss(require('./json.scss')) - this._data = [data] - this._data.erudaId = uniqId('erudaJson') + this._data = { + id: uniqId('json'), + enumerable: { + 0: data + } + } this._$el = $el this._map = {} createMap(this._map, this._data) @@ -36,63 +38,65 @@ export default class JsonViewer { jsonToHtml(data, firstLevel) { let ret = '' - for (let key in data) { - let val = data[key] + each(['enumerable', 'unenumerable', 'symbol'], type => { + if (!data[type]) return - if ( - key === 'erudaObjAbstract' || - key === 'erudaCircular' || - key === 'erudaId' || - key === 'erudaSplitArr' || - (isStr(val) && startWith(val, 'erudaJson')) - ) - continue + const typeKeys = keys(data[type]) + typeKeys.sort(sortObjName) + for (let i = 0, len = typeKeys.length; i < len; i++) { + const key = typeKeys[i] + ret += this.createEl(key, data[type][key], type, firstLevel) + } + }) - if (Object.hasOwnProperty.call(data, key)) - ret += this.createEl(key, val, firstLevel) + if (data.proto) { + if (ret === '') { + ret = this.jsonToHtml(data.proto, firstLevel) + } else { + ret += this.createEl('__proto__', data.proto, 'proto', firstLevel) + } } return ret } - createEl(key, val, firstLevel = false) { - let type = 'object', - isUnenumerable = false, - id - - if (key === 'erudaProto') key = '__proto__' - if (startWith(key, 'erudaUnenumerable')) { - key = trim(key.replace('erudaUnenumerable', '')) - isUnenumerable = true - } + createEl(key, val, keyType, firstLevel = false) { + let type = 'object' + let id if (isArr(val)) type = 'array' function wrapKey(key) { if (firstLevel) return '' - if (isObj(val) && val.erudaSplitArr) return '' + if (isObj(val) && val.jsonSplitArr) return '' let keyClass = 'eruda-key' - if (isUnenumerable || contain(LIGHTER_KEY, key)) + if ( + keyType === 'unenumerable' || + keyType === 'proto' || + keyType === 'symbol' + ) { keyClass = 'eruda-key-lighter' + } return `${encode(key)}: ` } if (val === null) { - return `
  • - ${wrapKey(key)} - null -
  • ` - } - - if (isObj(val)) { - id = val.erudaId - let circularId = val.erudaCircular - let objAbstract = val['erudaObjAbstract'] || upperFirst(type) + return `
  • ${wrapKey(key)}null
  • ` + } else if (type === 'Number' || isNum(val) || isBool(val)) { + return `
  • ${wrapKey(key)}${encode( + val + )}
  • ` + } else if (type === 'Undefined' || val === 'Symbol' || val === '(...)') { + return `
  • ${wrapKey(key)}${val}
  • ` + } else if (isObj(val)) { + id = val.id + let referenceId = val.reference + let objAbstract = getObjAbstract(val) || upperFirst(type) let obj = `
  • + } ${'data-object-id="' + (referenceId || id) + '"'}> @@ -108,35 +112,13 @@ export default class JsonViewer { return obj + '
  • ' } - if (isNum(val) || isBool(val)) { - return `
  • - ${wrapKey(key)} - ${encode(val)} -
  • ` - } - if (isStr(val) && startWith(val, 'function')) { - return `
  • - ${wrapKey(key)} - ${encode(val).replace( - 'function', - '' - )} -
  • ` - } - if (val === 'undefined' || val === 'Symbol' || val === '(...)') { - return `
  • - ${wrapKey(key)} - ${val} -
  • ` - } - return `
  • - ${wrapKey(key)} - "${encode(val)}" -
  • ` + return `
  • ${wrapKey(key)}"${encode( + val + )}"
  • ` } _appendTpl() { - let data = this._map[this._data.erudaId] + let data = this._map[this._data.id] this._$el.html(this.jsonToHtml(data, true)) } @@ -146,11 +128,11 @@ export default class JsonViewer { let self = this this._$el.on('click', 'li', function(e) { - let $this = $(this), - circularId = $this.data('object-id'), - $firstSpan = $(this) - .find('span') - .eq(0) + let $this = $(this) + let circularId = $this.data('object-id') + let $firstSpan = $(this) + .find('span') + .eq(0) if ($this.data('first-level')) return if (circularId) { @@ -175,77 +157,164 @@ export default class JsonViewer { } function createMap(map, data) { - let id + let id = data.id - if (data.erudaId) { - id = data.erudaId - } else { - id = uniqId('erudaJson') - data.erudaId = id + if (!id && id !== 0) return + + let isArr = data.type && startWith(data.type, 'Array') + if (isArr && data.enumerable) { + let arr = objToArr(data, id, data.type) + if (arr.length > 100) data = splitBigArr(arr) } + map[id] = data - if (id) { - let objAbstract = data.erudaObjAbstract - - let isArr = objAbstract && startWith(objAbstract, 'Array') - if (isArr) { - let arr = objToArr(data) - if (arr.length > 100) data = splitBigArr(objToArr(data), id) + const values = [] + each(['enumerable', 'unenumerable', 'symbol'], type => { + if (!data[type]) return + for (let key in data[type]) { + values.push(data[type][key]) } - map[id] = data + }) + if (data.proto) { + values.push(data.proto) } - - for (let key in data) { - let val = data[key] + for (let i = 0, len = values.length; i < len; i++) { + const val = values[i] if (isObj(val)) createMap(map, val) } } -function splitBigArr(val, id) { - let ret = chunk(val, 100) +function splitBigArr(data) { let idx = 0 - ret = map(ret, val => { + const enumerable = {} + each(chunk(data, 100), val => { let obj = {} let startIdx = idx - obj.erudaObjAbstract = '[' + startIdx + obj.type = '[' + startIdx + obj.enumerable = {} each(val, val => { - obj[idx] = val + obj.enumerable[idx] = val idx += 1 }) let endIdx = idx - 1 - obj.erudaObjAbstract += (endIdx - startIdx > 0 ? ' … ' + endIdx : '') + ']' - obj.erudaId = uniqId('erudaJson') - obj.erudaSplitArr = true - return obj + obj.type += (endIdx - startIdx > 0 ? ' … ' + endIdx : '') + ']' + obj.id = uniqId('json') + obj.jsonSplitArr = true + enumerable[idx] = obj }) - each(val.erudaStrKeys, (val, key) => (ret[key] = val)) - ret.erudaId = id + + const ret = {} + ret.enumerable = enumerable + ret.id = data.id + ret.type = data.type + if (data.unenumerable) ret.unenumerable = data.unenumerable + if (data.symbol) ret.symbol = data.symbol + if (data.proto) ret.proto = data.proto return ret } -function objToArr(val) { +function objToArr(data, id, type) { let ret = [] - let strKeys = {} - - each(val, (val, key) => { + const enumerable = {} + each(data.enumerable, (val, key) => { let idx = toNum(key) if (!isNaN(idx)) { ret[idx] = val } else { - strKeys[key] = val + enumerable[key] = val } }) - ret['erudaStrKeys'] = strKeys + ret.enumerable = enumerable + ret.type = type + ret.id = id + if (data.unenumerable) ret.unenumerable = data.unenumerable + if (data.symbol) ret.symbol = data.symbol + if (data.proto) ret.proto = data.proto return ret } -const LIGHTER_KEY = ['__proto__'] - -let encode = str => - escape(toStr(str)) +let encode = str => { + return escape(toStr(str)) .replace(/\n/g, '↵') .replace(/\f|\r|\t/g, '') +} + +// $, upperCase, lowerCase, _ +function sortObjName(a, b) { + const numA = toNum(a) + const numB = toNum(b) + if (!isNaN(numA) && !isNaN(numB)) { + if (numA > numB) return 1 + if (numA < numB) return -1 + return 0 + } + + if (startWith(a, 'get ') || startWith(a, 'set ')) a = a.slice(4) + if (startWith(b, 'get ') || startWith(b, 'set ')) b = b.slice(4) + + let lenA = a.length + let lenB = b.length + let len = lenA > lenB ? lenB : lenA + + for (let i = 0; i < len; i++) { + let codeA = a.charCodeAt(i) + let codeB = b.charCodeAt(i) + let cmpResult = cmpCode(codeA, codeB) + + if (cmpResult !== 0) return cmpResult + } + + if (lenA > lenB) return 1 + if (lenA < lenB) return -1 + + return 0 +} + +function cmpCode(a, b) { + a = transCode(a) + b = transCode(b) + + if (a > b) return 1 + if (a < b) return -1 + return 0 +} + +function transCode(code) { + // _ should be placed after lowercase chars. + if (code === 95) return 123 + return code +} + +function getObjAbstract(data) { + const { type, value } = data + if (!type) return + + if (type === 'Function') { + return getFnAbstract(value) + } + if (type === 'Array' && data.unenumerable) { + return `Array(${data.unenumerable.length})` + } + + return data.type +} + +const regFnHead = /function(.*?)\((.*?)\)/ + +function extractFnHead(str) { + let fnHead = str.match(regFnHead) + + if (fnHead) return fnHead[0] + + return str +} + +function getFnAbstract(str) { + if (str.length > 500) str = str.slice(0, 500) + '...' + + return 'ƒ ' + trim(extractFnHead(str).replace('function', '')) +} diff --git a/src/lib/getAbstract.js b/src/lib/getAbstract.js index dedc8f4..2671165 100644 --- a/src/lib/getAbstract.js +++ b/src/lib/getAbstract.js @@ -100,7 +100,7 @@ export default function getAbstract( } else if (isRegExp) { json = wrapRegExp(escapeJsonStr(obj.toString())) } else if (isFn) { - json = wrapStr('function') + json = wrapStr('ƒ') } else if (isArr) { if (doStringify) { json = '[' @@ -174,7 +174,7 @@ export default function getAbstract( return json } -const SPECIAL_VAL = ['(...)', 'undefined', 'Symbol', 'Object', 'function'] +const SPECIAL_VAL = ['(...)', 'undefined', 'Symbol', 'Object', 'ƒ'] function canBeProto(obj) { let emptyObj = isEmpty(Object.getOwnPropertyNames(obj)) diff --git a/src/lib/stringify.js b/src/lib/stringify.js deleted file mode 100644 index 388c332..0000000 --- a/src/lib/stringify.js +++ /dev/null @@ -1,314 +0,0 @@ -import { - escapeJsonStr, - toStr, - each, - endWith, - contain, - filter, - isEmpty, - isArr, - isFn, - isRegExp, - uniqId, - last, - extend -} from './stringifyUtil' - -// Modified from: https://jsconsole.com/ -export default function stringify( - obj, - { - visitor = new Visitor(), - topObj, - level = 0, - getterVal = false, - unenumerable = true - } = {} -) { - let json = '' - let type - let parts = [] - let names = [] - let proto - let objAbstract - let circularObj - let allKeys - let keys - let id = '' - - topObj = topObj || obj - - let passOpts = { visitor, getterVal, unenumerable, level: level + 1 } - let passProtoOpts = { - visitor, - getterVal, - topObj, - unenumerable, - level: level + 1 - } - - let wrapKey = key => `"${escapeJsonStr(key)}"` - let wrapStr = str => `"${escapeJsonStr(toStr(str))}"` - - type = getType(obj) - - let isFn = type === '[object Function]' - let isStr = type === '[object String]' - let isArr = type === '[object Array]' - let isObj = type === '[object Object]' - let isNum = type === '[object Number]' - let isSymbol = type === '[object Symbol]' - let isBool = type === '[object Boolean]' - - circularObj = visitor.check(obj) - - if (circularObj) { - let abstract = circularObj.abstract - json = `{"erudaObjAbstract": ${wrapStr( - abstract.erudaObjAbstract - )}, "erudaCircular": ${wrapStr(abstract.erudaCircular)}}` - } else if (isStr) { - json = wrapStr(obj) - } else if (isArr || isObj || isFn) { - id = visitor.visit(obj) - - if (canBeProto(obj)) { - obj = Object.getPrototypeOf(obj) - id = visitor.visit(obj) - } - - names = getKeys(obj) - keys = names.keys - allKeys = names.allKeys - names = unenumerable ? allKeys : keys - - proto = Object.getPrototypeOf(obj) - if (proto) { - proto = `${wrapKey('erudaProto')}: ${stringify(proto, passProtoOpts)}` - } - if (isFn) { - // We don't need these properties to display for functions. - names = names.filter(val => ['arguments', 'caller'].indexOf(val) < 0) - } - json = '{ ' - objAbstract = getObjAbstract(obj) - visitor.updateAbstract(id, { - erudaObjAbstract: objAbstract, - erudaCircular: id - }) - parts.push(`${wrapKey('erudaObjAbstract')}: ${wrapStr(objAbstract)}`) - parts.push(`"erudaId": "${id}"`) - each(names, objIteratee) - if (proto) parts.push(proto) - json += parts.join(', ') + ' }' - } else if (isNum) { - json = obj + '' - if (endWith(json, 'Infinity') || json === 'NaN') json = `"${json}"` - } else if (isBool) { - json = obj ? 'true' : 'false' - } else if (obj === null) { - json = 'null' - } else if (isSymbol) { - json = wrapStr('Symbol') - } else if (obj === undefined) { - json = wrapStr('undefined') - } else if (type === '[object HTMLAllCollection]') { - // https://docs.webplatform.org/wiki/dom/HTMLAllCollection - // Might cause a performance issue when stringify a dom element. - json = wrapStr('[object HTMLAllCollection]') - } else if (type === '[object HTMLDocument]' && level > 1) { - // Same as reason above. - json = wrapStr('[object HTMLDocument]') - } else { - try { - id = visitor.visit(obj) - if (canBeProto(obj)) { - obj = Object.getPrototypeOf(obj) - id = visitor.visit(obj) - } - - json = '{ ' - objAbstract = getObjAbstract(obj) - visitor.updateAbstract(id, { - erudaObjAbstract: objAbstract, - erudaCircular: id - }) - parts.push(`${wrapKey('erudaObjAbstract')}: ${wrapStr(objAbstract)}`) - parts.push(`"erudaId": "${id}"`) - - names = getKeys(obj) - keys = names.keys - allKeys = names.allKeys - names = unenumerable ? allKeys : keys - - proto = Object.getPrototypeOf(obj) - if (proto) { - try { - proto = `${wrapKey('erudaProto')}: ${stringify(proto, passProtoOpts)}` - } catch (e) { - proto = `${wrapKey('erudaProto')}: ${wrapStr(e.message)}` - } - } - each(names, objIteratee) - if (proto) parts.push(proto) - json += parts.join(', ') + ' }' - } catch (e) { - json = wrapStr(obj) - } - } - - function objIteratee(name) { - let unenumerable = !contain(keys, name) ? 'erudaUnenumerable ' : '' - let key = wrapKey(unenumerable + name) - let getKey = wrapKey(unenumerable + 'get ' + name) - let setKey = wrapKey(unenumerable + 'set ' + name) - - let descriptor = Object.getOwnPropertyDescriptor(obj, name) - let hasGetter = descriptor && descriptor.get - let hasSetter = descriptor && descriptor.set - - if (!getterVal && hasGetter) { - parts.push(`${key}: "(...)"`) - parts.push(`${getKey}: ${stringify(descriptor.get, passOpts)}`) - } else { - let val - try { - val = topObj[name] - } catch (e) { - val = e.message - } - parts.push(`${key}: ${stringify(val, passOpts)}`) - } - if (hasSetter) { - parts.push(`${setKey}: ${stringify(descriptor.set, passOpts)}`) - } - } - - return json -} - -function getKeys(obj) { - let allKeys = Object.getOwnPropertyNames(obj) - let keys = Object.keys(obj).sort(sortObjName) - - allKeys = keys.concat( - filter(allKeys, val => !contain(keys, val)).sort(sortObjName) - ) - - return { keys, allKeys } -} - -// $, upperCase, lowerCase, _ -function sortObjName(a, b) { - let lenA = a.length - let lenB = b.length - let len = lenA > lenB ? lenB : lenA - - for (let i = 0; i < len; i++) { - let codeA = a.charCodeAt(i) - let codeB = b.charCodeAt(i) - let cmpResult = cmpCode(codeA, codeB) - - if (cmpResult !== 0) return cmpResult - } - - if (lenA > lenB) return 1 - if (lenA < lenB) return -1 - - return 0 -} - -function cmpCode(a, b) { - a = transCode(a) - b = transCode(b) - - if (a > b) return 1 - if (a < b) return -1 - return 0 -} - -function transCode(code) { - // _ should be placed after lowercase chars. - if (code === 95) return 123 - return code -} - -let regFnHead = /function(.*?)\((.*?)\)/ - -function extractFnHead(fn) { - let str = fn.toString(), - fnHead = str.match(regFnHead) - - if (fnHead) return fnHead[0] - - return str -} - -function getFnAbstract(fn) { - let fnStr = fn.toString() - if (fnStr.length > 500) fnStr = fnStr.slice(0, 500) + '...' - - return extractFnHead(fnStr).replace('function', '') -} - -function canBeProto(obj) { - let emptyObj = isEmpty(Object.getOwnPropertyNames(obj)) - let proto = Object.getPrototypeOf(obj) - - return emptyObj && proto && proto !== Object.prototype -} - -function getObjAbstract(obj) { - if (isArr(obj)) return `Array(${obj.length})` - if (isFn(obj)) return getFnAbstract(obj) - if (isRegExp(obj)) return obj.toString() - - let type = getType(obj) - - return type.replace(/(\[object )|]/g, '') -} - -function getType(obj) { - let type - - try { - type = {}.toString.call(obj) - } catch (e) { - type = '[object Object]' - } - - return type -} - -class Visitor { - constructor() { - this._visited = [] - this._map = {} - } - visit(val) { - /* Add 0 to distinguish stringify generated id from JsonViewer id. - * When used in web worker, they are not calling the same uniqId method, thus result may be repeated. - */ - let id = uniqId('erudaJson0') - - this._visited.push({ id, val, abstract: {} }) - this._map[id] = last(this._visited) - - return id - } - check(val) { - let visited = this._visited - - for (let i = 0, len = visited.length; i < len; i++) { - if (val === visited[i].val) return visited[i] - } - - return false - } - update(id, data) { - extend(this._map[id], data) - } - updateAbstract(id, abstract) { - this.update(id, { abstract }) - } -} diff --git a/src/lib/stringifyUtil.js b/src/lib/stringifyUtil.js index acc6ba8..f9dc44f 100644 --- a/src/lib/stringifyUtil.js +++ b/src/lib/stringifyUtil.js @@ -4,6 +4,78 @@ var _ = {}; +/* ------------------------------ inherits ------------------------------ */ + +export var inherits = _.inherits = (function (exports) { + /* Inherit the prototype methods from one constructor into another. + * + * |Name |Type |Desc | + * |----------|--------|-----------| + * |Class |function|Child Class| + * |SuperClass|function|Super Class| + */ + + /* example + * function People(name) { + * this._name = name; + * } + * People.prototype = { + * getName: function () { + * return this._name; + * } + * }; + * function Student(name) { + * this._name = name; + * } + * inherits(Student, People); + * var s = new Student('RedHood'); + * s.getName(); // -> 'RedHood' + */ + + /* typescript + * export declare function inherits(Class: Function, SuperClass: Function): void; + */ + exports = function exports(Class, SuperClass) { + if (objCreate) return (Class.prototype = objCreate(SuperClass.prototype)); + noop.prototype = SuperClass.prototype; + Class.prototype = new noop(); + }; + + var objCreate = Object.create; + + function noop() {} + + return exports; +})({}); + +/* ------------------------------ has ------------------------------ */ + +export var has = _.has = (function (exports) { + /* Checks if key is a direct property. + * + * |Name |Type |Desc | + * |------|-------|--------------------------------| + * |obj |object |Object to query | + * |key |string |Path to check | + * |return|boolean|True if key is a direct property| + */ + + /* example + * has({one: 1}, 'one'); // -> true + */ + + /* typescript + * export declare function has(obj: {}, key: string): boolean; + */ + var hasOwnProp = Object.prototype.hasOwnProperty; + + exports = function exports(obj, key) { + return hasOwnProp.call(obj, key); + }; + + return exports; +})({}); + /* ------------------------------ idxOf ------------------------------ */ export var idxOf = _.idxOf = (function (exports) { @@ -57,6 +129,62 @@ export var isUndef = _.isUndef = (function (exports) { return exports; })({}); +/* ------------------------------ restArgs ------------------------------ */ + +export var restArgs = _.restArgs = (function (exports) { + /* This accumulates the arguments passed into an array, after a given index. + * + * |Name |Type |Desc | + * |------------|--------|---------------------------------------| + * |function |function|Function that needs rest parameters | + * |[startIndex]|number |The start index to accumulates | + * |return |function|Generated function with rest parameters| + */ + + /* example + * var paramArr = restArgs(function (rest) { return rest }); + * paramArr(1, 2, 3, 4); // -> [1, 2, 3, 4] + */ + + /* typescript + * export declare function restArgs(fn: Function, startIndex?: number): Function; + */ + exports = function exports(fn, startIdx) { + startIdx = startIdx == null ? fn.length - 1 : +startIdx; + return function() { + var len = Math.max(arguments.length - startIdx, 0), + rest = new Array(len), + i; + + for (i = 0; i < len; i++) { + rest[i] = arguments[i + startIdx]; + } // Call runs faster than apply. + + switch (startIdx) { + case 0: + return fn.call(this, rest); + + case 1: + return fn.call(this, arguments[0], rest); + + case 2: + return fn.call(this, arguments[0], arguments[1], rest); + } + + var args = new Array(startIdx + 1); + + for (i = 0; i < startIdx; i++) { + args[i] = arguments[i]; + } + + args[startIdx] = rest; + return fn.apply(this, args); + }; + }; + + return exports; +})({}); + /* ------------------------------ optimizeCb ------------------------------ */ export var optimizeCb = _.optimizeCb = (function (exports) { @@ -245,25 +373,6 @@ export var escapeJsStr = _.escapeJsStr = (function (exports) { return exports; })({}); -/* ------------------------------ escapeJsonStr ------------------------------ */ - -export var escapeJsonStr = _.escapeJsonStr = (function (exports) { - /* Escape json string. - */ - - /* dependencies - * escapeJsStr - */ - - exports = function (str) { - return escapeJsStr(str) - .replace(/\\'/g, "'") - .replace(/\t/g, '\\t') - } - - return exports; -})({}); - /* ------------------------------ isObj ------------------------------ */ export var isObj = _.isObj = (function (exports) { @@ -312,34 +421,6 @@ export var isObj = _.isObj = (function (exports) { return exports; })({}); -/* ------------------------------ has ------------------------------ */ - -export var has = _.has = (function (exports) { - /* Checks if key is a direct property. - * - * |Name |Type |Desc | - * |------|-------|--------------------------------| - * |obj |object |Object to query | - * |key |string |Path to check | - * |return|boolean|True if key is a direct property| - */ - - /* example - * has({one: 1}, 'one'); // -> true - */ - - /* typescript - * export declare function has(obj: {}, key: string): boolean; - */ - var hasOwnProp = Object.prototype.hasOwnProperty; - - exports = function exports(obj, key) { - return hasOwnProp.call(obj, key); - }; - - return exports; -})({}); - /* ------------------------------ identity ------------------------------ */ export var identity = _.identity = (function (exports) { @@ -392,38 +473,169 @@ export var objToStr = _.objToStr = (function (exports) { return exports; })({}); -/* ------------------------------ isArgs ------------------------------ */ +/* ------------------------------ isArr ------------------------------ */ -export var isArgs = _.isArgs = (function (exports) { - /* Check if value is classified as an arguments object. +export var isArr = _.isArr = (function (exports) { + /* Check if value is an `Array` object. * - * |Name |Type |Desc | - * |------|-------|------------------------------------| - * |val |* |Value to check | - * |return|boolean|True if value is an arguments object| + * |Name |Type |Desc | + * |------|-------|----------------------------------| + * |val |* |Value to check | + * |return|boolean|True if value is an `Array` object| */ /* example - * (function () { - * isArgs(arguments); // -> true - * })(); + * isArr([]); // -> true + * isArr({}); // -> false */ /* typescript - * export declare function isArgs(val: any): boolean; + * export declare function isArr(val: any): boolean; */ /* dependencies * objToStr */ - exports = function exports(val) { - return objToStr(val) === '[object Arguments]'; + exports = + Array.isArray || + function(val) { + return objToStr(val) === '[object Array]'; + }; + + return exports; +})({}); + +/* ------------------------------ castPath ------------------------------ */ + +export var castPath = _.castPath = (function (exports) { + /* Cast value into a property path array. + * + * |Name |Type |Desc | + * |------|------------|-------------------| + * |path |string array|Value to inspect | + * |[obj] |object |Object to query | + * |return|array |Property path array| + */ + + /* example + * castPath('a.b.c'); // -> ['a', 'b', 'c'] + * castPath(['a']); // -> ['a'] + * castPath('a[0].b'); // -> ['a', '0', 'b'] + * castPath('a.b.c', {'a.b.c': true}); // -> ['a.b.c'] + */ + + /* typescript + * export declare function castPath(path: string | string[], obj?: any): string[]; + */ + + /* dependencies + * has isArr + */ + + exports = function exports(str, obj) { + if (isArr(str)) return str; + if (obj && has(obj, str)) return [str]; + var ret = []; + str.replace(regPropName, function(match, number, quote, str) { + ret.push(quote ? str.replace(regEscapeChar, '$1') : number || match); + }); + return ret; + }; // Lodash _stringToPath + + var regPropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g, + regEscapeChar = /\\(\\)?/g; + + return exports; +})({}); + +/* ------------------------------ safeGet ------------------------------ */ + +export var safeGet = _.safeGet = (function (exports) { + /* Get object property, don't throw undefined error. + * + * |Name |Type |Desc | + * |------|------------|-------------------------| + * |obj |object |Object to query | + * |path |array string|Path of property to get | + * |return|* |Target value or undefined| + */ + + /* example + * var obj = {a: {aa: {aaa: 1}}}; + * safeGet(obj, 'a.aa.aaa'); // -> 1 + * safeGet(obj, ['a', 'aa']); // -> {aaa: 1} + * safeGet(obj, 'a.b'); // -> undefined + */ + + /* typescript + * export declare function safeGet(obj: any, path: string | string[]): any; + */ + + /* dependencies + * isUndef castPath + */ + + exports = function exports(obj, path) { + path = castPath(path, obj); + var prop; + prop = path.shift(); + + while (!isUndef(prop)) { + obj = obj[prop]; + if (obj == null) return; + prop = path.shift(); + } + + return obj; }; return exports; })({}); +/* ------------------------------ flatten ------------------------------ */ + +export var flatten = _.flatten = (function (exports) { + /* Recursively flatten an array. + * + * |Name |Type |Desc | + * |------|-----|-------------------| + * |arr |array|Array to flatten | + * |return|array|New flattened array| + */ + + /* example + * flatten(['a', ['b', ['c']], 'd', ['e']]); // -> ['a', 'b', 'c', 'd', 'e'] + */ + + /* typescript + * export declare function flatten(arr: any[]): any[]; + */ + + /* dependencies + * isArr + */ + + exports = function exports(arr) { + return flat(arr, []); + }; + + function flat(arr, res) { + var len = arr.length, + i = -1, + cur; + + while (len--) { + cur = arr[++i]; + isArr(cur) ? flat(cur, res) : res.push(cur); + } + + return res; + } + + return exports; +})({}); + /* ------------------------------ isFn ------------------------------ */ export var isFn = _.isFn = (function (exports) { @@ -503,6 +715,30 @@ export var getProto = _.getProto = (function (exports) { return exports; })({}); +/* ------------------------------ isMiniProgram ------------------------------ */ + +export var isMiniProgram = _.isMiniProgram = (function (exports) { + /* Check if running in wechat mini program. + */ + + /* example + * console.log(isMiniProgram); // -> true if running in mini program. + */ + + /* typescript + * export declare const isMiniProgram: boolean; + */ + + /* dependencies + * isFn + */ + /* eslint-disable no-undef */ + + exports = typeof wx !== 'undefined' && isFn(wx.openLocation); + + return exports; +})({}); + /* ------------------------------ isStr ------------------------------ */ export var isStr = _.isStr = (function (exports) { @@ -533,39 +769,6 @@ export var isStr = _.isStr = (function (exports) { return exports; })({}); -/* ------------------------------ isArr ------------------------------ */ - -export var isArr = _.isArr = (function (exports) { - /* Check if value is an `Array` object. - * - * |Name |Type |Desc | - * |------|-------|----------------------------------| - * |val |* |Value to check | - * |return|boolean|True if value is an `Array` object| - */ - - /* example - * isArr([]); // -> true - * isArr({}); // -> false - */ - - /* typescript - * export declare function isArr(val: any): boolean; - */ - - /* dependencies - * objToStr - */ - - exports = - Array.isArray || - function(val) { - return objToStr(val) === '[object Array]'; - }; - - return exports; -})({}); - /* ------------------------------ isNum ------------------------------ */ export var isNum = _.isNum = (function (exports) { @@ -960,44 +1163,6 @@ export var extendOwn = _.extendOwn = (function (exports) { return exports; })({}); -/* ------------------------------ isEmpty ------------------------------ */ - -export var isEmpty = _.isEmpty = (function (exports) { - /* Check if value is an empty object or array. - * - * |Name |Type |Desc | - * |------|-------|----------------------| - * |val |* |Value to check | - * |return|boolean|True if value is empty| - */ - - /* example - * isEmpty([]); // -> true - * isEmpty({}); // -> true - * isEmpty(''); // -> true - */ - - /* typescript - * export declare function isEmpty(val: any): boolean; - */ - - /* dependencies - * isArrLike isArr isStr isArgs keys - */ - - exports = function exports(val) { - if (val == null) return true; - - if (isArrLike(val) && (isArr(val) || isStr(val) || isArgs(val))) { - return val.length === 0; - } - - return keys(val).length === 0; - }; - - return exports; -})({}); - /* ------------------------------ isMatch ------------------------------ */ export var isMatch = _.isMatch = (function (exports) { @@ -1040,57 +1205,95 @@ export var isMatch = _.isMatch = (function (exports) { return exports; })({}); -/* ------------------------------ isRegExp ------------------------------ */ +/* ------------------------------ isNaN ------------------------------ */ -export var isRegExp = _.isRegExp = (function (exports) { - /* Check if value is a regular expression. +export var isNaN = _.isNaN = (function (exports) { + /* Check if value is an NaN. * - * |Name |Type |Desc | - * |------|-------|-------------------------------------| - * |val |* |Value to check | - * |return|boolean|True if value is a regular expression| + * |Name |Type |Desc | + * |------|-------|-----------------------| + * |val |* |Value to check | + * |return|boolean|True if value is an NaN| + * + * Undefined is not an NaN, different from global isNaN function. */ /* example - * isRegExp(/a/); // -> true + * isNaN(0); // -> false + * isNaN(NaN); // -> true */ /* typescript - * export declare function isRegExp(val: any): boolean; + * export declare function isNaN(val: any): boolean; */ /* dependencies - * objToStr + * isNum */ exports = function exports(val) { - return objToStr(val) === '[object RegExp]'; + return isNum(val) && val !== +val; }; return exports; })({}); -/* ------------------------------ last ------------------------------ */ +/* ------------------------------ isNil ------------------------------ */ -export var last = _.last = (function (exports) { - /* Get the last element of array. +export var isNil = _.isNil = (function (exports) { + /* Check if value is null or undefined, the same as value == null. * - * |Name |Type |Desc | - * |------|-----|-------------------------| - * |arr |array|The array to query | - * |return|* |The last element of array| + * |Name |Type |Desc | + * |------|-------|----------------------------------| + * |val |* |Value to check | + * |return|boolean|True if value is null or undefined| */ /* example - * last([1, 2]); // -> 2 + * isNil(null); // -> true + * isNil(void 0); // -> true + * isNil(undefined); // -> true + * isNil(false); // -> false + * isNil(0); // -> false + * isNil([]); // -> false */ /* typescript - * export declare function last(arr: any[]): any; + * export declare function isNil(val: any): boolean; */ - exports = function exports(arr) { - var len = arr ? arr.length : 0; - if (len) return arr[len - 1]; + exports = function exports(val) { + return val == null; + }; + + return exports; +})({}); + +/* ------------------------------ isPromise ------------------------------ */ + +export var isPromise = _.isPromise = (function (exports) { + /* Check if value looks like a promise. + * + * |Name |Type |Desc | + * |------|-------|----------------------------------| + * |val |* |Value to check | + * |return|boolean|True if value looks like a promise| + */ + + /* example + * isPromise(new Promise(function () {})); // -> true + * isPromise({}); // -> false + */ + + /* typescript + * export declare function isPromise(val: any): boolean; + */ + + /* dependencies + * isObj isFn + */ + + exports = function exports(val) { + return isObj(val) && isFn(val.then); }; return exports; @@ -1209,6 +1412,40 @@ export var filter = _.filter = (function (exports) { return exports; })({}); +/* ------------------------------ difference ------------------------------ */ + +export var difference = _.difference = (function (exports) { + /* Create an array of unique array values not included in the other given array. + * + * |Name |Type |Desc | + * |---------|-----|----------------------------| + * |arr |array|Array to inspect | + * |[...rest]|array|Values to exclude | + * |return |array|New array of filtered values| + */ + + /* example + * difference([3, 2, 1], [4, 2]); // -> [3, 1] + */ + + /* typescript + * export declare function difference(arr: any[], ...rest: any[]): any[]; + */ + + /* dependencies + * restArgs flatten filter contain + */ + + exports = restArgs(function(arr, rest) { + rest = flatten(rest); + return filter(arr, function(val) { + return !contain(rest, val); + }); + }); + + return exports; +})({}); + /* ------------------------------ unique ------------------------------ */ export var unique = _.unique = (function (exports) { @@ -1384,32 +1621,669 @@ export var extend = _.extend = (function (exports) { return exports; })({}); -/* ------------------------------ uniqId ------------------------------ */ +/* ------------------------------ map ------------------------------ */ -export var uniqId = _.uniqId = (function (exports) { - /* Generate a globally-unique id. +export var map = _.map = (function (exports) { + /* Create an array of values by running each element in collection through iteratee. * - * |Name |Type |Desc | - * |--------|------|------------------| - * |[prefix]|string|Id prefix | - * |return |string|Globally-unique id| + * |Name |Type |Desc | + * |---------|------------|------------------------------| + * |object |array object|Collection to iterate over | + * |iterator |function |Function invoked per iteration| + * |[context]|* |Function context | + * |return |array |New mapped array | */ /* example - * uniqId('eusita_'); // -> 'eustia_xxx' + * map([4, 8], function (n) { return n * n; }); // -> [16, 64] */ /* typescript - * export declare function uniqId(prefix?: string): string; + * export declare function map( + * list: types.List, + * iterator: types.ListIterator, + * context?: any + * ): TResult[]; + * export declare function map( + * object: types.Dictionary, + * iterator: types.ObjectIterator, + * context?: any + * ): TResult[]; */ - var idCounter = 0; - exports = function exports(prefix) { - var id = ++idCounter + ''; - return prefix ? prefix + id : id; + /* dependencies + * safeCb keys isArrLike types + */ + + exports = function exports(obj, iterator, ctx) { + iterator = safeCb(iterator, ctx); + + var _keys = !isArrLike(obj) && keys(obj), + len = (_keys || obj).length, + results = Array(len); + + for (var i = 0; i < len; i++) { + var curKey = _keys ? _keys[i] : i; + results[i] = iterator(obj[curKey], curKey, obj); + } + + return results; }; return exports; })({}); +/* ------------------------------ toArr ------------------------------ */ + +export var toArr = _.toArr = (function (exports) { + /* Convert value to an array. + * + * |Name |Type |Desc | + * |------|-----|----------------| + * |val |* |Value to convert| + * |return|array|Converted array | + */ + + /* example + * toArr({a: 1, b: 2}); // -> [{a: 1, b: 2}] + * toArr('abc'); // -> ['abc'] + * toArr(1); // -> [1] + * toArr(null); // -> [] + */ + + /* typescript + * export declare function toArr(val: any): any[]; + */ + + /* dependencies + * isArrLike map isArr isStr + */ + + exports = function exports(val) { + if (!val) return []; + if (isArr(val)) return val; + if (isArrLike(val) && !isStr(val)) return map(val); + return [val]; + }; + + return exports; +})({}); + +/* ------------------------------ Class ------------------------------ */ + +export var Class = _.Class = (function (exports) { + /* Create JavaScript class. + * + * |Name |Type |Desc | + * |---------|--------|---------------------------------| + * |methods |object |Public methods | + * |[statics]|object |Static methods | + * |return |function|Function used to create instances| + */ + + /* example + * var People = Class({ + * initialize: function People(name, age) { + * this.name = name; + * this.age = age; + * }, + * introduce: function () { + * return 'I am ' + this.name + ', ' + this.age + ' years old.'; + * } + * }); + * + * var Student = People.extend({ + * initialize: function Student(name, age, school) { + * this.callSuper(People, 'initialize', arguments); + * + * this.school = school; + * }, + * introduce: function () { + * return this.callSuper(People, 'introduce') + '\n I study at ' + this.school + '.'; + * } + * }, { + * is: function (obj) { + * return obj instanceof Student; + * } + * }); + * + * var a = new Student('allen', 17, 'Hogwarts'); + * a.introduce(); // -> 'I am allen, 17 years old. \n I study at Hogwarts.' + * Student.is(a); // -> true + */ + + /* typescript + * export declare namespace Class { + * class Base { + * toString(): string; + * } + * class IConstructor extends Base { + * constructor(...args: any[]); + * static extend(methods: any, statics: any): IConstructor; + * static inherits(Class: Function): void; + * static methods(methods: any): IConstructor; + * static statics(statics: any): IConstructor; + * [method: string]: any; + * } + * } + * export declare function Class(methods: any, statics?: any): Class.IConstructor; + */ + + /* dependencies + * extend toArr inherits safeGet isMiniProgram + */ + + exports = function exports(methods, statics) { + return Base.extend(methods, statics); + }; + + function makeClass(parent, methods, statics) { + statics = statics || {}; + var className = + methods.className || safeGet(methods, 'initialize.name') || ''; + delete methods.className; + var ctor; + + if (isMiniProgram) { + ctor = function ctor() { + var args = toArr(arguments); + return this.initialize + ? this.initialize.apply(this, args) || this + : this; + }; + } else { + ctor = new Function( + 'toArr', + 'return function ' + + className + + '()' + + '{' + + 'var args = toArr(arguments);' + + 'return this.initialize ? this.initialize.apply(this, args) || this : this;' + + '};' + )(toArr); + } + + inherits(ctor, parent); + ctor.prototype.constructor = ctor; + + ctor.extend = function(methods, statics) { + return makeClass(ctor, methods, statics); + }; + + ctor.inherits = function(Class) { + inherits(ctor, Class); + }; + + ctor.methods = function(methods) { + extend(ctor.prototype, methods); + return ctor; + }; + + ctor.statics = function(statics) { + extend(ctor, statics); + return ctor; + }; + + ctor.methods(methods).statics(statics); + return ctor; + } + + var Base = (exports.Base = makeClass(Object, { + className: 'Base', + callSuper: function callSuper(parent, name, args) { + var superMethod = parent.prototype[name]; + return superMethod.apply(this, args); + }, + toString: function toString() { + return this.constructor.name; + } + })); + + return exports; +})({}); + +/* ------------------------------ now ------------------------------ */ + +export var now = _.now = (function (exports) { + /* Gets the number of milliseconds that have elapsed since the Unix epoch. + */ + + /* example + * now(); // -> 1468826678701 + */ + + /* typescript + * export declare function now(): number; + */ + exports = + Date.now || + function() { + return new Date().getTime(); + }; + + return exports; +})({}); + +/* ------------------------------ type ------------------------------ */ + +export var type = _.type = (function (exports) { + /* Determine the internal JavaScript [[Class]] of an object. + * + * |Name |Type |Desc | + * |--------------|-------|-----------------| + * |val |* |Value to get type| + * |lowerCase=true|boolean|LowerCase result | + * |return |string |Type of object | + */ + + /* example + * type(5); // -> 'number' + * type({}); // -> 'object' + * type(function () {}); // -> 'function' + * type([]); // -> 'array' + * type([], false); // -> 'Array' + * type(async function () {}, false); // -> 'AsyncFunction' + */ + + /* typescript + * export declare function type(val: any, lowerCase: boolean): string; + */ + + /* dependencies + * objToStr isNaN + */ + + exports = function exports(val) { + var lowerCase = + arguments.length > 1 && arguments[1] !== undefined + ? arguments[1] + : true; + if (val === null) return lowerCase ? 'null' : 'Null'; + if (val === undefined) return lowerCase ? 'undefined' : 'Undefined'; + if (isNaN(val)) return lowerCase ? 'nan' : 'NaN'; + var ret = objToStr(val).match(regObj); + if (!ret) return ''; + return lowerCase ? ret[1].toLowerCase() : ret[1]; + }; + + var regObj = /^\[object\s+(.*?)]$/; + + return exports; +})({}); + +/* ------------------------------ toSrc ------------------------------ */ + +export var toSrc = _.toSrc = (function (exports) { + /* Convert function to its source code. + * + * |Name |Type |Desc | + * |------|--------|-------------------| + * |fn |function|Function to convert| + * |return|string |Source code | + */ + + /* example + * toSrc(Math.min); // -> 'function min() { [native code] }' + * toSrc(function () {}) // -> 'function () { }' + */ + + /* typescript + * export declare function toSrc(fn: Function): string; + */ + + /* dependencies + * isNil + */ + + exports = function exports(fn) { + if (isNil(fn)) return ''; + + try { + return fnToStr.call(fn); + /* eslint-disable no-empty */ + } catch (e) {} + + try { + return fn + ''; + /* eslint-disable no-empty */ + } catch (e) {} + + return ''; + }; + + var fnToStr = Function.prototype.toString; + + return exports; +})({}); + +/* ------------------------------ stringifyAll ------------------------------ */ + +export var stringifyAll = _.stringifyAll = (function (exports) { + function _typeof(obj) { + if (typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol') { + _typeof = function _typeof(obj) { + return typeof obj; + }; + } else { + _typeof = function _typeof(obj) { + return obj && + typeof Symbol === 'function' && + obj.constructor === Symbol && + obj !== Symbol.prototype + ? 'symbol' + : typeof obj; + }; + } + return _typeof(obj); + } + + /* Stringify object into json with types. + * + * |Name |Type |Desc | + * |---------|------|-------------------| + * |obj |* |Object to stringify| + * |[options]|object|Stringify options | + * |return |string|Stringified object | + * + * Available options: + * + * |Name |Type |Desc | + * |------------------|-------|-------------------------| + * |unenumerable=false|boolean|Include unenumerable keys| + * |symbol=false |boolean|Include symbol keys | + * |accessGetter=false|boolean|Access getter value | + * |timeout=0 |number |Timeout of stringify | + * |depth=0 |number |Max depth of recursion | + * + * When time is out, all remaining values will all be "Timeout". + */ + + /* example + * stringifyAll(function test() {}); // -> '{"value":"function test() {}","type":"Function",...}' + */ + + /* typescript + * export declare namespace stringifyAll { + * interface IOptions { + * unenumerable?: boolean; + * symbol?: boolean; + * accessGetter?: boolean; + * timeout?: number; + * depth?: number; + * } + * } + * export declare function stringifyAll( + * obj: any, + * options?: stringifyAll.IOptions + * ): string; + */ + + /* dependencies + * escapeJsStr type toStr endWith toSrc keys each Class getProto difference extend isPromise filter now allKeys + */ + + exports = (function(_exports) { + function exports(_x) { + return _exports.apply(this, arguments); + } + + exports.toString = function() { + return _exports.toString(); + }; + + return exports; + })(function(obj) { + var _ref = + arguments.length > 1 && arguments[1] !== undefined + ? arguments[1] + : {}, + self = _ref.self, + _ref$startTime = _ref.startTime, + startTime = _ref$startTime === void 0 ? now() : _ref$startTime, + _ref$timeout = _ref.timeout, + timeout = _ref$timeout === void 0 ? 0 : _ref$timeout, + _ref$depth = _ref.depth, + depth = _ref$depth === void 0 ? 0 : _ref$depth, + _ref$curDepth = _ref.curDepth, + curDepth = _ref$curDepth === void 0 ? 1 : _ref$curDepth, + _ref$visitor = _ref.visitor, + visitor = _ref$visitor === void 0 ? new Visitor() : _ref$visitor, + _ref$unenumerable = _ref.unenumerable, + unenumerable = _ref$unenumerable === void 0 ? false : _ref$unenumerable, + _ref$symbol = _ref.symbol, + symbol = _ref$symbol === void 0 ? false : _ref$symbol, + _ref$accessGetter = _ref.accessGetter, + accessGetter = _ref$accessGetter === void 0 ? false : _ref$accessGetter; + + var json = ''; + var options = { + visitor: visitor, + unenumerable: unenumerable, + symbol: symbol, + accessGetter: accessGetter, + depth: depth, + curDepth: curDepth + 1, + timeout: timeout, + startTime: startTime + }; + var t = type(obj, false); + + if (t === 'String') { + json = wrapStr(obj); + } else if (t === 'Number') { + json = toStr(obj); + + if (endWith(json, 'Infinity')) { + json = '{"value":"'.concat(json, '","type":"Number"}'); + } + } else if (t === 'NaN') { + json = '{"value":"NaN","type":"Number"}'; + } else if (t === 'Boolean') { + json = obj ? 'true' : 'false'; + } else if (t === 'Null') { + json = 'null'; + } else if (t === 'Undefined') { + json = '{"type":"Undefined"}'; + } else if (t === 'Symbol') { + var val = 'Symbol'; + + try { + val = toStr(obj); + /* eslint-disable no-empty */ + } catch (e) {} + + json = '{"value":'.concat(wrapStr(val), ',"type":"Symbol"}'); + } else { + if (timeout && now() - startTime > timeout) { + return wrapStr('Timeout'); + } + + if (depth && curDepth > depth) { + return wrapStr('{...}'); + } + + json = '{'; + var parts = []; + var visitedObj = visitor.get(obj); + var id; + + if (visitedObj) { + id = visitedObj.id; + parts.push('"reference":'.concat(id)); + } else { + id = visitor.set(obj); + parts.push('"id":'.concat(id)); + } + + parts.push('"type":"'.concat(t, '"')); + + if (endWith(t, 'Function')) { + parts.push('"value":'.concat(wrapStr(toSrc(obj)))); + } else if (t === 'RegExp') { + parts.push('"value":'.concat(wrapStr(obj))); + } + + if (!visitedObj) { + var enumerableKeys = keys(obj); + + if (enumerableKeys.length) { + parts.push( + iterateObj( + 'enumerable', + enumerableKeys, + self || obj, + options + ) + ); + } + + if (unenumerable) { + var unenumerableKeys = difference( + allKeys(obj, { + prototype: false, + unenumerable: true + }), + enumerableKeys + ); + + if (unenumerableKeys.length) { + parts.push( + iterateObj( + 'unenumerable', + unenumerableKeys, + self || obj, + options + ) + ); + } + } + + if (symbol) { + var symbolKeys = filter( + allKeys(obj, { + prototype: false, + symbol: true + }), + function(key) { + return _typeof(key) === 'symbol'; + } + ); + + if (symbolKeys.length) { + parts.push( + iterateObj('symbol', symbolKeys, self || obj, options) + ); + } + } + + var prototype = getProto(obj); + + if (prototype) { + var proto = '"proto":'.concat( + exports( + prototype, + extend(options, { + self: self || obj + }) + ) + ); + parts.push(proto); + } + } + + json += parts.join(',') + '}'; + } + + return json; + }); + + function iterateObj(name, keys, obj, options) { + var parts = []; + each(keys, function(key) { + var val; + var descriptor = Object.getOwnPropertyDescriptor(obj, key); + var hasGetter = descriptor && descriptor.get; + var hasSetter = descriptor && descriptor.set; + + if (!options.accessGetter && hasGetter) { + val = '(...)'; + } else { + try { + val = obj[key]; + + if (isPromise(val)) { + val.catch(function() {}); + } + } catch (e) { + val = e.message; + } + } + + parts.push(''.concat(wrapKey(key), ':').concat(exports(val, options))); + + if (hasGetter) { + parts.push( + '' + .concat(wrapKey('get ' + toStr(key)), ':') + .concat(exports(descriptor.get, options)) + ); + } + + if (hasSetter) { + parts.push( + '' + .concat(wrapKey('set ' + toStr(key)), ':') + .concat(exports(descriptor.set, options)) + ); + } + }); + return '"'.concat(name, '":{') + parts.join(',') + '}'; + } + + function wrapKey(key) { + return '"'.concat(escapeJsonStr(key), '"'); + } + + function wrapStr(str) { + return '"'.concat(escapeJsonStr(toStr(str)), '"'); + } + + function escapeJsonStr(str) { + return escapeJsStr(str) + .replace(/\\'/g, "'") + .replace(/\t/g, '\\t'); + } + + var Visitor = Class({ + initialize: function initialize() { + this.id = 0; + this.visited = []; + }, + set: function set(val) { + var visited = this.visited, + id = this.id; + var obj = { + id: id, + val: val + }; + visited.push(obj); + this.id++; + return id; + }, + get: function get(val) { + var visited = this.visited; + + for (var i = 0, len = visited.length; i < len; i++) { + var obj = visited[i]; + if (val === obj.val) return obj; + } + + return false; + } + }); + + return exports; +})({}); + export default _; \ No newline at end of file diff --git a/src/lib/stringifyWorker.js b/src/lib/stringifyWorker.js index ae93aad..58f7fae 100644 --- a/src/lib/stringifyWorker.js +++ b/src/lib/stringifyWorker.js @@ -1,7 +1,7 @@ -import stringify from './stringify' +import { stringifyAll } from './stringifyUtil' onmessage = function(e) { let [id, obj, options] = e.data - let result = stringify(obj, options) + let result = stringifyAll(obj, options) postMessage([id, result]) } diff --git a/src/lib/util.js b/src/lib/util.js index 2c4d175..4507dfc 100644 --- a/src/lib/util.js +++ b/src/lib/util.js @@ -1017,6 +1017,62 @@ export var detectOs = _.detectOs = (function (exports) { return exports; })({}); +/* ------------------------------ restArgs ------------------------------ */ + +export var restArgs = _.restArgs = (function (exports) { + /* This accumulates the arguments passed into an array, after a given index. + * + * |Name |Type |Desc | + * |------------|--------|---------------------------------------| + * |function |function|Function that needs rest parameters | + * |[startIndex]|number |The start index to accumulates | + * |return |function|Generated function with rest parameters| + */ + + /* example + * var paramArr = restArgs(function (rest) { return rest }); + * paramArr(1, 2, 3, 4); // -> [1, 2, 3, 4] + */ + + /* typescript + * export declare function restArgs(fn: Function, startIndex?: number): Function; + */ + exports = function exports(fn, startIdx) { + startIdx = startIdx == null ? fn.length - 1 : +startIdx; + return function() { + var len = Math.max(arguments.length - startIdx, 0), + rest = new Array(len), + i; + + for (i = 0; i < len; i++) { + rest[i] = arguments[i + startIdx]; + } // Call runs faster than apply. + + switch (startIdx) { + case 0: + return fn.call(this, rest); + + case 1: + return fn.call(this, arguments[0], rest); + + case 2: + return fn.call(this, arguments[0], arguments[1], rest); + } + + var args = new Array(startIdx + 1); + + for (i = 0; i < startIdx; i++) { + args[i] = arguments[i]; + } + + args[startIdx] = rest; + return fn.apply(this, args); + }; + }; + + return exports; +})({}); + /* ------------------------------ optimizeCb ------------------------------ */ export var optimizeCb = _.optimizeCb = (function (exports) { @@ -1569,6 +1625,49 @@ export var safeGet = _.safeGet = (function (exports) { return exports; })({}); +/* ------------------------------ flatten ------------------------------ */ + +export var flatten = _.flatten = (function (exports) { + /* Recursively flatten an array. + * + * |Name |Type |Desc | + * |------|-----|-------------------| + * |arr |array|Array to flatten | + * |return|array|New flattened array| + */ + + /* example + * flatten(['a', ['b', ['c']], 'd', ['e']]); // -> ['a', 'b', 'c', 'd', 'e'] + */ + + /* typescript + * export declare function flatten(arr: any[]): any[]; + */ + + /* dependencies + * isArr + */ + + exports = function exports(arr) { + return flat(arr, []); + }; + + function flat(arr, res) { + var len = arr.length, + i = -1, + cur; + + while (len--) { + cur = arr[++i]; + isArr(cur) ? flat(cur, res) : res.push(cur); + } + + return res; + } + + return exports; +})({}); + /* ------------------------------ isDate ------------------------------ */ export var isDate = _.isDate = (function (exports) { @@ -2536,6 +2635,37 @@ export var isNull = _.isNull = (function (exports) { return exports; })({}); +/* ------------------------------ isPromise ------------------------------ */ + +export var isPromise = _.isPromise = (function (exports) { + /* Check if value looks like a promise. + * + * |Name |Type |Desc | + * |------|-------|----------------------------------| + * |val |* |Value to check | + * |return|boolean|True if value looks like a promise| + */ + + /* example + * isPromise(new Promise(function () {})); // -> true + * isPromise({}); // -> false + */ + + /* typescript + * export declare function isPromise(val: any): boolean; + */ + + /* dependencies + * isObj isFn + */ + + exports = function exports(val) { + return isObj(val) && isFn(val.then); + }; + + return exports; +})({}); + /* ------------------------------ isRegExp ------------------------------ */ export var isRegExp = _.isRegExp = (function (exports) { @@ -3068,6 +3198,40 @@ export var filter = _.filter = (function (exports) { return exports; })({}); +/* ------------------------------ difference ------------------------------ */ + +export var difference = _.difference = (function (exports) { + /* Create an array of unique array values not included in the other given array. + * + * |Name |Type |Desc | + * |---------|-----|----------------------------| + * |arr |array|Array to inspect | + * |[...rest]|array|Values to exclude | + * |return |array|New array of filtered values| + */ + + /* example + * difference([3, 2, 1], [4, 2]); // -> [3, 1] + */ + + /* typescript + * export declare function difference(arr: any[], ...rest: any[]): any[]; + */ + + /* dependencies + * restArgs flatten filter contain + */ + + exports = restArgs(function(arr, rest) { + rest = flatten(rest); + return filter(arr, function(val) { + return !contain(rest, val); + }); + }); + + return exports; +})({}); + /* ------------------------------ evalCss ------------------------------ */ export var evalCss = _.evalCss = (function (exports) { @@ -5869,62 +6033,6 @@ export var now = _.now = (function (exports) { return exports; })({}); -/* ------------------------------ restArgs ------------------------------ */ - -export var restArgs = _.restArgs = (function (exports) { - /* This accumulates the arguments passed into an array, after a given index. - * - * |Name |Type |Desc | - * |------------|--------|---------------------------------------| - * |function |function|Function that needs rest parameters | - * |[startIndex]|number |The start index to accumulates | - * |return |function|Generated function with rest parameters| - */ - - /* example - * var paramArr = restArgs(function (rest) { return rest }); - * paramArr(1, 2, 3, 4); // -> [1, 2, 3, 4] - */ - - /* typescript - * export declare function restArgs(fn: Function, startIndex?: number): Function; - */ - exports = function exports(fn, startIdx) { - startIdx = startIdx == null ? fn.length - 1 : +startIdx; - return function() { - var len = Math.max(arguments.length - startIdx, 0), - rest = new Array(len), - i; - - for (i = 0; i < len; i++) { - rest[i] = arguments[i + startIdx]; - } // Call runs faster than apply. - - switch (startIdx) { - case 0: - return fn.call(this, rest); - - case 1: - return fn.call(this, arguments[0], rest); - - case 2: - return fn.call(this, arguments[0], arguments[1], rest); - } - - var args = new Array(startIdx + 1); - - for (i = 0; i < startIdx; i++) { - args[i] = arguments[i]; - } - - args[startIdx] = rest; - return fn.apply(this, args); - }; - }; - - return exports; -})({}); - /* ------------------------------ partial ------------------------------ */ export var partial = _.partial = (function (exports) { @@ -7444,6 +7552,335 @@ export var LocalStore = _.LocalStore = (function (exports) { return exports; })({}); +/* ------------------------------ stringifyAll ------------------------------ */ + +export var stringifyAll = _.stringifyAll = (function (exports) { + function _typeof(obj) { + if (typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol') { + _typeof = function _typeof(obj) { + return typeof obj; + }; + } else { + _typeof = function _typeof(obj) { + return obj && + typeof Symbol === 'function' && + obj.constructor === Symbol && + obj !== Symbol.prototype + ? 'symbol' + : typeof obj; + }; + } + return _typeof(obj); + } + + /* Stringify object into json with types. + * + * |Name |Type |Desc | + * |---------|------|-------------------| + * |obj |* |Object to stringify| + * |[options]|object|Stringify options | + * |return |string|Stringified object | + * + * Available options: + * + * |Name |Type |Desc | + * |------------------|-------|-------------------------| + * |unenumerable=false|boolean|Include unenumerable keys| + * |symbol=false |boolean|Include symbol keys | + * |accessGetter=false|boolean|Access getter value | + * |timeout=0 |number |Timeout of stringify | + * |depth=0 |number |Max depth of recursion | + * + * When time is out, all remaining values will all be "Timeout". + */ + + /* example + * stringifyAll(function test() {}); // -> '{"value":"function test() {}","type":"Function",...}' + */ + + /* typescript + * export declare namespace stringifyAll { + * interface IOptions { + * unenumerable?: boolean; + * symbol?: boolean; + * accessGetter?: boolean; + * timeout?: number; + * depth?: number; + * } + * } + * export declare function stringifyAll( + * obj: any, + * options?: stringifyAll.IOptions + * ): string; + */ + + /* dependencies + * escapeJsStr type toStr endWith toSrc keys each Class getProto difference extend isPromise filter now allKeys + */ + + exports = (function(_exports) { + function exports(_x) { + return _exports.apply(this, arguments); + } + + exports.toString = function() { + return _exports.toString(); + }; + + return exports; + })(function(obj) { + var _ref = + arguments.length > 1 && arguments[1] !== undefined + ? arguments[1] + : {}, + self = _ref.self, + _ref$startTime = _ref.startTime, + startTime = _ref$startTime === void 0 ? now() : _ref$startTime, + _ref$timeout = _ref.timeout, + timeout = _ref$timeout === void 0 ? 0 : _ref$timeout, + _ref$depth = _ref.depth, + depth = _ref$depth === void 0 ? 0 : _ref$depth, + _ref$curDepth = _ref.curDepth, + curDepth = _ref$curDepth === void 0 ? 1 : _ref$curDepth, + _ref$visitor = _ref.visitor, + visitor = _ref$visitor === void 0 ? new Visitor() : _ref$visitor, + _ref$unenumerable = _ref.unenumerable, + unenumerable = _ref$unenumerable === void 0 ? false : _ref$unenumerable, + _ref$symbol = _ref.symbol, + symbol = _ref$symbol === void 0 ? false : _ref$symbol, + _ref$accessGetter = _ref.accessGetter, + accessGetter = _ref$accessGetter === void 0 ? false : _ref$accessGetter; + + var json = ''; + var options = { + visitor: visitor, + unenumerable: unenumerable, + symbol: symbol, + accessGetter: accessGetter, + depth: depth, + curDepth: curDepth + 1, + timeout: timeout, + startTime: startTime + }; + var t = type(obj, false); + + if (t === 'String') { + json = wrapStr(obj); + } else if (t === 'Number') { + json = toStr(obj); + + if (endWith(json, 'Infinity')) { + json = '{"value":"'.concat(json, '","type":"Number"}'); + } + } else if (t === 'NaN') { + json = '{"value":"NaN","type":"Number"}'; + } else if (t === 'Boolean') { + json = obj ? 'true' : 'false'; + } else if (t === 'Null') { + json = 'null'; + } else if (t === 'Undefined') { + json = '{"type":"Undefined"}'; + } else if (t === 'Symbol') { + var val = 'Symbol'; + + try { + val = toStr(obj); + /* eslint-disable no-empty */ + } catch (e) {} + + json = '{"value":'.concat(wrapStr(val), ',"type":"Symbol"}'); + } else { + if (timeout && now() - startTime > timeout) { + return wrapStr('Timeout'); + } + + if (depth && curDepth > depth) { + return wrapStr('{...}'); + } + + json = '{'; + var parts = []; + var visitedObj = visitor.get(obj); + var id; + + if (visitedObj) { + id = visitedObj.id; + parts.push('"reference":'.concat(id)); + } else { + id = visitor.set(obj); + parts.push('"id":'.concat(id)); + } + + parts.push('"type":"'.concat(t, '"')); + + if (endWith(t, 'Function')) { + parts.push('"value":'.concat(wrapStr(toSrc(obj)))); + } else if (t === 'RegExp') { + parts.push('"value":'.concat(wrapStr(obj))); + } + + if (!visitedObj) { + var enumerableKeys = keys(obj); + + if (enumerableKeys.length) { + parts.push( + iterateObj( + 'enumerable', + enumerableKeys, + self || obj, + options + ) + ); + } + + if (unenumerable) { + var unenumerableKeys = difference( + allKeys(obj, { + prototype: false, + unenumerable: true + }), + enumerableKeys + ); + + if (unenumerableKeys.length) { + parts.push( + iterateObj( + 'unenumerable', + unenumerableKeys, + self || obj, + options + ) + ); + } + } + + if (symbol) { + var symbolKeys = filter( + allKeys(obj, { + prototype: false, + symbol: true + }), + function(key) { + return _typeof(key) === 'symbol'; + } + ); + + if (symbolKeys.length) { + parts.push( + iterateObj('symbol', symbolKeys, self || obj, options) + ); + } + } + + var prototype = getProto(obj); + + if (prototype) { + var proto = '"proto":'.concat( + exports( + prototype, + extend(options, { + self: self || obj + }) + ) + ); + parts.push(proto); + } + } + + json += parts.join(',') + '}'; + } + + return json; + }); + + function iterateObj(name, keys, obj, options) { + var parts = []; + each(keys, function(key) { + var val; + var descriptor = Object.getOwnPropertyDescriptor(obj, key); + var hasGetter = descriptor && descriptor.get; + var hasSetter = descriptor && descriptor.set; + + if (!options.accessGetter && hasGetter) { + val = '(...)'; + } else { + try { + val = obj[key]; + + if (isPromise(val)) { + val.catch(function() {}); + } + } catch (e) { + val = e.message; + } + } + + parts.push(''.concat(wrapKey(key), ':').concat(exports(val, options))); + + if (hasGetter) { + parts.push( + '' + .concat(wrapKey('get ' + toStr(key)), ':') + .concat(exports(descriptor.get, options)) + ); + } + + if (hasSetter) { + parts.push( + '' + .concat(wrapKey('set ' + toStr(key)), ':') + .concat(exports(descriptor.set, options)) + ); + } + }); + return '"'.concat(name, '":{') + parts.join(',') + '}'; + } + + function wrapKey(key) { + return '"'.concat(escapeJsonStr(key), '"'); + } + + function wrapStr(str) { + return '"'.concat(escapeJsonStr(toStr(str)), '"'); + } + + function escapeJsonStr(str) { + return escapeJsStr(str) + .replace(/\\'/g, "'") + .replace(/\t/g, '\\t'); + } + + var Visitor = Class({ + initialize: function initialize() { + this.id = 0; + this.visited = []; + }, + set: function set(val) { + var visited = this.visited, + id = this.id; + var obj = { + id: id, + val: val + }; + visited.push(obj); + this.id++; + return id; + }, + get: function get(val) { + var visited = this.visited; + + for (var i = 0, len = visited.length; i < len; i++) { + var obj = visited[i]; + if (val === obj.val) return obj; + } + + return false; + } + }); + + return exports; +})({}); + /* ------------------------------ stripHtmlTag ------------------------------ */ export var stripHtmlTag = _.stripHtmlTag = (function (exports) {