Daily-Question
Daily-Question copied to clipboard
【Q198】如何实现类似 lodash.get 函数
使用 get
函数可避免长链的 key 时获取不到属性而出现问题,此时进行异常避免时及其服务,如 o.a && o.a.b && o.a.b.c && o.a.b.c.d
实现类似lodash.get,有以下测试用例:
const object = { 'a': [{ 'b': { 'c': 3 } }] };
//=> 3
get(object, 'a[0].b.c');
//=> 3
get(object, 'a[0]["b"]["c"]')
//=> 10086
get(object, 'a[100].b.c', 10086);
问题追问:
1. 如何使用 ts 写法来实现 lodash.get 函数?
function lodashGet(obj,exps){ if(typeof exps !== 'string') return obj if(typeof obj !== 'object') return obj let res = obj const arr = exps.split('.') for(let i=0;i<arr.length;i++){ const exp = arr[i] if(res[exp]){ res = res[exp] } else{ return undefined } } return res }
var obj = {test:{arr:[{name:1}]}}
lodashGet(obj,'test.arr.0.name')
代码见 如何实现类似 lodash.get 函数 - codepen
function get (source, path, defaultValue = undefined) {
// a[3].b -> a.3.b -> [a, 3, b]
const paths = path.replace(/\[(\w+)\]/g, '.$1').replace(/\["(\w+)"\]/g, '.$1').replace(/\['(\w+)'\]/g, '.$1').split('.')
let result = source
for (const p of paths) {
result = result?.[p]
}
return result === undefined ? defaultValue : result
}
const object = { a: [{ b: { c: 3 } }] };
const result = _.get(object, 'a[0].b.c', 1);
function getValue(context, path, defaultValue) {
if (Object.prototype.toString.call(context) !== '[object Object]'
&& Object.prototype.toString.call(context) !== '[object Array]') {
return context;
}
let paths = [];
if (Array.isArray(path)) {
paths = [...path];
} else if (Object.prototype.toString.call(path) === '[object String]') {
paths = path.replace(/\[/g, '.').replace(/\]/g, '').split('.').filter(Boolean);
} else {
paths = [String(path)];
}
let result = undefined;
for (let i = 0; i < paths.length; i++) {
const key = paths[i];
result = result ? result[key] : context[key];
if (result !== null && typeof result !== 'undefined') {
continue;
}
return defaultValue || undefined;
}
return result;
}
// 其实原本是按照lodash实现的 但是这里有个差异是如果属性存在就返回其实没有把目标元素是`undefined`的时候设置回default
function get (arm, params = '', defaultVal) {
if (typeof params !== 'string' && !Array.isArray(params)) {
throw new Error(`${params} is not string or array`)
}
if (!Array.isArray(params)) {
params = params.split(/\].|[\[.]/)
}
for (let i = 0; i < params.length; i++) {
if (Object.prototype.hasOwnProperty.call(arm, params[i])) {
arm = arm[params[i]]
} else {
return defaultVal
}
}
return arm
}
function get (obj, keyStr, defVal = undefined) {
let matchArr = Array.from(keyStr.matchAll(/(\[).*?(\])|(?<=\.).*?(?=\.)|(?<=\.).*?$/g))
let val = obj
for (let i = 0; i < matchArr.length; i++) {
if (typeof val === 'object' && val !== null || typeof val === 'function') {
let key = matchArr[i][0]
if (key[0] === '[') {
key = key.slice(1, key.length - 1)
}
val = obj[key]
} else {
return defVal
}
}
if (val === undefined) {
return defVal
} else {
return val
}
}
type strToPoint<S> =
S extends `${infer F}["${infer M}` ? strToPoint<`${F}.${M}`> :
S extends `${infer F}"]${infer M}` ? strToPoint<`${F}${M}`> :
S extends `${infer F}['${infer M}` ? strToPoint<`${F}.${M}`> :
S extends `${infer F}']${infer M}` ? strToPoint<`${F}${M}`> :
S extends `${infer F}[${infer M}` ? strToPoint<`${F}.${M}`> :
S extends `${infer F}]${infer M}` ? strToPoint<`${F}${M}`> : S
type strPointToArr<S, A extends string[] = []> =
S extends `${infer F}.${infer M}` ? strPointToArr<M, [...A, F]> :
S extends '' ? A : [...A, S]
type getReturnType<O extends unknown, K extends string[], D extends unknown = undefined> =
K extends [] ? O extends undefined ? D : O :
O extends Record<string, any> ? getReturnType<K[0] extends keyof O ? O[K[0]] : undefined, K extends [first:infer F, ...args: infer L] ? L : [] ,D > :
D
let obj = {
a: [1, 'lisi', {
b: {
c: 4
},
f: {
g: 'wangwu'
}
}]
} as const
type get<O extends Record<string, any>, K extends string, Def extends unknown = undefined> =
(obj: O, keyStr: K, defVal: Def) => getReturnType<O, strPointToArr<strToPoint<K>>, Def>
type zz = get<typeof obj, 'a[2][b].c', '123'>
type zzz = get<typeof obj, 'd[e]', 'defaultVal'>
const lodashGet = (object: { [key: string]: any }, path: Array<string> | string, defaultValue?: any): any => {
let result: any
const findArrayPath = (path: Array<string>): any => {
if (path.length === 0) {
return result = defaultValue
}
result = object
for (const p of path) {
if (p in result) {
result = result[p]
} else {
result = defaultValue
break
}
}
return result
}
if (Array.isArray(path)) {
result = findArrayPath(path)
} else {
path.replace
let normalizedPath = path.replace(/\.|\[|\]/g, ' ').split(/\s+/)
result = findArrayPath(normalizedPath)
}
return result
}
const object = { 'a': [{ 'b': { 'c': 3 } }] }
console.log(lodashGet(object, 'a[0].b.c'))
console.log(lodashGet(object, ['a', '0', 'b', 'c']))
console.log(lodashGet(object, 'a.b.c', 'default'))
function get(obj, keys, defaultValue) {
let tempObj = obj;
let arr = [];
if (typeof keys === 'string') {
let key = '';
let index = 0;
while (index < keys.length) {
const k = keys[index];
if ((['[', '\'', "\"", '.', ']']).includes(k)) {
if (key.length) {
arr.push(key);
}
key ='';
} else {
key = key + k;
}
index = index +1;
}
key && arr.push(key);
} else {
arr = keys;
}
while (arr.length) {
tempObj = tempObj[arr.shift()]
if (tempObj === undefined || tempObj === null) {
return defaultValue;
}
}
return tempObj;
}
function get(obj, oriPath, defaultVal) {
const paths = oriPath.split(".");
const keys = [];
for (const path of paths) {
keys.push(...pathHandler(path));
}
let res = obj;
for (const key of keys) {
if (res[key] === undefined || res[key] === null) return defaultVal;
res = res[key];
}
return res;
}
// 将 path 处理为能用的 keys
function pathHandler(path) {
const res = [];
path = path.split("");
let left = 0;
let right = 0;
let str = "";
while (left < path.length && right < path.length) {
if (path[left] === "[") {
right = left + 1;
while (path[right] !== "]") {
right++;
}
temp = path.slice(left + 1, right).join("");
left = right + 1;
temp && res.push(temp);
} else {
str += path[left++];
}
}
res.unshift(str);
return res.map((item) => {
if (item[0] >= "0" && item[0] <= "9") {
// 转化为整数
return parseInt(item);
}
if (item.indexOf('"') !== -1 || item.indexOf("'") !== -1) {
// 去除两边的引号
return item.slice(1, item.length - 1);
}
return item;
});
}