2021SC@SDUSC
概述
本周学习amis
中怎么泛化地书写将数据从各个对象中取出来的代码以及几个其他值得学习的功能函数。
resolveVariable(path,data)
对于给定的一个路径path
,可以分成两类:
// 1
a.b.c | a.b[1].c
// 2
ns: varname
当为第一种的时候,给的是一个属性路径,这需要我们从data
中获取各个级别对应的属性值,层层查找,直到最后一级即为目标值。
当为第二种的时候,是一个键值对的形式,ns 是数据存储的对象,varname 是同第一种的属性路径。包括window
、localStorage
、sessionStorage
、Cookit
以及以下自定义的位置。其实,第一种方式相当于省略了ns = data
的条件。
代码分析:
// 获取 data 对象在 Path 路径的变量值
function objectGet(data: any, path: string) {
if (typeof data[path] !== 'undefined') {
return data[path]; // 只有一级路径时
}
// 将 path 切分成各级路径
let parts = keyToPath(path.replace(/^{|}$/g, ''));
return parts.reduce((data, path) => {
// 迭代的取每一级的路径中的属性值
if ((isObject(data) || Array.isArray(data)) && path in data) {
return (data as {[propName: string]: any})[path];
}
// 只要有一级没有,总为 undefined
return undefined;
}, data);
}
function parseJson(str: string, defaultValue?: any) {
try {
return JSON.parse(str);
} catch (e) {
return defaultValue;
}
}
// 在 Cookie 中获取对应的键值 name
function getCookie(name: string) {
// Cookit 总是以分号分割,结尾不加分号
const value = `; ${document.cookie}`; // 手动补充一个分号。注意空格!
const parts = value.split(`; ${name}=`); // 寻找目标属性
if (parts.length === 2) { // 说明 split 切割成功
return parts.pop()!.split(';').shift(); // 后面的一个 `;` 之前的部分 // 没有分号同样满足
}
return undefined;
}
// 获取 data 在 path 路径下对应的值
export const resolveVariable = (path?: string, data: any = {}): any => {
if (!path || !data || typeof path !== 'string') {
return undefined;
}
// 从这里就可以推断出 path 的格式
// ns : varname
// 其中 ns 是数据存储的位置,varname 是最后的变量名
let [ns, varname] = path.split(':');
// 不含有 varname 时,说明不为上述格式
// path 结构为 a.b.c , 数据存储在 data 中的
if (!varname && ns) {
varname = ns;
ns = '';
}
if (ns === 'window') {
data = window; // window 中
} else if (ns === 'ls' || ns === 'ss') {
// ls -> localStorage
// ss -> sessionStorage
let parts = keyToPath(varname.replace(/^{|}$/g, '')); // 切割路径
const key = parts.shift()!; // 第一个为在本地储存的键值
const raw = // 获取对应值,此时为 string 类型
// 本地存储不能存储大对象,存储的是 JSON.stringify 后的对象字符串
ns === 'ss' ? sessionStorage.getItem(key) : localStorage.getItem(key);
// string 类型,不为 Null
if (typeof raw === 'string') {
const data = parseJson(raw, raw); // 重新还原成对象
if (isObject(data) && parts.length) {
// 从该对象的中获取 剩下的 parts.join('.') 下的值
return objectGet(data, parts.join('.'));
}
return data;
}
return undefined;
// cookit 中
} else if (ns === 'cookie') {
const key = varname.replace(/^{|}$/g, '').trim();
return getCookie(key);
}
// 以下是自定义的另一些标识符
if (varname === '$$') {
return data;
} else if (varname[0] === '$') {
varname = path.substring(1);
} else if (varname === '&') {
return data;
}
return objectGet(data, varname);
};
对于我自己来说,对于不同的对象的操作可能会使用不同的类来描述,并且在对应的类中当然还有其他操作。而amis
开发人员则是将函数用功能组织起来,一个函数完成多个不同对象的方面的功能,这也是一种较好的方式,看个人习惯即可。而我更喜欢方向对象的开发模式。
prettyBytes(num)
格式化数据大小,将统一用 B
做单位的数据大小标识标准化。
// 数据单位
const UNITS = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
// 格式化数据大小
export const prettyBytes = (num: number) => {
// isFinite // 判断数值是否为有限的数值 !== inf
if (!Number.isFinite(num)) {
throw new TypeError(`Expected a finite number, got ${typeof num}: ${num}`);
}
// 存储符号
const neg = num < 0;
if (neg) {
num = -num;
}
// 数据小于最小单位 1B
if (num < 1) {
return (neg ? '-' : '') + num + ' B';
}
const exponent = Math.min(
Math.floor(Math.log(num) / Math.log(1000)), // 数据含有多少个 10^3,每一个标志着单位的一个等级
UNITS.length - 1 // 最大单位等级
);
// toPrecision 将数据格式化为指定长度,不够就添加,过长就用科学计数法
// 将 num 化成对应单位的数值
const numStr = Number((num / Math.pow(1000, exponent)).toPrecision(3));
const unit = UNITS[exponent]; // 对应单位
return (neg ? '-' : '') + numStr + ' ' + unit;
};
escapeHtml
将 HTML 字符串中对应符号转成网页中的形式。要知道的是,在网页中某些符号比如空格,并不是敲敲空格就可以的,而是将其转化成一种特定的格式。
const entityMap: {
[propName: string]: string;
} = { // 网页中对应符号的 HTML 中的形式
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
'/': '/'
};
// 将 HTML 字符串中对应符号转成网页中的形式
export const escapeHtml = (str: string) =>
String(str).replace(/[&<>"'\/]/g, function (s) {
return entityMap[s];
});
formatDuration
// 将时间段时间戳格式化成标准时间形式
export function formatDuration(value: number): string {
const unit = ['秒', '分', '时', '天', '月', '季', '年']; // 单位
const steps = [1, 60, 3600, 86400, 2592000, 7776000, 31104000]; // 各单位对应的秒数
let len = steps.length;
const parts = []; // 用于存储每个单位量级对应的时间字符串
while (len--) {
// 当时第 len 个单位时,value 超出了1单位数值,就可以将该单位的数值提出来
if (steps[len] && value >= steps[len]) {
parts.push(Math.floor(value / steps[len]) + unit[len]);
value %= steps[len]; // 剩下的秒数
} else if (len === 0 && value) {
parts.push((value.toFixed ? value.toFixed(2) : '0') + unit[0]);// 最后部分包含小数在内,都为 B
}
}
return parts.join('');
}
makeSorter
自定义产生排序方法的函数工厂
function makeSorter(
key: string, // 用于排序的属性
method?: 'alpha' | 'numerical', // 字典序 | 数值大小
order?: 'desc' | 'asc' // 降序 | 升序
) {
// 返回的函数部分 a,b 为其中的两个值
return function (a: any, b: any) {
if (!a || !b) { // 含有空值时
return 0; // 不做处理,即仍然按原顺序
}
const va = resolveVariable(key, a); // 获取 a 的 key 路径下的值
const vb = resolveVariable(key, b);
let result = 0;
if (method === 'numerical') { // 数值大小排序
result = (parseFloat(va) || 0) - (parseFloat(vb) || 0);
} else { // 字典序排序
result = String(va).localeCompare(String(vb));
}
// -1 为降序
// 1 为升序
return result * (order === 'desc' ? -1 : 1);
};
}
工厂模式,顾名思义,是一种用来批量生产特定函数的工厂函数,它的返回值是一个可以独立运行的函数。根据工厂参数的不同,可以创造出不同功能的函数。比如上面这个函数,生产的函数就可以用于ort
排序中,实现不同的排序方法。
let arr = [2,1,3,11,21,321,32];
sort(arr,makeSorter('','alpha','desc'))
// 按降序字典序排列
总结
本周我学习到了如何处理多种对象的同功能函数的内聚(有点软件工程的味道),以及工厂函数的实现。