# 关于阿里

Hi,大家好,我们是阿里巴巴新成立的 BU,目前还有大量的 Web 前端职位空缺,机会难得,希望正在找工作的同学们可以来试试:

  • 目前 Web 前端急缺 P6 和 P7(阿里的很多 BU 都只招 P7 了)
  • 新的 BU 你进来即是元老😂😂😂
  • 前端技术体系大部分需要一起重新开拓,可以学习到更多的新内容
  • 主要负责 PC 端、客户端、钉钉 E 应用以及支付宝小程序的开发(我本人完全不会小程序,不用担心😂😂😂)
  • 技术栈是 React(如果你是 Vue 技术栈完全不用担心,因为我也是😂😂😂)
  • 其他 BU 面试可能有五轮,我们这边只有 4 轮面试

真的机会难得哦,如果想更多了解我们 BU 以及找我内推的同事加我钉钉或者微信(纯粹找我了解或者沟通技术也行,啊哈哈):18768107826

# 简历

我的简历只是简单的用 MD 做了一份,大致包含了以下几个部分:

  • 基本资料
  • 专业技能
  • 工作经历
  • 实习经历(可选)
  • 项目经历

小提示:在基本资料里一定要填写正确的邮箱地址,我在前期面试的时候都没有打开邮箱查看面试情况,导致一些面试的时间点和面试结果都不清楚(一直以为会发短信通知)。

如果去现场面试,一定要记得带上笔和简历。一方面你给面试官的简历必定是最新的(在不断面试的过程中你必定会修改简历),另一方面这也会给面试官一种非常舒心的感觉。

对于简历这里提一点,在写自己的专业技能项目经历时尽量不要给自己挖坑,这里展示一下我的专业技能(我会的不多):

  • 熟悉嵌入式 C、JavaScript、Node.js
  • 熟悉 Vue.js 框架

切忌写一大堆让人感觉花里胡哨的技能,尤其是一些很浅显的技能(基本技能除外)。如果你有一些别人很难替代的技能,那这些技能就是亮点了,我这里就没什么亮点技能。有些技能你会但是不熟练,你可以适当的在你的项目经历中体现出来。对于项目经历尽量挑自己觉得非常有技术含量的项目进行说明(宁缺毋滥),对于自己参加过但不是特别熟悉的项目尽量不要填写,防止给自己挖坑。

小提示:这里附上的我的面试简历供大家参考。感谢 jsliang 的文章 2019 面试系列 - 简历,大家制作简历时也可以参考这篇文章。

在投递简历时大家千万不要被招聘信息中的要求吓到,记得有一次投递简历时我对招聘者说自身不太符合要求,招聘者当时说要求都是唬人的,觉得有兴趣就投,有些招聘要求可能正是你未来学习或者深入的领域。

# 面试

简历制作完后我大概投了四家公司:有赞、滴滴、51 信用卡和阿里。其中有赞挂在二面,滴滴挂在一面,51 信用卡挂在一面,阿里两个部门挂在一面,一个部门面试成功。很多面试者的经历可能都是像我这样,在一次次的面试失败中不断的总结进步,最终拿到理想的 Offer。

小提示:建议大家在投递简历时可以先投递一些试水的小公司,先检验一下自己是不是可以胜任这些公司的面试。同时在每一次面试完后记得把面试官提问的问题记录下来,对于没有答上来的问题还是要好好搞懂或者实践一下,因为很有可能下一家的面试官会问同样的问题。

在面试的过程中,这里我给出几点意见:

  • 心态放平稳,假设第一题你答不上来很正常,面试官不会因为第一题你不会就 PASS 你
  • 不会的题目一定不要瞎猜,往往面试官给你挖的坑就是希望你往错的方向猜,一定要答不知道
  • 不要说太多跟当前面试题无关的内容,问你什么问题尽量就答什么问题,除非面试官指定你发散一下思维
  • 如果没有听懂面试题可以试着询问面试官,您要问的是关于 xxx 的问题么
  • 对于某些问题一定要自己先提前精炼一下(例如作用域链、继承以及原型链等问题)
  • 如果面试官问的某项技术自己在某些场景使用过或看到别的场景有使用,可结合这些场景进行讲解(让面试官知道你不仅仅理解它,你还会很好的使用它)
  • 如果是 Vue 技术栈希望可以深入源码或者至少理解一些别人的源码分析
  • 如果面试阿里那么面试之前一定要好好准备这样一个问题:你觉得你最擅长什么
  • 面试一定要真诚,切勿投机取巧
  • 面试态度一定要谦虚

接下来我会按照面试顺序给出面试题以及自己理解的一些答案:

  • 大部分答案都是借鉴别人的博客
  • 有些答案不一定合理
  • 有些答案写的很零散
  • 有些答案会举一反三
  • 有些题目太基础或者重复了就没有写答案
  • 有些题目太宏观或者不知道怎么回答合理,希望大家可以在评论中补充答案供更多的人受益

# 有赞(一面)

# 说说 CSS 选择器以及这些选择器的优先级

  • !important
  • 内联样式(1000)
  • ID 选择器(0100)
  • 类选择器 / 属性选择器 / 伪类选择器(0010)
  • 元素选择器 / 伪元素选择器(0001)
  • 关系选择器 / 通配符选择器(0000)

# 你知道什么是 BFC 么

小提示:这个问题重点是 BFC 是什么,BFC 触发的条件有哪些,BFC 可以干什么。这里我试着讲解了一下 Boostrap 的清除浮动(display:table 创建匿名 table-cell 间接触发 BFC),如果有看到别的场景使用或者自身有使用的场景可以尝试讲解一下使用技巧。这样可以让面试官觉得你不仅仅知道他问的东西是什么,你还能很好的使用它。

# 什么是 BFC

BFC 全称为块级格式化上下文 (Block Formatting Context) 。BFC 是 W3C CSS 2.1 规范中的一个概念,它决定了元素如何对其内容进行定位以及与其他元素的关系和相互作用,当涉及到可视化布局的时候,Block Formatting Context 提供了一个环境,HTML 元素在这个环境中按照一定规则进行布局。一个环境中的元素不会影响到其它环境中的布局。比如浮动元素会形成 BFC,浮动元素内部子元素的主要受该浮动元素影响,两个浮动元素之间是互不影响的。这里有点类似一个 BFC 就是一个独立的行政单位的意思。可以说 BFC 就是一个作用范围,把它理解成是一个独立的容器,并且这个容器里 box 的布局与这个容器外的 box 毫不相干。

