快速构建初级前端知识体系,面试题汇总

6,745 阅读35分钟

前言

原文来自 我的个人博客

最近一直在学习 vue3 源码,搞的头有点大,用这篇文章来换换脑子~

PS:面试题下附有解答,解答会结合我的思考以及扩展,仅供参考,如果有误麻烦指出,好了就酱~

1. HTML

在面试中,HTML 的面试题相对来说比较简单,一般面试官不会花太多时间去关注 HTML 的细节。

1.1 如何理解 HTML 语义化

语义化的含义就是用正确的标签做正确的事情,html 语义化就是让页面的内容结构化。

打个比方就是,如果我要实现一个一级标题,可以用 div+css 设置样式字体来达到效果,也可以用 h1,前者当然也能实现效果,但是语义化的方式还是使用 h1 标签,因为我们一看到 h1 标签就会知道他是一个 一级标题,这就是 html 语义化。

  1. 语义化不仅能方便我们开发人员阅读维护理解
  2. 还有利于搜索引擎的建立索引、抓取(SEO 优化)
  3. 而且有还利于不同设备的解析(屏幕阅读器,盲人阅读器等)

1.2 谈谈 SEO

这个问题可以从三个方向回答:

  1. SEO 是什么?
  2. 它的原理是?
  3. SEO 优化方法有哪些?

回答:

  1. SEO(Search Engine Optimization),意思就是搜索引擎优化。通俗点讲就是提高你的网页在搜索结果中的排名(排名越高越靠前)
  2. SEO 的原理可以大致分为四个步骤:
    1. 爬行和抓取:搜索引擎派出称为爬虫的程序,从一只网页出发,就像正常用户的浏览器一样访问这些网页抓取文件,并且会跟踪网页上的链接,访问更多的网页。
    2. 索引:搜索引擎将爬取的网页文件分解、分析存入数据库,这个过程就是索引。
    3. 搜索词处理:用户输入关键词,点击搜索后,搜索引擎会进行 分词 检查拼音 错别字 等等。。。
    4. 排序:搜索引擎从索引数据库中找出所有包含搜索词的网页,并按照计算法进行排序返回。
  3. SEO 优化可以分为内部优化和外部优化
    • 内部优化
      • 标签优化:语义化标签、META 标签
      • 网站内容更新:每天保持站内的更新(主要是文章的更新等)
      • 服务器端渲染(SSR)
      • 内部链接的优化,包括相关性链接(Tag 标签),锚文本链接,各导航链接,及图片链接
    • 外部优化:
      • 外部链接类别:博客、论坛、B2B、新闻、分类信息、贴吧、知道、百科、相关信息网等尽量保持链接的多样性
      • 外链运营:每天添加一定数量的外部链接,使关键词排名稳定提升。
      • 外链选择:与一些和你网站相关性比较高,整体质量比较好的网站交换友情链接,巩固稳定关键词排名

1.3 HTML 有哪些块级元素,有哪些行内元素以及他们的区别

display: block/inline/inline-block

  • 常用的块级元素:div、p、h1-6、table、form、ul、dl
  • 常用的行内元素:a、span、br、label、strong
  • 常用的内联块状元素有:img、input
  1. 默认情况下块级元素会独占一行而行内元素不会
  2. 块级元素可以设置 width, height 属性而行内元素设置无效
  3. 块级元素可以设置 margin 和 padding,而行内元素只有水平方向有效,竖直方向无效
  4. 块级元素可以包含行内元素和块级元素。行内元素不能包含块级元素

1.4 什么是 HTML5,和 HTML 的区别是?

HTML5 是 HTML 的新标准,其主要目标是无需任何额外的插件如 Flash、Silverlight 等,就可以传输所有内容。它囊括了动画、视频、丰富的图形用户界面等。

区别:

  • 从文档声明类型上看:HTML 是很长的一段代码,很难记住。HTML5 却只有简简单单的声明,方便记忆。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "
http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">

<!DOCTYPE html>
  • 从语义结构上看:HTML4.0 没有体现结构语义化的标签,通常都是这样来命名的<div id="header"></div>,这样表示网站的头部。HTML5 在语义上却有很大的优势。提供了一些新的标签,比如:<header><article><footer>。

1.5 HTML5 有哪些新特性?

  1. 新增语义化标签:nav、header、footer、aside、section、article
  2. 音频、视频标签:audio、video
  3. 数据存储:localStorage、sessionStorage
  4. canvas(画布)、Geolocation(地理定位)、websocket(通信协议)
  5. input 标签新增属性:placeholder、autocomplete、autofocus、required
  6. history API:go、forward、back、pushstate

2. CSS

不多说了,前端面试 CSS 是必考知识,不过关直接回家

2.1 谈谈你对盒模型的理解

思路:

  1. 先讲盒模型是什么
  2. 再介绍两种盒模型的区别
  3. 可以再提一下 box-sizing 这个属性

回答:

  1. 当对一个文档进行布局(layout)的时候,浏览器的渲染引擎会根据标准之一的 CSS基础框盒模型(CSS basic box model),将所有元素表示为一个个矩形的盒子(box)。通俗来讲就是网页是由一个一个盒子组成的,而这个盒子是有自己的标准的,一个盒子由以下四个部分组成:

    • content(盒子的内容)
    • padding(盒子的内边距)
    • border(盒子的边框)
    • margin(盒子的外边距)
  2. 在 CSS 中,盒子模型可以分成:标准盒子模型 和 怪异盒子模型

    • 标准盒子模型:浏览器默认的盒子模型

      盒子总宽度 = width + padding + border + margin 盒子总高度 = height + padding + border + margin

      width/height 只是内容高度,不包含 padding 和 border 值

    • 怪异盒子模型

      盒子总宽度 = width + margin 盒子总高度 = height + margin

      width/height 包含了 padding 和 border 值

  3. CSS 中的 box-sizing 属性定义了引擎应该如何计算一个元素的总宽度和总高度

  • content-box 默认值,元素的 width/height 不包含padding,border,与 标准盒子模型表现一致
  • border-box 元素的 width/height 包含 padding,border,与怪异盒子模型表现一致
  • inherit 指定 box-sizing 属性的值,应该从父元素继承

image.png

2.2 margin 的纵向重叠问题

下面我们来看一段代码:

<body>
  <div style="margin-top: 20px">
    <div style="margin-top: 10px">aaa</div>
  </div>

  <p style="margin-bottom: 50px">bbb</p>
  <p style="margin-top: 10px">ccc</p>

  <p style="margin-bottom: 50px">ddd</p>
  <p></p>
  <p></p>
  <p></p>
  <p style="margin-top: 10px">eee</p>
</body>

请问:

  1. aaa 距离最顶部多少 px?
  2. bbb 距离 ccc 多少 px?
  3. ddd 距离 eee 多少 px?

