【20230601】数字马力二面面经复盘

4,604 阅读8分钟

今天是六一儿童节,祝各位掘友们六一节快乐🎉🎉🎉

昨天进行了数字马力的二面,问了一些八股,也问了一些值得思考的问题,总体面试体验还可以吧,不过就结果而言应该是挂了。虽然死的很惨,但是复盘工作还是要做好,毕竟人不能在同样的地方跌倒第二次。

有没有写过webpack的自定义loader

答案肯定是没写过,咱搞业务代码的哪知道这个。不过因为面试要造火箭,所以还是得学习下怎么写。

首先我们要明确下loader做了什么事情:由于webpack只认识js,只能对js代码进行处理(当然这样说也不准确,在webpack5中增加了资源模块的特性,能对一些常见的静态资源文件进行处理),所以对于一些特殊的文件如.vue.tstsx.less等就需要用loader来处理。

下面我们来实现一个loader,把传入的文本文件(.txt)中的所有空格替换成-

  • 配置文件中增加loader

    const path = require('path');
    module.exports = {
      // ...
      module: {
        rules: [
          {
            test: /\.txt$/,
            use: [
              {
                loader: path.resolve('loaders/txt-loader.js'),
              },
            ],
          },
        ],
      },
    };
    
  • 编写loader代码:

    module.exports = function(source) {
      return source.replace(/\s/g, '-');
    }
    

从上面的代码我们可以看到,loader的编写其实就是对传入的source进行处理,之后把得到的结果作为返回值,这个返回值可能是最终结果,也可能会被配置文件指定的下一个loader进行处理。

关于loader,还有一个值得我们注意的点。我们在配置loader的时候,总是从后往前配置,即先执行的loader放后面,这是因为在实际(从右到左)执行 loader 之前,会先从左到右调用loader上的pitch 方法。这部分的源码实现可见:loader-runner/LoaderRunner.js at main · webpack/loader-runner · GitHub

为什么使用webpack进行打包,如果不进行打包会怎样

关于这个问题,我仅仅回答了面试官关于webpack的作用,实际上,如果不用打包工具会是怎样的问题,之前还真的没思考过,所以没答上来,也是比较尴尬。

于是我在网上搜索了一番,发现在前端真的需要打包工具吗?-知乎中的回答还是比较全面的。根据这个回答,并结合我自己的理解,有了以下几个分析问题的角度:

  • 模块化:在现代化前端开发中,我们通常会将代码按照功能模块进行拆分,这样可以提高代码的可维护性和可读性。我们比较常用的两种模块化方案是cjsesm,当然cjs在浏览器环境不可用,利用打包工具我们可以进行兼容性处理(如webpack会对cjs引入的代码进行处理,在runtime中实现一套模块化的方案)

  • 代码的转译和产物优化处理:代码转译在现代前端中算是逃不开的话题了,因为有诸如JSX语法、TypeScriptLESS/SASS等特殊语法或预编译语言等。如果不用打包工具,靠人为的去做代码转译的工作是一件很蠢的事情。这时候我们去使用打包工具,以webpack为例,我们只需要配置各种loader对对应的文件进行处理就行了。

  • 产物优化:诸如代码压缩丑化、代码分割、TreeShaking等产物优化的需求,通过打包工具来做是非常方便的

  • devServer:除了打包之外,构建工具还往往提供devServer的功能,允许我们在开发阶段对代码更改进行实时预览。个人认为相比于不用打包工具,使用诸如webpackvite的打包工具在开发效率上的提升是非常明显的。

泛型的作用,对泛型的理解,平时怎么去用

我举了一个Promise的例子,因为在平时开发的时候,我们需要进行网络请求,并对返回的数据进行使用,我们可以定义请求返回内容的typeinterface,并将其注入Promise预留的泛型中,便可以获得对then中返回数据的类型提示能力。当然,因为我对这个东西的认知仅仅是使用层面,说不出啥概念性的东西,面试官并不是很满意。

首先是关于泛型的概念:泛型是一种在定义函数、接口或类时使用类型变量的方式,它可以让我们在不确定具体类型的情况下编写代码,从而提高代码的可复用性和可读性。

使用泛型的好处如下:在不确定传入类型的情况下编写代码,在使用时才传入类型,传入的类型可以是多样的。从这个角度看待的话,泛型可以让我们编写更加通用的代码,从而提高代码的可复用性和可读性。

