Interview

在前端面试过程中,难免会有一些问题不在自己的知识圈内,做个总结,以便查看。

一、HTML篇

1、页面文件引入

要求:

  • 页面标题为「我的页面」
  • 页面中引入了一个外部 CSS 文件,文件路径为 /style.css
  • 页面中引入了另一个外部 CSS 文件,路径为 /print.css,该文件仅在打印时生效
  • 页面中引入了另一个外部 CSS 文件,路径为 /mobile.css,该文件仅在设备宽度小于 500 像素时生效
  • 页面中引入了一个外部 JS 文件,路径为 /main.js
  • 页面中引入了一个外部 JS 文件,路径为 /gbk.js,文件编码为 GBK
  • 页面中有一个 SVG 标签,SVG 里面有一个直径为 100 像素的圆圈,颜色随意
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0", max-scale=1.0,>
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>我的页面</title>
    <link rel="stylesheet" type="text/css" href="/style.css">
    <link rel="stylesheet" type="text/css" href="/print.css" media="print">
    <link rel="stylesheet" type="text/css" href="/mobile.css" media="all and (max-width:500px)">
    <style>
    *{
    padding: 0px;
    margin: 0px;
    }
    </style>
    </head>
    <body>
    <svg version="1.1">
    <circle cx="100" cy="50" r="50" stroke="#e6e6e6" stroke-width="1" fill="#fcf" />
    </svg>
    <script type="text/javascript" src="/main.js"></script>
    <script type="text/javascript" src="/gbk.js" charset=”GBK″></script>
    </body>
    </html>

2、移动端适配

在移动端做适配主要有三点:

1)meta viewport

此方法是在head标签内引入:

1
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">

这个标签的作用是使当前的viewport宽度等于设备的宽度,同时不允许用户手动缩放,并且页面的初始缩放值为1.0,即未进行缩放。

2)媒体查询

媒体查询,即使用@media查询,可针对不同的媒体类型定义不同的样式。@media可以针对不同的屏幕尺寸设置不同的样式。当你重置浏览器大小的过程中,页面会根据浏览器的宽度和高度重新渲染页面。不同的媒体类型可以是print、screen、speech。具体使用如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@media screen and (max-width: 320px) {
body{
background:red;
}
}
@media screen and (min-width: 321px) and (max-width: 640px) {
body{
background:green;
}
}
@media screen and (min-width: 641px) and (max-width: 768px) {
body{
background:grey;
}
}

3)动态rem

动态rem能够更加便捷的完成移动端布局。代码如下:

1
2
3
4
5
6
7
8
9
10
11
<script>
setFontsize();
window.onresize = function(){
setFontsize();
}
function setFontsize(){
let html = document.documentElement;
let windowWidth = html.clientWidth;
html.style.fontSize = windowWidth * 100 / 640 + 'px';
}
</script>

通常情况下,设计稿是以640px为基本宽度的,默认认为640px时跟字体大小为100px,那么对应到width:10px就是width:0.1rem。为什么要这么做呢,就是存粹出于方便计算考虑。同时要注意到Chorme浏览器的最小字体为12px。

二、CSS篇

1、如何实现圆角矩形和阴影?

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.radius{
width: .8rem;
height: .8rem;
background: #cff;
border-radius: 4px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
-o-border-radius: 4px;
}
.shadow{
width: .8rem;
height: .8rem;
box-shadow: 0 0 4px #4ff;
-webkit-box-shadow: 0 0 4px 4px #4ff;
-moz-box-shadow: 0 0 4px 4px #4ff;
-o-box-shadow: 0 0 4px 4px #4ff;
}

在border-radius可以分别对各个圆角进行设置,如border-top-left-radiusborder-top-right-radiusborder-bottom-left-radiusborder-bottom-right-radius
在box-shaodw中,五个值对应的意义分别为水平阴影的位置垂直阴影的位置模糊距离阴影的大小阴影的颜色
写个好玩的小例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
...
<div class="circle"></div>
...
<style>
.circle{
width: 0.1rem;
height: 0.1rem;
background: #f8f;
border-radius: 50%;
box-shadow: 0 -60px 4px 10px #f3f,0 60px 4px 10px #f3f,-60px 0px 4px 10px #f3f,60px 0px 4px 10px #f3f;
margin: 1rem auto;
}
</style>

效果图如下所示:

shadow

三、JavaScript篇

1、onchange()事件

onchange()可绑定的标签为input,select,textarea

2、闭包

1)闭包是什么?

如果一个函数,使用了它范围外的变量,那么(这个函数+这个变量)就叫做闭包。MDN定义为:闭包是函数和声明该函数的词法环境的组合。请看如下代码:

1
2
3
4
5
6
7
8
9
function init(){
let str = 'hello';
function sayHello(){
return str;
}
return sayHello
}
let a = init();
a();

以上变量str函数sayHello()构成一个闭包。
可以这样理解,闭包其实是函数作用域的副产品。即函数能够访问当前作用域内的任何变量。但是如果函数外部想要访问函数内部的变量,就需要通过闭包获得。如果我们想要访问到函数init()中的变量str,只能通过闭包获得。

2)闭包的用途是什么?

闭包可以暂存数据,给变量开辟私有空间避免变量污染,并且让这些变量始终保存在内存中。
暂存数据,开辟私有空间可以拿上述示例进行参考。此处说明一下变量始终保存在内存中:

1
2
3
4
5
6
7
8
9
10
11
function init(){
let a = 0;
function add(){
return a++
}
return add
}
let num = init();
num(); //0
num(); //1
num(); //2

以上,每次执行完num()之后,变量a会一直保存在内存中并未被销毁,在下次被调用时使用的时内存中的值,所以才会有变量a的不断累加。

3、call、apply、bind 的用法分别是什么?

JavaScript权威指南的解释为:call()、apply()可以看作是某个对象的方法,通过调用方法的形式来间接调用函数。bind()就是将某个函数绑定到某个对象上。这么说可能比较晦涩,转成普通话就是:call()和apply()都是为了改变某个函数运行时的上下文(context)而存在的,换句话说就是为了改变函数体内部this的指向。
JavaScript的一大特点就是存在定义时上下文运行时上下文上下文是可以改变的这些概念。

1) call()/apply()的相同点

call()可以传递一个thisArgs参数和一个参数列表,thisArgu指定了函数在运行期间的调用者,也就是函数中的this对象,而参数列表会被传入调用函数中。其中,thisArgs有以下几种情况:

  • 不传或者传undefinednull ,函数中的this指向window对象
  • 传递另一个函数的函数名,函数中的this指向这个函数的引用
  • 传递字符串、数值、布尔等基础类型,函数中的this指向其对应的包装对象,如StringNumberBoolean
  • 传递一个对象,函数中的this指向这个对象

具体例子请看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function a(){
console.log(this);
}
function b(){}
let obj = {
name : 'abc'
}
a.call(); //window
a.call(undefined); //window
a.call(null); //window
a.call(1); //Number
a.call(''); //String
a.call(true); //Boolean
a.call(b); //function b(){}
a.call(obj); //{name:'abc'}

这是call()的核心功能,它允许你在一个对象上调用该对象没有定义的方法,并且这个方法可以访问该对象中的属性。看以下代码:

1
2
3
4
5
6
7
8
9
10
11
function animals(){}
animals.prototype = {
type:'dog',
say:function(){
console.log("I am a " + this.type);
}
}
let dog = new animals();
dog.say(); //输出 I am a dog
let cat = {type:'cat'};
dog.say.call(cat); //输出 I am a cat

2) call()/apply()不同点

二者作用完全一样,可以重新指定this,不同点是接受的参数方式不一样。

1
2
对象.函数名.call(thisArgs,arg1,arg2)
对象.函数名.apply(thisArgs,[arg1,arg2])

call()需要将参数按顺序传入,apply()需要将参数以数组形式传入

3) bind()

bind()的传参和call()类似,但是call()/apply()都会自动执行对应的函数,而bind()不会执行对应函数,只是返回了对函数的引用。
继续以上代码用例:

1
2
3
let bird = {type:'bird'};
let sayBird = dog.say.bind(bird);
sayBird(); //输出: I am a bird

综上,当你希望改变上下文环境后并非立即执行而是在需要调用的时候再执行,使用bind()方法。call()/apply()会立即执行函数。

以上内容参考 call、apply和 bind的简单使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let obj = {
x : 2,
y : 4,
sum:function(){
console.log(this.x + this.y)
},
msg:function(arg1,arg2){
console.log('传参,相乘结果:'+arg1 * arg2)
console.log('属性,相加结果:'+(this.x + this.y))
}
}
let a ={x:1,y:1}
let b = {x:2,y:2}
let c = {x:3,y:3}
obj.msg.call(a,1,1);
obj.msg.apply(b,[2,2])
obj.msg.bind(c,3,3)()

4、说出至少 8 个 HTTP 状态码,并描述各状态码的意义。

  • 200:请求被正常处理
  • 204:请求被受理但没有资源可以返回
  • 206:客户端只是请求资源的一部分,服务器只对请求的部分资源执行GET方法,相应报文中通过Content-Range指定范围的资源
  • 301:永久性重定向
  • 302:临时重定向
  • 303:与302状态码有相似功能,只是它在请求一个URI的时候,能够通过GET方法重定向到另外一个URI上
  • 304:发送附带条件的请求时,条件不满足时返回,与重定向无关
  • 400:请求报文语法有误,服务器无法识别
  • 401:请求需要认证
  • 403:请求的对应资源禁止被访问
  • 404:服务器无法找到对应资源
  • 500:服务器内部错误,服务器端在执行请求时发生错误
  • 503:服务器正忙,服务器处于超负荷或正在停机维护,无法处理请求

5、请求示例

题目:请写出一个 HTTP post 请求的内容,包括四部分。其中:第四部分的内容是 username=ff&password=123,第二部分必须含有 Content-Type 字段,请求的路径为 /path

1
2
3
4
5
6
7
> POST /path HTTP/1.1
>Connection:keep-alive
> Conent-Length:24
> Content-Type: application/x-www-form-urlencoded
> Host: localhost:8080
>
> username=ff&password=123