公布答案: 分别是 20px 50px 50px

image.png

常见的重叠现象

  1. 同一层相邻元素之间,相邻元素外边距重叠
  2. 父元素与子元素重叠
  3. 空的块级元素重叠

解决方法:

  1. 相邻元素之间:
    1. 底部元素设置为浮动 float:left;
    2. 底部元素的 position 的值为 absolute/fixed
    3. 在设置 margin-top/bottom 值时统一设置上或下(推荐)
  2. 父子元素之间:
    1. 外层元素添加 padding
    2. 外层元素 overflow:hidden/auto(推荐);
    3. 外层元素透明边框 border:1px solid transparent;
    4. 内层元素绝对定位 postion:absolute:
    5. 内层元素加 float:left;或 display:inline-block;

2.3 margin-left/right/bottom/top 分别设为负值会怎么样?

  • margin-top 和 margin-left 负值,元素向上、向左移动
  • margin-right 负值,右侧元素左移,自身不受影响
  • margin-bottom 负值,下方元素上移,自身不受影响

第一点应该很好理解,下面这张图分别展示了第二和第三点。 image.png

2.4 BFC 的理解

1. 定义:

BFC 全称:Block Formatting Context, 名为 "块级格式化上下文"。(全程说出来也能加分)

W3C 官方解释为:BFC 它决定了元素如何对其内容进行定位,以及与其它元素的关系和相互作用,当涉及到可视化布局时,Block Formatting Context 提供了一个环境,HTML 在这个环境中按照一定的规则进行布局。

简单来说就是,BFC 就是一块独立渲染区域,内部元素的渲染不会影响边界以外的元素。BFC 有自己的规则和触发条件。

2. 规则

  • BFC 就是一个块级元素,块级元素会在垂直方向一个接一个的排列
  • BFC 就是页面中的一个隔离的独立容器,容器里的标签不会影响到外部标签
  • 垂直方向的距离由 margin 决定, 属于同一个 BFC 的两个相邻的标签外边距会发生重叠
  • 计算 BFC 的高度时,浮动元素也参与计算

3. 怎样触发 BFC

  1. overflow: hidden
  2. display: flex / inline-block / table-cell
  3. position: absolute / fixed

4. BFC 解决了什么问题

1. 使用 float 脱离文档流,造成高度塌陷

<style>
  .box {
    margin: 100px;
    width: 100px;
    height: 100px;
    background: red;
    float: left;
  }
  .container {
    border: 1px solid #000;
  }
</style>
<div class="container">
  <div class="box"></div>
  <div class="box"></div>
</div>

效果:

image.png

给 container 触发 BFC 就可解决问题,例如 给 container 元素 添加 display:inline-block。效果:

image.png

2. margin 边距重叠问题

这个问题 2.2 已经讲过:

<style>
  .box {
    margin: 10px;
    width: 100px;
    height: 100px;
    background: #000;
  }
  .container {
    margin-top: 20px;
    overflow: hidden;
  }
</style>
<div class="container">
  <div class="box"></div>
</div>

效果以及触发 BFC 后的效果如下:

image.png

2.5 css 如何画三角形

原理:css 画三角形的原理就是通过 border 画的,因为在 css 中 border 并不是一个矩形,而是一个梯形,在 box 的内容越来越小时,border 的一条底边也会越来越小,直到 box 的宽高都是 0 时,此时的四条 border 就组成了四个三角形,将其他三个隐藏,就能得到一个三角形。

image.png

<style>
  .box {
    /* 内部大小 */
    width: 0px;
    height: 0px;
    /* 边框大小 只设置两条边*/
    border-top: #4285f4 solid;
    border-right: transparent solid;
    border-width: 85px;
    /* 其他设置 */
    margin: 50px;
  }
</style>
<div class="box"></div>

效果:

image.png

2.6 absolute 和 relative 分别依据什么定位?

  • relative 依据自身定位
  • absolute 依据最近一层的定位元素

2.8 居中对齐的实现方式

居中的方式有很多,我最常用的还是 flexbox:

display:flex;
justify-content: center; // 水平居中
align-items: center; // 垂直居中

另外常见的还有:

  1. text-align: center
  2. line-height 和 height 设置成一样的高度
  3. margin: auto 0
  4. 绝对定位 + margin 负值
  5. 绝对定位 + transform 负值

2.9 line-height 的继承问题

请问下面代码 p 标签的行高是多少

<style>
  body {
    font-size: 20px;
    line-height: 200%;
  }

  p {
    font-size: 16px;
  }
</style>
<p>AAA</p>

因为上面的代码中 p 标签没有自己的行高,所以会继承 body 的行高,这题考的就是行高是如何继承的。

规则:

  1. 写具体数值,如 30px, 则直接继承该值
  2. 写比例,如 2 / 1.5,则继承该比例
  3. 写百分比,如 200%, 则继承计算出来的值

答案:40px

2.10 rem 是什么?

rem 是一个长度单位:

  • px,绝对长度单位,最常用
  • em,相对长度单位,相对于父元素,不常用
  • rem,相对长度单位,相对于 html 根元素,常用于响应式布局

2.11 CSS3 新增了哪些特性?

1. css3 中新增了一些选择器,主要为下图:

image.png

2. css3 中新增了一些样式

  • 边框:border-radius box-shadow border-image

  • 背景:background-clip、background-origin、background-size 和 background-break

  • 文字:word-wrap text-overflow text-shadow text-decoration

  • 颜色:rgba 与 hsla

  • 动画相关: transition transform animation

  • 渐变:linear-gradient radial-gradient

  • 其他: flex布局 grid布局

(上面提及的属性都应该去了解!)

3. JS 基础-变量类型和计算

不会变量,别说会 JS

3.1 JavaScript 中的数据类型有哪些?

JS 中的数据类型分为 值类型(基本类型) 和 引用类型(对象类型)

  • 基本类型有:undefined、null、boolean、string、number、symbol、BigInt 七种
  • 引用类型有:Object、Function、Array、RegExp、Date 等等

3.2 null 和 undefined 有什么区别?

首先 undefined 和 null 都是基本数据类型。

undefined 翻译过来是 未定义。表示 此处应该有一个值,但是还没有定义

null 翻译过来是 空的。表示 没有对象,即此处不应该有值

典型用法:

  1. 一般变量声明了但还没有定义的时候会返回 undefined,函数中没有返回值时默认返回undefined,以及函数参数没提供时这个参数也是 undefined

  2. null 作为对象原型链的终点。null 作为函数的参数,表示该函数的参数不是对象。

typeof undefined 值为 undefined,typeof null 是 'object'

undefined == null  // true
undefined === null // false

