前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >见识过的坑

见识过的坑

作者头像
李才哥
发布2019-07-10 16:12:07
6840
发布2019-07-10 16:12:07
举报
文章被收录于专栏:李才哥李才哥
人难免天生有自怜的情绪,唯有时刻保持清醒,才能看清真正的价值在哪里。

3月5日,从中山去往广州,一大早7点多就做好准备了,在高铁站了30分钟,转广州地铁又站了90分钟,去到地铁口,就有一辆cvte的大巴车过来接送,我选择的面试时间是11:00-12:00,但前面的人还没面试完而且12:00的时候又去吃了饭,所以面试的开始时间是下午1点,直到下午3点才面完。

我面试的岗位是前端开发,一面问的挺基础的,那就过了,二面感觉大多数是业务的,由于我后台学的是php,面试官喜欢考node的知识,估计这也是我凉的最大原因吧。作为一名普通二本非科班的我,能够闯进二面觉得是非常幸运的了,继续加油!

线上笔试:

线上笔试我是2月21日做的,其实做完之后自我感觉很一般,没想到能够进入面试的。题型分为选择题和两道编程题,其实那时我应该利用python后台截屏的,这样就能够把所有的题目截下来。 选择题涉及的知识面涉及的挺广的,让我回想一下,有:

①、EventLoop机制及微任务 ②、阻止相同事件的其他侦听器被调用(stopImmediatePropagation) ③、css中margin的%是以父元素的宽度作为基准(这个真不知道呀) 大概记得这么多。。。

编程题可以参考我这篇文章:https://segmentfault.com/a/1190000018246337

一面:

面试官人比较随和,所以我不怎么紧张,一面问的是基础,大部分我觉得都ok,面试是一对一的,首先自我介绍,我就说我是非科班的,前端的知识都是自学的,然后就说了各种各样的自学方法。接下来看看问的都是什么知识

①、css盒子模型: 有两种, IE 怪异盒子模型(border-box) 和 W3C标准盒子模型(content-box)

代码语言:javascript
复制
怪异:width = content + border + padding
标准:width = content

可以通过css的box-sizing属性来切换这两种盒子

代码语言:javascript
复制
box-sizing: border-box    怪异盒子模型
box-sizing: content-box   标准盒子模型

②、http状态码:

代码语言:javascript
复制
1开头:(被接受,需要继续处理。)

100:客户端继续请求 、101:客户端切换协议

代码语言:javascript
复制
2开头:(请求成功)

200:请求成功 202:服务器已接受请求,但尚未处理 204:服务器成功处理了请求,但未返回内容

代码语言:javascript
复制
3开头:(请求被重定向)

301:(永久重定向)、 302: (临时重定向) 、 303:http1.1协议,禁止被缓存 304:(协商缓存成功(资源未修改)的返回值)

代码语言:javascript
复制
4开头:(客户端请求错误)

400:客户端请求的语法错误,服务器无法理解 403:服务器理解请求客户端的请求,但是拒绝执行此请求 404:服务器无法根据客户端的请求找到资源(网页)

代码语言:javascript
复制
5开头:(服务器错误)

③、强缓存和协商缓存: 当说到304状态码的时候,面试问我控制协商缓存的字段有哪些: 控制协商缓存的字段分别有:

代码语言:javascript
复制
Last-Modified / If-Modified-Since 和 Etag / If-None-Match

*其中Etag / If-None-Match的优先级比Last-Modified / If-Modified-Since高 然后又问了我知道Etag是通过什么生成的,这个我还真没了解,只是知道一个标识符而已,面试官就说了是通过时间值生成的

接着又问了强缓存的状态码 我说强缓存成功的状态是200,在读取缓存缓存的时候,分为两种情况,在chrome浏览器的Network下的Size可以看到两种字段

代码语言:javascript
复制
from memory cache 和 from disk cache

④、闭包的概念以及内存泄漏:

代码语言:javascript
复制
1、概念:有权访问另一个函数作用域和变量的函数,创建闭包最简单的方式就是在一个函数内部创建另一个函数。
2、好处:由于可以读取函数内部的变量,如果希望一个变量常驻于内存中又可全局访问,同时又想避免全局变量的污染,此时使用闭包就是一种恰当的方式
3、缺点:但正是因为函数内部变量被外部所引用,不会被垃圾回收,就会造成常驻内存,使用过多容易造成内存泄漏

有些时候真是给自己挖坑,哈哈哈,我说闭包使用过多会造成内存泄漏,紧接着他就问我怎么查看内存泄漏,我说chrome浏览器有个面板是专门用来查看内存泄漏的,但是平时不常用,就没怎么留意,接下来他就问我常见的内存泄漏方式

1.意外的全局变量

代码语言:javascript
复制
a、在一个函数你忘记用变量声明符(var或let)来声明的变量,一个意外的全局变量就被创建了。
b、在函数中通过this赋予变量,在函数中,this指向window

2.定时器setTimeout setInterval以及回调函数

代码语言:javascript
复制
当不需要setInterval或者setTimeout时,定时器没有被clear,定时器的回调函数以及内部依赖的变量都不能被回收,造成内存泄漏。
比如:vue使用了定时器,需要在beforeDestroy 中做对应销毁处理。js也是一样的。

3.闭包(在全局作用域上保留着闭包局部变量的引用)

4.循环引用的变量或者对象

⑤、防抖(debounce):手撕代码 面试官把他的电脑转向我,我看到lodash,之前我只是知道这个玩意可以用来克服JSON深拷贝的缺陷,他叫我实现一个debounce的加强版(随时点击次数增加,延迟也增加)。一开始,我没有好的思路,他就叫我先实现一个普通的debounce,代码大致如下:

代码语言:javascript
复制
function debounce(fn, wait=1000) {    
    let timeout = 0;    
    return function(...args) {     
        if(timeout){
            clearTimeout(timeout);   
        }        
        timeout = setTimeout(() => { 
            fn.apply(this, args)
        }, wait);
    }
}

写出来后,要求写个加强版的,可能我想太多了吧。。。当时没写出来,其实只要加一条语句即可

代码语言:javascript
复制
// 上面代码省略
 timeout = setTimeout(() => { 
    wait = wait*1.5;        // 主要增加这条语句
    fn.apply(this, args)
}, wait);

⑥、css三角形:手撕代码 一开始我以为是三角箭头,挺兴奋的,觉得很简单,就说了使用两边的border然后在rotate即可,后来才发现是三角形,一时想不出来,他问我之前有没有实现过,我说没有,他就说如果之前没有实现的话,一时半会也是想不出来的

这里我百度的答案:

代码语言:javascript
复制
div{
    width:0;
    height:0;
    border-right:40px solid transparent;
    border-left: 40px solid transparent;
    border-bottom:40px solid red;
}

对于css方面,代码的实现并不重要,面试官更注重思路

当他和我说了思路后,又叫我实现一个等边三角形.... 这个我就说了等边三角形每个角是60度,哈哈,具体不知道怎么实现

⑦、原生js读取cookie 一般读写cookie的时候我都是用 js-cookie 这个库的,所以对于原生忘得七七八八了 因为原生js获取cookie只能通过

代码语言:javascript
复制
document.cookie

然后获得的是所有cookie集合在一起的字符串,需要使用正则什么的对此解析

二面:

从一面完到二面起码等了半个小时以上吧,面试我的又是另外一个面试官,这次面试的内容大多涉及到业务层次的,一上来就是问你使用过哪些库和框架,最后还是败在了二面

①、实现一个斐波那契数列 手撕代码 斐波那契数列就是

代码语言:javascript
复制
1 1 2 3 5 8 13 21 34 55...

这里我采用的递归的思路,因为我是非科班的,数据结构和算法没怎么学,厉害点的同学这道题就会用动态规划的方案

代码语言:javascript
复制
function recurFib(num){
    if(num < 3){
        return 1;
    }else{
        return recurFib(num-1) +  recurFib(num-2)
    }
}

②、vue和react的差异 React 和 Vue 有许多相似之处,它们都有:

代码语言:javascript
复制
使用 Virtual DOM
提供了响应式 (Reactive) 和组件化 (Composable) 的视图组件。
将注意力集中保持在核心库,而将其他功能如路由和全局状态管理交给相关的库。

然后又涉及到虚拟dom:

代码语言:javascript
复制
Vitual DOM是一种虚拟dom技术,本质上是基于javascript实现的,相对于dom对象,javascript对象更简单,处理速度更快,dom树的结构,属性信息都可以很容易的用javascript对象来表示
原生JS或JQ操作DOM时,浏览器会从构建DOM树开始从头到尾执行一遍流程。
操作DOM的代价仍旧是昂贵的,频繁操作还是会出现页面卡顿,影响用户体验。
创建虚拟DOM并将其映射成真实DOM,这样所有的更新都可以先反应到虚拟DOM上,需要用到Diff算法。

③、上下固定,中间滚动布局 这种布局一看就是移动端的,主要之前没有去了解移动端的布局,可能说的太不好,自己回来用代码实现了一下: 功能:头部和底部自适应高度;中间占满剩余部分,超出自动滚动 思路:让容器占满整个页面的高度,整体采用flex布局,中间滚动部分用 overflow: auto

代码语言:javascript
复制
<div class="cotainer">
    <div class="header">
        header<br/><br/><br/><br/>header
    </div>
    <div class="middle">
        middle<br/><br/><br/><br/><br/><br/><br/><br/>
        middle<br/><br/><br/><br/><br/><br/><br/><br/>
        middle<br/><br/><br/><br/><br/><br/><br/><br/>
        middle<br/><br/><br/><br/><br/><br/><br/><br/>
    </div>
    <div class="footer">
        footer<br/><br/><br/><br/>footer
    </div>
</div>
代码语言:javascript
复制
html, body{
    margin: 0; 
    padding: 0;
    width: 100%; 
    height: 100%;
}
.cotainer{
    display: flex;
    flex-direction: column;
    text-align: center;
    height: 100%;
}
.middle{
    background-color: aquamarine;
    flex-grow: 1;
    overflow: auto;
}
.header, .footer{
    background-color: chartreuse;
}
/* 隐藏PC浏览器的滚动条,移动端无需考虑 */
.middle::-webkit-scrollbar {
    display: none;  
}

④、事件执行机制 javascript是一门单线程语言 JS 在执行的过程中会产生执行环境,这些执行环境会被按照顺序的加入到执行栈中。 同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的代码,会被挂起并加入到 Task(有多种 task) 队列中

除了广义的同步任务和异步任务,还包括有更加精确的微任务和宏任务 微任务包括 process.nextTick ,promise ,Object.observe ,MutationObserver 宏任务包括 script , setTimeout ,setInterval ,setImmediate ,I/O ,UI rendering

所以正确的一次 Event loop 顺序是这样的

代码语言:javascript
复制
1.执行同步代码,这属于宏任务
2.执行栈为空,查询是否有微任务需要执行
3.执行所有微任务
4.必要的话渲染 UI
5.然后开始下一轮 Event loop,执行宏任务中的异步代码

⑤、跨域 我说一般都是用CORS比较多 CORS原理:使用自定义的HTTP头部让浏览器和服务器沟通 如添加一个额外的Origin头部,包含请求页面的的地址信息(协议、域名、端口号) 在后台设置 Access-Control-Allow-Origin即可

然后进一步问了CORS的预检请求,问了OPTIONS的作用 接着又问了在满足什么条件下不会触发CORS的预检请求, 这个一时想不起来,MDN上面总结得比较齐全 满足简单请求(不会触发 CORS 预检请求)的条件:

代码语言:javascript
复制
1、请求为GET、HEAD、POST其一
2、请求字段满足CORS安全集合的字段
3、Content-Type 有限制

⑥、node.js的知识 对node不太了解,涉及到websocket...看来要好好加油了

⑦、微信公众号的知识 面试问了我(前端开发必备)微信公众号:(前端大学)的知识....我只是做过微信小程序,并没有涉及到公众号。

总结:

面试整体难度适中,其实对于这次面试我自己本身就是抱着一种尝试的心态,在面试的时候,我们应该要以一种学习者的心态,不会就去问面试官和面试官讨论,不断强化自己的实力,路还漫长,今天也要加油鸭!

任何时代,

教育说起来都是一件高大上的事,

但却没有什么真正有价值的东西是教得会的,

没有任何一种文化模因

可以说清楚一个个体的全部问题。

在任何时代,

想要抓住人性的弱点来赚钱都非常容易,

没有一点高级。

相反,想要建设一种文化,

耐心地拆除信息壁垒,

并且能够坚持下来,

那真不是一般的不易。

在任何时代,

在一秒钟内看到本质的人,

和花半辈子看不清的人,

自然是不一样的命运。

现在扫描下方二位码 登陆我的微信

近日,GitHub 上一位名为木易杨(yygmind)的开发者,在 GitHub 中建了一个名为 Advanced-Frontend/Daily-Interview-Question 的项目,该项目每天会更新一道大厂前端面试题,并邀请开发者在 issue 区中作答,以下是我们从该项目中挑选的 9 道题和答案,如有问题,欢迎大家在评论区或 GitHub issue 上提出建议。

GitHub 链接:

https://github.com/Advanced-Frontend/Daily-Interview-Question

1. 写 React/Vue 项目时为什么要在组件中写 key,其作用是什么?

key 的作用是为了在 diff 算法执行时更快的找到对应的节点,提高 diff 速度。

vue 和 react 都是采用 diff 算法来对比新旧虚拟节点,从而更新节点。在 vue 的 diff 函数中。可以先了解一下 diff 算法。

在交叉对比的时候,当新节点跟旧节点头尾交叉对比没有结果的时候,会根据新节点的 key 去对比旧节点数组中的 key,从而找到相应旧节点(这里对应的是一个 key => index 的 map 映射)。如果没找到就认为是一个新增节点。而如果没有 key,那么就会采用一种遍历查找的方式去找到对应的旧节点。一种一个 map 映射,另一种是遍历查找。相比而言。map 映射的速度更快。

vue 部分源码如下:

代码语言:javascript
复制
// vue 项目  src/core/vdom/patch.js  -488 行
// oldCh 是一个旧虚拟节点数组, 
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
       idxInOld = isDef(newStartVnode.key)
         ? oldKeyToIdx[newStartVnode.key]
         : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)

创建 map 函数:

代码语言:javascript
复制
function createKeyToOldIdx (children, beginIdx, endIdx) {
 let i, key
 const map = {}
 for (i = beginIdx; i <= endIdx; ++i) {
   key = children[i].key
   if (isDef(key)) map[key] = i
 }
 return map
}

遍历寻找:

代码语言:javascript
复制
// sameVnode 是对比新旧节点是否相同的函数
function findIdxInOld (node, oldCh, start, end) {
   for (let i = start; i < end; i++) {
     const c = oldCh[i]

     if (isDef(c) && sameVnode(node, c)) return i
   }
 }

本题链接:

https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/1

2. 解析 ['1', '2', '3'].map(parseInt)

第一眼看到这个题目的时候,脑海跳出的答案是 [1, 2, 3],但是 真正的答案是 [1, NaN, NaN]

  • 首先让我们回顾一下,map 函数的第一个参数 callback:
代码语言:javascript
复制
var new_array = arr.map(function callback(currentValue[, index[, array]]) { // Return element for new_array }[, thisArg])

这个 callback 一共可以接收三个参数,其中第一个参数代表当前被处理的元素,而第二个参数代表该元素的索引。

  • 而 parseInt 则是用来解析字符串的,使字符串成为指定基数的整数。

parseInt(string, radix)接收两个参数,第一个表示被处理的值(字符串),第二个表示为解析时的基数。

  • 了解这两个函数后,我们可以模拟一下运行情况;
  • parseInt('1', 0) //radix 为 0 时,且 string 参数不以“0x”和“0”开头时,按照 10 为基数处理。这个时候返回 1;
  • parseInt('2', 1) // 基数为 1(1 进制)表示的数中,最大值小于 2,所以无法解析,返回 NaN;
  • parseInt('3', 2) // 基数为 2(2 进制)表示的数中,最大值小于 3,所以无法解析,返回 NaN。
  • map 函数返回的是一个数组,所以最后结果为 [1, NaN, NaN]。
  • 最后附上 MDN 上对于这两个函数的链接,具体参数大家可以到里面看:

1. https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/parseInt

2. https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/map

本题链接:

https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/4

3. 什么是防抖和节流?有什么区别?如何实现?

  1. 防抖

触发高频事件后 n 秒内函数只会执行一次,如果 n 秒内高频事件再次被触发,则重新计算时间;

  • 思路:

每次触发事件时都取消之前的延时调用方法:

代码语言:javascript
复制
function debounce(fn) {
     let timeout = null; // 创建一个标记用来存放定时器的返回值
     return function () {
       clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉
       timeout = setTimeout(() => { // 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话,就不会执行 fn 函数
         fn.apply(this, arguments);
       }, 500);
     };
   }
   function sayHi() {
     console.log('防抖成功');
   }

   var inp = document.getElementById('inp');
   inp.addEventListener('input', debounce(sayHi)); // 防抖
  1. 节流

高频事件触发,但在 n 秒内只会执行一次,所以节流会稀释函数的执行频率。

  • 思路:

每次触发事件时都判断当前是否有等待执行的延时函数。

代码语言:javascript
复制
function throttle(fn) {
     let canRun = true; // 通过闭包保存一个标记
     return function () {
       if (!canRun) return; // 在函数开头判断标记是否为 true,不为 true 则 return
       canRun = false; // 立即设置为 false
       setTimeout(() => { // 将外部传入的函数的执行放在 setTimeout 中
         fn.apply(this, arguments);
         // 最后在 setTimeout 执行完毕后再把标记设置为 true(关键) 表示可以执行下一次循环了。当定时器没有执行的时候标记永远是 false,在开头被 return 掉
         canRun = true;
       }, 500);
     };
   }
   function sayHi(e) {
     console.log(e.target.innerWidth, e.target.innerHeight);
   }
   window.addEventListener('resize', throttle(sayHi));

本题链接:

https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/5

4. 介绍下 Set、Map、WeakSet 和 WeakMap 的区别?

Set

  • 成员唯一、无序且不重复;
  • [value, value],键值与键名是一致的(或者说只有键值,没有键名);
  • 可以遍历,方法有:add、delete、has。

WeakSet

  • 成员都是对象;
  • 成员都是弱引用,可以被垃圾回收机制回收,可以用来保存 DOM 节点,不容易造成内存泄漏;
  • 不能遍历,方法有 add、delete、has。

Map

  • 本质上是键值对的集合,类似集合;
  • 可以遍历,方法很多,可以跟各种数据格式转换。

WeakMap

  • 只接受对象最为键名(null 除外),不接受其他类型的值作为键名;
  • 键名是弱引用,键值可以是任意的,键名所指向的对象可以被垃圾回收,此时键名是无效的;
  • 不能遍历,方法有 get、set、has、delete。

本题链接:

https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/6

5. 介绍下深度优先遍历和广度优先遍历,如何实现?

深度优先遍历(DFS)

深度优先遍历(Depth-First-Search),是搜索算法的一种,它沿着树的深度遍历树的节点,尽可能深地搜索树的分支。当节点 v 的所有边都已被探寻过,将回溯到发现节点 v 的那条边的起始节点。这一过程一直进行到已探寻源节点到其他所有节点为止,如果还有未被发现的节点,则选择其中一个未被发现的节点为源节点并重复以上操作,直到所有节点都被探寻完成。

简单的说,DFS 就是从图中的一个节点开始追溯,直到最后一个节点,然后回溯,继续追溯下一条路径,直到到达所有的节点,如此往复,直到没有路径为止。

DFS 可以产生相应图的拓扑排序表,利用拓扑排序表可以解决很多问题,例如最大路径问题。一般用堆数据结构来辅助实现 DFS 算法。

注意:深度 DFS 属于盲目搜索,无法保证搜索到的路径为最短路径,也不是在搜索特定的路径,而是通过搜索来查看图中有哪些路径可以选择。

步骤:

  • 访问顶点 v;
  • 依次从 v 的未被访问的邻接点出发,对图进行深度优先遍历;直至图中和 v 有路径相通的顶点都被访问;
  • 若此时途中尚有顶点未被访问,则从一个未被访问的顶点出发,重新进行深度优先遍历,直到所有顶点均被访问过为止。

实现:

代码语言:javascript
复制
Graph.prototype.dfs = function() {
   var marked = []
   for (var i=0; i<this.vertices.length; i++) {
       if (!marked[this.vertices[i]]) {
           dfsVisit(this.vertices[i])
       }
   }

   function dfsVisit(u) {
       let edges = this.edges
       marked[u] = true
       console.log(u)
       var neighbors = edges.get(u)
       for (var i=0; i<neighbors.length; i++) {
           var w = neighbors[i]
           if (!marked[w]) {
               dfsVisit(w)
           }
       }
   }
}

测试:

代码语言:javascript
复制
graph.dfs()
// 1
// 4
// 3
// 2
// 5

测试成功。

广度优先遍历(BFS)

广度优先遍历(Breadth-First-Search)是从根节点开始,沿着图的宽度遍历节点,如果所有节点均被访问过,则算法终止,BFS 同样属于盲目搜索,一般用队列数据结构来辅助实现 BFS。

BFS 从一个节点开始,尝试访问尽可能靠近它的目标节点。本质上这种遍历在图上是逐层移动的,首先检查最靠近第一个节点的层,再逐渐向下移动到离起始节点最远的层

步骤:

  • 创建一个队列,并将开始节点放入队列中;
  • 若队列非空,则从队列中取出第一个节点,并检测它是否为目标节点;
    • 若是目标节点,则结束搜寻,并返回结果;
    • 若不是,则将它所有没有被检测过的字节点都加入队列中;
  • 若队列为空,表示图中并没有目标节点,则结束遍历。

实现:

代码语言:javascript
复制
Graph.prototype.bfs = function(v) {
   var queue = [], marked = []
   marked[v] = true
   queue.push(v) // 添加到队尾
   while(queue.length > 0) {
       var s = queue.shift() // 从队首移除
       if (this.edges.has(s)) {
           console.log('visited vertex: ', s)
       }
       let neighbors = this.edges.get(s)
       for(let i=0;i<neighbors.length;i++) {
           var w = neighbors[i]
           if (!marked[w]) {
               marked[w] = true
               queue.push(w)
           }
       }
   }
}

测试:

代码语言:javascript
复制
graph.bfs(1)
// visited vertex:  1
// visited vertex:  4
// visited vertex:  3
// visited vertex:  2
// visited vertex:  5

测试成功。

本题链接:

https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/9

6. 异步笔试题

请写出下面代码的运行结果:

代码语言:javascript
复制
// 今日头条面试题
async function async1() {
   console.log('async1 start')
   await async2()
   console.log('async1 end')
}
async function async2() {
   console.log('async2')
}
console.log('script start')
setTimeout(function () {
   console.log('settimeout')
})
async1()
new Promise(function (resolve) {
   console.log('promise1')
   resolve()
}).then(function () {
   console.log('promise2')
})
console.log('script end')

题目的本质,就是考察setTimeoutpromiseasync await的实现及执行顺序,以及 JS 的事件循环的相关问题。

答案:

代码语言:javascript
复制
script start
async1 start
async2
promise1
script end
async1 end
promise2
settimeout

过程详解链接:

https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/7

7. 将数组扁平化并去除其中重复数据,最终得到一个升序且不重复的数组

代码语言:javascript
复制
Array.from(new Set(arr.flat(Infinity))).sort((a,b)=>{ return a-b})

本题链接:

https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/8

8.JS 异步解决方案的发展历程以及优缺点。

1. 回调函数(callback)

代码语言:javascript
复制
setTimeout(() => {
   // callback 函数体
}, 1000)

缺点:回调地狱,不能用 try catch 捕获错误,不能 return

回调地狱的根本问题在于:

  • 缺乏顺序性: 回调地狱导致的调试困难,和大脑的思维方式不符;
  • 嵌套函数存在耦合性,一旦有所改动,就会牵一发而动全身,即(控制反转);
  • 嵌套函数过多的多话,很难处理错误。
代码语言:javascript
复制
ajax('XXX1', () => {
   // callback 函数体
   ajax('XXX2', () => {
       // callback 函数体
       ajax('XXX3', () => {
           // callback 函数体
       })
   })
})

优点:解决了同步的问题(只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行)。

2. Promise

Promise 就是为了解决 callback 的问题而产生的。

Promise 实现了链式调用,也就是说每次 then 后返回的都是一个全新 Promise,如果我们在 then 中 return ,return 的结果会被 Promise.resolve() 包装。

优点:解决了回调地狱的问题

代码语言:javascript
复制
ajax('XXX1')
 .then(res => {
     // 操作逻辑
     return ajax('XXX2')
 }).then(res => {
     // 操作逻辑
     return ajax('XXX3')
 }).then(res => {
     // 操作逻辑
 })

缺点:无法取消 Promise ,错误需要通过回调函数来捕获

3. Generator

特点:可以控制函数的执行,可以配合 co 函数库使用。

代码语言:javascript
复制
function *fetch() {
   yield ajax('XXX1', () => {})
   yield ajax('XXX2', () => {})
   yield ajax('XXX3', () => {})
}
let it = fetch()
let result1 = it.next()
let result2 = it.next()
let result3 = it.next()

4. Async/await

async、await 是异步的终极解决方案。

优点是:代码清晰,不用像 Promise 写一大堆 then 链,处理了回调地狱的问题

缺点:await 将异步代码改造成同步代码,如果多个异步操作没有依赖性而使用 await 会导致性能上的降低

代码语言:javascript
复制
async function test() {
 // 以下代码没有依赖性的话,完全可以使用 Promise.all 的方式
 // 如果有依赖性的话,其实就是解决回调地狱的例子了
 await fetch('XXX1')
 await fetch('XXX2')
 await fetch('XXX3')
}

下面来看一个使用 await 的例子:

代码语言:javascript
复制
let a = 0
let b = async () => {
 a = a + await 10
 console.log('2', a) // -> '2' 10
}
b()
a++
console.log('1', a) // -> '1' 1

对于以上代码你可能会有疑惑,让我来解释下原因:

  • 首先函数 b 先执行,在执行到 await 10 之前变量 a 还是 0,因为 await 内部实现了 generatorgenerator 会保留堆栈中东西,所以这时候 a = 0 被保存了下来
  • 因为 await 是异步操作,后来的表达式不返回 Promise 的话,就会包装成Promise.reslove(返回值),然后会去执行函数外的同步代码;
  • 同步代码执行完毕后开始执行异步代码,将保存下来的值拿出来使用,这时候 a = 0 + 10

上述解释中提到了 await 内部实现了 generator,其实 await 就是 generator加上 Promise的语法糖,且内部实现了自动执行 generator。如果你熟悉 co 的话,其实自己就可以实现这样的语法糖。

本题链接:

https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/11

9. 谈谈你对 TCP 三次握手和四次挥手的理解

本题链接:

https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/15

注:1.今天星期六,你是在加班还是在干嘛?欢迎下方留言交流!

2.对于以上面试题及答案你有什么建议?欢迎下方留言交流!

每一天,你将受到才哥的理论,结合历史、政治、文化、艺术、商业故事,令人防不胜防的高纯度无死角知识轰炸。以及,不间断的私藏书籍、电影推荐。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-03-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 李才哥 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 线上笔试:
  • 一面:
  • 二面:
  • 总结:
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档