前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >一道面试题引起的思考

一道面试题引起的思考

作者头像
嘿嘿嘿
发布于 2018-12-17 02:53:25
发布于 2018-12-17 02:53:25
36800
代码可运行
举报
文章被收录于专栏:陈纪庚陈纪庚
运行总次数:0
代码可运行

今天在认真干(划)活(水)的时候,看到群里有人发了一道头条的面试题,就顺便看了一下,发现挺有意思的,就决定分享给大家,并且给出我的解决方案和思考过程。

题目如下:

实现一个get函数,使得下面的调用可以输出正确的结果

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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将每一个参数分隔开,然后遍历取值,最终返回结果。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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方法,可以返回匹配成功的结果。那么就让我们来做个测试:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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]"]

诶,我们现在已经找到了解决这种问题的方法,那让我们赶紧来继续改进下代码。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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];
                    // 将target[0]里的target储存到cmd里
                    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。因此,我们还需要完善这个方法。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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的详细介绍点击这里)。由于代码很简单,我就直接贴出来了:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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的方式没办法处理以下的处理:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let obj = {time : new Date(), a : "this is a", b : 30};

因为JSON.stringfy后,Date、Function和RegExp类型的变量都会失效。对于这种情况,评论区有个大佬(冯恒智)也提到了一种很好的解决方案:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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"。具体代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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;
    })
}

而且这两种方式的好处在于,它也可以处理多维数组的情况。

总结

学习完之后,最重要就是要总结,只有总结下来了,知识才是你自己的。那么我来总结下文章想表达的内容:

  1. 对于具有固定格式的字符串,可以考虑使用正则表达式来识别和匹配。
  2. 实现一个功能的时候,不要只考虑正常情况,要多考虑一些非正常情况,比如输入格式不对、用户不按套路来或者因为一些奇奇怪怪的事情报错。并且能对可预见的非正常情况做一个容错处理。
  3. 有时候还是可以多学习了解一下一些黑科技(比如Function),说不定哪天就可以用它来解决问题。
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018-11-23 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
js引擎v8源码分析之HeapObject(基于v8 0.1.5)
HeapObject是Object的子类。是所有基于堆分配的对象的基类。 class HeapObject: public Object { public: // 每个堆对象都有一个map对象,记录对象的类型,大小等信息 inline Map* map(); inline void set_map(Map* value); // 对象的地址+对象标记 static inline HeapObject* FromAddress(Address address); // 对象的真正
theanarkh
2020/02/25
9840
js引擎v8源码分析之HeapObject(基于v8 0.1.5)
js引擎v8源码解析之对象第一篇(基于v8 0.1.5)
我们看到类中有一个静态属性kSize,这个属性是标记该类的对象,属性需要占据的内存字节大小。下面我们看第一个继承于Object的类Smi。Smi是表示小整形。我们看他的定义。
theanarkh
2019/11/23
9460
js引擎v8源码解析之map对象上篇(基于v8 0.1.5)
从上面的代码中我们知道,只是对某些属性或标记进行读写。根据对象的内存布局对号入座就行,至于每个属性和标记的意义,后续再慢慢探讨。map还有很多函数,但是会涉及很多其他的类,等后面分析完了,再继续分析。
theanarkh
2019/11/23
1.6K0
v8源码解析之数组系列1(v8 0.1.5)
v8中很多数据结构都具备数组的特性,今天我们先介绍Array和FixedArray。他们是V8中很多数据结构的基类。
theanarkh
2020/11/02
9040
v8源码解析之数组系列1(v8 0.1.5)
v8源码解析之ByteArray(v8 0.1.5)
ByteArray是字节数组的实现,顾名思义,该数组的元素大小的一个字节,不过类似js的Uint16Array,Uint32Array数组一样,我们可以把多个元素看做一个,把多个字节合并成一个元素看待。下面我们看一下实现。
theanarkh
2020/11/02
7660
v8源码解析之ByteArray(v8 0.1.5)
js引擎v8源码解析之对象第四篇(基于v8 0.1.5)
SemiSpace他自己不申请内存。他是负责管理某块内存的,内存申请在其他地方处理。
theanarkh
2019/11/24
5800
js引擎v8源码分析之Object(基于v8 0.1.5)
1 c++对象的类型 1 v8的对象是4字节对齐的,用地址的低两位出来标记对象的类型。 2 堆对象(HeapObject)是Object的子类。Object里面的很多方法都是用于堆对象。堆对象有自己的一套对象类型判断方式。每个堆对象有一个map属性,他记录了堆对象的类型type,大小size。 1 type主要是用于记录c++对象的类型。 2 有一些单例的map对象,也是用于判断c++对象的类型的。 3 map的type属性的低八位是用来区分是不是字符串类型的。高位8位是1说明不是字符串,是0说明是字符串类型。低7位记录字符串的子类型。 下面是定义。
theanarkh
2020/02/25
1.5K0
js引擎v8源码分析之Object(基于v8 0.1.5)
js引擎v8源码解析之对象第三篇(基于v8 0.1.5)
我们从代码可以知道,这几个类没有太多的逻辑,都有一系列的属性和对应的读写函数。对号入座就行。
theanarkh
2019/11/24
6840
js引擎v8源码解析之allocation(基于0.1.5)
#ifndef V8_ALLOCATION_H_ #define V8_ALLOCATION_H_ namespace v8 { namespace internal { // A class that controls whether allocation is allowed. This is for // the C++ heap only! class NativeAllocationChecker { public: typedef enum { ALLOW, DISALLOW }
theanarkh
2019/07/30
8220
js引擎v8源码解析之allocation(基于0.1.5)
js引擎v8源码分析之HeapNumber(基于v8 0.1.5)
HeapNumber是保存大整形的对象。v8里有smi保存整形,但是他只有31位,超过31位的就需要用HeapNumber。
theanarkh
2020/02/25
8500
js引擎v8源码分析之NewSpace(基于v8 0.1.5)
NewSpace是v8内存管理中,负责管理新生代区的类。分为from和to两个区,每个区由SemiSpace对象管理。和SemiSpace一样,NewSpace也不负责内存的分配和释放,他只负责内存的使用和管理。下面是类的定义。
theanarkh
2020/02/17
7850
CVE-2018-17463详细分析及复现
(https://bbs.pediy.com/thread-274865.htm)
h1J4cker
2022/12/01
5050
js引擎v8源码分析之SemiSpace(基于v8 0.1.5)
SemiSpace的管理新生代内存的类,即我们常听到的from区和to区。from区和to区都由一个SemiSpace对象管理。SemiSpace只管理地址,不负责分配和释放管理的内存。下面是Semispace类的定义。
theanarkh
2020/02/17
1K0
js引擎v8源码解析之zone(基于0.1.5)
zone也是用于内存管理的,不过他是增量分配,一次销毁的。下面是结构图。 zone.h #ifndef V8_ZONE_H_ #define V8_ZONE_H_ namespace v8 { namespace internal { // The Zone supports very fast allocation of small chunks of // memory. The chunks cannot be deallocated individually, but instead // t
theanarkh
2019/07/30
1.1K0
js引擎v8源码解析之zone(基于0.1.5)
js引擎v8源码解析之平台相关(上篇)(基于v8 0.1.5)
PlatformData 是管理线程中,不同系统中的数据。这里只看linux系统。只保存了线程id。
theanarkh
2019/11/23
6750
V8 新生代垃圾回收的实现
前言:因为最近在做一些 gc track 的事情,所以打算了解一下 V8 GC 的实现。介绍 V8 GC 的文章网上已经有很多,就不打算再重复介绍。本文主要介绍一下新生代 GC 的实现,代码参考 V8 10.2,因为 GC 的实现非常复杂,只能介绍一些大致的实现,读者需要对 V8 GC 有一定的了解,比如新生代是分为 from 和 to 两个 space,然后在 GC 时是如何处理的。
theanarkh
2022/05/16
7500
V8 GC 的实现
前言:GC 是一个古老、复杂并且很 Cool 的技术,本文大概介绍一下早期 V8 中关于 GC 实现的部分,代码版本 0.1.5,早期版本利于快速理解整体的逻辑,因为现代版本已经非常复杂。
theanarkh
2022/12/06
3450
V8 GC 的实现
V8 CPU Profiler 的实现
前言:CPU Profiler 是应用性能诊断和优化的利器,本文介绍 V8 中关于这部分的实现,细节比较多也比较复杂,大致分析一下原理,代码来自 V8 10.2。
theanarkh
2022/05/16
8490
V8 CPU Profiler 的实现
js引擎v8源码解析之token(基于0.1.5)
#ifndef V8_TOKEN_H_ #define V8_TOKEN_H_ namespace v8 { namespace internal { // TOKEN_LIST takes a list of 3 macros M, all of which satisfy the // same signature M(name, string, precedence), where name is the // symbolic token name, string is the corres
theanarkh
2019/07/30
3.2K0
js引擎v8源码解析之list(基于0.1.5)
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef V8_LIST_H_ #define V8_LIST_H_ namespace v8 { namespace internal { // ---------------------------------------------------------------------------- // The list is a template for v
theanarkh
2019/07/30
6980
推荐阅读
相关推荐
js引擎v8源码分析之HeapObject(基于v8 0.1.5)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档