滴滴系统平台实习一面的复盘总结

396 阅读6分钟

部门是系统平台,主要使用 node; 第一次面试,不禁感慨面试经验还是非常重要的,很多前端的东西都没问,问了的东西又没答好,下次加油~~

1、对 node 的看法;

node是 JavaScript 的运行时,基于 V8 引擎实现,其架构如下:

image-20211205091034080

2、用过 node 的哪些东西;

常用的node内置模块:path、fs、http、url、zlib、stream、events 等。

回答的时候最好结合业务和应用场景一起回答;

3、let、const、var 的区别

  1. var 是ES5的语法,var 声明的变量有如下特性

    • 具有变量提升;
    • 在手动初始化之前会被系统自动初始化为 undefined 或 “”;(不同系统的实现不同)
    • 可以被重复声明而变量的值不会丢失;
    • 在最外层声明或不声明的变量会被挂载到全局对象(window、global、self)上;
    • 声明变量在所在上下文环境中不可配置,非声明变量是可配置的;
    • 对应的作用域分为全局作用域和函数内作用域;
  2. let 和 const 是 ES6 的语法

    let:

    • 没有变量提升,要先声明再使用;
    • 在块级作用域内有效;
    • 在最外层声明时不会成为全局对象的属性;
    • 在同一作用域中使用let重复声明已经存在的变量(被其它任何关键字声明的变量)会报错;

    const:

    • 声明时要赋初值;
    • 使用const声明的标识符的值无法改变(是一个常量);
    • 没有变量提升,要先声明再使用;
    • 在块级作用域内有效;
    • 在最外层声明时不会成为全局对象的属性;
    • 在同一作用域中使用const重复声明已经存在的标识符(被其它任何关键字声明的标识符)会报错;

根据这个问题,我们总结一下 JS 中声明自定义标识符的方式:var、function、let、const、class、import;总的来说,ES6 的语法趋于让 JS 变成严格模式下的 JS;

4、手写一个 new;

// 手写 new
let myNew = function(constructor,...props) {
​
    let context = Object.create(constructor.prototype);
​
    let result = constructor.apply(context,props);
​
    return (typeof result === 'object' && result !== null) ? result : context;
    
}
​
// 自定义构造函数
function Person(name) {
    this.name = name;
}
​
// test1:使用 myNew 和自定义构造函数
let obj = myNew(Person,'daxia');
console.log(obj); // {name:'daxia}// test2:使用 myNew 和 JS 内置构造函数
console.log(myNew(Set,[1,2,2,3])); 
// 会有如下报错:
// let result = constrouct.apply(context,props);
// ^
// TypeError: Constructor Set requires 'new'

当通过我的自定义 myNew 使用 JS 内置的 Set 构造函数时会报错,报错的的原因是 ES6 规定了只能通过 new 来使用 Set ,如下(截图出处:ES6):

image-20211127094825222

注:有一个属性 new.target 可以在函数内部用来判断这个函数是否是被 new 调用的;

5、Promise 的用法,方法

  1. Promise 对象 有三个状态:pending、fulfilled、rejected

  2. 构造函数 Promise 的用法

    let p = new Promise((resolve,reject) => {
        // 执行 resolve(),将 Promise 实例对象的状态变为 fulfilled
        // 或则
        // 执行 reject(),将 Promise 实例对象的状态变为 rejected
    })
    
  3. Promise 对象具有的方法

    • Promise.prototype.then()

      处理状态变为 fulfilled 的 Promise 对象的放回值;

    • Promise.prototype.catch()

      处理状态变为 rejected 的 Promise 对象的放回值;

    • Promise.prototype.finally()

      用于指定不管 Promise 对象最后状态如何,都会执行的操作。

    • Promise.prototype.all()

      传入一个由 Promise 对象组成的可迭代对象,所有 Promise 对象并发执行;

      所有 Promise 执行完后返回结果,只要有其中一个返回错误,Promise.prototype.all() 就返回错误;

    • Promise.prototype.race()

      传入一个由 Promise 对象组成的可迭代对象,所有 Promise 对象并发执行;

      返回先完成的Promise,后面的Promise不再返回;速度快的Promise对象出错时,会返回错误,但依旧会返回后面的其它Promise对象,成功返回后不再返回;

    • Promise.prototype.allSettled()

      接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束;

    • Promise.prototype.any()

      接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。

      只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。

    • Promise.prototype.resolve()

      返回 fulfilled 状态的 Promise 实例;

    • Promise.prototype.reject()

      返回 rejected 状态的 Promise 实例;

    • Promise.prototype.try()

      包装任务,使同步任务和异步任务都可以用Promise来处理,使之有共同的错误处理机制;

