2021SC@SDUSC
概述
本周继续分析 helper 工具包。主要学习了对多级原型链上的多级属性进行增删查改的操作。
keyToPath
该函数可以将例如像 a.b.c 或 a[1].b 的字符串转换为路径数组。用于将对象属性的链式调用转化成逐级调用,便可遍历的对调用过程加以中层判断。
export const keyToPath = (string: string) => {
const result = [];
if (string.charCodeAt(0) === '.'.charCodeAt(0)) {
result.push('');
}
// 正则匹配
string.replace(
new RegExp(
'[^.[\\]]+|\\[(?:([^"\'][^[]*)|(["\'])((?:(?!\\2)[^\\\\]|\\\\.)*?)\\2)\\]|(?=(?:\\.|\\[\\])(?:\\.|\\[\\]|$))',
'g'
),
(match, expression, quote, subString) => {
let key = match;
if (quote) {
key = subString.replace(/\\(\\)?/g, '$1');
} else if (expression) {
key = expression.trim();
}
result.push(key);
return '';
}
);
return result;
};
示例:
keyToPath('a.b.c');
// -> ['a','b','c']
keyToPath('a[1].b.c');
// -> ['a','1','b','c']
getVariable
该函数是在data的属性中找 key 属性或将key分割后在data的各属性中一级一级地寻找对应属性,canAccessSuper表示是否能在这些对象的原型链上找该属性。
export function getVariable(
data: {[propName: string]: any},
key: string | undefined, // key 可以是一个多级属性值
canAccessSuper: boolean = true
// 可以推断出 canAccessSuper 参数的作用:属性是否可以来自于原型链
): any {
if (!data || !key) {
return undefined;
// 如果可以来自于原型链的话,判断 data 以及 data 的原型链上是否包含 key 属性
// 否则,判断data的自身属性中是否包含 key 属性
} else if (canAccessSuper ? key in data : data.hasOwnProperty(key)) {
// 包含,即返回该属性值
return data[key];
}
// 将 key 属性划分成属性列表
// reduce 上一次迭代的返回值做为下一次迭代的输入值
return keyToPath(key).reduce(
(obj, key) =>
obj && // obj 不为空 且为对象 且在该对象上包含 key 属性
typeof obj === 'object' &&
(canAccessSuper ? key in obj : obj.hasOwnProperty(key))
// 返回该属性值,下一次迭代时再次下级属性
? obj[key]
: undefined, // 若其中有一级为 undefined,则之后的各级均为 undefined
data // 初始值为data
);
}
setVariable
该函数用于在data中寻找一级一级地寻找key属性,没有就创建
export function setVariable(
data: {[propName: string]: any},
key: string,
value: any
) {
// data 为空时赋值为空对象
data = data || {};
if (key in data) {
// 当 key 包含在 data 的自身属性或是在原型链上时,直接赋值
data[key] = value;
return;
}
// 将 key 属性分割成各级属性
const parts = keyToPath(key);
// 最后一级属性
const last = parts.pop() as string;
// 依次寻找 目标 partKey 在 data 下级对象中对应的属性值
while (parts.length) {
// 从头部弹出一个 key,作为本次迭代的目标属性
let key = parts.shift() as string;
// 如果 data[key] 是原生对象, Number | string
if (isPlainObject(data[key])) {
// 将其包装成一个对象,为了使用它的指针,才能继续找下一级
data = data[key] = {
...data[key]
};
// 如果 data[key] 是一个数组
} else if (Array.isArray(data[key])) {
data[key] = data[key].concat(); // 这一个不知道有什么意义?
data = data[key];
} else if (data[key]) {
// throw new Error(`目标路径不是纯对象,不能覆盖`);
// 强行转成对象
data[key] = {};
data = data[key];
} else {
data[key] = {}; // 其他情况即没有 key 时, 创建 key 属性
data = data[key];
}
}
// 将最后一级属性赋值为 value; 不管它存不存在,不存在即创建
data[last] = value;
}
deleteVariable
该函数用于删除对象的多级属性
export function deleteVariable(data: {[propName: string]: any}, key: string) {
// data为空
if (!data) {
return;
// data自身属性中包含 key
} else if (data.hasOwnProperty(key)) {
delete data[key];
return;
}
// key 属性切割
const parts = keyToPath(key);
const last = parts.pop() as string; // 最后的目标属性
while (parts.length) {
let key = parts.shift() as string; // 从头部弹出
// 存在该一级的属性,才继续寻找下一级属性
// 不存在则直接 break
if (isPlainObject(data[key])) {
data = data[key] = {
...data[key]
};
} else if (data[key]) {
throw new Error(`目标路径不是纯对象,不能修改`);
} else {
break;
}
}
// data 不为空 且自身属性包含 last 目标属性
if (data && data.hasOwnProperty && data.hasOwnProperty(last)) {
delete data[last]; // 删除
}
}
hasOwnProperty
判断data对象自身是否含有多级属性key
export function hasOwnProperty(
data: {[propName: string]: any},
key: string
): boolean {
const parts = keyToPath(key);
while (parts.length) {
let key = parts.shift() as string;
if (!isObject(data) || !data.hasOwnProperty(key)) {
return false;
}
data = data[key];
}
return true;
}
总结
从上述对复杂对象多级原型链上的多级属性进行增删改查的代码思路来看,这一部分的内容是很复杂的,对原型链不熟悉的话很难理解一层层的递归过程及原理。学习完后,我对原型链操作又有了更深层次的了解,相信今后在处理此类问题的时候不在那么手足无措了。