We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
今天在认真干(划)活(水)的时候,看到群里有人发了一道头条的面试题,就顺便看了一下,发现挺有意思的,就决定分享给大家,并且给出我的解决方案和思考过程。
题目如下:
实现一个get函数,使得下面的调用可以输出正确的结果
const obj = { selector: { to: { toutiao: "FE Coder"} }, target: [1, 2, { name: 'byted'}] }; get(obj, 'selector.to.toutiao', 'target[0]', 'target[2].name'); // [ 'FE Coder', 1, 'byted']
乍眼一看,这不就是实现一个lodash.get方法吗?看上去好像很简单。所以我就开始写了第一个版本。思想其实很简单,遍历传进来的参数,使用split将每一个参数分隔开,然后遍历取值,最终返回结果。
function get(data, ...args) { return args.map((item) => { const paths = item.split('.'); let res = data; paths.map(path => res = res[path]); return res; }) }
一运行,果不其然,报错了。
后来仔细看了一下提供测试代码,发现居然有target[0]这种东西。。居然还带了个数组索引。
冷静分析一下,对于后面带了个索引的类型,比如'target[0]',我们肯定是要特殊对待的。所以,我们首先得先识别到这种特殊的类型,然后再对它进行额外处理。
这个时候,很快的就可以想到使用正则表达式来做这个事情。为什么呢?因为像这种带有索引的类型,他们都有一个特色,就是有固定的格式:[num],那么我们只需要能构造出可以匹配这种固定格式的正则,就可以解决这个问题。
对于这种格式,不难想到可以用这个正则表达式来做判断:/\[[0-9]+\]/gi,可是我们还需要将匹配值取出来。这个时候查了下正则表达式的文档(文档点击这里),发现有一个match方法,可以返回匹配成功的结果。那么就让我们来做个测试:
const reg = /\[[0-9]+\]/gi; const str = "target[123123]"; const str1 = "target[]" if (reg.test(str)) { console.log('test success'); } if (!reg.test(str1)) { console.log('test fail'); } const matchResult = str.match(reg); console.log(matchResult); // ["[123123]"]
诶,我们现在已经找到了解决这种问题的方法,那让我们赶紧来继续改进下代码。
function get(data, ...args) { const reg = /\[[0-9]+\]/gi; return args.map((item) => { const paths = item.split('.'); let res = data; paths.map(path => { if (reg.test(path)) { const match = path.match(reg)[0]; const cmd = path.replace(match, ''); const arrIndex = match.replace(/[\[\]]/gi, ''); res = res[cmd][arrIndex]; } else { res = res[path]; } }); return res; }); } const obj = { selector: { to: { toutiao: "FE Coder"} }, target: [1, 2, { name: 'byted'}]}; console.log(get(obj, 'selector.to.toutiao', 'target[0]', 'target[2].name'));
写完赶紧运行一下,完美,输出了正确的结果了。那么到这里就结束了?
可是总感觉有点不妥,感觉事情没有那么简单。一般来说,面试题除了考验你解决问题的能力之外,可能还考验着你思考问题的全面性、严谨性。像上面那种写法,如果用户传入了一个不存在的path链或者一些其他特殊情况,就可能导致整个程序crash掉。想下lodash.get调用方式,即使你传入了错误的path,他也可以帮你做处理,并且返回一个undefined。因此,我们还需要完善这个方法。
function get(data, ...args) { const reg = /\[[0-9]+\]/gi; return args.map((item) => { const paths = item.split('.'); let res = data; paths.map(path => { try { if (reg.test(path)) { const match = path.match(reg)[0]; const cmd = path.replace(match, ''); const arrIndex = match.replace(/[\[\]]/gi, ''); res = res[cmd][arrIndex]; } else { res = res[path]; } } catch (err) { console.error(err); res = undefined; } }); return res; }); }
在这里,我们对每一个path的处理进行了try catch处理。若出错了,则返回undefined。哇,这样看起来就比较稳了。
那么,有没有别的解决方法呢?
群里有一个大佬提出了一种更简单也很取巧的解决方案,就是通过构建一个Function解决这个问题(Function的详细介绍点击这里)。由于代码很简单,我就直接贴出来了:
function get(data, ...args) { const res = JSON.stringify(data); return args.map((item) => (new Function(`try {return ${res}.${item} } catch(e) {}`))()); } const obj = { selector: { to: { toutiao: "FE Coder"} }, target: [1, 2, { name: 'byted'}]}; console.log(get(obj, 'selector.to.toutiao', 'target[0]', 'target[2].name', 'asd'));
看完之后,就两个字,牛逼。
这种方法我承认一开始我确实没想到,确实是很奇技淫巧。不过仔细思考了下,其实很多框架都用到了这个奇技淫巧。比如说vue里,就使用new Function的方式来动态创建函数,解决执行动态生成的代码的问题。
再比如说,Function.prototype.bind方法里(我写了个类似的bind方法:仓库),也使用了Function来解决一些问题(fn.length丢失问题)。说明这个东西还是挺有用的,得学习了解一波,说不定哪天就用到了。
有人提到了那种Function的方式没办法处理以下的处理:
let obj = {time : new Date(), a : "this is a", b : 30};
因为JSON.stringfy后,Date、Function和RegExp类型的变量都会失效。对于这种情况,评论区有个大佬(冯恒智)也提到了一种很好的解决方案:
function get(data, ...args) { return args.map((item) => (new Function('data',`try {return data.${item} } catch(e) {}`))(data)); }
除此之外, 代码宇宙提出了另一种解决方案,就是将"target[0]"分为两个key,也很简单粗暴,就是将在split之前,将字符串里的'['替换为'.',将']'直接去掉。这样就可以将"target[0]"变为"target.0"。具体代码如下:
function get(data, ...args) { return args.map((item) => { let res = data; item .replace(/\[/g, ".") .replace(/\]/g, "") .split('.') .map(path => res = res && res[path]); return res; }) }
而且这两种方式的好处在于,它也可以处理多维数组的情况。
学习完之后,最重要就是要总结,只有总结下来了,知识才是你自己的。那么我来总结下文章想表达的内容:
本文地址在->本人博客地址, 欢迎给个 start 或 follow
The text was updated successfully, but these errors were encountered:
No branches or pull requests
今天在认真干(划)活(水)的时候,看到群里有人发了一道头条的面试题,就顺便看了一下,发现挺有意思的,就决定分享给大家,并且给出我的解决方案和思考过程。
题目如下:
实现一个get函数,使得下面的调用可以输出正确的结果
乍眼一看,这不就是实现一个lodash.get方法吗?看上去好像很简单。所以我就开始写了第一个版本。思想其实很简单,遍历传进来的参数,使用split将每一个参数分隔开,然后遍历取值,最终返回结果。
一运行,果不其然,报错了。
后来仔细看了一下提供测试代码,发现居然有target[0]这种东西。。居然还带了个数组索引。
冷静分析一下,对于后面带了个索引的类型,比如'target[0]',我们肯定是要特殊对待的。所以,我们首先得先识别到这种特殊的类型,然后再对它进行额外处理。
这个时候,很快的就可以想到使用正则表达式来做这个事情。为什么呢?因为像这种带有索引的类型,他们都有一个特色,就是有固定的格式:[num],那么我们只需要能构造出可以匹配这种固定格式的正则,就可以解决这个问题。
对于这种格式,不难想到可以用这个正则表达式来做判断:/\[[0-9]+\]/gi,可是我们还需要将匹配值取出来。这个时候查了下正则表达式的文档(文档点击这里),发现有一个match方法,可以返回匹配成功的结果。那么就让我们来做个测试:
诶,我们现在已经找到了解决这种问题的方法,那让我们赶紧来继续改进下代码。
写完赶紧运行一下,完美,输出了正确的结果了。那么到这里就结束了?
改进
可是总感觉有点不妥,感觉事情没有那么简单。一般来说,面试题除了考验你解决问题的能力之外,可能还考验着你思考问题的全面性、严谨性。像上面那种写法,如果用户传入了一个不存在的path链或者一些其他特殊情况,就可能导致整个程序crash掉。想下lodash.get调用方式,即使你传入了错误的path,他也可以帮你做处理,并且返回一个undefined。因此,我们还需要完善这个方法。
在这里,我们对每一个path的处理进行了try catch处理。若出错了,则返回undefined。哇,这样看起来就比较稳了。
那么,有没有别的解决方法呢?
群里有一个大佬提出了一种更简单也很取巧的解决方案,就是通过构建一个Function解决这个问题(Function的详细介绍点击这里)。由于代码很简单,我就直接贴出来了:
看完之后,就两个字,牛逼。
这种方法我承认一开始我确实没想到,确实是很奇技淫巧。不过仔细思考了下,其实很多框架都用到了这个奇技淫巧。比如说vue里,就使用new Function的方式来动态创建函数,解决执行动态生成的代码的问题。
再比如说,Function.prototype.bind方法里(我写了个类似的bind方法:仓库),也使用了Function来解决一些问题(fn.length丢失问题)。说明这个东西还是挺有用的,得学习了解一波,说不定哪天就用到了。
更新
有人提到了那种Function的方式没办法处理以下的处理:
因为JSON.stringfy后,Date、Function和RegExp类型的变量都会失效。对于这种情况,评论区有个大佬(冯恒智)也提到了一种很好的解决方案:
除此之外, 代码宇宙提出了另一种解决方案,就是将"target[0]"分为两个key,也很简单粗暴,就是将在split之前,将字符串里的'['替换为'.',将']'直接去掉。这样就可以将"target[0]"变为"target.0"。具体代码如下:
而且这两种方式的好处在于,它也可以处理多维数组的情况。
总结
学习完之后,最重要就是要总结,只有总结下来了,知识才是你自己的。那么我来总结下文章想表达的内容:
本文地址在->本人博客地址, 欢迎给个 start 或 follow
The text was updated successfully, but these errors were encountered: