前言
话说两周前,我发了这样一条沸点:
于是我真的就建群收集了题目,和团队的同事一起写答案,我们也不图什么,就是想做一件有意义的事情,现在我整理了下我们的回答,有的不一定就是非常具体的回答,但也提供了思路和参考资料,大家看看是否还有什么补充的?
[字节跳动] 怎么与服务端保持连接
和服务端保持连接,最简单粗暴的方法就是通过请求轮询保持跟服务端的通信,客户端不光要花成本维护定时轮询器,还会使得服务器压力变大,所以不推荐。
还有一种可以借助请求超时的设置,将超时时间设置一个足够大的值,客户端发起连接后,只要服务端不返回消息,整个连接阶段都会受到阻塞,所以这种方式也不推荐。
最后一种是WebSocket,当服务器完成协议从HTTP到WebSocket的升级后,服务端可以主动推送信息给客户端,解决了轮询造成的同步延迟问题。由于 WebSocket 只需要一次 HTTP 握手,服务端就能一直与客户端保持通信,直到关闭连接,这样就解决了服务器需要反复解析 HTTP 协议,减少了资源的开销。
[字节跳动] 谈一下隐式类型转换
这个问题可以拓展讲成 JavaScript 中的类型转换,分为两类,显式类型转换和隐式类型转换,当我们用 Number() 等函数的时候,就是显式类型转换,其转换规则是当是基本类型时,参照规范中的对应表进行转换,当不是基本类型的时候,先参照规范中的 ToPrimitive
方法转换为基本类型,再按照对应表转换,当执行 ToPrimitive 的时候,又会根据情况不同,判断先执行对象的 valueOf 方法还是 toString 方法进行准换,这个可以参照 JavaScript 深入之头疼的类型转换(上),而当我们进行运算的时候,经常发生的就是隐式类型转换,比如 +
和 ==
运算符,当 + 运算符的时候,更倾向于转成字符串,而当 ==
的时候,更倾向于转为数字,这个在 JavaScript 深入之头疼的类型转换(下)中会讲到……但是我还在写……总之,回答类型转换的时候,最好是扯到规范中,表明你在研究这块内容的时候,还专门去看过规范。基本上回答了这里,对类型转换说明已经有很多的了解,但你可以再拓展讲一下,比如当时我在学习类型转换的时候,还专门去研究了如何花式表示 26 个字母,比如在控制台打印这样一句话:
[[][0] + []][0][5]+[[][[[][0] + []][0][4]+[[][0] + []][0][5]+[[][0] + []][0][1]+[[][0] + []][0][2]] + []][0][8]+[[[] == []][0] + []][0][2]+[[][[[][0] + []][0][4]+[[][0] + []][0][5]+[[][0] + []][0][1]+[[][0] + []][0][2]] + []][0][6]+[[][[[][0] + []][0][4]+[[][0] + []][0][5]+[[][0] + []][0][1]+[[][0] + []][0][2]]+[]][0][23]+[[][0] + []][0][3]+[[][[[][0] + []][0][4]+[[][0] + []][0][5]+[[][0] + []][0][1]+[[][0] + []][0][2]] + []][0][8]+[+[1 + [[][0] + []][0][3] +309][0] + []][0][7]+[[][[[][0] + []][0][4]+[[][0] + []][0][5]+[[][0] + []][0][1]+[[][0] + []][0][2]] + []][0][6]+[[][0] + []][0][0] 复制代码
花式表示字母的原理大家也可以探索一下,虽然看起来很无聊,但可以作为一个小案例,表明下你对技术的钻研和兴趣。
[各种公司] 输入url后发生了什么
这个问题的核心是在问从输入URL到页面渲染经历了哪些过程。
从耗时过程来看,可以分为DNS解析、TCP连接、HTTP请求与响应、客户端浏览器解析渲染、连接结束。其中浏览器解析渲染包含HTML词法、语法的解析、CSS解析、DOM树生成、渲染树建立、屏幕绘制。
下面针对几个较为重要的过程做下介绍。
DNS解析
当我们在浏览器中输入如www.taobao.com
的时候,DNS解析充当了一个翻译的角色,把网址「翻译」成了IP地址。DNS解析的过程就是域名到IP地址的转换的过程。域名解析也叫域名指向、服务器设置、域名配置以及反向IP登记等等。说得简单点就是将好记的域名解析成IP,服务由DNS服务器完成,把域名解析到一个IP地址,然后在此IP地址的主机上将一个子目录与域名绑定。
TCP连接
TCP连接的重要目的,是为了保证消息的有序和不丢包,为了建立可靠的数据传输,TCP通信双方相互告知初始化序列号,并确定对方已经收到ISN的,整个链接的过程就是我们俗称的三次握手
。
HTTP请求与响应
HTTP请求它主要发生在客户端,发送HTTP请求的过程就是构建HTTP请求报文并通过TCP协议发送到服务器指定端口的过程。
还是用 www.taobao.com 举例子。
当在地址栏输入后,浏览器会分析这个url,并设置好请求报文发出。请求报文中包括请求行(包括请求的方法,路径和协议版本)、请求头(包含了请求的一些附加的信息,一般是以键值的形式成对存在)、空行(协议中规定请求头和请求主体间必须用一个空行隔开)、请求主体(对于post请求,所需要的参数都不会放在url中,这时候就需要一个载体了,这个载体就是请求主体)。服务端收到这个请求后,会根据url匹配到的路径做相应的处理,最后返回浏览器需要的页面资源。处理后,浏览器会收到一个响应报文,而所需要的资源就就在报文主体上。与请求报文相同,响应报文也有与之对应的起始行(响应报文的起始行同样包含了协议版本,与请求的起始行不同的是其包含的还有状态码和状态码的原因短语)、响应头(对应请求报文中的请求头,格式一致,但是各自有不同的首部)、空行、报文主体(请求所需要的资源),不同的地方在于包含的东西不一样。
HTML词法、语法解析
对我们来说HTML其实是一坨字符串,而实际上我们要面对的是"字符流"。为了把字符流解析成正确的可被浏览器识别的结构,我们需要做的事情分为两步:
词法分析:把字符流初步解析成我们可理解的"词",学名叫token。
语法分析:把开始结束标签配对、属性赋值好、父子关系连接好、构成dom树。
html结构其实不算太复杂,我们平时见到的大部分都只是标签、属性、注释、CDATA节点。
屏幕绘制
DOM树的生成和渲染树建立比较好理解这个就不做展开。完成了这「两棵树」的构造后,就进入屏幕绘制阶段。
在绘制的过程中,会遍历渲染树,调用由浏览器的UI组件的paint()方法在屏幕上显示对应的内容,并根据渲染树布局,计算CSS样式(即每个节点在页面中的大小和位置等几何信息)。
HTML默认是从上到下流式布局的,CSS和JS的加入会打破这种布局,改变DOM的外观样式以及大小和位置。这就引出两个非常重要的概念:replaint重绘和reflow重排。
replaint重绘,屏幕的一部分重新绘制,不影响整体布局,比如某个CSS的背景色变了,但元素的几何尺寸和位置不变。eflow重排: 意味着元件的几何尺寸变了,我们需要重新验证并计算渲染树。是渲染树的一部分或全部发生了变化。无论是重绘还是重排,对浏览器而言都是一种「消耗」,所以我们应该尽量减少这两种状态的触发。
[百词斩] CDN的原理
CDN的基本原理是广泛采用各种缓存服务器,将这些缓存服务器分布到用户访问相对集中的地区或网络中,在用户访问网站时,利用全局负载技术将用户的访问指向距离最近的工作正常的缓存服务器上,由缓存服务器直接响应用户请求。 最简单的CDN网络由一个DNS 服务器和几台缓存服务器就可以组成,当用户输入URL按下回车,经过本地DNS系统解析,DNS系统会最终将域名的解析权交给CNAME指向的CDN专用DNS服务器,然后将得到全局负载均衡设备的IP地址,用户向全局负载均衡设备发送内容访问请求,全局负载均衡设备将实时地根据网络流量和各节点的连接、负载状况以及到用户的距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上,使用户可就近取得所需内容,解决 Internet网络拥挤的状况,提高用户访问网站的响应速度
[百词斩] 性能监控平台是如何捕获错误的
一般从如下两个方面考虑:
1.全局捕获:
通过全局的接口,将捕获代码集中写在一个地方,可以利用的接口有:
- window.addEventListener(‘error’) / window.addEventListener(“unhandledrejection”) / document.addEventListener(‘click’) 等
- 框架级别的全局监听,例如aixos中使用interceptor进行拦截,vue、react都有自己的错误采集接口
- 通过对全局函数进行封装包裹,实现在在调用该函数时自动捕获异常
- 对实例方法重写(Patch),在原有功能基础上包裹一层,例如对console.error进行重写,在使用方法不变的情况下也可以异常捕获
2.单点捕获:
在业务代码中对单个代码块进行包裹,或在逻辑流程中打点,实现有针对性的异常捕获:
- try…catch
- 专门写一个函数来收集异常信息,在异常发生时,调用该函数
- 专门写一个函数来包裹其他函数,得到一个新函数,该新函数运行结果和原函数一模一样,只是在发生异常时可以捕获异常
注:详情可参见Fundebug 发表的 《前端异常监控解决方案研究》
[腾讯一面] 性能优化从哪些方面入手?
• 分屏加载,当页面需要渲染的数据较多时,先渲染首屏,下滑时再加载第二屏的数据; • 图片大小优化,在不影响视觉效果的前提下,把图片尺寸降到最小;
• 图片懒加载,on appear时再加载图片;
• Code splitting,或者拆包,应用下的某些组件不需要立刻import,可以采用动态import的方式,打包时也可以将它们打到不同的bundle里,给index bundle瘦身;
• Chrome Devtools - Trace & Timeline等一系列强大的分析工具可以去研究一下,它们可以深入到内核分析应用的性能问题所在;
[腾讯二面] 如何加快首屏渲染,你有哪些方案?
- 降低请求量:合并资源,减少 HTTP 请求数,minify / gzip 压缩,webP,lazyload。
- 加快请求速度:预解析DNS,减少域名数,并行加载,CDN 分发。
- 增加缓存:HTTP 协议缓存请求,离线缓存 manifest,离线数据缓存 localStorage、PWA。
- 渲染优化:首屏内容最小化,JS/CSS优化,加载顺序,服务端渲染,pipeline。
[腾讯一面] webpack 针对模块化做的处理
这一块建议去读 webpack4 文档中对于 library,libraryTarget 的描述。当我们开发一个 JS 库的时候,通常最终的 npm package 需要输出的是一些组件或 api,这个时候我们需要了解webpack4所提供的模块化的打包能力。通过对libraryTarget的设置,我们可以将我们的工程打包成amd,umd,或commonJS模块。webpack.js.org/configurati…
[腾讯一面] 概述一下 Node.js 中的进程与线程
Node.js 中的进程 Process 是一个全局对象,无需 require 直接使用,给我们提供了当前进程中的相关信息。Node.js 中进程可以使用 child_process 模块创建。
关系:
- 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程(通常说的主线程)。
- 同一进程的所有线程共享该进程的所有资源。
- 进程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
- 处理机分给线程,即真正在处理机上运行的是线程。
- 线程是指进程内的一个执行单元,也是进程内的可调度实体。
区别:
- 调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位。
- 拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源。
[字节跳动] 鼠标滚动的时候,会触发很多次事件,如何解决的?
这里选择的方法应该是节流,可以拓展讲到防抖和节流,防抖是指连续触发的时候只会执行一次,停止触发 N 秒后才能继续执行,而节流是指如果你持续触发事件,每隔一段时间,只执行一次事件。像防止按钮多次点击就用防抖,像是监听滚动事件就用节流,函数实现都可以参照 underscore 代码中的实现,以前我写过 JavaScript专题之跟着underscore学防抖 和 JavaScript专题之跟着 underscore 学节流 两篇文章讲述了 underscore 中的实现方式