其实说到平时怎么用,场景就很多了。举一个简单的例子,使用ReactuseState,我们可以在useState预留的泛型接口中传入类型,如:

const [a, setA] = useState<number>(0)

TS中常见的高级类型

这也是我回答的比较差的一个问题,因为之前没怎么复习这块,忘记了。所以这一块的知识也是需要加以重视的地方。

下面是常用到的高级类型以及实现:

  • Record:用于创建索引类型的方法,如:

    type keys = 'name' | 'sex' | 'age'
    
    type people = Record<keys, any> 
    

    源码如下:

    /**
     * Construct a type with a set of properties K of type T
     */
     type Record<K extends keyof any, T> = {
        [P in K]: T;
     };
    
    
  • Exclude:在一个联合类型T中排除联合类型U中的项,源码如下:

    /**
     * Exclude from T those types that are assignable to U
     */
    type Exclude<T, U> = T extends U ? never : T;
    
  • Omit:在一个索引类型T内部排除索引满足K的项:

    /**
     * Construct a type with the properties of T except for those in type K.
     */
    type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
    
  • ReturnType:获取函数类型的返回值,如:

    type request<T> = (options: any) => Promise<T>
    
    type requestReturnType<T> = ReturnType<request<T>>
    

    源码如下:

    /**
     * Obtain the return type of a function type
     */
    type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
    
  • Partical:把索引类型的所有属性变成可选,源码如下:

    /**
    * Make all properties in T optional
    */
    type Partial<T> = {
        [P in keyof T]?: T[P];
    };
    
    

篇幅有限,这里就不再列举了,这里推荐一个项目:type-challenges,可以用来练习类型体操,还有对应配套的vscode插件,可以说是非常方便了。

如何评价后端全用post的现象

这个问题就见仁见智了,我是觉得都可以,毕竟:

OIP-C.jfif

不过我觉得这样回答可能有失专业性,所以顺带提了下Restful API的诸多好处,当然答得也并不是特别准确。下面是chatgpt老师给出的关于Restful API的介绍:

RESTful API是一种基于HTTP协议设计的Web API,它的核心思想是将资源和行为统一抽象为资源和HTTP动词,通过URI来标识资源,通过HTTP动词来定义对资源的操作。

举一个生动的例子,假设我们正在开发一个博客系统,我们可以使用RESTful API来设计博客文章的API。具体来说,我们可以定义以下资源和HTTP动词:

  1. 资源:文章(Article

  2. HTTP动词:GETPOSTPUTDELETE

基于这些资源和HTTP动词,我们可以设计以下RESTful API

  1. 获取文章列表:GET /articles

  2. 获取指定文章:GET /articles/{id}

  3. 创建文章:POST /articles

  4. 更新指定文章:PUT /articles/{id}

  5. 删除指定文章:DELETE /articles/{id}

例如,我们可以使用以下URI来获取指定ID1的文章:GET /articles/1。这个URI中的/articles表示文章资源,/1表示文章的 ID,而GET表示获取文章的操作。通过这种方式,我们可以使用统一的方式来定义和管理博客文章的API,使得API的设计更加清晰和易于理解。

在网络上关于这个问题的争论也是多种多样,这个是我比较认同的回答:公司规定所有接口都用 post 请求,这是为什么? - 程墨Morgan的回答 - 知乎

如何获取一个元素在视口中的定位信息

这个问题属于是知识盲区了,完全不知道怎么回答...

可以使用Element.getBoundingClientRect()来获取元素在视口中的定位信息。该方法的返回值为DOMRect类型的对象,这个对象里面有如下关于定位的信息:

  • 元素相对视口的定位(相对于视口左上角计算):lefttoprightbottomxy

  • 元素本身的宽高:width 和 height

下面是一个简单的例子:

<div id="parent" style="position: relative;">
  <div id="child" style="margin-top: 10px; margin-left: 20px;"></div>
</div>

现在我们想要获取child元素相对于其父元素的定位信息,可以这么做:

const parent = document.querySelector('#parent');
const child = document.querySelector('#child');
const rect = child.getBoundingClientRect();
const parentRect = parent.getBoundingClientRect();
const top = rect.top - parentRect.top; // 10
const left = rect.left - parentRect.left; // 20

关于该API的其他用法可见:Element.getBoundingClientRect() - Web API 接口参考 | MDN