# 触发 BFC 的条件

  • 根元素或其它包含它的元素
  • 浮动元素 (元素的 float 不是 none )
  • 绝对定位元素 (元素具有 positionabsolutefixed )
  • 内联块 (元素具有 display: inline-block )
  • 表格单元格 (元素具有 display: table-cell ,HTML 表格单元格默认属性)
  • 表格标题 (元素具有 display: table-caption , HTML 表格标题默认属性)
  • 具有 overflow 且值不是 visible 的块元素
  • 弹性盒( flexinline-flex
  • display: flow-root
  • column-span: all

# BFC 的约束规则

  • 内部的盒会在垂直方向一个接一个排列(可以看作 BFC 中有一个的常规流)
  • 处于同一个 BFC 中的元素相互影响,可能会发生外边距重叠
  • 每个元素的 margin box 的左边,与容器块 border box 的左边相接触 (对于从左往右的格式化,否则相反),即使存在浮动也是如此
  • BFC 就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素,反之亦然
  • 计算 BFC 的高度时,考虑 BFC 所包含的所有元素,连浮动元素也参与计算
  • 浮动盒区域不叠加到 BFC 上

# BFC 可以解决的问题

  • 垂直外边距重叠问题
  • 去除浮动
  • 自适用两列布局( float + overflow

# 了解盒模型么

包括内容区域内边距区域边框区域外边距区域

box-sizing: content-box (W3C 盒子模型):元素的宽高大小表现为内容的大小。 box-sizing: border-box (IE 盒子模型):元素的宽高表现为内容 + 内边距 + 边框的大小。背景会延伸到边框的外沿。

IE5.x 和 IE6 在怪异模式中使用非标准的盒子模型,这些浏览器的 width 属性不是内容的宽度,而是内容内边距边框的宽度的总和。

# 如何实现左侧宽度固定,右侧宽度自适应的布局

小提示:这个问题面试官会要求说出几种解决方法。

DOM 结构

<div class="box">
  <div class="box-left"></div>
  <div class="box-right"></div>
</div>
复制代码

# 利用 float + margin 实现

.box {
 height: 200px;
}

.box > div {
  height: 100%;
}

.box-left {
  width: 200px;
  float: left;
  background-color: blue;
}

.box-right {
  margin-left: 200px;
  background-color: red;
}
复制代码

# 利用 calc 计算宽度

.box {
 height: 200px;
}

.box > div {
  height: 100%;
}

.box-left {
  width: 200px;
  float: left;
  background-color: blue;
}

.box-right {
  width: calc(100% - 200px);
  float: right;
  background-color: red;
}
复制代码

# 利用 float + overflow 实现

.box {
 height: 200px;
}

.box > div {
  height: 100%;
}

.box-left {
  width: 200px;
  float: left;
  background-color: blue;
}

.box-right {
  overflow: hidden;
  background-color: red;
}
复制代码

# 利用 flex 实现

这里不是最佳答案,应该是使用 flex-basis 实现更合理

.box {
  height: 200px;
  display: flex;
}

.box > div {
  height: 100%;
}

.box-left {
  width: 200px;
  background-color: blue;
}

.box-right {
  flex: 1; // 设置flex-grow属性为1,默认为0
  overflow: hidden;
  background-color: red;
}
复制代码

# 了解跨域吗,一般什么情况下会导致跨域

小提示: 如果平常自身有使用场景可结合使用场景进行讲解,比如我在这里使用过的场景是 CORS 和 Nginx 反向代理。

# 跨域行为

  • 同源策略限制、安全性考虑
  • 协议、IP 和端口不一致都是跨域行为

# JSONP

小提示:如果你提到 JSONP,面试官肯定会问你整个详细的实现过程,所以一定要搞懂 JSONP 的实现原理,如果不是很理解可以自己起一个 Express 服务实践一下。

Web 前端事先定义一个用于获取跨域响应数据的回调函数,并通过没有同源策略限制的 script 标签发起一个请求(将回调函数的名称放到这个请求的 query 参数里),然后服务端返回这个回调函数的执行,并将需要响应的数据放到回调函数的参数里,前端的 script 标签请求到这个执行的回调函数后会立马执行,于是就拿到了执行的响应数据。

缺点: JSONP 只能发起 GET 请求

# 如何实现一个 JSONP

这里给出几个链接:

segmentfault.com/a/119000001…

zhangguixu.github.io/2016/12/02/…

www.cnblogs.com/iovec/p/531…

# JSONP 安全性问题

# CSRF 攻击

前端构造一个恶意页面,请求 JSONP 接口,收集服务端的敏感信息。如果 JSONP 接口还涉及一些敏感操作或信息(比如登录、删除等操作),那就更不安全了。

解决方法:验证 JSONP 的调用来源(Referer),服务端判断 Referer 是否是白名单,或者部署随机 Token 来防御。

# XSS 漏洞

不严谨的 content-type 导致的 XSS 漏洞,想象一下 JSONP 就是你请求 http://youdomain.com?callback=douniwan , 然后返回 douniwan({ data }) ,那假如请求 http://youdomain.com?callback=<script>alert(1)</script> 不就返回 <script>alert(1)</script>({ data }) 了吗,如果没有严格定义好 Content-Type( Content-Type: application/json ),再加上没有过滤 callback 参数,直接当 html 解析了,就是一个赤裸裸的 XSS 了。

解决方法:严格定义 Content-Type: application/json,然后严格过滤 callback 后的参数并且限制长度(进行字符转义,例如 <换成 & lt,> 换成 & gt)等,这样返回的脚本内容会变成文本格式,脚本将不会执行。

# 服务器被黑,返回一串恶意执行的代码

可以将执行的代码转发到服务端进行校验 JSONP 内容校验,再返回校验结果。

# CORS(跨域资款共享)

小提示:如果你回答跨域解决方案 CORS,那么面试官一定会问你实现 CORS 的响应头信息 Access-Control-Allow-Origin。

# 什么是 CORS

CORS(跨域资源共享 Cross-origin resource sharing)允许浏览器向跨域服务器发出 XMLHttpRequest 请求,从而克服跨域问题,它需要浏览器和服务器的同时支持。

  • 浏览器端会自动向请求头添加 origin 字段,表明当前请求来源。
  • 服务器端需要设置响应头的 Access-Control-Allow-Methods,Access-Control-Allow-Headers,Access-Control-Allow-Origin 等字段,指定允许的方法,头部,源等信息。
  • 请求分为简单请求和非简单请求,非简单请求会先进行一次 OPTION 方法进行预检,看是否允许当前跨域请求。
# 简单请求

请求方法是以下三种方法之一:

  • HEAD
  • GET
  • POST

HTTP 的请求头信息不超出以下几种字段:

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type:只限于三个值 application/x-www-form-urlencoded、multipart/form-data、text/plain

后端的响应头信息:

  • Access-Control-Allow-Origin:该字段是必须的。它的值要么是请求时 Origin 字段的值,要么是一个 *,表示接受任意域名的请求。
  • Access-Control-Allow-Credentials:该字段可选。它的值是一个布尔值,表示是否允许发送 Cookie。
  • Access-Control-Expose-Headers:该字段可选。CORS 请求时,XMLHttpRequest 对象的 getResponseHeader () 方法只能拿到 6 个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在 Access-Control-Expose-Headers 里面指定。
# 非简单请求

非简单请求是那种对服务器有特殊要求的请求,比如请求方法是 PUT 或 DELETE,或者 Content-Type 字段的类型是 application/json。非简单请求的 CORS 请求,会在正式通信之前,增加一次 HTTP 查询请求,称为 "预检" 请求(preflight)。

  • Access-Control-Request-Method:该字段是必须的,用来列出浏览器的 CORS 请求会用到哪些 HTTP 方法,上例是 PUT。

  • Access-Control-Request-Headers:该字段是一个逗号分隔的字符串,指定浏览器 CORS 请求会额外发送的头信息字段,上例是 X-Custom-Header。

如果浏览器否定了 "预检" 请求,会返回一个正常的 HTTP 回应,但是没有任何 CORS 相关的头信息字段。这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被 XMLHttpRequest 对象的 onerror 回调函数捕获。

# JSONP 和 CORS 的对比

  • JSONP 只支持 GET 请求,CORS 支持所有类型的 HTTP 请求
  • JSONP 的优势在于支持老式浏览器,以及可以向不支持 CORS 的网站请求数据

# 其他跨域解决方案

  • Nginx 反向代理
  • postMessage
  • document.domain

# HTTP2 和 HTTP1 有什么区别

相对于 HTTP1.0,HTTP1.1 的优化:

  • 缓存处理:多了 Entity tag,If-Unmodified-Since, If-Match, If-None-Match 等缓存信息(HTTTP1.0 If-Modified-Since,Expires)
  • 带宽优化及网络连接的使用
  • 错误通知的管理
  • Host 头处理
  • 长连接: HTTP1.1 中默认开启 Connection: keep-alive,一定程度上弥补了 HTTP1.0 每次请求都要创建连接的缺点。

相对于 HTTP1.1,HTTP2 的优化:

  • HTTP2 支持二进制传送(实现方便且健壮),HTTP1.x 是字符串传送
  • HTTP2 支持多路复用
  • HTTP2 采用 HPACK 压缩算法压缩头部,减小了传输的体积
  • HTTP2 支持服务端推送

# 你能说说缓存么

小提示:如果平常有遇到过缓存的坑或者很好的利用缓存,可以讲解一下自己的使用场景。如果没有使用注意过缓存问题你也可以尝试讲解一下和我们息息相关的 Webpack 构建(每一次构建静态资源名称的 hash 值都会变化),它其实就跟缓存相关。有兴趣的同学可以查看张云龙的博客大公司里怎样开发和部署前端代码?

缓存分为强缓存和协商缓存。强缓存不过服务器,协商缓存需要过服务器,协商缓存返回的状态码是 304。两类缓存机制可以同时存在,强缓存的优先级高于协商缓存。当执行强缓存时,如若缓存命中,则直接使用缓存数据库中的数据,不再进行缓存协商。

# 强缓存

Expires(HTTP1.0):Exprires 的值为服务端返回的数据到期时间。当再次请求时的请求时间小于返回的此时间,则直接使用缓存数据。但由于服务端时间和客户端时间可能有误差,这也将导致缓存命中的误差。另一方面,Expires 是 HTTP1.0 的产物,故现在大多数使用 Cache-Control 替代。

缺点:使用的是绝对时间,如果服务端和客户端的时间产生偏差,那么会导致命中缓存产生偏差。

Pragma(HTTP1.0):HTTP1.0 时的遗留字段,当值为 "no-cache" 时强制验证缓存,Pragma 禁用缓存,如果又给 Expires 定义一个还未到期的时间,那么 Pragma 字段的优先级会更高。服务端响应添加’Pragma’: ‘no-cache’,浏览器表现行为和刷新 (F5) 类似。

Cache-Control(HTTP1.1):有很多属性,不同的属性代表的意义也不同:

  • private:客户端可以缓存
  • public:客户端和代理服务器都可以缓存
  • max-age=t:缓存内容将在 t 秒后失效
  • no-cache:需要使用协商缓存来验证缓存数据
  • no-store:所有内容都不会缓存

请注意 no-cache 指令很多人误以为是不缓存,这是不准确的,no-cache 的意思是可以缓存,但每次用应该去想服务器验证缓存是否可用。no-store 才是不缓存内容。当在首部字段 Cache-Control 有指定 max-age 指令时,比起首部字段 Expires,会优先处理 max-age 指令。命中强缓存的表现形式:Firefox 浏览器表现为一个灰色的 200 状态码。Chrome 浏览器状态码表现为 200 (from disk cache) 或是 200 OK (from memory cache)。

# 协商缓存

协商缓存需要进行对比判断是否可以使用缓存。浏览器第一次请求数据时,服务器会将缓存标识与数据一起响应给客户端,客户端将它们备份至缓存中。再次请求时,客户端会将缓存中的标识发送给服务器,服务器根据此标识判断。若未失效,返回 304 状态码,浏览器拿到此状态码就可以直接使用缓存数据了。

Last-Modified:服务器在响应请求时,会告诉浏览器资源的最后修改时间。

if-Modified-Since:浏览器再次请求服务器的时候,请求头会包含此字段,后面跟着在缓存中获得的最后修改时间。服务端收到此请求头发现有 if-Modified-Since,则与被请求资源的最后修改时间进行对比,如果一致则返回 304 和响应报文头,浏览器只需要从缓存中获取信息即可。

  • 如果真的被修改:那么开始传输响应一个整体,服务器返回:200 OK
  • 如果没有被修改:那么只需传输响应 header,服务器返回:304 Not Modified

if-Unmodified-Since: 从某个时间点算起,是否文件没有被修改,使用的是相对时间,不需要关心客户端和服务端的时间偏差。

  • 如果没有被修改:则开始 ` 继续’传送文件,服务器返回: 200 OK
  • 如果文件被修改:则不传输,服务器返回: 412 Precondition failed (预处理错误)

这两个的区别是一个是修改了才下载一个是没修改才下载。如果在服务器上,一个资源被修改了,但其实际内容根本没发生改变,会因为 Last-Modified 时间匹配不上而返回了整个实体给客户端(即使客户端缓存里有个一模一样的资源)。为了解决这个问题,HTTP1.1 推出了 Etag。

Etag:服务器响应请求时,通过此字段告诉浏览器当前资源在服务器生成的唯一标识(生成规则由服务器决定)

If-Match:条件请求,携带上一次请求中资源的 ETag,服务器根据这个字段判断文件是否有新的修改

If-None-Match: 再次请求服务器时,浏览器的请求报文头部会包含此字段,后面的值为在缓存中获取的标识。服务器接收到次报文后发现 If-None-Match 则与被请求资源的唯一标识进行对比。

  • 不同,说明资源被改动过,则响应整个资源内容,返回状态码 200。
  • 相同,说明资源无心修改,则响应 header,浏览器直接从缓存中获取数据信息。返回状态码 304.

但是实际应用中由于 Etag 的计算是使用算法来得出的,而算法会占用服务端计算的资源,所有服务端的资源都是宝贵的,所以就很少使用 Etag 了。

  • 浏览器地址栏中写入 URL,回车浏览器发现缓存中有这个文件了,不用继续请求了,直接去缓存拿(最快)
  • F5 就是告诉浏览器,别偷懒,好歹去服务器看看这个文件是否有过期了。于是浏览器就胆胆襟襟的发送一个请求带上 If-Modify-since
  • Ctrl+F5 告诉浏览器,你先把你缓存中的这个文件给我删了,然后再去服务器请求个完整的资源文件下来。于是客户端就完成了强行更新的操作

# 缓存场景

对于大部分的场景都可以使用强缓存配合协商缓存解决,但是在一些特殊的地方可能需要选择特殊的缓存策略

  • 对于某些不需要缓存的资源,可以使用 Cache-control: no-store ,表示该资源不需要缓存
  • 对于频繁变动的资源,可以使用 Cache-Control: no-cache 并配合 ETag 使用,表示该资源已被缓存,但是每次都会发送请求询问资源是否更新
  • 对于代码文件来说,通常使用 Cache-Control: max-age=31536000 并配合策略缓存使用,然后对文件进行指纹处理,一旦文件名变动就会立刻下载新的文件

# 能说说首屏加载优化有哪些方案么

小提示:如果做过类似优化的同学,可能就比较好回答,没有做过类似优化的同学可以重点讲解一下懒加载(当然我这里被面试官追问过懒加载的 Webpack 配置问题)。同时不知道使用 Vue 技术栈的同学们有没有仔细观察过 Vue CLI 3 构建的 html 文件中的 link 标签的 rel 属性。

  • Vue-Router 路由懒加载(利用 Webpack 的代码切割)
  • 使用 CDN 加速,将通用的库从 vendor 进行抽离
  • Nginx 的 gzip 压缩
  • Vue 异步组件
  • 服务端渲染 SSR
  • 如果使用了一些 UI 库,采用按需加载
  • Webpack 开启 gzip 压缩
  • 如果首屏为登录页,可以做成多入口
  • Service Worker 缓存文件处理
  • 使用 link 标签的 rel 属性设置 prefetch(这段资源将会在未来某个导航或者功能要用到,但是本资源的下载顺序权重比较低,prefetch 通常用于加速下一次导航)、preload(preload 将会把资源得下载顺序权重提高,使得关键数据提前下载好,优化页面打开速度)

# 如何在 Node 端配置路径别名(类似于 Webpack 中的 alias 配置)

  • 全局变量
  • 环境变量
  • 自己 HACK 一个 @符号,指向特定的路径
  • HACK require 方法

# 参考

这种问题还是附上参考链接

segmentfault.com/a/119000001…

chashaobao.net/2017/09/03/…

www.zhihu.com/question/26…

# 谈谈你对作用域链的理解

小提示:同类型的问题还可以是原型链、继承、闭包等,这种概念性的问题你肯定不是一句两句能说清楚的,建议在理解之后自己尝试总结一下,如何把重要的知识点用简短的话语说明白。

了解作用域链之前我们要知道一下几个概念:

  • 函数的生命周期
  • 变量和函数的声明
  • Activetion Object(AO)、Variable Object(VO)

函数的生命周期:

  • 创建:JS 解析引擎进行预解析,会将函数声明提前,同时将该函数放到全局作用域中或当前函数的上一级函数的局部作用域中。

  • 执行:JS 引擎会将当前函数的局部变量和内部函数进行声明提前,然后再执行业务代码,当函数执行完退出时,释放该函数的执行上下文,并注销该函数的局部变量。

变量和函数的声明:如果变量名和函数名声明时相同,函数优先声明。

Activetion Object(AO)、Variable Object(VO):

  • AO:Activetion Object(活动对象)
  • VO:Variable Object(变量对象)

VO 对应的是函数创建阶段,JS 解析引擎进行预解析时,所有的变量和函数的声明,统称为 Variable Object。该变量与执行上下文相关,知道自己的数据存储在哪里,并且知道如何访问。VO 是一个与执行上下文相关的特殊对象,它存储着在上下文中声明的以下内容:

  • 变量 (var, 变量声明);
  • 函数声明 (FunctionDeclaration, 缩写为 FD);
  • 函数的形参

AO 对应的是函数执行阶段,当函数被调用执行时,会建立一个执行上下文,该执行上下文包含了函数所需的所有变量,该变量共同组成了一个新的对象就是 Activetion Object。该对象包含了:

  • 函数的所有局部变量
  • 函数的所有命名参数
  • 函数的参数集合
  • 函数的 this 指向

作用域链:

当代码在一个环境中创建时,会创建变量对象的一个作用域链(scope chain)来保证对执行环境有权访问的变量和函数。作用域第一个对象始终是当前执行代码所在环境的变量对象(VO)。如果是函数执行阶段,那么将其 activation object(AO)作为作用域链第一个对象,第二个对象是上级函数的执行上下文 AO,下一个对象依次类推。

在《JavaScript 深入之变量对象》中讲到,当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级 (词法层面上的父级) 执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。

# 你知道 nullundefined 有什么区别么

# 闭包有什么作用

# Vue 响应式原理

小提示:如果面试者使用的是 Vue 技术栈,那么响应式原理是一个必问的问题,同时面试官经常也会问 Vue 3.0 在响应式原理上的优化方案。

如果对于响应式原理不是很清楚可以查看我之前写的文章基于 Vue 实现一个简易 MVVM / 数据劫持的实现

# 了解 Event Loop 么

小提示:这个题目问到的概率还是蛮大的,这里面试官询问了我浏览器端和 Node 端的 Event Loop 有什么不同点。如果想要知道更多浏览器端的 Event Loop 机制可以查看我之前写的文章你真的理解 $nextTick 么 / JS 引擎线程和事件触发线程 / 事件循环机制

事件触发线程管理的任务队列是如何产生的呢?事实上这些任务就是从 JS 引擎线程本身产生的,主线程在运行时会产生执行栈,栈中的代码调用某些异步 API 时会在任务队列中添加事件,栈中的代码执行完毕后,就会读取任务队列中的事件,去执行事件对应的回调函数,如此循环往复,形成事件循环机制。JS 中有两种任务类型:微任务(microtask)和宏任务(macrotask),在 ES6 中,microtask 称为 jobs,macrotask 称为 task:

  • 宏任务: script (主代码块)、 setTimeoutsetIntervalsetImmediate 、I/O 、UI rendering
  • 微任务: process.nextTick (Nodejs) 、 PromiseObject.observeMutationObserver

Node.js 中 Event Loop 和浏览器中 Event Loop 有什么区别

   ┌───────────────────────┐
┌─>│        timers         │<————— 执行 setTimeout()、setInterval() 的回调
│  └──────────┬────────────┘
|             |<-- 执行所有 Next Tick Queue 以及 MicroTask Queue 的回调
│  ┌──────────┴────────────┐
│  │     pending callbacks │<————— 执行由上一个 Tick 延迟下来的 I/O 回调(待完善,可忽略)
│  └──────────┬────────────┘
|             |<-- 执行所有 Next Tick Queue 以及 MicroTask Queue 的回调
│  ┌──────────┴────────────┐
│  │     idle, prepare     │<————— 内部调用(可忽略)
│  └──────────┬────────────┘     
|             |<-- 执行所有 Next Tick Queue 以及 MicroTask Queue 的回调
|             |                   ┌───────────────┐
│  ┌──────────┴────────────┐      │   incoming:   │ - (执行几乎所有的回调,除了 close callbacks、timers、setImmediate)
│  │         poll          │<─────┤  connections, │ 
│  └──────────┬────────────┘      │   data, etc.  │ 
│             |                   |               | 
|             |                   └───────────────┘
|             |<-- 执行所有 Next Tick Queue 以及 MicroTask Queue 的回调
|  ┌──────────┴────────────┐      
│  │        check          │<————— setImmediate() 的回调将会在这个阶段执行
│  └──────────┬────────────┘
|             |<-- 执行所有 Next Tick Queue 以及 MicroTask Queue 的回调
│  ┌──────────┴────────────┐
└──┤    close callbacks    │<————— socket.on('close', ...)
   └───────────────────────┘
复制代码

Node.js 中宏任务分成了几种类型,并且放在了不同的 task queue 里。不同的 task queue 在执行顺序上也有区别,微任务放在了每个 task queue 的末尾:

  • setTimeout/setInterval 属于 timers 类型;
  • setImmediate 属于 check 类型;
  • socket 的 close 事件属于 close callbacks 类型;
  • 其他 MacroTask 都属于 poll 类型。
  • process.nextTick 本质上属于 MicroTask,但是它先于所有其他 MicroTask 执行;
  • 所有 MicroTask 的执行时机在不同类型的 MacroTask 切换后。
  • idle/prepare 仅供内部调用,我们可以忽略。
  • pending callbacks 不太常见,我们也可以忽略。

# 如何避免回流和重绘

# 浏览器渲染过程

  • 浏览器使用流式布局模型 (Flow Based Layout)
  • 浏览器会把 HTML 解析成 DOM,把 CSS 解析成 CSSOM,DOM 和 CSSOM 合并就产生了 Render Tree
  • 有了 RenderTree 就能知道所有节点的样式,计算节点在页面上的大小和位置,把节点绘制到页面上
  • 由于浏览器使用流式布局,对 Render Tree 的计算通常只需要遍历一次就可以完成,但 table 及其内部元素除外,通常需要多次计算且要花费 3 倍于同等元素的时间,这也是为什么要避免使用 table 布局的原因之一

浏览器渲染过程如下:

  • 解析 HTML,生成 DOM 树
  • 解析 CSS,生成 CSSOM 树
  • 将 DOM 树和 CSSOM 树结合,生成渲染树 (Render Tree)
  • Layout (回流):根据生成的渲染树,进行回流 (Layout),得到节点的几何信息(位置,大小)
  • Painting (重绘):根据渲染树以及回流得到的几何信息,得到节点的绝对像素
  • Display:将像素发送给 GPU,展示在页面上。(这一步其实还有很多内容,比如会在 GPU 将多个合成层合并为同一个层,并展示在页面中。而 css3 硬件加速的原理则是新建合成层,这里我们不展开,之后有机会会写一篇博客)

# 何时触发回流和重绘

何时发生回流:

  • 添加或删除可见的 DOM 元素
  • 元素的位置发生变化
  • 元素的尺寸发生变化(包括外边距、内边框、边框大小、高度和宽度等)
  • 内容发生变化,比如文本变化或图片被另一个不同尺寸的图片所替代。
  • 页面一开始渲染的时候(这肯定避免不了)
  • 浏览器的窗口尺寸变化(因为回流是根据视口的大小来计算元素的位置和大小的)

何时发生重绘(回流一定会触发重绘):

当页面中元素样式的改变并不影响它在文档流中的位置时(例如:color、background-color、visibility 等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘。

有时即使仅仅回流一个单一的元素,它的父元素以及任何跟随它的元素也会产生回流。现代浏览器会对频繁的回流或重绘操作进行优化,浏览器会维护一个队列,把所有引起回流和重绘的操作放入队列中,如果队列中的任务数量或者时间间隔达到一个阈值的,浏览器就会将队列清空,进行一次批处理,这样可以把多次回流和重绘变成一次。你访问以下属性或方法时,浏览器会立刻清空队列:

  • clientWidthclientHeightclientTopclientLeft
  • offsetWidthoffsetHeightoffsetTopoffsetLeft
  • scrollWidthscrollHeightscrollTopscrollLeft
  • widthheight
  • getComputedStyle()
  • getBoundingClientRect()

以上属性和方法都需要返回最新的布局信息,因此浏览器不得不清空队列,触发回流重绘来返回正确的值。因此,我们在修改样式的时候,** 最好避免使用上面列出的属性,他们都会刷新渲染队列。** 如果要使用它们,最好将值缓存起来。

# 如何避免触发回流和重绘

CSS:

  • 避免使用 table 布局。
  • 尽可能在 DOM 树的最末端改变 class。
  • 避免设置多层内联样式。
  • 将动画效果应用到 position 属性为 absolutefixed 的元素上
  • 避免使用 CSS 表达式(例如: calc()
  • CSS3 硬件加速(GPU 加速)

JavaScript:

  • 避免频繁操作样式,最好一次性重写 style 属性,或者将样式列表定义为 class 并一次性更改 class 属性
  • 避免频繁操作 DOM,创建一个 documentFragment ,在它上面应用所有 DOM 操作,最后再把它添加到文档中
  • 也可以先为元素设置 display: none ,操作结束后再把它显示出来。因为在 display 属性为 none 的元素上进行的 DOM 操作不会引发回流和重绘
  • 避免频繁读取会引发回流 / 重绘的属性,如果确实需要多次使用,就用一个变量缓存起来
  • 对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流

# 有赞(二面)

小提示:进入现场面试需要注意好好准备自己的简历,面试官一般会根据项目进行问答。

# 笔试题环节

一开始面试官就发了两张笔试题试卷,总共四道题目,大致考了以下知识点:

  • 作用域
  • 原型链(例如实例属性和原型属性一样,删除实例属性后可以继续访问原型属性问题)
  • 宏任务和微任务的打印顺序
  • Array.prototype.map 的第二个参数

# 项目问答环节

答完试卷面试官就开始问简历上的一些项目,我记得其中几个问题如下(事实上他问的一些问题和简历不是很相关):

  • 你们产品的服务器部署在哪里
  • 你是如何实现一个 Tooltip 组件的,能写一下怎么使用这个组件么(这算什么问题…)
  • 我认识你们海康的一些开发,我知道你们的产品按套数卖的…

我当场就感受到了面试官问的问题很敷衍,可能他觉得我的简历不够好,又或者觉得我能力不行,接下来面试官又让我做了一道算法题…

# 算法题环节

  • 1 块、4 块、5 块,求总数 n 块的最小硬币数

当时没做出来,非科班出身可能做这些确实有些困难,也没有系统的学习,面试官看我很困难的样子,就换了一道题。

  • 1、1、2、3、5、8… 计算第 n 个数的值(斐波那契数列)

这道题还是做出来了,毕竟比较简单,然后面试官说今天先到这里,面试结果会在一星期内通知,然后回来的那天晚上就收到了面试没过的通知。

# 小结

还是蛮感谢这次现场面试的经历,让我知道如果自身不够硬,到哪里都会很被动。面试的好处不仅仅在于检验自己到底有多少能力,更应该发现自身的不足,同时不断的去弥补这些不足。于是我再次捧起之前搁置的《算法导论》,并且创建了一个算法学习演示文档 I-Algorithms,希望可以简化《算法导论》的一些理论知识,使大家对于算法的学习可以变得更加系统全面和简单,也希望通过这个学习使得算法面试会变得更加得心应手,希望感兴趣的同学可以 star 一下。

# 滴滴(一面)

# 你知道哪些安全问题,如何避免

小提示:这里我简单讲解了一下 Vue 中的 v-html 防范 XSS 攻击。

# XSS(跨站脚本攻击)

XSS,即 Cross Site Script,中译是跨站脚本攻击;其原本缩写是 CSS,但为了和层叠样式表 (Cascading Style Sheet) 有所区分,因而在安全领域叫做 XSS。

XSS 攻击是指攻击者在网站上注入恶意的客户端代码,通过恶意脚本对客户端网页进行篡改,从而在用户浏览网页时,对用户浏览器进行控制或者获取用户隐私数据的一种攻击方式。

攻击者对客户端网页注入的恶意脚本一般包括 JavaScript,有时也会包含 HTML 和 Flash。有很多种方式进行 XSS 攻击,但它们的共同点为:将一些隐私数据像 cookie、session 发送给攻击者,将受害者重定向到一个由攻击者控制的网站,在受害者的机器上进行一些恶意操作。

XSS 攻击可以分为 3 类:反射型(非持久型)、存储型(持久型)、基于 DOM。

# 反射型

反射型 XSS 只是简单地把用户输入的数据 “反射” 给浏览器,这种攻击方式往往需要攻击者诱使用户点击一个恶意链接(攻击者可以将恶意链接直接发送给受信任用户,发送的方式有很多种,比如 email, 网站的私信、评论等,攻击者可以购买存在漏洞网站的广告,将恶意链接插入在广告的链接中),或者提交一个表单,或者进入一个恶意网站时,注入脚本进入被攻击者的网站。最简单的示例是访问一个链接,服务端返回一个可执行脚本:

const http = require('http');
function handleReequest(req, res) {
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.writeHead(200, {'Content-Type': 'text/html; charset=UTF-8'});
    res.write('<script>alert("反射型 XSS 攻击")</script>');
    res.end();
}

const server = new http.Server();
server.listen(8001, '127.0.0.1');
server.on('request', handleReequest);
复制代码
# 存储型

存储型 XSS 会把用户输入的数据 “存储” 在服务器端,当浏览器请求数据时,脚本从服务器上传回并执行。这种 XSS 攻击具有很强的稳定性。比较常见的一个场景是攻击者在社区或论坛上写下一篇包含恶意 JavaScript 代码的文章或评论,文章或评论发表后,所有访问该文章或评论的用户,都会在他们的浏览器中执行这段恶意的 JavaScript 代码:

// 例如在评论中输入以下留言
// 如果请求这段留言的时候服务端不做转义处理,请求之后页面会执行这段恶意代码
<script>alert('xss 攻击')</script>
复制代码
# 基于 DOM

基于 DOM 的 XSS 攻击是指通过恶意脚本修改页面的 DOM 结构,是纯粹发生在客户端的攻击:

<h2>XSS: </h2>
<input type="text" id="input">
<button id="btn">Submit</button>
<div id="div"></div>
<script>
    const input = document.getElementById('input');
    const btn = document.getElementById('btn');
    const div = document.getElementById('div');

    let val;
     
    input.addEventListener('change', (e) => {
        val = e.target.value;
    }, false);

    btn.addEventListener('click', () => {
        div.innerHTML = `<a href=${val}>testLink</a>`
    }, false);
</script>
复制代码

点击 Submit 按钮后,会在当前页面插入一个链接,其地址为用户的输入内容。如果用户在输入时构造了如下内容:

'' onclick=alert(/xss/)
复制代码

用户提交之后,页面代码就变成了:

<a href onlick="alert(/xss/)">testLink</a>
复制代码

此时,用户点击生成的链接,就会执行对应的脚本。

# XSS 攻击防范

HttpOnly 防止劫取 Cookie:HttpOnly 最早由微软提出,至今已经成为一个标准。浏览器将禁止页面的 Javascript 访问带有 HttpOnly 属性的 Cookie。上文有说到,攻击者可以通过注入恶意脚本获取用户的 Cookie 信息。通常 Cookie 中都包含了用户的登录凭证信息,攻击者在获取到 Cookie 之后,则可以发起 Cookie 劫持攻击。所以,严格来说,HttpOnly 并非阻止 XSS 攻击,而是能阻止 XSS 攻击后的 Cookie 劫持攻击。

输入检查:不要相信用户的任何输入。 对于用户的任何输入要进行检查、过滤和转义。建立可信任的字符和 HTML 标签白名单,对于不在白名单之列的字符或者标签进行过滤或编码。在 XSS 防御中,输入检查一般是检查用户输入的数据中是否包含 <,> 等特殊字符,如果存在,则对特殊字符进行过滤或编码,这种方式也称为 XSS Filter。而在一些前端框架中,都会有一份 decodingMap, 用于对用户输入所包含的特殊字符或标签进行编码或过滤,如 <,>,script,防止 XSS 攻击:

// vuejs 中的 decodingMap
// 在 vuejs 中,如果输入带 script 标签的内容,会直接过滤掉
const decodingMap = {
  '<': '<',
  '>': '>',
  '"': '"',
  '&': '&',
  '&#10;': '\n'
}
复制代码

输出检查:用户的输入会存在问题,服务端的输出也会存在问题。一般来说,除富文本的输出外,在变量输出到 HTML 页面时,可以使用编码或转义的方式来防御 XSS 攻击。例如利用 sanitize-html 对输出内容进行有规则的过滤之后再输出到页面中。

# CSRF/XSRF(跨站请求伪造)

CSRF,即 Cross Site Request Forgery,中译是跨站请求伪造,是一种劫持受信任用户向服务器发送非预期请求的攻击方式。通常情况下,CSRF 攻击是攻击者借助受害者的 Cookie 骗取服务器的信任,可以在受害者毫不知情的情况下以受害者名义伪造请求发送给受攻击服务器,从而在并未授权的情况下执行在权限保护之下的操作。

Cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。Cookie 主要用于以下三个方面:

  • 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
  • 个性化设置(如用户自定义设置、主题等)
  • 浏览器行为跟踪(如跟踪分析用户行为等)

而浏览器所持有的 Cookie 分为两种:

  • Session Cookie (会话期 Cookie):会话期 Cookie 是最简单的 Cookie,它不需要指定过期时间(Expires)或者有效期(Max-Age),它仅在会话期内有效,浏览器关闭之后它会被自动删除。
  • Permanent Cookie (持久性 Cookie):与会话期 Cookie 不同的是,持久性 Cookie 可以指定一个特定的过期时间(Expires)或有效期(Max-Age)。
res.setHeader('Set-Cookie', ['mycookie=222', 'test=3333; expires=Sat, 21 Jul 2018 00:00:00 GMT;']);
复制代码

上述代码创建了两个 Cookie:mycookie 和 test,前者属于会话期 Cookie,后者则属于持久性 Cookie。

# CSRF 攻击

使登录用户访问攻击者的网站,发起一个请求,由于 Cookie 中包含了用户的认证信息,当用户访问攻击者准备的攻击环境时,攻击者就可以对服务器发起 CSRF 攻击。

在这个攻击过程中,攻击者借助受害者的 Cookie 骗取服务器的信任,但并不能拿到 Cookie,也看不到 Cookie 的内容。而对于服务器返回的结果,由于浏览器同源策略的限制,攻击者也无法进行解析。(攻击者的网站虽然是跨域的,但是他构造的链接是源网站的,跟源网站是同源的,所以能够携带 cookie 发起访问)。

但是攻击者无法从返回的结果中得到任何东西,他所能做的就是给服务器发送请求,以执行请求中所描述的命令,在服务器端直接改变数据的值,而非窃取服务器中的数据。例如删除数据、修改数据,新增数据等,无法获取数据。

# CSRF 攻击防范

验证码:验证码被认为是对抗 CSRF 攻击最简洁而有效的防御方法。从上述示例中可以看出,CSRF 攻击往往是在用户不知情的情况下构造了网络请求。而验证码会强制用户必须与应用进行交互,才能完成最终请求。因为通常情况下,验证码能够很好地遏制 CSRF 攻击。但验证码并不是万能的,因为出于用户考虑,不能给网站所有的操作都加上验证码。因此,验证码只能作为防御 CSRF 的一种辅助手段,而不能作为最主要的解决方案。

Referer Check:根据 HTTP 协议,在 HTTP 头中有一个字段叫 Referer,它记录了该 HTTP 请求的来源地址。通过 Referer Check,可以检查请求是否来自合法的 "源"。

添加 token 验证:要抵御 CSRF,关键在于在请求中放入攻击者所不能伪造的信息,并且该信息不存在于 Cookie 之中。可以在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器端建立一个拦截器来验证这个 token,如果请求中没有 token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。

# 介绍一下 Graphql

小提示:这道题是给自己挖了一个坑,抱着学习的心态尝试使用 Graphql 技术,却没有好好理解是在什么场景下为了解决什么问题才应该使用,也没有好好准备如何描述新技术,往往这种不熟悉的技术自己在简历中应该留存一些心眼,尽量不要提,否则答不上来会很尴尬,让面试官怀疑你的项目成分。

# 什么是 Graphql

GraphQL 是一种 API 查询语言。API 接口的返回值可以从静态变为动态,即调用者来声明接口返回什么数据,可以进一步解耦前后端。在 Graphal 中,预先定义 Schema 和声明 Type 来达到动态获取接口数据的目的:

  • 对于数据模型的抽象是通过 Type 来描述的
  • 对于接口获取数据的逻辑是通过 Schema 来描述的

# 为什么要使用 Graphql:

  • 接口数量众多维护成本高
  • 接口扩展成本高
  • 接口响应的数据格式无法预知
  • 减少无用数据的请求, 按需获取
  • 强类型约束(API 的数据格式让前端来定义,而不是后端定义)

# Type(数据模型的抽象)

Type 简单可以分为两种,一种叫做 Scalar Type (标量类型),另一种叫做 Object Type (对象类型):

  • Scalar Type(标量类型):内建的标量包含,String、Int、Float、Boolean、Enum

  • Object Type(对象类型):感觉类似于 TypeScript 的接口类型

  • Type Modifier(类型修饰符):用于表明是否必填等

# Schema(模式)

定义了字段的类型、数据的结构,描述了接口数据请求的规则

# Query(查询、操作类型)

查询类型: query(查询)、mutation(更改)和 subscription(订阅)

  • query(查询):当获取数据时,应当选取 Query 类型
  • mutation(更改):当尝试修改数据时,应当使用 mutation 类型
  • subscription(订阅):当希望数据更改时,可以进行消息推送,使用 subscription 类型
# Resolver(解析函数)

提供相关 Query 所返回数据的逻辑。Query 和与之对应的 Resolver 是同名的,这样在 GraphQL 才能把它们对应起来。解析的过程可能是递归的,只要遇到非标量类型,会尝试继续解析,如果遇到标量类型,那么解析完成,这个过程叫做解析链。

# 说说 Vue 中 $nextTick 的实现原理

小提示:如果面试者使用的是 Vue 技术栈,那么 $nextTick 的原理是一个高频问题,面试者借此可以追问的东西较多,例如浏览器的 Event Loop、微任务和宏任务、Node.js 的 Event Loop、异步更新 DOM(响应式的数据 for 循环改变了 1000 次为什么视图只更新了一次)、 $nextTick 历史版本问题等等。

这个如果不是很清楚的具体可查看我之前写的文章你真的理解 $nextTick 么

# Vue 响应式原理

# 谈谈对闭包的理解

# JSONP 的实现原理

# CSS 中的 BFC

# 如何实现居中

# 水平居中

  • 若是行内元素,给其父元素设置 text-align:center 即可实现行内元素水平居中
  • 若是块级元素,该元素设置 margin:0 auto 即可(元素需要定宽)
  • 若是块级元素,设置父元素为 flex 布局,子元素设置 margin:0 auto 即可(子元素不需要定宽)
  • 使用 flex 2012 年版本布局,可以轻松的实现水平居中,子元素设置如下:
// flex容器
<div class="box"> 
 // flex项目
 <div class="box-center">
 </div>
</div>


.box {
  width: 200px;
  height: 200px;
  display: flex;
  // 使内部的flex项目水平居中
  justify-content: center;
  background-color: pink;
}

/* .box-center {
  width: 50%;
  background-color: greenyellow;
} */


复制代码
  • 使用绝对定位和 CSS3 新增的属性 transform (这个属性还和 GPU 硬件加速、固定定位相关)
.box {
  width: 200px;
  height: 200px;
  position: relative;
  background-color: pink;
}

.box-center {
  position: absolute;
  left:50%;
  // width: 50%;
  height: 100%;
  // 通过 translate() 方法,元素从其当前位置移动,根据给定的 left(x 坐标) 和 top(y 坐标) 位置参数:
  // translate(x,y)	定义 2D 转换。
  // translateX(x)	定义转换,只是用 X 轴的值。
  // translateY(y)	定义转换,只是用 Y 轴的值。
  // left: 50% 先整体向父容器的左侧偏移50%,此时是不能居中的,因为元素本身有大小
  // 接着使用transform使用百分比向左偏移本身的宽度的一半实现水平居中(这里的百分比以元素本身的宽高为基准)
  transform:translate(-50%,0);
  background-color: greenyellow;
}
复制代码
  • 使用绝对定位和 margin-left (元素定宽)
.box {
  width: 200px;
  height: 200px;
  position: relative;
  background-color: pink;
}

.box-center {
  position: absolute;
  left:50%;
  height: 100%;
  // 类似于transform
  // width: 50%;
  // margin-left: -25%;
  width: 100px;
  margin-left: -50px;
  background-color: greenyellow;
}
复制代码

# 垂直居中

  • 若元素是单行文本,则可设置 line-height 等于父元素高度
  • 若是块级元素,设置父元素为 flex 布局,子元素设置 margin: auto 0 即可(子元素不需要定宽)
  • 若元素是行内块级元素,基本思想是使用 display: inline-block, vertical-align: middle 和一个伪元素让内容块处于容器中央:
.box {
  height: 100px;
}

.box::after, .box-center{
  display:inline-block;
  vertical-align:middle;
}
.box::after{
  content:'';
  height:100%;
}
复制代码
# 居中元素高度不定
  • 可用 vertical-align 属性( vertical-align 只有在父层为 td 或者 th 时才会生效,,对于其他块级元素,例如 div、p 等,默认情况是不支持的),为了使用 vertical-align ,我们需要设置父元素 display:table , 子元素 display:table-cell;vertical-align:middle
.box {
  height: 100px;
  display: table;
}

 .box-center{
    display: table-cell;
    vertical-align:middle;
}
复制代码
  • 可用 Flex 2012 版,这是 CSS 布局未来的趋势。Flexbox 是 CSS3 新增属性,设计初衷是为了解决像垂直居中这样的常见布局问题:
.box {
  height: 100px;
  display: flex;
  align-items: center;
}
复制代码

优点:内容块的宽高任意,优雅的溢出。可用于更复杂高级的布局技术中。缺点:IE8/IE9 不支持、需要浏览器厂商前缀、渲染上可能会有一些问题。

  • 可用 transform ,设置父元素相对定位:
.box {
  height: 100px;
  position: relative;
  background-color: pink;
}

.box-center {
  position: absolute;
  top: 50%;
  transform: translate(0, -50%);
  background-color: greenyellow;
}
复制代码

缺点:IE8 不支持,属性需要追加浏览器厂商前缀,可能干扰其他 transform 效果,某些情形下会出现文本或元素边界渲染模糊的现象。

# 居中元素高度固定
  • 设置父元素相对定位,子元素如下 css 样式:
.box {
  position:relative;
  height: 100px;
  background-color: pink;
}

.box-center{
  position:absolute;
  top:50%;
  // 注意不能使用百分比
  // margin的百分比计算是相对于父容器的width来计算的,甚至包括margin-top和margin-bottom
  height: 50px;
  margin-top: -25px;
}
复制代码
  • 设置父元素相对定位,子元素如下 css 样式:
.box {
  position:relative;
  width: 200px;
  height: 200px;
  background-color: pink;
}

.box-center{
  position:absolute;
  top: 0;
  bottom: 0;
  margin: auto 0;
  height: 100px;
  background-color: greenyellow;
}
复制代码

# 水平垂直居中

  • Flex 布局(子元素是块级元素)
.box {
  display: flex;
  width: 100px;
  height: 100px;
  background-color: pink;
}

.box-center{
  margin: auto;
  background-color: greenyellow;
}
复制代码
  • Flex 布局
.box {
  display: flex;
  width: 100px;
  height: 100px;
  background-color: pink;
  justify-content: center;
  align-items: center;
}

.box-center{
  background-color: greenyellow;
}
复制代码
  • 绝对定位实现 (定位元素定宽定高)
.box {
  position: relative;
  height: 100px;
  width: 100px;
  background-color: pink;
}

.box-center{
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  top: 0;
  margin: auto;
  width: 50px;
  height: 50px;
  background-color: greenyellow;
}
复制代码

# 用过 Flex 么,能简单介绍一下么

小提示:如果在项目中使用过,可简单介绍一下自己使用 Flex 解决过什么问题,这里我在项目中印象比较深刻的是使用 Flex 解决上面内容高度不固定,下面内容高度自动撑满父容器剩余高度的问题。

如果不是很清楚 Flex,可以查看阮一峰的文章 Flex 布局教程:语法篇。面试官追问,那么除了 Flex,你还知道 Grid 么?这个由于兼容性问题,我一直没有好好研究过,这里可查看阮一峰的文章 CSS Grid 网格布局教程

# bind 的源码实现

小提示:这里我回答使用函数柯里化加上 apply 或者 call 可实现 bind ,面试官追问了一些具体的实现细节。

后来我自己粗糙的实现了一下,仅供参考:

Function.prototype.myCall = function (obj) {
  obj.fn = this
  let args = [...arguments].splice(1)
  let result = obj.fn(...args)
  delete obj.fn
  return result
}

Function.prototype.myApply = function (obj) {
  obj.fn = this
  let args = arguments[1]
  let result
  if (args) {
    result = obj.fn(...args)
  } else {
    result = obj.fn()
  }

  delete obj.fn

  return result
}

Function.prototype.myBind = function (obj) {
  let context = obj || window
  let _this = this
  let _args = [...arguments].splice(1)

  return function () {
    let args = arguments
    // 产生副作用
    // return obj.fn(..._args, ...args)
    return _this.apply(context, [..._args, ...args])
  }
}

function myFun (argumentA, argumentB) {
  console.log(this.value)
  console.log(argumentA)
  console.log(argumentB)
  return this.value
}

let obj = {
  value: 'ziyi2'
}
console.log(myFun.myCall(obj, 11, 22))
console.log(myFun.myApply(obj, [11, 22]))
console.log(myFun.myBind(obj, 33)(11, 22))
复制代码

# 伪类和伪元素的区别

小提示:这个问题我当时懵了一下,一下子没反应过来面试官想要问什么,就答了这两者在 CSS 优先级上有区别,然后由于遇到不会的问题有些紧张就多说了一些废话,但显然这不是面试官想要的答案并且消耗了面试官面试的耐心,说他问的不是这个。这里再次提示大家,如果你感觉你说不清楚,但是你又知道一点,我建议你说不知道,不要纠结,面试官不会因为你不知道一个问题就 PASS 你,相反你说了一些无关紧要的废话,反而在消耗面试官的耐性,增加负面印象。

伪类和伪元素是用来修饰不在文档树中的部分,比如,一句话中的第一个字母,或者是列表中的第一个元素。下面分别对伪类和伪元素进行解释:

伪类用于当已有元素处于的某个状态时,为其添加对应的样式,这个状态是根据用户行为而动态变化的。比如说,当用户悬停在指定的元素时,我们可以通过:hover 来描述这个元素的状态。虽然它和普通的 css 类相似,可以为已有的元素添加样式,但是它只有处于 dom 树无法描述的状态下才能为元素添加样式,所以将其称为伪类。

伪元素用于创建一些不在文档树中的元素,并为其添加样式。比如说,我们可以通过:before 来在一个元素前增加一些文本,并为这些文本添加样式。虽然用户可以看到这些文本,但是这些文本实际上不在文档树中。

# 区别

伪类的操作对象是文档树中已有的元素,而伪元素则创建了一个文档树外的元素。因此,伪类与伪元素的区别在于:有没有创建一个文档树之外的元素。

CSS3 规范中的要求使用双冒号 (::) 表示伪元素,以此来区分伪元素和伪类,比如::before 和::after 等伪元素使用双冒号 (:😃,:hover 和:active 等伪类使用单冒号 (😃。除了一些低于 IE8 版本的浏览器外,大部分浏览器都支持伪元素的双冒号 (::) 表示方法。

# 小结

对于滴滴的这次面试,我感觉到自己准备的不是很充分,尤其是自己简历上的项目技术 Graphql。同时对于自己不会的题目强行做了一些解释说明,其实应该简洁明了的告诉面试官不会。

# 51 信用卡(一面)

# 说说 DOM 事件流

# 在 ES5 中如何实现继承

小提示:这里我说了很多,从借用构造函数到组合继承到寄生组合继承,但面试官其实最想听到的是寄生组合继承。面试官还追问我具体要如何实现寄生组合继承。当然这里其实问的问题还可以很多,比如 ES6 的类继承和 ES5 中的继承有什么区别。

如果对于继承以及继承的区别不是很清楚的,可以随便看看我之前写的大笔记 js 类和继承

# 绝对定位

小提示:这个建议大家好好回忆一下,例如子元素是相对父元素的 padding、border 还是 content 进行定位之类的,当时面试官问的就这么细。

# 消抖和节流

小提示:面试官只是问了一下具体的使用场景,没有问实现原理。

# 简单消抖

function debounce (fn, wait = 1000) {
  let timeOutId

  return function () {
    let context = this

    if (timeOutId) {
      clearTimeout(timeOutId)
    }

    timeOutId = setTimeout(() => {
      fn.apply(context, arguments)
    }, wait)
  }
}
复制代码

# 带立即执行参数的消抖

function debounceImmediate (fn, wait = 1000, immediate) {
  let timeOutId, context, args

  const later = (immediate) => setTimeout(() => {
    if (!immediate) {
      fn.apply(context, args)
      timeOutId = context = args = null
    }
  }, wait)

  return function () {
    if (!timeOutId) {
      timeOutId = later(true)

      if (immediate) {
        fn.apply(this, arguments)
      }

      context = this
      args = arguments
    } else {
      clearTimeout(timeOutId)
      timeOutId = later(false)
    }
  }
}
复制代码

# 节流

function throttle (fn, wait) {
  let timeoutId = null
  return function () {
    let context = this
    if (!timeoutId) {
      timeoutId = setTimeout(() => {
        fn.apply(context, arguments)
        timeoutId = null
      }, wait)
    }
  }
}
复制代码

# Vue 中的 computed 实现原理

小提示:这个问题面试官问的很细,绝对是想问你是否阅读过源码。他首先问 computed 的实现原理,其次问了这样一个问题:现在有两个 computed 计算值,其中一个 computed 计算值为什么可以依赖另外一个 computed 计算值。这里顺便将 watch 的实现原理也贴上。

# watch 的实现原理

watch 的分类:

  • deep watch(深层次监听)
  • user watch(用户监听)
  • computed watcher(计算属性)
  • sync watcher(同步监听)

watch 实现过程:

  • watch 的初始化在 data 初始化之后(此时的 data 已经通过 Object.defineProperty 的设置成响应式)
  • watch 的 key 会在 Watcher 里进行值的读取,也就是立马执行 get 获取 value(从而实现 data 对应的 key 执行 getter 实现对于 watch 的依赖收集),此时如果有 immediate 属性那么立马执行 watch 对应的回调函数
  • 当 data 对应的 key 发生变化时,触发 user watch 实现 watch 回调函数的执行

# computed 运行原理

  • computed 的属性是动态挂载到 vm 实例上的,和普通的响应式数据在 data 里声明不同
  • 设置 computed 的 getter,如果执行了 computed 对应的函数,由于函数会读取 data 属性值,因此又会触发 data 属性值的 getter 函数,在这个执行过程中就可以处理 computed 相对于 data 的依赖收集关系了
  • 首次计算 computed 的值时,会执行 vm.computed 属性对应的 getter 函数(用户指定的 computed 函数,如果没有设置 getter,那么将当前指定的函数赋值 computed 属性的 getter),进行上述的依赖收集
  • 如果 computed 的属性值又依赖了其他 computed 计算属性值,那么会将当前 target 暂存到栈中,先进行其他 computed 计算属性值的依赖收集,等其他计算属性依赖收集完成后,在从栈中 pop 出来,继续进行当前 computed 的依赖收集
var vm = new Vue({
  el: '#demo',
  data: {
    firstName: 'Foo',
    lastName: 'Bar'
  },
  computed: {
    fullName: function () {
      return this.firstName + ' ' + this.lastName
    }
  }
})
复制代码

由于 this.firstNamethis.lastName (上面是 Vue 官方示例)都是响应式变量,因此会触发它们的 getter,根据我们之前的分析,它们会把自身持有的 dep 添加到当前正在计算的 watcher 中,这个时候 Dep.target 就是这个 computed watcher,具体步骤如下:

  • data 属性初始化 getter setter
  • computed 计算属性初始化,提供的函数将用作属性 vm.fullName 的 getter
  • 当首次获取 fullName 计算属性的值时,Dep 开始依赖收集
  • 在执行 message getter 方法时,如果 Dep 处于依赖收集状态,则判定 firstNamelastNamefullName 的依赖,并建立依赖关系
  • firstNamelastName 发生变化时,根据依赖关系,触发 fullName 的重新计算
  • 如果计算值没有发生变化,不会触发视图更新

通过以上的分析,我们知道计算属性本质上就是一个 computed watcher,也了解了它的创建过程和被访问触发 getter 以及依赖更新的过程,其实这是最新的计算属性的实现,之所以这么设计是因为 Vue 想确保不仅仅是计算属性依赖的值发生变化,而是当计算属性最终计算的值发生变化才会触发渲染 watcher 重新渲染,本质上是一种优化。

# computed 计算值为什么还可以依赖另外一个 computed 计算值

小提示:这个问题当时完全不知道,哎,官方源码的套路太深了…

这里希望有大神可以补充说明一下。

# 周期函数有哪些( beforeCreatedcreated 中间都做了什么

初始化 datapropscomputedwatcherprovide 。官方源码具体位置 src/core/instance/init.js

callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
复制代码

# 小结

51 信用卡的这次面试其实面试官考察的点还是蛮深入的,问了一些 Vue 底层源码的实现,总体感觉自己回答的还可以,但是面试官说:你应该去阿里…

# 阿里部门未知(一面)

# 说说 Webpack 的实现原理

小提示:这个直接回答不知道,问题较大,我这里猜测一下是类似 Babel 和 AST 抽象语法树相关,有空去看下源码。

这个问题希望同学可以补充一下。

# 首屏优化有哪些解决方案

小提示:这个问题在回答懒加载的过程中,面试官追问懒加载的 Webpack 配置,我说了和代码切割相关。

关于懒加载,这里推荐一篇非常好的文章:Webpack 大法之 Code Splitting

# Node.js 的加载机制( requiremodule.exports

小提示:这个问题其实是非常常见的问题,建议大家阅读一下源码,有些也可能会问一下比较简单的问题,例如 module.exportsexports 的区别,或者也可能问 CommonJS 引入和 ES6 引入的区别。

# 你觉得你最擅长什么

小提示:这个问题是个大坑阿,我这里直接回答我什么都不擅长,这样回答显然面试官是不会不满意的,建议大家在面试前好好想想自己到底擅长啥。

# React 和 Vue 的区别

小提示:这里 React 真的好久没用了,几乎忘记了,大致说了下单向数据流、双向数据绑定、数据监听方式、JSX 以及 Vue 的单文件组件、函数式编程、Vue 的指令之类的。

这个问题希望同时熟悉 React 和 Vue 的同学可以补充一下。

# React、Vue 和 JQuery 在什么场景下怎么选型

这个问题希望同学可以补充一下。

# Vue 的响应式原理

# 什么情况下会阻塞 DOM 渲染

小提示:面试官这里应该想问 DOM 渲染的过程中可能有哪些情况会阻塞渲染。我当时回答不知道。

这个问题希望同学可以补充一下。

# 有哪些异步函数

小提示:回答了宏任务和微任务。

# 讲讲 MVVM,说说与 MVC 有什么区别

小提示:这个问题我专门发了一篇掘金文章,但是很多人好像都不是很感兴趣的样子,但是面试官真的就问了这样一个问题。

这里推荐我之前写的掘金文章基于 Vue 实现一个简易 MVVM/MV * 设计模式的演变历史,一开始重点讲解了 MVC、MVP 以及 MVVM 的演变过程和区别。

# 阿里 CBU 技术部(一面)

# 说说 z-index 有什么需要注意的地方

小提示:真的忘记的差不多了,就简单说了只能在同一层叠上下文中进行 z-index 值比较、和绝对定位的关系, z-index 值不需要设置过大,只需要理清楚层级关系即可。面试官追问了 z-index 值和 background 的覆盖关系,还追问了绝对定位元素以及后来居上的准则。面试官还问了 z-index 默认值是什么, 0auto 有没有区别?真的对于 CSS 可能平常就用的不多,所以这个问题答的不是很好。

可能面试官最想知道的是下面这张图:

这里附上张鑫旭的文章深入理解 CSS 中的层叠上下文和层叠顺序

这里由于回答了定位,面试官追问固定定位的元素是相对于什么进行定位?相对定位会脱离正常文档流么?绝对定位是相对于什么元素进行定位?

# 熟悉 CSS3 动画么

小提示:CSS3 动画硬件加速?CSS3 动画的性能问题(重绘和重流,是否需要脱离正常文档流)?这个我当时答不知道,确实平常用的很少,如果熟悉 Vue 过渡动画的同学可以讲讲过渡动画?

# 有没有做过什么可视化的项目

小提示:我的回答:地图算么?基于 OpenLayers 设计过地图的 Vue 组件库。

对于可视化希望同学可以补充一下。

# 你觉得你最擅长的是什么

小提示:这个问题简直就是给人挖坑。

# Flex 实现两列布局

这里简单实现一下(其实应该使用 flex-basis 属性):

<div class="box">
  <div class="box-left"></div>
  <div class="box-right"></div>
</div>
复制代码
.box {
  height: 200px;
  display: flex;
}

.box > div {
  height: 100%;
}

.box-left {
  width: 200px;
  background-color: blue;
}

.box-right {
  flex: 1; // 设置flex-grow属性为1,默认为0
  overflow: hidden;
  background-color: red;
}
复制代码

# ES6/ES7/ES8 的特性

# 说说 DOM 事件流

小提示:面试官追问事件委托有什么优点(起码两个以上)、 target / currentTarget / relateTarget 具体指向什么目标。

# 你觉得你有做过推动流程或者改善流程的事件么,举例说明

小提示:这个如果做过什么规范或者开发工具之类的,应该比较好回答。

# 小结

总体来说这次面试面得很细,有些知识点已经忘记,建议大家面试前把一些感觉不是很熟悉的原生知识点回忆起来,尤其是在开发中都不怎么会使用一些 CSS 样式设计的童鞋(现在很多都是组件库的设计方案,样式早已经封装掉了)。

# 阿里企业智能事业部(一面)

# Event Loop

# Webpack 的 loader 和 plugins 的区别

小提示:当时直接回答不知道,确实 Webpack 我只会用,还没了解过内部的实现原理和构成。这个后续无论如何都要好好理解一下原理。

这个问题希望同学可以补充一下。

# HTTP 状态码 206 是干什么的

小提示:工作中没有遇到过需要上传下载大型文件,所以这个问题当时老老实实回答不知道。具体应该和断点续传相关,可能也需要回答一些 range 的头部信息等。

# React 高阶组件的作用有哪些

小提示:好久没用过 React 了,大致只知道 Racct 是单向数据流的,利用高阶组件可以实现类似于 Vue 的双向数据绑定。

这个问题希望同学可以补充一下。

# React 和 Vue 的区别

# Service Worker 有哪些作用

小提示:当时怕说错,老老实实回答不知道。后来查了一下应该和缓存以及 HTTP 请求拦截相关。

这个问题希望同学可以补充一下。

# 跨域

# 文件上传的二进制具体是怎么处理的

小提示:只知道上传的头信息是 application/x-www-form-urlencoded ,也可以对上传的文件的数据进行拦截处理,例如对上传文件的信息进行加密处理。

这个问题希望同学可以补充一下。

# Vue 响应式原理

# 首屏加载性能优化

# 小结

其实这一次面试自己感觉面试的不是很好(尽管面试官问的确实比我上面列出的问题多),因为有好几个问题自己确实不清楚。这里再次建议大家不知道就是回答不知道,这样不会对面试官造成一些负面印象。这一次面试能够通过运气占了很大一部分。

# 阿里企业智能事业部(二面)

# computed 的实现原理

# Vue 的整个实现原理

小提示:当时面试官问的蛮好玩的,他问从开始写一个.vue 文件开始到 DOM 渲染到页面上,Vue 做了哪些工作。然后我当时没理解面试官是要问 vue-loader?DOM 树的渲染过程?来来回回试探性的问了面试官几次,才理解原来面试官想知道 Vue 源码的整个实现过程。

大家如果想了解 Vue 源码实现的整个粗略过程,可以看下之前写的文章基于 Vue 实现一个简易 MVVM/Vue 的运行机制简述

# 通讯

小提示:由于这边涉及到一些海康的设备(上下位机通信),面试官问我如何知道上位机软件给下位机设备发送了 5 次信息。这个其实大部分 Web 前端开发在工作上很难遇到类似的问题,辛亏我以前毕业设计中做过上下位机的 TCP 通讯。后来我从 Leader 面那里了解到二面面试官应该是做 iot 物联网开发这一块的。

请求帧数据结构如下:

帧头 帧序号 帧负载 帧校验 帧尾
2 Byte 1 Byte N Byte 1 Byte 1 Byte

这里帧头使用 2 字节识别,校验可以采用 CRC 校验,帧序号用来识别发送了几次信息。

# Chrome 插件如何屏蔽广告

小提示:这个问题当时回答不知道,其实后面想想最简单的办法是先找出广告元素的一些通用特性,然后在 Chrome 插件中通过注入脚本的形式将这些广告元素隐藏掉。

这里不知道有没有更好的其他方式,例如不知道 Service Work 对请求拦截处理是否可以有效屏蔽广告等,这个问题希望同学可以补充一下。

# 如何判断两个变量相等

小提示:这里需要分基本类型和引用类型,面试官在这里具体想问的是 Object.is 的实现原理。这是面试官问我的第一个问题,当时直接回答不知道,内心都觉得接下来要凉凉了。

# Watch 的运行原理

# Vue 的数据为什么频繁变化但只会更新一次

小提示:这里问的是 Vue 源码对于视图更新的优化。我这里的回答是乱糟糟的,希望有同学能够给出一个精准并且简短的回答。

Vue 异步执行 DOM 更新。只要观察到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作上非常重要。然后,在下一个的事件循环 “tick” 中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部尝试对异步队列使用原生的 Promise.thenMessageChannel ,如果执行环境不支持,会采用 setTimeout (fn, 0) 代替。

另外,关于 waiting 变量,这是很重要的一个标志位,它保证 flushSchedulerQueue 回调($nextTick 中执行)允许被置入 callbacks 一次。

因为 Vue 的事件机制是通过事件队列来调度执行,会等主进程执行空闲后进行调度,所以先会去等待所有的同步代码执行完成之后再去一次更新。这样的性能优势很明显,比如:

现在有这样的一种情况, mounted 的时候 test 的值会被循环执行 1000 次。 每次时,都会根据响应式触发 setter->Dep->Watcher->update->run 。 如果这时候没有异步更新视图,那么每次 ++ 都会直接操作 DOM 更新视图,这是非常消耗性能的。 所以 Vue 实现了一个 queue 队列,在下一个 tick(或者是当前 tick 的微任务阶段)统一执行 queue 中 Watcher 的 run。同时,拥有相同 id 的 Watcher 不会被重复加入到该 queue 中去,所以不会执行 1000 次 Watcher 的 run。最终更新视图只会直接将 test 对的 DOM 的 0 变成 1000。 保证更新视图操作 DOM 的动作是在当前栈执行完以后下一个 tick(或者是当前 tick 的微任务阶段)的时候调用,大大优化了性能。

执行顺序 update -> queueWatcher -> 维护观察者队列(重复id的Watcher处理) -> waiting标志位处理(保证需要更新DOM或者Watcher视图更新的方法flushSchedulerQueue只会被推入异步执行的$nextTick回调数组一次) -> 处理$nextTick(在为微任务或者宏任务中异步更新DOM)->

  • Vue 是异步更新 Dom 的,Dom 的更新放在下一个宏任务或者当前宏任务的末尾(微任务)中进行执行

由于 VUE 的数据驱动视图更新是异步的,即修改数据的当下,视图不会立刻更新,而是等同一事件循环中的所有数据变化完成之后,再统一进行视图更新。在同一事件循环中的数据变化后,DOM 完成更新,立即执行 nextTick(callback) 内的回调。

vue 和 react 一样,对 dom 的修改都是异步的。它会在队列里记录你对 dom 的操作并进行 diff 操作,后一个操作会覆盖前一个,然后更新 dom。

# Event Loop

# 除了 Flex 还可以用什么进行布局

小提示:我猜这里面试官想问的是 Grid,当时说不知道。

# 绝对定位、固定定位和 z-index

小提示:感谢 CBU 技术部的面试官。

# 绝对定位

  • 一旦给元素加上 absolutefloat 就相当于给元素加上了 display:block
  • absolute 元素覆盖正常文档流内元素(不用设 z-index,自然覆盖)
  • 可以减少重绘和回流的开销(如 absolute+ top:-9999em ,或 absolute + visibility:hidden ,将动画效果放到 absolute 元素中)

# 属性介绍

  • static ,默认值。位置设置为 static 的元素,它始终会处于文档流给予的位置。
  • inherit ,规定应该从父元素继承 position 属性的值。但是任何的版本的 Internet Explorer (包括 IE8)都不支持属性值 “inherit”。
  • fixed ,生成绝对定位的元素。默认情况下,可定位于相对于浏览器窗口的指定坐标。元素的位置通过 “left”, “top”, “right” 以及 “bottom” 属性进行规定。不论窗口滚动与否,元素都会留在那个位置。但当祖先元素具有 transform 属性且不为 none 时,就会相对于祖先元素指定坐标,而不是浏览器窗口。
  • absolute ,生成绝对定位的元素,相对于距该元素最近的已定位的祖先元素进行定位。此元素的位置可通过 “left”、”top”、”right” 以及 “bottom” 属性来规定。
  • relative ,生成相对定位的元素,相对于该元素在文档中的初始位置进行定位。通过 “left”、”top”、”right” 以及 “bottom” 属性来设置此元素相对于自身位置的偏移。

浮动、绝对定位和固定定位会脱离文档流,相对定位不会脱离文档流,绝对定位相对于该元素最近的已定位的祖先元素,如果没有一个祖先元素设置定位,那么参照物是 body 层。

绝对定位相对于包含块的起始位置:

  • 如果祖先元素是块级元素,包含块则设置为该元素的内边距边界。
  • 如果祖先元素是行内元素,包含块则设置为该祖先元素的内容边界。

问答题:

  • 定位的元素的起始位置为父包含块的内边距(不会在 border 里,除非使用负值,会在 padding 里)
  • 定位的元素的 margin 还是能起作用的
  • background 属性是会显示在 border 里的
  • z-index 是有层叠层级的,需要考虑同一个层叠上下文的层叠优先级
  • z-index 是负值不会覆盖包含块的背景色(但是如果有内容,会被包含块的内容覆盖)
  • z-index 的值影响的元素是定位元素以及 flex 盒子
  • 上面一个定位元素,下面一个正常流的元素,定位元素会覆盖在正常流元素之上,除非给 z-index 是负值
  • 页面根元素 html 天生具有层叠上下文,称之为 “根层叠上下文”

# 小结

这一次面试官问我的第一个问题 Object.is 就没答上来,不过面试官显然没有因为开头答的不好就否定面试者。大家如果在面试时第一个问题就答不上来,不要慌,要保持良好的心态,把接下来能答的问题好好答上来。可能很多同学会疑问,好像还有好几个问题感觉没答上来,但是可能只要有一个问题答的非常出彩,仍然可以弥补那些没答上来的问题(这里面试官当时说 Vue 源码的实现过程我说的比较清楚,还没有一个面试者答的比我更清楚的)。

# 阿里企业智能事业部(Leader 面)

三面是 Leader 现场面,我当时特别担心有赞二面的情况发生,冷不丁又给你来一道算法题,这些真是我最不擅长的点。因为有点心虚我就问了下在阿里的师兄(师兄可能也做招聘工作,当时还怪我没有找他内推…),他说现场面其实最主要的是好好准备简历上的内容,面试官一般都会根据简历进行问答,还说他当时面试阿里时会让他画一些框架层次图(这个我当时没在意,结果面试官确实让我根据其中某个项目画一个框架层次图)。Leader 面的时候在场的有两个面试官和一个 HR。

# Leader(一)面试

先是进来一个气场很足的 Leader,看起来很权威,但是问问题还蛮随意的,就简单的让我介绍一下自己做的项目,然后翻看了我做的一些东西。感觉他好像有点心不在焉,翻看的很随意,我在回答问题的时候用余光关注了一下大佬的表情,感觉他在我项目经历那一块停留了非常长的时间。

# Leader(二)面试

我正回答着自己的项目经历,Leader 二和 HR 进来了,等我回答完 Leader 一就让 Leader 二开始面我。Leader 二就问了我其中的两个项目。问我的第一个项目是自己做的公司内部的工具,他问这个平台有什么可以衡量的数据表明公司内部人员的使用情况。我回答当时因为领导觉得没必要做,就没有做数据统计这一块,告诉了他数据库里的一些真实数据情况。然后他问 PV、UV 应该怎么统计(我当时还厚脸皮的问他 PV 和 UV 是什么)?如果访问的页面出不来 PV 怎么统计?页面有没有做什么行为监测?页面访问量过大怎么处理?我大致讲了一些我的思路。

接着问我第二个项目(Low Code 相关),我就回答了这个项目的技术体系,从以前做了什么到现在做到什么程度,到未来需要做成什么样,统统仔细的说了一遍。Leader 二就问我未来做成什么样能不能思考一下怎么做,给了我 5 分钟的时间(这期间他一直反复的在翻阅我的简历)。然后我就假装思考了 5 分钟左右,其实脑子里一片空白,当时对于未来要做成什么样还只是个构思。然后 Leader 二还是很体贴的,他说你可以在墙上画一画(墙上可以写字),我就大致画了画,Leader 二问我能不能画一画这个项目的框架层次图,我就简单的画了画… 最后 Leader 二直接说你们做的太 Low 了,这个(Low Code)在我们这里已经是两年前的技术了…(这个我还是要解释下,我所在的部门从开始用 Vue 到目前只有短短的两年时间,在这两年时间里技术体系还是飞速的在沉淀和发展,我离开之前已经构思并实现了部分 Vue 技术栈的 Low Code 解决方案,如果这方面感兴趣的同学也可以找我沟通)。

Leader 二还蛮好玩的,他说 Low Code 如果真的做出来了,都没前端什么事情了,那你干嘛去?顺着这个问题他还问我未来的前端应该怎么发展?未来前端有哪些可以挖掘的点?我回答了一些 Graphql、可视化等,我还说了一个特别搞笑的回答,我说从以往的发展来看,前端应该抢占后端的资源,把后端限制我们的事情让前端也能做,让前端更加解放。Leader 二当场就进行了反驳,说是要有价值才做,而不是为了能做而做,吓得我不轻… 然后 Leader 二还详细的跟我解释了未来发展这个问题他希望得到什么回答,当时还是觉得 Leader 二蛮亲切的。

# HR 面试

Leader 二问完以后 HR 就接着问我了以下几个问题:

  • 为什么要离开现在的公司
  • 以前公司的岗位制度是什么样
  • 你是校招进去的么
  • 你现在的岗位等级情况
  • 你的绩效情况
  • 你领导对你的评价是怎么样的
  • 领导是不是经常找你沟通

然后 Leader 一顺着 HR 问了一个小问题:

  • 你未来对于你的职业有什么规划

最后问我还有什么想问的,我当时已经被三个人问的有点迷迷糊糊了,然后想了想说没有。

# 小结

这次现场面其实我感觉自己面得不是很好,总感觉自己要挂了。总共面了将近 1 个半小时左右,尤其是 Leader 二的问题很多不是他想要的答案,但是最终居然过了。

# 阿里企业智能事业部(HR 面)

企业智能事业部 Leader 面后又收到了 HR 面的面试通知,这一轮面试大致问了以下问题:

  • 你为什么要离开现在的公司
  • 你们公司的岗位等级是怎么评定的,你现在是什么岗位等级
  • 谈谈你在公司的绩效情况
  • 你觉得你做的最有成就感的一件事
  • 你一般解决问题的方法有哪些
  • 你是因为什么契机选择做前端
  • 你有对你所在的公司做过什么流程或制度规范上的改进么
  • 你最近在看什么书,和工作相关么,你为什么要看这些书
  • 看到你之前还面试了其他两个部门都挂在了一面,你感觉是什么原因
  • 你期望的薪资待遇是多少

小提示:这里 HR 会问的其实不止这些问题,例如你为什么喜欢 Web 前端这个岗位、你未来的职业规划、你觉得你的优点和缺点有哪些、为什么选择阿里巴巴、对之前几个面试官做下评价、你用过阿里的哪些产品顺便谈谈这些产品的优缺点、你对于互联网是怎么理解的…

# 小结

对于 HR 面还是要好好准备的,尤其是有些问题还是很容易挖坑的,例如你为什么离开现在的公司(你当然不应该抱怨现在的公司有哪些不好的地方,更多的应该表明自己想要寻找更好的发展机会,自己的一些现实因素,比如对于我而言是现在应聘的公司离自己的家更近,又或者是自己工作到达了迷茫期,想跳出迷茫期等等),你觉得你做的最有成就感的一件事(你要是说个简单的,HR 会觉得你工作能力不强),你一般解决问题的方法有哪些(HR 当然也想考察你解决问题的能力,你要是说什么百度啊之类的 HR 当然会觉得你解决问题的能力不强),你期望的薪资待遇是多少(你要是不喜欢这家公司,可以期望高一些,你要是很喜欢这家公司面试过程很愉快上浮个 30% 左右,面试过程一般上浮个 20% 左右)。

# 友情链接

这里推荐阅读之前写的文章(前面两篇实用型,后面三篇对面试应该会有帮助):