至于为什么在 js 中会有 null 和 undefined 吗,就要从历史谈起了,建议阅读一下阮一峰老师的文章 undefined与null的区别

我的总结就是,JS 最初是只设计了一种表示 “无” 的值的就是 null,这点很好理解就像 java 以及其他语言中的 null 一样,但是在 JS 中的数据类型分为 基本类型 和 对象类型,JS 的作者认为表示 “无” 的变量最好不是一个对象,另外 JS 早期没有错误处理机制,有时null 自动转为 0,很不容易发现错误,而 undefined 会转换成 NaN。

3.3 谈谈 Javascript 中的类型转换机制

JS 的类型转换分为:显式转换 和 隐式转换

1. 显式转换

显式转换常见的方法有:

  • Number:将任意类型的值转化为数值
Number(324) // 324

// 字符串:如果可以被解析为数值,则转换为相应的数值
Number('324') // 324

// 字符串:如果不可以被解析为数值,返回 NaN
Number('324abc') // NaN

// 空字符串转为0
Number('') // 0

// 布尔值:true 转成 1,false 转成 0
Number(true) // 1
Number(false) // 0

// undefined:转成 NaN
Number(undefined) // NaN

// null:转成0
Number(null) // 0

// 对象:通常转换成NaN(除了只包含单个数值的数组)
Number({a: 1}) // NaN
Number([1, 2, 3]) // NaN
Number([5]) // 5
  • parseInt:和 Number 相比,Number 会更严格一些,只要有一个字符无法转换成数值,整个字符串就会被转为 NaN,而 parseInt 函数逐个解析字符,遇到不能转换的字符就停下来例如:
Number('32a3') // NaN
parseInt('32a3') // 32
  • String:可以将任意类型的值转化成字符串
// 数值:转为相应的字符串
String(1) // "1"

//字符串:转换后还是原来的值
String("a") // "a"

//布尔值:true转为字符串"true",false转为字符串"false"
String(true) // "true"

//undefined:转为字符串"undefined"
String(undefined) // "undefined"

//null:转为字符串"null"
String(null) // "null"

//对象
String({a: 1}) // "[object Object]"
String([1, 2, 3]) // "1,2,3"
  • Boolean:可以将任意类型的值转为布尔值
Boolean(undefined) // false
Boolean(null) // false
Boolean(0) // false
Boolean(NaN) // false
Boolean('') // false
Boolean({}) // true
Boolean([]) // true
Boolean(new Boolean(false)) // true
  1. 隐式转换

在 JS 中,很多时候会发生隐式转换,我们可以归纳为两种场景:

  1. 比较运算(==、!=、>、<)、if、while 需要布尔值的地方
  2. 算术运算(+、-、*、/、%)

上面的场景有个前提就是运算符两边的操作数要是不同一类型的

  • 自动转换为布尔值

在需要布尔值的地方,就会将非布尔值的参数自动转为布尔值,系统内部会调用Boolean函数。

undefined null false +0 -0 NaN "" 这些都会被转化成 false,其他都换被转化成 true

  • 自动转换成字符串

遇到预期为字符串的地方,就会将非字符串的值自动转为字符串

常发生在 + 运算中,一旦存在字符串,则会进行字符串拼接操作

'5' + 1 // '51'
'5' + true // "5true"
'5' + false // "5false"
'5' + {} // "5[object Object]"
'5' + [] // "5"
'5' + function (){} // "5function (){}"
'5' + undefined // "5undefined"
'5' + null // "5null"
  • 自动转换成数值

除了 + 有可能把运算子转为字符串,其他运算符都会把运算子自动转成数值

'5' - '2' // 3
'5' * '2' // 10
true - 1  // 0
false - 1 // -1
'1' - 1   // 0
'5' * []    // 0
false / '5' // 0
'abc' - 1   // NaN
null + 1 // 1
undefined + 1 // NaN

3.4 == 和 ===有什么区别,分别在什么情况使用?

  1. == 叫 等于操作符
  2. === 叫 全等操作符

== 在比较时会进行隐式的类型转换

'' == '0' // false
0 == '' // true
0 == '0' // true

false == 'false' // false
false == '0' // true

false == undefined // false
false == null // false
null == undefined // true

' \t\r\n' == 0 // true

比较 null 的情况的时候,我们一般使用相等操作符 ==

const obj = {};

if(obj.x == null){
  console.log("1");  //执行
}

等同于下面写法

if(obj.x === null || obj.x === undefined) {
    ...
}

使用相等操作符 (==) 的写法明显更加简洁了

所以,除了在比较对象属性为 null 或者 undefined 的情况下,我们可以使用相等操作符 (==),其他情况建议一律使用全等操作符 (===)

3.5 let、var、const 的区别

let 和 const 都是 ES6 新增加了两个重要的关键字,var 是 ES6 之前就有的, 他们都是用来声明变量的,const 是用来声明一个只读的常量。

var、let、const 三者区别可以围绕下面五点展开:

  1. 变量提升:var 可以在声明之前调用,let 和 const 不行(会报错)
  2. 块级作用域:var 不存在跨级作用于,let 和 const 有
  3. 重复声明:var 允许重复声明变量,let 和 const 不行
  4. 修改声明的变量:var 和 let 可以,const 不行
  5. 使用:能用 const 的情况尽量使用 const,其他情况下大多数使用 let,避免使用 var

3.6 const 声明了数组,还能 push 元素吗,为什么?

可以

数组是引用类型,const 声明的引用类型变量,不可以变的是变量引用始终指向某个对象,不能指向其他对象,但是所指向的某个对象本身是可以变的

3.7 0.1 + 0.2 == 0.3?

答案是 false 不相等。

原因:0.1 和 0.2 在转换成二进制后会无限循环,由于标准位数的限制后面多余的位数会被截掉,此时就已经出现了精度的损失,相加后因浮点数小数位的限制而截断的二进制数字在转换为十进制就会变成 0.30000000000000004。

总结一句话就是:二进制模拟十进制进行计算时的精度问题

3.8 值类型 和 引用类型 的值得计算

1. 第一道

var a = {"x": 1};
var b = a;
a.x = 2;
console.log(b.x);


a = {"x": 3};
console.log(b.x);
a.x = 4;
console.log(b.x);

答案放在了下面,思考一下哦~

原理:

  1. 首先:第一个 log 打印 2 大家应该都没问题 image.png

  2. 然后 a 指向了栈内存中的另一块地址,而 b 没变,所以 b.x 仍然为 2

image.png

  1. 接着修改 a.x = 4 ,因为此时 b 仍然指向之前的地址,所以修改 a.x 并不会去影响 b,所以打印仍然为 2

image.png

答案:2 2 2

2. 第二道:

var a = {n:1};
var b = a;
a.x = a = {n:2};

console.log(a.x);
console.log(b.x);