6、Map 和 普通对象怎么转换

Map 和 普通对象 都是存储键值对的数据结构,不同的是普通对象的键只能是 String 类型 和 Symbol 类型,而 Map 的键可以是任何其它数据类型;

并且 Map 是可迭代对象,可以被 for ... of、yield* 等消费,而普通对象不是可迭代对象;

  1. Map 转 普通对象

    当 Map 的键只有 String 类型和 Symbol 类型时,转换方法可以是:

    function mapToObject(map) {
        let obj = {};
        for(let [key,value] of map) {
            obj[key] = value;
        }
        return obj;
    }
    let map = new Map([['name','daxia'],['age',18]]);
    let obj1 = mapToObject(map);
    console.log(obj1);
    ​
    // 或者
    ​
    let obj2 = Object.fromEntries(map);
    console.log(obj2);
    

    当 map 中的键有其他类型时,要分情况考虑,如果用上面的方法键会被转换为字符串;

  2. 普通对象 转 Map

    let obj = {
        name:'daxia',
        age:18
    }
    let map = new Map(Object.entries(obj));
    ​
    console.log(map);
    

7、数组去重

1、先排序,再使用双指针一次遍历去重;
function delArrayReapet(arr) {
    // 使用 sort 排序
    arr.sort((a,b) => {
​
        return a-b;
    })
    // 使用 双指针 去重
    let i = 0,
        j = 0;
    let len = arr.length;
    while(j < len) {
​
        if(arr[i] === arr[j]) {
            j++;
        }else {
            arr[++i] = arr[j] 
        }
    }
​
    // 改变数组长度,将重复元素都删除
    arr.length = i + 1;
​
    return arr;
}
2、使用 indexOf 方法来判断某个元素是否有重复,并使用 splice 方法删除重复元素;
function delArrayReapet(arr) { // 在原数组的基础上修改
    
    for(let i = 0;i < arr.length;) { // 注意 arr.length 是变化的;
        let index = arr.indexOf(arr[i],i+1); // 得到数组中当前元素的后面的元素中相同的元素的索引
        if(index !== -1) {
            arr.splice(index,1); // 删除对应索引的元素
        }else {
            i++;
        }
        
    }
​
    return arr;
}

同样的,我们也可以使用 lastIndexOf 来得到重复元素的索引,然后使用 splice 方法删除重复元素,这里就不再写出相应代码;

3、利用 Set 不能有重复元素的特性
function delArrayReapet(arr) {
    
    let newArr; = [...new Set(arr)];
    // h
    // let newArr = Array.from(new Set(arr));
    
    return newArr;
​
}

更加详细的数组去重方法的介绍请看这里:

8、koa 的洋葱模型

koa的中间件(一个函数)的组织方式是以洋葱模型进行的,这类似于函数递归调用,逐层深入,然后逐层返回;

1、洋葱模式如下经典图:

2892151181-5ab48de7b5013_fix732.png

2、koa中间件的使用:
const Koa = require('koa');
​
const app = new Koa();
​
// 自定义两个中间件
async function middleware1(ctx,next) {
    console.log('1-1');
    await next(); // 执行下一个中间件
    console.log('1-2');
}
​
async function middleware2(ctx,next) {
    console.log('2-1');
    await next(); // 执行下一个中间件
    console.log('2-');
}
​
app.use(middleware1);
app.use(middleware2);
​
app.listen(3000);
3、实现洋葱模型简要代码

Koa 洋葱模型的主要代码实现在 koa-compose 这个模块中,简化代码如下,其依靠 函数的嵌套调用 和 Promise.resolve() 来实现;

const [fn1, fn2, fn3] = stack;
const fnMiddleware = function(context){
    return Promise.resolve(
      fn1(context, function next(){
        return Promise.resolve(
          fn2(context, function next(){
              return Promise.resolve(
                  fn3(context, function next(){
                    return Promise.resolve();
                  })
              )
          })
        )
    })
  );
};
​