6、一个页面从输入 URL 到页面加载显示完成,这个过程中都发生了什么?

1) DNS解析

根据浏览器缓存->操作系统缓存(host)->路由器缓存->DNS服务器…..->全世界的顶级DNS上,逐级递归向上找,找到该域名对应的IP地址。

2) TCP连接

此时开始TCP的三次握手。
第一次握手:建立连接时,客户端发送syn(同步序列号:Synchronize Sequence Numbers)包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认。
第二次握手:服务器收到syn包,必须确认客户的syn(ack=j+1),同时自己也发送一个SYN包(syn=k)即SYN+ACK包,此时服务器进入SYN_RECV状态。
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。

3) 发送HTTP请求

TCP连接完成后,根据解析的IP发起请求,通常请求格式为:
> 请求方式 路径 协议/版本
> Key1: value1
> Key2: value2
> Key3: value3
> Content-Type: application/x-www-form-urlencoded
> Host: www.xxx.com
> User-Agent: curl/7.54.0
>
> 要上传的数据

4) 服务器处理请求并返回HTTP报文

服务器接收到请求 -> 服务器处理请求 -> 服务器发起响应并发送HTTP报文,通常响应数据的格式为:
所有的响应格式为:
> 协议/版本 状态码 状态解释
> Key1: value1
> Key2: value2
> Key3: value3
> Content-Length: 17931
> Content-Length: text/html
>
> 要下载的内容

5) 浏览器解析渲染页面

浏览器边解析边渲染:解析HTML文件并构建DOM树 -> 解析css文件并渲染 -> 解析JS并进行Ajax请求

6) 连接结束

7、数组去重

要求:不要做多重循环,只能遍历一次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//ES5方法:
let arr = [1,5,2,3,4,2,3,1,3,4];
unique(arr);
function unique(arr){
let hash = {};
let result = [];
for(let i = 0;i<arr.length;i++){
if(!hash[arr[i]]){
hash[arr[i]] = 1;
result.push(arr[i])
}
}
console.log(result);
return result;
}
//ES6方法:
uniqueES6(arr);
function uniqueES6(arr){
console.log('es6方法:');
console.log([...new Set(arr)]);
return [...new Set(arr)];
}


四、vue篇


五、性能篇

六、算法

1、请说出至少三种排序的思路,这三种排序的时间复杂度分别为O(n*n)、(n log2 n)、O(n + max)

O(n*n)冒泡排序:
遍历整个数组,将相邻的两个元素进行比较,如果前一个数值小则不换位,如果前一个数值大则这两个数值换位。一轮下来能够保证最大的值为数组最后一个值。然后遍历除数组最后一个元素的所有值,重复上述操作直到没有值可以比较。
O(n log2 n)快速排序:
取出数组第一个值,将大于此值的元素放在右边,小于此值的元素放在左边,再以元素左边和右边的第一个元素为基准,重复以上操作直至只有一个元素为止。
O(n + max)基数排序:
创建10个桶,分别对应数组0~9,首先根据个位数遍历数组中的元素,将元素按照个位数值排到对应的桶中,再从桶中依次恢复到数组,再继续遍历十位、百位,直到遍历到最大元素的位数。
其中考试题:
call():
callapplybind主要是为了改变上下文环境即this的指向。
callapply的不同点在于传参不同,call是将参数按顺序传递,apply需要将参数以数组形式传递。
bindcallapply不同之处在于bind只返回对函数的引用,并不会立即执行,而callapply会立即执行函数。具体看以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let obj = {
x : 2,
y : 4,
msg:function(arg1,arg2){
console.log('传参,相乘结果:'+arg1 * arg2)
console.log('属性,相加结果:'+(this.x + this.y))
}
}
let a ={x:1,y:1}
let b = {x:2,y:2}
let c = {x:3,y:3}
obj.msg.call(a,1,1); //传参,相乘结果:1,属性,相加结果:2
obj.msg.apply(b,[2,2]) //传参,相乘结果:4,属性,相加结果:4
obj.msg.bind(c,3,3)() //传参,相乘结果:9,属性,相加结果:6

状态码:

  • 200:请求被正常处理
  • 204:请求被受理但没有资源可以返回
  • 206:客户端只是请求资源的一部分,服务器只对请求的部分资源执行GET方法,相应报文中通过Content-Range指定范围的资源
  • 301:永久性重定向
  • 302:临时重定向
  • 303:与302状态码有相似功能,只是它在请求一个URI的时候,能够通过GET方法重定向到另外一个URI上
  • 304:发送附带条件的请求时,条件不满足时返回,与重定向无关
  • 400:请求报文语法有误,服务器无法识别
  • 401:请求需要认证
  • 403:请求的对应资源禁止被访问
  • 404:服务器无法找到对应资源
  • 500:服务器内部错误,服务器端在执行请求时发生错误
  • 503:服务器正忙,服务器处于超负荷或正在停机维护,无法处理请求
    请求:
    > POST /path HTTP/1.1
    >Connection:keep-alive
    > Conent-Length:24
    > Content-Type: application/x-www-form-urlencoded
    > Host: localhost:8080
    >
    > username=ff&password=123