答案在下面思考下哦~

这次我尝试一张图解释:

image.png

答案: undefined {n: 2}

4. JS 基础-原型和原型链

三座大山之一,必考!!!

4.1 JavaScript 中的原型,原型链分别是什么?

在 JS 中,原型和原型链是一个很重要的概念,可以说原型本质就是一个对象。它分为两种: 对象的原型和 函数的原型

对象的原型:

  • 任何对象都有自己默认的原型(隐式原型),
  • 它的作用就是在当前对象查找某一个属性时, 如果找不到, 会在原型上面查找
  • 获取隐式原型的方法:
    • __proto__(这个不是规范,是浏览器加的,因为早期没有获取原型对象的方法)
    • Object.getPrototypeOf(obj)

函数的原型:

  • 首先,因为函数也是一个对象,所以他会有一个 __proto__ 隐示原型
  • 其次任何一个函数(非箭头函数)还会有自己的 prototype 属性(显式原型)
  • 获取显示原型的方法:
    • prototype
  • 作用:
    • 当通过 new 操作符调用函数时, 创建一个新的对象
    • 这个新的对象的 隐式原型 会指向这个函数的 显式原型
    • obj.__proto__ = F.prototype

原型链:

上面讲过,隐式原型的作用是 当前对象查找某一个属性时, 如果找不到, 会在原型上面查找,而原型也是一个对象,所以在原型对象上找不到的话,还会去原型对象的原型对象上找,这样一层一层、以此类推就形成了原型链(prototype chain)

4.2 如何准确判断一个变量不是数组?

1. instanceof

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上

如果让你实现一个 instanceof 应该就很简单了吧?(循环遍历对象的隐式原型知道为 null 或者为 Array)

let arr = [1, 2];
arr instanceof Array // true

2. toString

let arr = [1, 2];
Object.prototype.toString.call(arr) === '[object Array]'

3. constructor

let arr = [1,2];
arr.constructor === Array; // true

4. isArray

let arr = [1,2];
Array.isArray(arr) // true

4.3 JS 如何实现继承

在 ES6 中可以使用 class + extends 的方式很容易实现继承,而它的本质其实就是通过原型链来实现的

ES6 实现继承:

class Person {
  constructor(name) {
    this.name = name;
  }

  sayHello() {
    console.log("你好,我是" + this.name);
  }
}

class Student extends Person {
  constructor(name, sno) {
    super(name);
    this.sno = sno;
  }

  readingBooks() {
    console.log("我正在读书");
  }
}

const stu = new Student("张三", "001");

// Student 子类能调用父类的 sayHello 方法
stu.sayHello();
stu.readingBooks();

ES5 实现继承:

function Person(name) {
  this.name = name;
}

Person.prototype.sayHello = function () {
  console.log("你好,我是", this.name);
};

function Student(name, sno) {
  this.name = name;
  this.sno = sno;
}

const per = new Person()
Student.prototype = perper;

Student.prototype.readingBooks = function () {
  console.log("我正在读书");
};

const stu = new Student("张三", "001");
// Student 子类能调用父类的 sayHello 方法
stu.sayHello();
stu.readingBooks();

ES5 中的继承还会复杂一些,初级面知道这些就够了,如果感兴趣,还可以看下我的这篇文章 js 常见手写题汇总 第 14 章实现继承。

下面我们用一张图来解释原型链继承的原理:

image.png

5. JS 基础-作用域和闭包

三座大山之二,不会闭包,基本不会通过

5.1 什么是作用域?什么是自由变量?

作用域 就是即变量(变量作用域又称上下文)和函数生效(能被访问)的区域或集合。

我们一般将作用域分成:

  1. 全局作用域:任何不在函数中或是大括号中声明的变量,都是在全局作用域下,全局作用域下声明的变量可以在程序的任意位置访问
  2. 函数作用域:函数作用域也叫局部作用域,如果一个变量是在函数内部声明的它就在一个函数作用域下面。这些变量只能在函数内部访问,不能在函数以外去访问
  3. 块级作用域:ES6 引入了 let 和 const 关键字,和 var 关键字不同,在大括号中使用 let 和 const 声明的变量存在于块级作用域中。在大括号之外不能访问这些变量。

自由变量 就是一个变量在当前作用域没有定义,但被使用了。那这个时候怎么办呢?它会向上级作用域,一层一层依次寻找,知道找到为止。如果到全局作用域都没找到,则报错 xx is not defined

5.2 什么是闭包?闭包会用在哪儿?

闭包是什么?

一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)

也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域

在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来,作为函数内部与外部连接起来的一座桥梁

下面给出一个简单的例子:

function init() {
    var name = "Mozilla"; // name 是一个被 init 创建的局部变量
    function displayName() { // displayName() 是内部函数,一个闭包
        alert(name); // 使用了父函数中声明的变量
    }
    displayName();
}
init();

使用场景

闭包常常作为函数 参数 和 返回值:

  1. 作为函数参数:
function print(fn) {
  let a = 200;
  fn();
}

let a = 100;
function fn() {
  console.log(a);
}

print(fn); // 100 
  1. 作为函数的返回值
function create() {
  let a = 100;
  return function () {
    console.log(a);
  };
}

let fn = create();
let a = 200;

fn(); // 100

任何闭包的使用场景都离不开这两点:

  1. 创建私有变量
  2. 延长变量的生命周期

一般函数的词法环境在函数返回后就被销毁,但是闭包会保存对创建时所在词法环境的引用,即便创建时所在的执行上下文被销毁,但创建时所在词法环境依然存在,以达到延长变量的生命周期的目的

使用闭包的注意点

  1. 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在 IE 中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

  2. 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

5.3 this 的绑定规则有哪些?优先级?

this 关键字是函数运行时自动生成的一个内部对象,只能在函数内部使用,总指向调用它的对象。

根据不同的使用场合,this 有不同的值,主要分为下面几种情况:

1. 默认绑定:

什么情况下使用默认绑定呢?独立函数调用。

  • 独立的函数调用我们可以理解成函数没有被绑定到某个对象上进行调用;

案例一:普通函数调用

  • 该函数直接被调用,并没有进行任何的对象关联;
  • 这种独立的函数调用会使用默认绑定,通常默认绑定时,函数中的 this 指向全局对象(window);
function foo() {
  console.log(this); // window
}

foo();

案例二:函数调用链(一个函数又调用另外一个函数)

  • 所有的函数调用都没有被绑定到某个对象上;
// 2.案例二:
function test1() {
  console.log(this); // window
  test2();
}

function test2() {
  console.log(this); // window
  test3()
}

function test3() {
  console.log(this); // window
}
test1();

案例三:将函数作为参数,传入到另一个函数中

function foo(func) {
  func()
}

function bar() {
  console.log(this); // window
}

