* Copyright (c) 2022 - present TinyVue Authors.
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
*
* Use of this source code is governed by an MIT-style license.
*
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
*
*/
import { hasOwn, typeOf, isObject, isPlainObject, isNull } from '../type'
* 将对象的每个属性值进行循环处理。
*
* let obj = { name: 'jacky', age: 28, job: 'coder', dept: 'it' }
* each(obj, function (field, value) {
* if (field === 'name') {
* // do something
* }
* })
*
* @param {Object} obj 要处理的对象
* @param {Function} handle 进行循环处理的函数,函数返回false, 则跳出循环
*/
export const each = (obj: object, handle: (key: string, value?: any) => boolean) => {
if (typeof handle !== 'function') {
return
}
for (const name in obj) {
if (hasOwn.call(obj, name)) {
if (handle(name, obj[name]) === false) {
break
}
}
}
}
* 【首参为true】,则每一层是对象就合并, 但是简单值或数组时,就后面覆盖过来
* 【首参为对象】,则仅第一层合并, 类似Object.assign
* 合并对象中,有非object类型的,统统忽略!
* @returns {Object}
*/
let extend: (deep: boolean | object, ...values: object[]) => object
* 通过路径,获得对象指向位置的值。
*
* getObj({ a: { b: 1 } }, 'a.b') // 1
* getObj({ a: { b: 1 } }, 'data.a.b', true) // 1
* getObj({ a: { b: undefined } }, 'a.b') // undefined
*
* @param {Object} data 查找数据源
* @param {String} names 查找属性命名空间字符串
* @param {Boolean} [isExceptRoot] 是否排除 names 的第一个节点,默认 false
* @returns {Object}
*/
export const getObj = (data: object, names: string, isExceptRoot?: boolean) => {
if (!data || !isPlainObject(data) || !names || typeof names !== 'string') {
return
}
const nameArr = names.split('.')
let obj = data
const len = nameArr.length
if (len > 1) {
const startIndex = isExceptRoot ? 1 : 0
for (let i = startIndex; i < len; i++) {
obj = obj[nameArr[i]]
if (isNull(obj)) {
return obj
}
}
return obj
} else {
return obj[nameArr[0]]
}
}
* 通过路径,设置对象指向位置的值。
*
* let obj = { limit: 5, data: { a: 1, b: 2 }, info: { a: 1, b: 2 } }
* setObj(obj, 'limit', 10) // obj.limit = 10
* setObj(obj, 'data', { c: 3 }, true) // obj.data = { a: 1, b: 2, c: 3 }
* setObj(obj, 'info', { c: 3 }) // obj.info = { c: 3 }
* setObj(obj, 'info.c', { d: 4 }, true) // obj.info = { c: { d: 4 } }
* setObj(obj, 'info.c', { e: 5 }, true) // obj.info = { c: { d: 4, e: 5 } }
*
* @param {Object} data 设置数据源
* @param {String} names 查找属性命名空间字符串
* @param {Object} value 设置的值
* @param {boolean} [isMerge] 是否覆盖还是合并,默认覆盖
* @returns {Object}
*/
export const setObj = (data: object, names: string, value: any, isMerge) => {
if (!data || !isPlainObject(data) || !names || typeof names !== 'string') {
return data
}
const nameArr = names.split('.')
const obj = data
let len = nameArr.length
let item = nameArr[0]
if (len > 1) {
len--
let tmpl = obj
let name, target
for (let i = 0; i < len; i++) {
name = nameArr[i]
target = tmpl[name]
if (target === null || !isPlainObject(target)) {
tmpl[name] = {}
target = tmpl[name]
}
tmpl = target
}
item = nameArr[len]
isMerge
? isPlainObject(tmpl[item])
? extend(true, tmpl[item], value)
: (tmpl[item] = value)
: (tmpl[item] = value)
} else {
isMerge
? isPlainObject(obj[item])
? extend(true, obj[item], value)
: (obj[item] = value)
: (obj[item] = value)
}
return obj
}
* 根据指定的字段属性名,复制对应的数据。
*
* let obj = { a: 1, b: '2', c: [3, 4, 5], d: { e: 'good' } }
* copyField(obj, ['a', 'b']) // { a: 1, b: '2' }
* copyField(obj, ['a', 'b'], false, true) // { c: [3, 4, 5], d: { e: 'good' } }
*
* @param {Object} data 源数据,合并数据源
* @param {Array} [fields] 指定的值得命名空间字符串的数值。 不传入,默认为克隆一份数据出来
* @param {Boolean} [isMerge] 是否覆盖还是合并,默认false覆盖
* @param {Boolean} [isExclude] 是否排除指定的fields复制,默认false
* @returns {Array}
*/
export const copyField = (data: object, fields?: string[], isMerge?: boolean, isExclude?: boolean) => {
const setValue = (obj, result, name, key, isMerge?) => {
const include = key.indexOf(name) === 0
const keySplit = key.split(name)
const hasNextDot = keySplit[1] && keySplit[1].indexOf('.') === 0
if (name === key || (include && hasNextDot)) {
if (name !== key) {
each(getObj(obj, name), (field) => {
setValue(obj, result, `${name}.${field}`, key)
return true
})
}
} else {
if (fields && !fields.includes(name)) {
setObj(result, name, getObj(obj, name), isMerge)
}
}
}
const innerCopyFields = (obj, fields, isMerge, isExclude) => {
const result = {}
if (isExclude) {
each(obj, (name) => fields.forEach((key) => setValue(obj, result, name, key, isMerge)))
} else {
fields.forEach((field) => setObj(result, field, getObj(obj, field), isMerge))
}
return result
}
if (isPlainObject(data)) {
return Array.isArray(fields)
? innerCopyFields(data, fields, isMerge, isExclude)
: extend(isMerge !== false, {}, data)
}
return data
}
* 复制数组数据,数据如包含对象,则深度复制,并返回一个新数组,如果不是数组则直接返回原对象。
*
* let arr1 = [ 1, 2, { name: 'jacky' } ]
* let arr2 = copyArray(arr1)
*/
export const copyArray = (arr: any[]) => {
return Array.isArray(arr) ? arr.map((item) => copyField(item)) : arr
}
* 对象复制,支持深度复制,修复 $.extend 数组复制的问题, 参数同 $.extend。
*
* let obj1 = { a: 1, b: 2 }
* let obj2 = { c: 3, d: 4 }
* extend(obj1, obj2) // { a: 1, b: 2, c: 3, d: 4 }
*
* @param {Boolean} deep 如果是 true,合并成为递归(又叫做深拷贝)。仅支持 true | 空
* @param {Object} target 对象扩展,这将接收新的属性。
* @param {Object} object1 一个对象,它包含额外的属性合并到第一个参数。
* @param {Object} objectN 包含额外的属性合并到第一个参数
* @returns {Object}
*/
const deepCopy = (target, name, deep, copy, src) => {
let copyIsArray
if (deep && copy && (isPlainObject(copy) || (copyIsArray = Array.isArray(copy)))) {
if (copyIsArray) {
copyIsArray = false
target[name] = copyArray(copy)
} else {
const clone = src && isPlainObject(src) ? src : {}
target[name] = extend(deep, clone, copy)
}
} else if (copy !== undefined) {
try {
target[name] = copy
} catch (e) {
}
}
}
extend = function (...args) {
const length = args.length
let target = args[0] || {}
let i = 1
let deep = false
if (typeOf(target) === 'boolean') {
deep = target as boolean
target = args[i] || {}
i++
}
if (!isObject(target) && typeOf(target) !== 'function') {
target = {}
}
for (; i < length; i++) {
const options = args[i]
if (options !== null && isObject(options)) {
const names = Object.keys(options)
for (const name of names) {
const src = target[name]
const copy = options[name]
if (target !== copy) {
deepCopy(target, name, deep, copy, src)
}
}
}
}
return target
} as any
* 可深层比较两个对象或两个数组是否相等。注意以源对象为比较基础
*
* isEachEqual({a: 1}, {a: 1, b: 2}) // true
* isEachEqual({a: 1, b: 2}, {a: 1}) // false (以源对象为比较基础,所以它是false)
* isEachEqual({a: 1, b: {c: 3, d: 4}}, {a: 1, b: {c: 3, d: 5}}) // false
*
* @param {Object} data1 数据源对象
* @param {Object} data2 对比目标对象
* @param {Boolean} [deep] 是否深度遍历,默认为true
* @returns {Boolean}
*/
let isEachEqual: (data1: any, data2: any, deep?: boolean) => boolean
* 可深层比较两个对象是否相等。
* 与`isEachEqual` 区别是:
* 1、2个对象交换位置,判断2次
* 2、它可以指定要比较的属性分支["a.b","a.c"] (整个系统使用fields这块功能)
*
* isEqual({ a: { b: 1 } }, { a: { b: 1, c: 2 } }, false, [ 'a.b' ]) // false
* isEqual({ a: { b: 1 } }, { a: { b: 1, c: 2 } }, true, [ 'a.b' ]) // true
*
* @param {Object} sourceData 源对象
* @param {Object} targetData 目标对象
* @param {Boolean} [deep] 是否深度比较,默认为true
* @param {Array} [fields] 指定需要比较的字段的数组
* @returns {Boolean}
*/
export const isEqual: (sourceData: object, targetData: object, deep?: boolean, fields?: string[]) => boolean = (
sourceData: object,
targetData: object,
deep?: boolean,
fields?: string[]
) => {
if (typeOf(sourceData) === typeOf(targetData)) {
deep = deep !== false
if (Array.isArray(fields)) {
const _sourceData = copyField(sourceData, fields)
const _targetData = copyField(targetData, fields)
return isEqual(_sourceData, _targetData, deep) as boolean
}
const source = isEachEqual(sourceData, targetData, deep)
const target = isEachEqual(targetData, sourceData, deep)
return source && target
}
return false
}
isEachEqual = (data1: any, data2: any, deep?: boolean) => {
if (!isPlainObject(data1)) {
if (!Array.isArray(data1)) {
return data1 === data2
}
if (data1.length !== data2.length) {
return false
}
for (let i = 0, length = data1.length; i < length; i++) {
const result = isEqual(data1[i], data2[i], deep)
if (!result) {
return false
}
}
return true
}
let bEqual = true
const names = Object.keys(data1)
for (const name of names) {
if (hasOwn.call(data2, name)) {
const _data1 = data1[name]
const _data2 = data2[name]
if ((deep && isObject(_data1)) || Array.isArray(_data1)) {
bEqual = isEachEqual(_data1, _data2, deep)
} else {
bEqual = _data1 === _data2
}
} else {
bEqual = false
}
if (bEqual === false) {
break
}
}
return bEqual
}
export { isEachEqual, extend }
* 将json对象序列化为字符串。 ----------重复实现待移除
*
* let obj = { a: 1, b: 2 }
* toJsonStr(obj) // '{"a":1,"b":2}'
* obj.prop = obj
* toJsonStr(obj) // undefined 递归引用自己了,所以catch了
* toJsonStr(null) // 'null'
*
* @param {Object} obj
* @returns {String}
*/
export const toJsonStr = (obj: any) => {
try {
return JSON.stringify(obj)
} catch (e) {
return undefined
}
}
* 将一个或多个源对象简单合并到目标对象中,合并时排除非 OwnProperty 及 undefined 属性。
* 只处理第一层,功能基本等同于 Object.assign
*
* merge({ a: 1 }, { b: { c: 2 } }, { d: 3 }) // { a: 1, b: { c: 2 }, d: 3 }
*
* @param {Object} target 目标对象
* @param {Object} [source] 源对象
* @returns {Object}
*/
export const merge = function (target: object, ...rest: object[]) {
for (let i = 0, len = rest.length; i < len; i++) {
const source = rest[i] || {}
for (const prop in source) {
if (hasOwn.call(source, prop)) {
const value = source[prop]
if (value !== undefined) {
target[prop] = value
}
}
}
}
return target
}