滴滴实习一面
1、对 node 的看法;
node是 JavaScript 的运行时,基于 V8 引擎实现,其架构如下:
2、用过 node 的哪些东西;
常用的node内置模块:path、fs、http、url、zlib、stream、events 等。
回答的时候最好结合业务和应用场景一起回答;
3、let、const、var 的区别
-
var 是ES5的语法,var 声明的变量有如下特性
-
具有变量提升;
-
在手动初始化之前会被系统自动初始化为 undefined 或 “”;(不同系统的实现不同)
-
可以被重复声明而变量的值不会丢失;
-
在最外层声明或不声明的变量会被挂载到全局对象(window、global、self)上;
-
声明变量在所在上下文环境中不可配置,非声明变量是可配置的;
-
对应的作用域分为全局作用域和函数内作用域;
-
-
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):
注:有一个属性 new.target 可以在函数内部用来判断这个函数是否是被 new 调用的;
5、Promise 的用法,方法
-
Promise 对象 有三个状态:pending、fulfilled、rejected
-
构造函数 Promise 的用法
let p = new Promise((resolve,reject) => { // 执行 resolve(),将 Promise 实例对象的状态变为 fulfilled // 或则 // 执行 reject(),将 Promise 实例对象的状态变为 rejected })
-
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* 等消费,而普通对象不是可迭代对象;
-
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 中的键有其他类型时,要分情况考虑,如果用上面的方法键会被转换为字符串;
-
普通对象 转 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、洋葱模式如下经典图:
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(); }) ) }) ) }) ); };