foo(bar);

我们对案例进行一些修改,考虑一下打印结果是否会发生变化:

  • 这里的结果依然是 window,为什么呢?
  • 原因非常简单,在真正函数调用的位置,并没有进行任何的对象绑定,只是一个独立函数的调用;
function foo(func) {
  func()
}

var obj = {
  name: "why",
  bar: function() {
    console.log(this); // window
  }
}

foo(obj.bar);

2. 隐式绑定:

另外一种比较常见的调用方式是通过某个对象进行调用的:

  • 也就是它的调用位置中,是通过某个对象发起的函数调用。

案例一:通过对象调用函数

  • foo 的调用位置是 obj.foo() 方式进行调用的
  • 那么 foo 调用时 this 会隐式的被绑定到 obj 对象上
function foo() {
  console.log(this); // obj对象
}

var obj = {
  name: "why",
  foo: foo
}

obj.foo();

案例二:案例一的变化

  • 我们通过 obj2 又引用了 obj1 对象,再通过 obj1 对象调用 foo 函数;
  • 那么 foo 调用的位置上其实还是 obj1 被绑定了 this;
function foo() {
  console.log(this); // obj1 对象
}

var obj1 = {
  name: "obj1",
  foo: foo
}

var obj2 = {
  name: "obj2",
  obj1: obj1
}

obj2.obj1.foo();

案例三:隐式丢失

结果最终是 window,为什么是 window 呢?

  • 因为 foo 最终被调用的位置是 bar ,而 bar 在进行调用时没有绑定任何的对象,也就没有形成隐式绑定;
  • 相当于是一种默认绑定;
function foo() {
  console.log(this);
}

var obj1 = {
  name: "obj1",
  foo: foo
}

// 讲obj1的foo赋值给bar
var bar = obj1.foo;
bar();

3. 显示绑定

隐式绑定有一个前提条件:

  • 必须在调用的 对象内部 有一个对函数的引用(比如一个属性);
  • 如果没有这样的引用,在进行调用时,会报找不到该函数的错误;
  • 正是通过这个引用,间接的将this绑定到了这个对象上;

如果我们不希望在 对象内部 包含这个函数的引用,同时又希望在这个对象上进行强制调用,该怎么做呢?

  • JavaScript 所有的函数都可以使用 call 和 apply 方法(这个和 Prototype 有关)。
    • 它们两个的区别这里不再展开;
    • 其实非常简单,第一个参数是相同的,后面的参数,apply 为数组,call 为参数列表;
  • 这两个函数的第一个参数都要求是一个对象,这个对象的作用是什么呢?就是给 this 准备的。
  • 在调用这个函数时,会将 this 绑定到这个传入的对象上。

因为上面的过程,我们明确的绑定了 this 指向的对象,所以称之为 显示绑定。

案例一:call、apply

通过 call 或者 apply 绑定 this 对象

  • 显示绑定后,this 就会明确的指向绑定的对象
function foo() {
  console.log(this);
}

foo.call(window); // window
foo.call({name: "why"}); // {name: "why"}
foo.call(123); // Number对象,存放 123

案例二:bind 函数

如果我们希望一个函数总是显示的绑定到一个对象上,我们可以使用 bind 函数:

function foo() {
  console.log(this);
}

var obj = {
  name: "why"
}

var bar = foo.bind(obj);

bar(); // obj对象
bar(); // obj对象
bar(); // obj对象

案例三:内置函数

有些时候,我们会调用一些 JavaScript 的内置函数,或者一些第三方库中的内置函数。

  • 这些内置函数会要求我们传入另外一个函数;
  • 我们自己并不会显示的调用这些函数,而且 JavaScript 内部或者第三方库内部会帮助我们执行;
  • 这些函数中的 this 又是如何绑定的呢?
// 1. setTimeout中会传入一个函数,这个函数中的this通常是window
setTimeout(function() {
  console.log(this); // window
}, 1000);

// 2. forEach map filter 等高阶函数 this 通常指向 window对象,但我们可以通过第二个参数改变
var names = ["abc", "cba", "nba"];
names.forEach(function(item) {
  console.log(this); // 三次window
});

var names = ["abc", "cba", "nba"];
var obj = {name: "why"};
names.forEach(function(item) {
  console.log(this); // 三次obj对象
}, obj);

4. new 绑定

JavaScript 中的函数可以当做一个类的构造函数来使用,也就是使用 new 关键字。

使用 new 关键字来调用函数时,会执行如下的操作:

1.创建一个全新的对象; 2.这个新对象会被执行 Prototype 连接; 3.这个新对象会绑定到函数调用的 this 上 (this 的绑定在这个步骤完成); 4.如果函数没有返回其他对象,表达式会返回这个新对象;

// 创建Person
function Person(name) {
  console.log(this); // Person {}
  this.name = name; // Person {name: "why"}
}

var p = new Person("why");
console.log(p);

5. 优先级

new绑定 > 显示绑定(bind) > 隐式绑定 > 默认绑定

PS: new 绑定后可以使用 bind 但是 bind 不会生效。 new 绑定后使用 call 和 apply 会报错。

5. 规则之外

  • bind 绑定一个 null 或者 undefined 无效
  • 间接函数引用
function foo() {
  console.log(this);
}

var obj1 = {
  name: "obj1",
  foo: foo
}; 

var obj2 = {
  name: "obj2"
}

obj1.foo(); // obj1对象

// 赋值(obj2.foo = obj1.foo)的结果是foo函数 
// foo函数被直接调用,那么是默认绑定;
(obj2.foo = obj1.foo)();  // window 
  • ES6 箭头函数:箭头函数不使用 this 的四种标准规则(也就是不绑定 this ),而是根据外层作用域来决定 this。

6. JS 基础-异步

三座大山之三,必考!!!

6.1 同步 和 异步 的区别

JS 是一门单线程的编程语言,这就意味着一个时间里只能处理一件事,也就是说 JS 引擎一次只能在一个线程里处理一条语句。(浏览器和 nodejs 已经支持 js 启动进程,如 web worker)

虽然单线程简化了编程代码,因为这样咱们不必太担心并发引出的问题,这也意味着在阻塞主线程的情况下执行长时间的操作,如网络请求。

想象一下从 API 请求一些数据,根据具体的情况,服务器需要一些时间来处理请求,同时阻塞主线程,使网页长时间处于无响应的状态。这就是引入异步 JS 的原因。使用异步 (如 回调函数、promise、async/await),可以不用阻塞主线程的情况下长时间执行网络请求。

总结:基于 js 单线程本质,同步会阻塞代码执行,异步不会阻塞代码执行。

6.2 异步的应用场景

  • 网络请求,如 ajax 加载图片
  • 定时任务,setTimeout

6.3 你是怎么理解ES6中 Promise的?

1. 介绍

Promise,译为承诺,是异步编程的一种解决方案,比传统的解决方案(回调函数)更加合理和更加强大。

Promise 的出现主要解决了用回调函数处理多层异步操作出现的回调地狱问题

// 1. 经典的回调地狱
doSomething(function(result) {
  doSomethingElse(result, function(newResult) {
    doThirdThing(newResult, function(finalResult) {
      console.log('得到最终结果: ' + finalResult);
    }, failureCallback);
  }, failureCallback);
}, failureCallback);

// 2. 链式操作减低了编码难度 代码可读性明显增强
doSomething().then(function(result) {
  return doSomethingElse(result);
})
.then(function(newResult) {
  return doThirdThing(newResult);
})
.then(function(finalResult) {
  console.log('得到最终结果: ' + finalResult);
})
.catch(failureCallback);

promise 对象仅有三种状态:pending(进行中) fulfilled(已成功) rejected(已失败)

  • 对象的状态不受外界影响,只有异步操作的结果,可以决定当前是哪一种状态
  • 一旦状态改变(从 pending 变为 fulfilled 和从 pending 变为 rejected ),就不会再变,任何时候都可以得到这个结果

image.png

2. 用法

创建一个 Promise

// 1. Promise对象是一个构造函数,用来生成Promise实例
// resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”
// reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”
const promise = new Promise(function(resolve, reject) {});

实例方法:

  • then():then 是实例状态发生改变时的回调函数,第一个参数是 resolved 状态的回调函数,第二个参数是 rejected 状态的回调函数
  • catch():catch() 方法是 .then(null, rejection) 或 .then(undefined, rejection) 的别名,用于指定发生错误时的回调函数
  • finally():finally() 方法用于指定不管 Promise 对象最后状态如何,都会执行的操作

构造函数方法:

  • all()
  • race()
  • allSettled()
  • resolve():将现有对象转为 Promise 对象
  • reject():返回一个新的 Promise 实例,该实例的状态为rejected

ps:all race allSettled 这三个方法都能将多个 promise实例转换为 1 个 promise 实例,区别就是 all 只有当所有实例状态都变为 fulfilled 最终返回的实例状态才会变为 fulfilled。race 则返回最快改变状态的实例,allSettled 则会等所有实例状态改变才会改变。

6.4 手写 Promise 加载一张图片

const preloadImage = function (path) {
  return new Promise(function (resolve, reject) {
    const image = new Image();
    image.onload  = () => { resolve(image) };
    image.onerror = () => {
        const err = new Error(`图片加载失败${path}`)
        reject(err)
    };
    image.src = path;
  });
};

7. JS 异步进阶

关于异步还有更多的问题,很重要

7.1 什么是事件循环 event loop?

  1. 因为 js 是单线程的,为了防止代码阻塞会将代码分成同步和异步
  2. 同步代码交给 js 引擎执行,异步代码交给宿主环境(浏览器、node)
  3. 同步代码放入执行栈中,异步代码等待时机成熟送入任务队列排队
  4. 执行栈执行完毕,会去任务队列看是否有异步任务,有就送到执行栈执行,反复循环查看执行代码,这个过程就是事件循环(event loop)

7.2 什么是宏任务和微任务,它们的区别?

上题讲到 js 将代码分成同步和异步,而在异步任务中又分 宏任务(macro-task)和微任务(micro-task)。 宏任务是由宿主(浏览器、node)发起的,微任务是由 js 引擎发起的

宏任务大概包括

  1. script(整体代码)
  2. setTimeout
  3. setInterval
  4. setImmediate
  5. I/O
  6. UI render

微任务大概包括

  1. process.nextTick
  2. Promise
  3. Async/Await(实际就是promise)
  4. MutationObserver(html5新特性)

宏任务和微任务的执行过程

  1. 先执行同步代码
  2. 后执行微任务的异步代码
  3. 再执行宏任务的异步代码

如下图所示:

image.png

下面有个小例子:

setTimeout(() => {
  console.log(1);
});

Promise.resolve().then(() => {
  console.log(2);
});
console.log(3);

代码最终执行: 3 2 1

考异步代码执行顺序的题目有很多,这里推荐一个 练习事件循环的网站

7.3 async/await 语法

用法很简单,这里不说了。

你可以把 await 后面的代码理解为是放在 Promise.then 中,看上去相当于将链式的调用变成了同步的执行

关于async/await 的本质 最好去理解一下,其实就是 Promise 和 Generator 的语法糖

7.4 手写 Promise

详见 JavaScript 常见手写题汇总 第 15 章

8. JS-Web-API-DOM

学会DOM,才能具备网页开发的基础

8.1 DOM 是什么?

DOM:Document Object Model 指的是文档对象模型,它指的是把文档当做一个对象,这个对象主要定义了处理网页内容的方法和接口。

DOM 的本质就是一颗树

8.2 DOM 节点的操作

获取 DOM 节点

  1. getElementById
  2. getElementByClassName
  3. getElementByTagName
  4. querySeletorAll

DOM 节点的 property

通过 js 对象属性的方式来获取或修改 DOM 节点

const pList = document.querySelectorAll('p')
const p = pList[0]
console.log(p.style.width) // 获取样式
p.style.width = '100px' // 修改样式
console.log(p.className) // 获取 class
p.className = 'p1' // 修改 class

// 获取 nodeName 和 nodeType

console.log(p.nodeName)
console.log(p.nodeType)

DOM 节点的 attribute

通过 getAttribute setAttribute 这种 API 的方式来修改 DOM 节点

const pList = document.querySelectorAll('p')
const p = pList[0]

p.setAttribute('data-name', 'coder')  // 设置值
console.log(p.getAttribute('data-name')) // 获取值

property 和 attribute 形式都可以修改节点的属性,但是对于新增或删除的自定义属性,能在 html 的 dom 树结构上体现出来的,就必须要用到 attribute 形式了。

8.3 DOM 结构操作

新增插入节点:

<div id="div1">div1</div>
<div id="div2">div2</div>
<p id="p2">这是 p2 标签</p>
<script>
  const div1 = document.getElementById("div1");
  const div2 = document.getElementById("div2");

  // 新建节点
  const p1 = document.createElement("p");
  p1.innerHTML = "这是新的 p 标签";
  // 插入节点
  div1.appendChild(p1);

  // 移动节点
  const p2 = document.getElementById("p2");
  div2.appendChild(p2);
</script>

获取子元素列表,获取父元素

// 获取父元素
console.log(p1.parentNode);
// 获取子元素列表
console.log(div2.childNodes);

删除子元素

// 删除子元素
div2.removeChild(div2.childNodes[0]);

8.4 如何优化 DOM 性能

DOM 操作非常昂贵,避免频繁的 DOM 操作

  • 对 DOM 操作做缓存
  • 将频繁 DOM 操作改为一次性操作

9. JS-Web-API-BOM

内容虽然不多,但是你不能不会

9.1 什么 BOM ?

BOM:Browser Object Model 指的是浏览器对象模型,它指的是把浏览器当做一个对象来对待,这个对象主要定义了与浏览器进行交互的法和接口。BOM 的核心是 window,而 window 对象具有双重角色,它既是通过 js 访问浏览器窗口的一个接口,又是一个 Global(全局)对象。这意味着在网页中定义的任何对象,变量和函数,都作为全局对象的一个属性或者方法存在。window 对象含有 location 对象、navigator 对象、screen 对象等子对象,并且 DOM 的最根本的对象 document 对象也是 BOM 的 window 对象的子对象。

9.2 如何识别浏览器类型

navigator.userAgent 简称 ua,可以从 ua 里拿到浏览器信息

9.3 拆解 url 各部分

location:

  1. href
  2. protocol
  3. pathname
  4. search
  5. hash

10. JS-Web-API-事件

事件不会,等于残废,必考!必考!

10.1 谈谈你对事件冒泡和捕获的理解

事件冒泡和事件捕获分别由微软和网景公司提出,这两个概念都是为了解决页面中事件流(事件发生顺序)的问题。

<div id="outer">
    <p id="inner">Click me!</p>
</div>

上面的的两个元素,如果点击 inner,他们的执行顺序是什么呢?

  • 事件冒泡:先执行 inner 的监听事件在执行 outer 的监听事件
  • 事件捕获:先执行 outer 的监听事件在执行 inner 的监听事件

其实这个很好理解,冒泡就是从一个泡泡从水底往上冒当然是里面的先执行啦。

至于为什么会有这两种情况,这就要谈到网景和微软的战争了,两家公司的理念不同。网景主张捕获方式,微软主张冒泡方式。后来 w3c 将两者都保留了下来。

addEventListener 的第三个参数就是为冒泡和捕获准备的。第三个参数设置为 true 可以将让当前元素绑定的事件先于里面的元素绑定事件执行。默认是 false

10.2 什么是 事件代理

事件代理(Event Delegation)也称之为事件委托。是 JavaScript 中常用绑定事件的常用技巧。

顾名思义,事件代理 即是把原本需要绑定在子元素的响应事件委托给父元素,让父元素担当事件监听的职务。

事件代理的原理是DOM元素的事件冒泡。

11. JS-Web-API-Ajax

每个工程师必须熟练掌握的技能

11.1 什么是 Ajax ?

Ajax(全称 Asynchronous JavaScript And XML) 翻译过来就是 异步的 Javascript 和 XML

AJAX 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。

随着谷歌搜索建议功能在 2005 的发布,AJAX 开始流行起来。

11.2 手写一个简易的 Ajax

网页中实现 Ajax 最核心的 API 就是 XMLHttpRequest,如果不知道这个就别谈实现了。

function ajax(url) {
  const xhr = new XMLHttpRequest();
  xhr.open("get", url, true);
  xhr.onreadystatechange = function () {
    // 异步回调函数
    if (xhr.readyState === 4) {
      if (xhr.status === 200) {
        console.info("响应结果", xhr.response)
      }
    }
  }
  xhr.send(null);
}

11.3 浏览器的同源策略是什么??

同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说 Web 是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。

它的核心就在于它认为自任何站点装载的信赖内容是不安全的。当被浏览器半信半疑的脚本运行在沙箱时,它们应该只被允许访问来自同一站点的资源,而不是那些来自其它站点可能怀有恶意的资源。

所谓同源是指:域名、协议、端口相同。

另外,同源策略又分为以下两种:

  • DOM 同源策略:禁止对不同源页面 DOM 进行操作。这里主要场景是 iframe 跨域的情况,不同域名的 iframe 是限制互相访问的。
  • XMLHttpRequest 同源策略:禁止使用 XHR 对象向不同源的服务器地址发起 HTTP 请求。

11.4 什么是跨域?

跨域本质是浏览器基于同源策略的一种安全手段

同源策略(Sameoriginpolicy),是一种约定,它是浏览器最核心也最基本的安全功能

所谓同源(即指在同一个域)具有以下三个相同点

  1. 协议相同(protocol)
  2. 主机相同(host)
  3. 端口相同(port)

反之非同源请求,也就是协议、端口、主机其中一项不相同的时候,这时候就会产生跨域

一定要注意跨域是浏览器的限制,你用抓包工具抓取接口数据,是可以看到接口已经把数据返回回来了,只是浏览器的限制,你获取不到数据。用 postman 请求接口能够请求到数据。这些再次印证了跨域是浏览器的限制。

11.5 如何实现跨域请求

首先跨域是因为浏览器的同源策略造成的,他是浏览器的一种安全机制。跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了

实现跨域常见的方案:

  1. jsonp
  2. nginx反向代理
  3. webpack devServer代理
  4. cors跨源资源共享

其中 jsonp 由于其局限性,以及对比其他方案的效果。此处不做介绍。

1. nginx方向代理

nginx 反向代理常用在开发环境及线上环境。通过拦截转发请求来处理跨域问题。

假如现在前端项目运行在 8080 端口,而实际后端项目的地址为 https://1.1.1.1:9000 ,需要拦截前缀为 api 的请求,此时 nginx 配置为:

server {
    listen      8080 default_server;
    location /api {
        proxy_pass https://1.1.1.1:9000;
    }
}

假如现在有个接口为 /api/test,在没有做转发前为 http://localhost:8080/api/test ,实际接口位置为 https://1.1.1.1:9000/api/test.结果转发为实际接口位置。

2. webpack devserver代理 webpack devserver 代理用在开发环境。

配置如下:

devServer({
    proxy: {
        '/api': {
            target: 'https://1.1.1.1:9000',
            changeOrigin: true,
            pathRewrite: { '^/api': 'api' },
        },
    }
})

3. cors跨源资源共享(服务端设置)

跨源资源共享 (CORS) (或通俗地译为跨域资源共享)是一种基于 HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其它 origin(域,协议和端口),这样浏览器可以访问加载这些资源。

Access-Control-Allow-Origin: 'xxx'

可以通过服务端设置 Access-Control-Allow-Origin 字段的白名单来处理跨域问题。

如果在此情况下,发送请求时需要带上cookie的话,则需要配置Access-Control-Allow-Credentials,同时客户端需要同步设置xhr.withCredentials = true;,两者缺一不可

11.6 ajax fetch axios 的区别

  1. ajax 是 js 异步技术的术语,早期相关的 api 是 xhr ,它是一个术语。
  2. fetch 是 es6 新增的用于网络请求标准 api,它是一个 api。(是 es6 用来代替 xhr 的, xhr 很不好用)
  3. axios 是用于网络请求的第三方库,它是一个库。

12. JS-Web-API-存储

内容虽然不多,但不可不会

12.1 描述 cookie sessionStorage localStorage 的区别

cookie 本身是用于浏览器和 server 通讯的,他是被借用到本地用于存储的,因为后两者是在 H5 后才提出来的( 2010年左右),我们可以通过 document.cookie = 'xxx' 来改变 cookie。

cookie 的缺点:

  1. 存储大小最大 4kb (因为 cookie 本身就不是用来做存储的)
  2. http 请求时需要发送到服务端,增加请求数据量
  3. 只能用 document.cookie 来修改,太过简陋

localStorage 和 sessionStorage :

  1. 它俩是 H5 专门为了存储设计的,最大可存 5M 左右
  2. API 更简洁:getItem setItem
  3. 不会随着 http 被发送出去
  4. localStorage 数据会永久存储,除非代码或者手动删除, sessionStorage 数据只存在于房钱会话,浏览器关闭则清除。一般 localStorage 会更多一些

13. HTTP 面试题

前后端分离的时代,网络请求是前端的生命线

13.1 http 常见的状态码有哪些?

分类:

  1. 1xx 服务器收到请求
  2. 2xx 请求成功,如 200
  3. 3XX 重定向,如 302
  4. 4xx 客户端错误,如 404
  5. 5xx 服务端错误,如 500

常见状态码:

  • 1XX: 提供信息
    • 100 Continue 情景:客户端向服务端传很大的数据,这个时候询问服务端,如果服务端返回100,客户端就继续传 (历史,现在比较少了)
    • 101 Switching Protocols 协议切换。比如下面这种:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade

告诉客户端把协议切换为 Websocket

  • 2xx: 成功
    • 200 Ok 正常的返回成功 通常用在 GET
    • 201 Created 已创建 通常用在 POST
    • 202 Accepted 已接收 比如发送一个创建 POST 请求,服务端有些异步的操作不能马上处理先返回 202,结果需要等通知或者客户端轮询获取
    • 203 Non-Authoritative Infomation 非权威内容 原始服务器的内容被修改过
    • 204 No Content 没有内容 一般 PUT 请求修改了但是没有返回内容
    • 205 Reset Content 重置内容
    • 206 Partial Content 服务端下发了部分内容
  • 3XX: 重定向
    • 300 Multiple Choices 用户请求了多个选项的资源(返回选项列表)
    • 301 Moved Permanently 永久转移
    • 302 Found 资源被找到(以前是临时转移)不推荐用了 302 拆成了 303 和 307
    • 303 See Other 可以使用 GET 方法在另一个 URL 找到资源
    • 304 Not Modified 没有修改
    • 305 Use Proxy 需要代理
    • 307 Temporary Redirect 临时重定向 (和 303 的区别是,307 使用原请求的method 重定向资源, 303 使用 GET 方法重定向资源)
    • 308 Permanent Redirect 永久重定向 (和 301 区别是 客户端接收到 308 后,之前是什么 method,之后也会沿用这个 method 到新地址。301,通常给用户会向新地址发送 GET 请求)
  • 4XX: 客户端错误
    • 400 Bad Request 请求格式错误
    • 401 Unauthorized 没有授权
    • 402 Payment Required 请先付费
    • 403 Forbidden 禁止访问
    • 404 Not Found 没有找到
    • 405 Method Not Allowed 方法不允许
    • 406 Not Acceptable 服务端可以提供的内容和客户端期待的不一样
  • 5XX: 服务端错误
    • 500 Internal Server Error 内部服务器错误
    • 501 Not Implemented 没有实现
    • 502 Bad Gateway 网关错误
    • 503 Service Unavailable 服务不可用 (内存用光了,线程池溢出,服务正在启动)
    • 504 Gateway Timeout 网关超时
    • 505 HTTP Version Not Supported 版本不支持

面试的时候常见该记住的有:101 200 201 301 302 304 403 404 500 502 504

规范就是一个约定,要求大家跟着执行,不要违反规范,例如 IE 浏览器

13.2 http 常见的 header 头

1. Content-Length

  • 发送给接收者的 Body 内容长度(字节)

    • 一个 byte 是 8bit
    • UTF-8 编码的字符 1-4 个字节、

示例:Content-Length: 348

2. User-Agent

  • 帮助区分客户端特性的字符串

    • 操作系统
    • 浏览器
    • 制造商(手机类型等)
    • 内核类型
    • 版本号

示例:User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36

3. Content-Type

  • 帮助区分资源的媒体类型(Media Type/MIME Type)

    • text/html
    • text/css
    • application/json
    • image/jpeg

示例:Content-Type: application/x-www-form-urlencoded

4. Origin

  • 描述请求来源地址

    • scheme://host:port
    • 不含路径
    • 可以是null

示例: Origin: yewjiwei.com

5. Accept

  • 建议服务端返回何种媒体类型(MIME Type)

    • */*代表所有类型(默认)
    • 多个类型用逗号隔开
    • 衍生的还有
      • Accept-Charset 能够接受的字符集 示例:Accept-Charset: utf-8
      • Accept-Encoding 能够接受的编码方式列表 示例:Accept-Encoding: gzip, deflate
      • Accept-Language 能够接受的回应内容的自然语言列表 示例:Accept-Language: en-US

示例:

  1. Accept: text/plain

  2. Accept-Charset: utf-8

  3. Accept-Encoding: gzip, deflate

6. Referer

  • 告诉服务端打开当前页面的上一张页面的URL;如果是ajax请求那么就告诉服务端发送请求的URL是什么

    • 非浏览器环境有时候不发送Referer
    • 常常用户行为分析

7. Connection

  • 决定连接是否在当前事务完成后关闭

    • HTTP1.0默认是 close
    • HTTP1.1后默认是 keep-alive

13.3 什么是 RESTFUL API ?

Restful API 是一种新的 API 设计方法(早已推广)

  • 传统 API 设计:把每一个 url 当做一个功能
  • Restful API 设计:把每个 url 当做一个唯一的资源

传统

/api/list?pageIndex=2

Restful API

/api/list/2

13.4 描述一下 http 缓存机制

HTTP 缓存即是浏览器第一次向一个服务器发起 HTTP 请求后,服务器会返回请求的资源,并且在响应头中添加一些有关缓存的字段如:cache-control,expires, last-modifed,ETag, Date,等,之后浏览器再向该服务器请求资源就可以视情况使用强缓存和协商缓存,

  • 强缓存:浏览器直接从本地缓存中获取数据,不与服务器进行交互,
  • 协商缓存:浏览器发送请求到服务器,服务器判断是否可使用本地缓存,