🔒 前端面试题

总结了一些前端的相关面试题
http://ovenzeze.coding.me/
菜鸟教程前端面试题
2018春招-头条,腾讯,小米,百度面经
https://www.cnblogs.com/itlkNote/p/6831115.html
https://wenku.baidu.com/view/cbded359326c1eb91a37f111f18583d049640f99.html
https://github.com/h5bp/Front-end-Developer-Interview-Questions/blob/master/Translations/Chinese/README.md

解题套路

  1. 抽象的问题,通过举例子来说明
  2. 遇到不会的,转移话题到会的内容,比如:不了解async/await,就和面试官说,我平时处理异步用的是Promise,然后把话题转到Promise的知识
  3. 侃侃而谈,讲述这项技术的历史发展

HTML考题

DOCTYPE的作用

<!DOCTYPE>用于告知浏览器的解析器用什么文档标准来解析这个文档。DOCTYPE不存在或格式不正确会导致文档以兼容模式呈现。

HTML5的写法<!DOCTYPE HTML>

如何理解 HTML 语义化

目的:

  • 对人友好:标签语义化就是给某块内容用上最恰当合适的标签,使其通俗易懂;
  • 对搜索引擎友好:使搜索引擎能够更方便地识别页面的每个部分

方法1 举例子:

  • 块元素:头部用 header,nav导航,章节用 section,段落用 p,边栏用 aside,主要内容用 main
  • 行内元素:span,a链接,b加粗,i斜体,time时间,progress进度条

注意!!input,button等元素是inline-block元素

方法2 讲发展历史:

最开始是 PHP 后端写 HTML,不会 CSS,于是就用 table 来布局。table 使用展示表格的。严重违反了 HTML 语义化。
后来有了专门的写 CSS 的前端,他们会使用 DIV + CSS 布局,主要是用 float 和绝对定位布局。稍微符合了 HTML 语义化。
再后来,前端专业化,知道 HTML 的各个标签的用法,于是会使用恰当的标签来展示内容,而不是傻傻的全用 div,会尽量使用 h1、ul、p、main、header 等标签
语义化的好处是易读、有利于SEO等。

知乎专栏详细答案

meta viewport 是做什么用的,怎么写?

方法1 举例子:

1
2
3
快捷方式 meta:vp + tab键
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
控制页面在移动端不要缩小显示。

方法2 侃侃而谈:

一开始,所有页面都是给PC准备的,乔布斯推出 iPhone 3GS,页面是不适应手机屏幕的,所以乔布斯的工程师想了一个办法,默认把手机模拟成 980px,页面缩小。
后来,智能手机普及,这个功能在部分网站不需要了,所以我们就用 meta:vp 让手机不要缩小我的网页。

canvas 元素是干什么的?

把画板项目丢给他
看 MDN 的 canvas 入门手册

替换元素和非替换元素

img,input等自闭和标签都是替换元素,这些元素没有实际的内容,即是个空元素
<div>内容</div>这种内部可以有内容的都是非替换元素,他们将内容直接告诉浏览器,将其显示出来

css考题

选择器的权重和优先级

第一等:内联样式,如: style=””,权值为1000。
第二等:ID选择器,如:#content,权值为100。
第三等:属性选择器和伪类,如.content,:hover,[attribute]权值为10。
第四等:元素选择器和伪元素选择器,如div p,权值为1。

同样优先级,写在后面的覆盖前面的。
特殊的!important优先级最高

说说css盒模型

盒模型包括content、padding、border、margin
但width和height不包括margin

标准盒模型(https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Box_Model/Introduction_to_the_CSS_box_model)

设置 box-sizing:

  • content-box: width/height == 内容区宽度/高度 【标准盒模型】
  • border-box: width/height == 内容区宽度/高度 + padding 宽度/高度 + border 宽度/高度 【IE盒模型】

js如何获取盒模型的宽和高

  • el.style.width/height 【只能取到行间样式,结果带单位】
  • getComputedStyle(el).width/height 【获取最终计算后得到的样式,结果带单位】

    1
    2
    var x = getComputedStyle(box).width;
    console.log(x); //100px
  • el.getBoundingRect().width/height 【结果不带单位,且包含border】

    1
    2
    var x = box.getBoundingClientRect().width;
    console.log(x) //102 【width + 左右border】

css reset 和 normalize.css 有什么区别?

  • reset 重置,之前的样式我都不要了,抛弃默认样式
  • normalize 使标准化, 让所有浏览器的标签都跟标准规定的默认样式一致,各浏览器上的标签默认样式基本统一。

如何居中

尽量能写多少就写多少,让面试官看到你不仅熟悉css布局,而且很有探讨精神,竟然会多种布局方式
https://www.jianshu.com/p/841b9f395b8e

绝对定位50% + 负的margin【必须知道子元素的宽高,不然没法设置负的margin】
【点击查看代码】


绝对定位全为0,margin:auto【必须知道子元素的宽高】
【点击查看代码】


绝对定位 + calc【必须知道子元素的宽高】
【点击查看代码】


绝对定位50% + transform:translate(-50%,-50%)【不需要知道子元素宽高】
【点击查看代码】


flex布局

水平X居中

  • inline元素:爸爸身上写 text-align:center;
  • 块元素【必须设置width】:margin:0 auto;

【点击查看代码】

垂直Y居中

https://jscode.me/t/topic/1936

line-height === height

【点击查看代码】

多行文本垂直居中

父元素设置display:table,子元素设置display:table-cell;vertical-align:middle

【点击查看代码】

BFC 是什么?

块级格式化上下文(Block Formatting Contexts)
举例子:

  • 给爸爸overflow:hidden 清除浮动。(但我平常总是用 .clearfix 清除浮动)
  • 外边距合并,给爸爸添加overflow:hidden 取消父子 margin 合并(给儿子设置margin-top时,爸爸也会被顶下去)

BFC的原理

  • 在同一个BFC内的两个相邻Box的margin会发生重叠
  • BFC的区域不会与float box重叠
  • BFC在页面上是一个独立的容器,容器里面的子元素不会影响到外面的元素。反之也如此。
  • 计算BFC的高度时,浮动元素也参与计算

哪些元素会生成BFC

  • 根元素
  • float属性不为none
  • position为absolute或fixed
  • display为inline-block, table-cell, table-caption, flex, inline-flex
  • overflow不为visible【overflow:auto/hideen生成BFC】

line-height=150%和1.5的区别

150%是根据父元素的字体大小计算出行高,并且子元素依然沿用这个计算后的行高。而1.5则是根据子元素自己字体的大小去乘以1.5来计算行高。另,1.5em等也是按照150%的情况来算的。

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
26
27
28
29
<!--当父元素的行高line-height:150%时,会根据父元素的字体大小计算出行高值然后再让子元素继承。所以子元素的行高等于16px * 150% = 24px:-->
<div style="line-height:150%;font-size:16px;">
父元素内容
<div style="font-size:30px;">
Web前端开发<br/>
line-height行高问题
</div>
</div>
<!--当父元素行高line-height:1.5em时,和150%一样。所以,子元素行高等于16px * 1.5em = 45px:-->
<div style="line-height: 1.5em;;font-size:16px;">
父元素内容
<div style="font-size:30px;">
Web前端开发<br/>
line-height行高问题
</div>
</div>
<hr>
<!--当父元素行高line-height:1.5时,会根据子元素的字体大小动态计算出行高值让子元素继承。所以,子元素行高等于30px * 1.5 = 45px:-->
<div style="line-height: 1.5;font-size:16px;">
父元素内容
<div style="font-size:30px;">
Web前端开发<br/>
line-height行高问题
</div>
</div>

float布局的缺点

浮动布局,浮动元素脱离文档流,需要做清除浮动,这个处理不好的话,会带来很多问题,比如高度塌陷等。

如何清除浮动?

看笔记

  • 方法1: overflow:hidden
  • 方法2 给父级加上class=”clearfix”
    1
    2
    3
    4
    5
    6
    7
    8
    .clearfix:after{
    content:'';
    display:block;
    clear:both;
    }
    .clearfix{
    zoom:1; /*兼容低版本IE*/
    }

position定位

  • static:默认值,即没有定位
  • relative:相对于其原来的位置【导致自身位置发生相对变化,而不影响其他元素的位置】;原本所占的块空间不会改变。
  • absolute:相对于最近的已定位父元素(不为static的父元素),如果元素没有已定位的父元素,那么它的位置相对于<html>;absolute会脱离文档流;absolute具有“包裹性”,块元素在没有设置宽度的情况下,不再单独占满一行,而是宽度由内容决定
  • fixed:与absolute相同,除了fixed是相对于浏览器窗口或frame进行定位。
  • sticky:粘性定位,该定位相对于用户滚动的位置。

rem和em的区别

任意浏览器的默认字体高都是16px,最小12px。所有未经调整的浏览器都符合: 1em=16px。
em是相对于自身font-size进行计算的,自身没有设置font-size的话则继承自父元素的字体大小

1
2
3
4
5
6
7
p{
font-size:20px;
}
span {
font-size:40px;
margin: 1em; //margin:40px
}

rem(root em,根em),是根据HTML根元素字体大小进行计算的

css3

animation的api:

1
2
3
4
5
6
7
8
9
10
11
12
@keyframes changecolor
{
0% {background: red;}
25% {background: yellow;}
50% {background: blue;}
75% {background: green;}
100% {background: red;}
}
div{
animation:changecolor 5s infinite;
}

语法:animation: name duration timing-function delay iteration-count direction fill-mode play-state;
https://www.runoob.com/cssref/css3-pr-animation.html

如何用transform开启手机加速?

我们平常会使用left和top属性来修改节点的位置,但left和top会触发重reflow,代价相当大。取而代之的更好方法是使用translate,这个不会触发reflow

translateZ(0)或者translate3d(0,0,0) 开启GPU加速

transition 和 animation 的区别

transition是过渡,表示一个状态过渡到另一个状态
animation是动画,可以设置关键帧,一个动画可以由多个关键帧多个过渡组成,另外,animation可以设置infinite,暂停等

reflow重排/回流

回流是布局或者几何属性需要改变就称为回流。
回流必然导致重绘,重绘不一定会引发回流。

当你增加、删除、修改DOM节点时
当你移动DOM的位置
当你修改css尺寸(比如宽、高、display)
当你修改网页默认字体时

repaint重绘

重绘是当节点需要更改外观而不会影响布局的,比如改变 color,改变背景色 就称为重绘

当你需要往页面中插入10个<li>时,你可以:
通过文档碎片,或者拼接完成字符串后,再一次性塞入innerHTML,这样可以降低Repaint(因为它不是每次都触发Repaint,而是一次性打包Repaint)

requestAnimationFrame

浏览器是 60Hz 的刷新率,每 16ms 才会更新一次。

requestAnimationFrame的方式的优势如下:

  1. 经过浏览器优化,动画更流畅
  2. 窗口没激活时,动画将停止,省计算资源
  3. 更省电,尤其是对移动终端

requestAnimationFrame的用法与settimeout很相似,只是不需要设置时间间隔而已。

1
2
3
4
5
6
var timer;
var n = 0;
timer = setInterval(function(){
console.log(n);
n++;
},16);

1
2
3
4
5
6
7
var timer;
var n = 0;
timer = setTimeout(function loop(){
console.log(n);
n++;
timer = setTimeout(loop,16);
},16);
1
2
3
4
5
6
7
var timer;
var n = 0;
timer = requestAnimationFrame(function loop(){
console.log(n);
n++;
timer = requestAnimationFrame(loop);
});

cancelAnimationFrame(timer)可以取消动画

移动端布局

  • mata:viewport
  • @media媒体查询
  • js动态设置html字体大小 + dom元素使用rem单位 = 动态rem
  • 100vh == 视口高度,100vw == 视口宽度

js考题

7种数据类型[6种值类型 + 引用类型object]

原始类型/值类型:
number,string,boolean,symbol,null,undefined

引用类型:
object

typeof的结果

typeof的两个bug

  1. typeof 函数 //function,正确的应该是object
  2. typeof null //object,null不是object,它是7种数据类型之一
  • typeof console.log(1) //打印出1,然后类型为’undefined’,因为console返回undefined
  • typeof console.log // function
  • typeof 数组 //object
  • typeof null //object,【!!null是7种数据类型之一,它不是object,这是js遗留的历史bug】
  • typeof 函数//function 【!!function在7种数据类型中,属于object】
  • typeof NaN //number 【NaN表示非数字的数字类型】
1
2
3
4
5
6
7
var str1=new String('str1');
var str2='str2';
typeof str1;//object
typeof str2;//string
str1 instanceof String;//true
str2 instanceof String;//false

判断是否是数组类型

1
2
3
4
5
6
7
var arr = [1,2,3]
arr instanceof Array
//或者
Array.isArray(arr)
//或者
Object.prototype.toString.call(arr) === '[object Array]'

instanceof可以判断实例对象的构造函数

1
2
3
4
function Foo(){};
var foo = new Foo();
foo instanceof Foo //true

伪数组变成数组

1
2
3
4
5
ES5:
var arr = Array.prototype.slice.call(aLi);
ES6:
var arr = Array.from(aLi)

判断是否是空对象{}

用 JSON.stringify(obj)

1
2
var obj = {};
JSON.stringify(obj) === '{}' //true

function的length

函数的length值就是它的参数数量

1
2
function fn(a,b,c,d,e){}
fn.length //5

js中的内置函数(可以通过new的函数)

  • Number
  • String
  • Boolean
  • Object
  • Array
  • Function
  • Date
  • RegExp
  • Error

==与===

1
2
3
4
5
6
7
8
9
10
11
12
undefined == null //true
undefined === null //false
false == 'false' //false
undefined == false //false
null == false //false 【undefined和null与其他任何类型==都是false
!' ' == ' '
//空格为true,取反后为false
//布尔值与其他类型进行比较时,转成数字
//因此 0 == ' ' ,而空格和空字符串转成数字都是0,即
// '' == 0, ' ' == 0

必须用===,那么什么时候用==

1
2
3
4
if(obj.a == null){
//这里相当于obj.a === null || obj.a === undefined 的简写
//这是jQuery源码中的写法
}

js的垃圾回收机制

js具有自动垃圾回收机制。垃圾收集器会按照固定的时间间隔周期性的清理一下,释放内存。
垃圾回收方式是:标记清除、计数引用(该方法已经过时)。
原理:当变量进入环境时,将这个变量标记为“进入环境”。当变量离开环境时,则将其标记为“离开环境”。标记“离开环境”的就回收内存。
工作流程:

  1. 垃圾回收器,在运行的时候会给存储在内存中的所有变量都加上标记。
  2. 然后去掉环境中的变量以及被环境中的变量引用的变量(闭包)的标记。
  3. 剩下含有标记的会被视为准备删除的变量。
  4. 垃圾回收器完成内存清除工作,销毁那些被标记的值释放内存空间。

js运行机制

js是单线程的,同一时间只能做一件事。

同步和异步的区别

举例:点外卖
由于js是单线程运行的,如果同步会阻塞代码执行,异步则不会阻塞代码执行

一个ajax请求,由于网络慢,需要5s钟,如果是同步的,这5s页面就卡死在这了。异步的话,5s等待就等待,其他事情不耽误做

1
2
3
4
5
6
7
8
var a = true;
setTimeout(function(){
a=false;
},5000);
while(a){
console.log(1);
}
//很多人以为5s后,a变成false,while就中止了,实际上,由于一开始a=true,所以已经直接进入死循环了

哪些语句会放入异步任务队列中?

定时器
Promise
事件监听(比如click事件只有在被点击时才会触发)
观察者模式

Event loop事件循环

JavaScript只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

  1. 所有任务都在主线程上执行,形成一个执行栈。
  2. 主线程之外,还存在一个”任务队列”(task queue)。遇到异步代码就加入到任务队列中。
  3. 一旦”执行栈”中的所有同步任务执行完毕,系统就会从”任务队列”中取出需要执行的代码并放入执行栈中执行
  4. 主线程不断重复上面的第2,3步【执行栈中执行完成 -> 从任务队列中取 -> 执行栈中执行完成了 循环】。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 执行顺序问题
    console.log(0);
    setTimeout(function () {
    console.log(1);
    },0);
    new Promise(function(resolve,reject){
    console.log(2)
    resolve(3)
    }).then(function(val){
    console.log(val);
    })
    console.log(4);
    //答案0,2,4,3,1
    //同步执行完 > then > 定时器

Promise的知识点

3个状态

  • pending等待态: 初始状态,既不是成功,也不是失败状态。
  • fulfilled完成态: 异步操作成功。
  • rejected拒绝态: 异步操作失败。

2个过程

  • 异步操作成功,pending -> fullfilled (执行resolve)
  • 异步操作失败,pending -> rejected (执行reject)

Promise链式调用

1
2
$.ajax().then(成功1,失败1).then(成功2,失败2)
//只要前面一个then不报错,能够执行里面的任意一个函数,就能继续执行后面then里面的函数

手写实现Promise版的ajax

1
2
3
4
5
6
7
8
9
function xxx(){
return new Promise(function(resolve,reject){
//异步操作,成功执行resolve()
//失败执行reject()
xhr.send();
});
}
xxx().then(成功,失败)

async/await

作用:把异步代码写成同步代码的形式

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
26
27
28
29
30
31
32
33
34
35
function getData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.5) {
resolve('成功');
} else {
reject('失败');
}
}, 2000);
});
}
//.then的方法
function thenGetData(){
getData().then(res=>{
console.log(res)
},err=>{
console.log(err)
})
}
thenGetData();
//改写成async/await
//其中,reject需要try/catch(err)处理
async function asyncGetData() {
try {
const res = await getData(); //等待promise函数
console.log(res);
} catch (err) {
console.log(err);
}
}
asyncGetData();
  • async 表示这是一个async函数,await只能用在这个函数里面。
  • await 表示在这里等待promise返回结果了,再继续执行后面的代码。
  • async异步函数可以看成是generator函数的语法糖

ajax知识点

除了ajax,还能通过fetch进行通信

手写ajax

1
2
3
4
5
6
7
8
9
10
11
var xhr = new XMLHttpRequest(); //开机
xhr.open('GET','/',true); //打开浏览器
xhr.onreadystatechange = function(){
var data;
if(xhr.readyState===4 && xhr.status===200){
//返回的数据是字符串,需要转成对象的形式
data = JSON.parse(xhr.responseText);
console.log(data)
}
}
xhr.send();

【点击查看代码】

POST请求需要设置xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded')

5种readyState(请求状态)

  • 0:未初始化。尚未调用open()方法
  • 1:启动。已经调用open()方法,尚未调用send()方法
  • 2:发送。已经调用send()方法,尚未接收到响应
  • 3:接收。已经接收部分响应数据。
  • 4:完成。已经接收到全部响应数据,可以在客户端使用了。

xhr.status状态码

  • 2xx:请求成功
  • 3xx:需要重定向,浏览器会自动跳转
  • 4xx:客户端请求错误(用户发过来的地址不正确)
  • 5xx:服务端错误

200:正常。
301:永久重定向,浏览器会记住
302:临时重定向
304:该资源在上次请求之后没有任何修改(可以直接使用浏览器缓存)。
400:bad request,请求存在语法错误,服务器无法识别
401:unauthorized,请求需要认证
403:forbidden,请求的资源禁止被访问。
404:需要访问的资源不存在。
405:需要访问的资源被禁止。
500:internal sever error,服务器内部错误。
502:Bad GateWay,错误网关
503:service unavailable,服务器正忙,可能服务器临时过载或停机维护。

get和post的区别

  • GET 的参数放在 url 的查询参数里,POST 的参数(数据)放在请求体里。
  • POST比GET安全【因为GET的参数直接暴露在url中
  • GET 传送的参数有长度限制。POST 的参数(数据)没有长度限制(因为参数放在请求体里)
  • GET 用来获取数据,POST 用来写数据,POST 不幂等(注册 10 个和 11 个帐号是不幂等的;对文章进行更改 10 次和 11 次是幂等的。幂等指服务器上的资源总数不变)

除了get和post,还有哪些请求方式?

GET:获取资源
POST:提交或者创建
DELETE:删除资源
PATCH:部分修改数据
PUT:整体替换资源
HEAD:获得报文首部
OPTIONS:查询相应URI支持的HTTP方法。

怎么跨域?JSONP 是什么?CORS 是什么?

ajax无法跨域
可以通过 JSONP,CORS,document.domain,postMessage,WebSocket实现跨域

同源策略

  • 协议:http或https要一模一样
  • 域名:网址要一模一样
  • 端口:端口要一模一样【http默认端口80,https默认端口443】

jsonp

ajax不能跨域,但是script标签可以跨域~

JSONP = JSON + Padding(包裹)

原理是:动态创建script标签,并传递一个callback名(callback=abc)给服务端,然后服务端返回数据时会将这个callback名作为函数名来包裹住JSON数据,前端只需要定义好与callback名同名的函数abc,在函数内部处理参数即可

1
2
3
4
5
6
7
function flightHandler(data) {
alert('你查询的航班结果是:票价 ' + data.price + '元,余票' + data.tickets + '张。');
};
var url = 'http://flightQuery.com/flightResult.aspx?callback=flightHandler';
var script = document.createElement('script');
script.setAttribute('src', url);
document.body.appendChild(script);

浏览器传一个callback名过去告诉服务器,本地调用的函数叫做flightHandler
于是服务端将数据包裹在这个callback名内

1
2
3
4
5
flightHandler({
"code": "CA1998",
"price": 1780,
"tickets": 5
});

浏览器收到后,json数据直接以入参的方式, 放置到function中,自动执行该函数。

因为jsonp是通过动态创建<script>标签实现的,而script只能发送GET请求,不能POST,如果想post跨域怎么办?因此又有了cors跨域

cors

jsonp只能get跨域,不能post跨域,因此需要cors

服务端设置http header实现跨域
后端程序员添加response.setHeader("Access-Control-Allow-Origin", "*");

当你使用 ajax 发送请求时,浏览器发现该请求不符合同源策略,会给该请求加一个请求头:Origin,后台进行一系列处理,如果确定接受请求则在返回结果中加入一个响应头:Access-Control-Allow-Origin; 浏览器判断该相应头中是否包含 Origin 的值,如果有则浏览器会处理响应,我们就可以拿到响应数据,如果不包含浏览器直接驳回,这时我们无法拿到响应数据。所以 CORS 的表象是让你觉得它与同源的 ajax 请求没啥区别,代码完全一样。

document.domain

v.qq.comwww.qq.com不同源,但是他们的二级域名都是qq.com,如果v.qq.com需要获取www.qq.com的cookie,只需要设置document.domain = 'qq.com',即可共享cookie

postMessage

这种方式通常用于获取嵌入页面中的第三方iframe。一个页面发送消息,另一个页面判断来源并接收消息

1
2
3
4
5
6
7
8
9
10
// A窗口发送消息端
window.postMessage('data', 'http://B.com');
// B窗口接收消息端
window.addEventListener('message', event => {
console.log(event.origin)
if (event.origin === 'http://B.com') {
console.log('验证通过');
console.log(event.data)
}
});

闭包知识点【返回一个函数】

闭包的概念:「函数」和「函数内部能访问到的变量」的总和,就是一个闭包。【闭包就是能够读取其他函数内部变量的函数】
什么是闭包?
闭包的作用:「间接访问一个变量」,换句话说,「隐藏一个变量」。使得变量只在能该函数内部访问,外部无法直接访问和修改变量,从而达到收敛权限的目的
闭包缺点:对内存的消耗比较大,可能导致内存泄露

1
2
3
4
5
6
7
8
9
10
11
12
13
function add(){
var n = 1;
return function(){ //这个函数能访问到局部变量n,于是这个函数+变量n形成一个闭包
n++;
return n;
}
}
var plus = add();
plus(); //2
plus(); //3
plus(); //4
console.log(n) //报错,因为我们无法直接访问n

闭包的应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var divs = document.getElementsByTagName('div');
for(var i = 0;i<divs.length;i++){
divs[i].onclick = function(){
console.log(i);
}
}
//由于点击事件在for循环完成之后才被执行,而var是全局变量,此时全局的i是10
//因此点击时打印出10
//修正
//利用闭包和立即执行函数
var divs = document.getElementsByTagName('div');
for (i = 0; i < divs.length; i++) {
(function(i) {
divs[i].onclick = function() {
console.log(i);
}
})(i);
}
//每次for循环,都会生成一个立即执行函数(如果有10个div,则生成10个立即执行函数)
//通过闭包,访问的不再是全局的i,而是每次for循环传进来的参数i

this的指向

this只有在函数被执行时才能确认
箭头函数中的this是在定义函数的时候就确定了,而不是在执行函数时确定,继承自父执行上下文中的this

  • 直接调用,fn()的this指向window,在严格模式下为undefined
  • 对象调用,a.b.c.fn()的this指向a.b.c
  • 事件调用,div.onclick = function(){/这里的this指向div/}
  • 构造函数new Fn()的this指向新生成的实例对象
  • 箭头函数的this是继承自父执行上下文中的this【它的上一级的 this】
  • this指向 call,apply,bind 的第一个参数

https://zhuanlan.zhihu.com/p/23804247】

箭头函数say,它的父级是obj,而obj在window下【window.obj】,因此这里的this.x实际上表示的是window.x,因此输出的是11。

1
2
3
4
5
6
7
8
9
var x=11;
var obj={
x:22,
say:()=>{
console.log(this.x);
}
}
obj.say();
//相当于window.obj.say();

call,apply,bind的区别

call 和 apply 都是为了解决改变 this 的指向。作用都是相同的,只是传参的方式不同。

  • 除了第一个参数外,call 可以接收一个参数列表,apply 接受一个参数数组。
  • bind 和call一样,接收一个参数列表,只是call直接执行函数,而bind会返回一个函数,且该函数的this绑定死了,后期无法再次改变。
1
2
3
4
5
6
7
8
"use strict"
function fn(a,b){
console.log(this)
}
fn(1, 2) //undefined,严格模式下,this不为window而是undefined
fn.call(undefined, 1, 2) //undefined,非严格模式下为window
fn.apply(undefined, [1, 2]) //undefined,非严格模式下为window

在严格模式下, fn 里的 this 就是 call 的第一个参数,也就是 undefined。
在非严格模式下(不加”use strict”), call 传递的第一个参数如果是 undefined 或者 null, 那 this 会自动替换为 Window 对象

立即执行函数

作用:ES5没有块级作用域,所以只能通过 函数作用域 来防止污染全局变量
立即执行函数通过造出函数作用域,防止污染全局变量

1
2
3
(function(){
var n = 1;
})();

ES6可以使用块级作用域

1
2
3
{
let n = 1;
}

作用域链

  1. 函数在执行的过程中,先从自己内部找变量
  2. 如果找不到,再从创建当前函数所在的作用域去找, 以此往上,直到window
1
2
3
4
5
6
7
8
9
10
11
var a=1;
function f1(){
var b=2;
function f2(){
var c=3;
console.log(a);
}
f2();
}
f1(); //1
//当前作用域没有定义a,就去父级作用域f1里找,父级作用域里也没有定义a,就继续往上寻找,直到window对象为止

合并数组

1
2
3
4
5
6
7
8
9
var arr1 = [1,2,3];
var arr2 = [4,5,6];
//es5
var newArr = arr1.concat(arr2);
//es6
arr1.push(...arr2);
arr1 //[1,2,3,4,5,6]

浅拷贝

Object.assign

1
2
var obj = {a:1}
var copyObj = Object.assign({},obj)

深拷贝

JSON.parse(JSON.stringify(obj))

1
2
var obj = {a:1}
var copyObj = JSON.parse(JSON.stringify(obj))

缺点:不支持拷贝 函数、undefined、Symbol

递归拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function deepClone(obj){
var result = {};
for(var key in obj){
if(typeof obj[key] === 'object'){
result[key] = deepClone(obj[key]);
} else{
result[key] = obj[key];
}
}
return result;
}
var obj = {a:1, b:undefined, c:function(){}}
var copyObj = deepClone(obj)

数组去重

1
2
3
let arr = [1,1,1,2,3,4,5,1,3,6,3];
let newArr = [...new Set(arr)]; //去重后的数组
//原数组不变

原型

原型.PNG

这种抽象的题目,通过举例子回答。
https://zhuanlan.zhihu.com/p/23090041

  1. var arr = [1,2,3];
  2. 为什么arr可以使用push()方法,这个push()方法从哪得到的?当试图得到对象的某个属性时,如果它本身没有,就会去它的proto中寻找
  3. arr.proto === Array.prototype 【arr是个数组,因此它的proto指向构造函数Array的原型(公共属性和方法)】
  4. 在Array的公共方法中,有push()这个方法
  5. arr就是顺着原型链调用的push()方法
1
2
3
4
5
6
function People(){
this.name = 'name';
}
var p = new People();
People.prototype // {constructor: ƒ People(), __proto__: Object.prototype}
People.__proto__ // Function.prototype;函数的__proto__指向Function的原型

编写原型/面向对象的例子

js中没有类的概念,因此面向对象是依靠原型来实现的

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
26
function El(id){
this.el = document.querySelector(id);
}
El.prototype.html = function(val){
if(val){//如果传入参数,说明是修改innerHTML
this.el.innerHTML = val;
return this;//返回这个对象,用于链式操作
}else{//如果没传参数,说明是获取
return this.el.innerHTML;
}
}
El.prototype.on = function(type,fn){
this.el.addEventListener(type,fn);
return this; //用于链式调用
}
var div1 = new El('#app');
//获取
console.log(div1.html());
//链式调用
div1.html('<h1>hello<h1>').on('click',function(){
console.log(1);
});

JS Bin


改写成ES6写法

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
26
27
28
class El{
constructor(id) {
//私有属性
this.el = document.querySelector(id);
};
//公共方法prototype
html(val) {
if (val) { //如果传入参数,说明是修改innerHTML
this.el.innerHTML = val;
return this; //返回这个对象,用于链式操作
} else { //如果没传参数,说明是获取
return this.el.innerHTML;
}
};
on(type, fn) {
this.el.addEventListener(type, fn);
return this; //用于链式调用
};
}
var div1 = new El('#app');
//获取
console.log(div1.html());
//链式调用
div1.html('<h1>hello<h1>').on('click', function() {
console.log(1);
});

JS Bin

原型链

当一个对象想要获取某个属性或调用某个方法,而它本身没有时,就会顺着它的proto === 它的构造函数.prototype 里找。

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
26
27
28
29
30
31
32
33
34
35
function User(name){
this.name = name; //自身属性
}
User.prototype.sayName = function(){ //公共属性
console.log('我叫' + this.name);
}
var whh = new User('王花花');
whh.sayAge = function(){
console.log('今年22岁');
}
//whh的自身属性和方法:{name:'王花花', sayAge:f()}
whh.sayName(); //whh本身没有sayName这个方法,于是去它的 构造函数.prototype 里找
whh.toString(); //whh里没有该方法,于是顺着whh.__proto__ === User.prototype,这里也没有toString方法,于是继续顺着User.prototype__proto__ === Object.prototype,在对象的公共方法里找到了
```
整个原型链:whh由User构造而来,于是whh.__proto__ === User.prototype, User.prototype.__proto__ === Object.prototype, Object.prototype.__proto__ === null
---
**改写成ES6语法**
```javascript
class User{
constructor(name){
//私有属性
this.name = name;
};
//公有属性
sayName(){
console.log("我叫" + this.name);
};
}
let whh = new User('王花花'); // {name:'王花花'}
whh.sayName(); //我叫王花花

用instanceof判断构造函数

1
2
3
4
5
whh instanceof User //true,说明whh由User构造而来
User.prototype instanceof Object // true
//最好用constructor
whh.__proto__.constructor === User //true

如何判断这个属性是否是对象本身拥有的属性?

遍历自身属性和方法

1
2
3
4
5
6
7
8
9
10
11
12
for(let prop in whh){
console.log(prop)
}
//会将公共方法sayName也给打印出来
//正确的方法如下
for(prop in whh){
if(whh.hasOwnProperty(prop)){
console.log(prop);//将自有属性和方法打印出来
}
}
//此时,公共属性和方法不会打印出来

哪些方法可以创建一个对象

1
2
3
4
5
6
7
8
9
10
11
var obj1 = {a:1,b:2}
var obj2 = new Object({a:1,b:2})
function Fn(a,b){
this.a=a;
this.b=b;
}
var obj3 = new Fn(1,2)
var obj4 = Object.create({a:1,b:2})

new做了什么?

  1. 创建一个空对象
  2. this指向这个空对象
  3. 这个空对象的proto指向构造函数的prototype
  4. 开始执行代码(即对this赋值)
  5. return 这个对象

如果构造函数中没有手动写return,则默认return这个对象;如果手动return的话,基础类型自动无效,比如return 123,最终还是return这个对象;如果手动return引用类型,则返回手动写的这个引用类型

1
2
3
4
5
6
function Fn(name){
this.name = name;
return 1 //无效
return [1,2,3] //则返回这个引用类型
}
const fn = new Fn('Peter')

如何实现继承

ES5通过原型链

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
26
//父类
function Human(name){
this.name=name;
}
Human.prototype.say=function(){
console.log('我叫'+this.name);
}
//子类
function Male(name,age){
Human.call(this,name);//将父类的私有属性name传过来
this.gender = "男性";
this.age = age;
}
//寄生组合继承
//将父类的公有属性传过来
function Fn(){} //声明一个空的构造函数
Fn.prototype = Human.prototype;
Male.prototype = new Fn();
Male.prototype.hobby = function(){
console.log("男生喜欢玩电动");
}
var stage = new Male('stage',22);

ES6通过extends继承,super导入

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
//定义父类
class Human{
constructor(name){
//私有属性
this.name = name;
};
//公有属性
say(){
console.log("我叫"+this.name);
};
}
//子类
class Male extends Human{//将子类的prototype.__proto__链接到Human的公有属性
constructor(name,age){
super(name); //导入父类,将参数name传给父类
this.gender = '男';
this.age = age;
};
hobby(){
console.log('男生喜欢玩电动');
};
}
let stage = new Male('stage',22);

ES6新增的数据类型有哪些?

  • Set:类似于数组,但Set中不允许出现重复的元素
  • Map:类似于对象,但Map的key可以是任何数据类型

DOM押题

DOM事件机制/js事件机制/事件模型/事件流是什么?

一个事件的触发过程主要有三个阶段:捕获阶段,目标阶段,冒泡阶段
可以通过addEventerListener设置冒泡和捕获【默认false冒泡】

描述DOM事件捕获的具体流程

window -> document -> html -> body -> … -> 目标元素

补充:
通过document.body获取body标签
通过document.documentElement获取html标签

mouseover和mouseenter的区别

  • mouseover:当鼠标移入元素或其子元素都会触发事件,所以有一个重复触发,冒泡的过程。对应的移除事件是mouseout
  • mouseenter:不会导致冒泡,对应的移除事件是mouseleave

event对象

  • event.preventDefault();
  • event.stopPropagation(); //停止冒泡
  • event.currentTarget 【绑定了该事件的这个对象】
  • event.target 【当前被点击的元素】

querySelector与getElementBy等的区别

  1. 接收的参数不同,getElementBy传字符串,querySelector传css选择器
  2. query返回一个静态的 NodeList,而getElement这种方法返回一个动态的HTMLCollection。静态的就是说选出的所有元素的数组,不会随着文档操作而改变

移动端的触摸事件了解吗?

touchstart touchmove touchend touchcancel

模拟 swipe 事件

记录两次 touchmove 的位置差,如果后一次在前一次的右边,说明向右滑了。

事件委托/事件代理是什么?有什么好处?

优点:使代码简洁;减少浏览器的内存占用

  1. 假设父元素有4个儿子,我不设置4个监听器监听4个儿子,而是只设置1个监听器监听父元素,看触发事件的元素是哪个儿子,这就是事件委托。好处:省监听器。【一个爸爸有4个儿子,他们在同一个班上上课,班主任可以分别检查4个儿子的作业完成情况,也可以委托他们的爸爸检查作业,然后将结果告诉班主任】
  2. 可以监听还没有出生的儿子(动态生成的元素)。【如果又生了一个儿子,爸爸会连同新儿子的作业一起检查】

编写通用的事件代理代码

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
26
27
28
29
30
function bindEvent(el, type, selector, fn) {
if (!fn) { //如果只传入3个参数,第四个参数没传入
fn = selector;
selector = null;
}
el.addEventListener(type, function(ev) {
if (selector) {//如果传入子选择器
var target = ev.target;
if (target.matches(selector)) { //如果点击的target与传入的子选择器匹配
fn.call(target, ev); //this指向所点击的目标target
}
} else {
fn(ev);
}
});
}
//////////////////////////
//使用代理
bindEvent(app, 'click', 'p', function(ev) {
console.log(this.innerHTML); //this指向所点击的目标
})
//不使用代理
var p = document.getElementsByTagName('p')[0];
bindEvent(p, 'click', function(ev) {
console.log(p.innerHTML)
})

【点击查看代码】

HTTP押题

http报文的组成部分

请求报文:

  1. 请求行
  2. 请求头
  3. 空行/换行
  4. 请求体

响应报文:

  1. 状态行
  2. 响应头
  3. 空行/换行
  4. 响应体

https与http的区别

HTTP:超文本传输协议
HTTPS = HTTP + SSL/TLS,在HTTP和TCP之间添加一个安全协议层(SSL或TSL)

默认HTTP的端口号为80,HTTPS的端口号为443

HTTP的缺点:

  1. HTTP在传输的过程中使用的是未加密的明文,内容可能被窃听
  2. 不验证通信方身份,可能遭到伪装
  3. 无法验证报文完整性,可能被篡改

HTTPS就是HTTP加上SSL加密+身份认证+完整性保护

https的加密过程

客户端浏览器在使用HTTPS方式与Web服务器通信时有以下几个步骤,如图所示。
https加密过程

  1. 客户端发送一个随机值1、需要的协议以及支持的加密方法
  2. 服务器确认双方使用的加密方法,并给出数字证书、以及一个服务器生成的随机数2。
  3. 客户端收到数字证书并验证是否有效,验证通过会再生成一个随机值3,并使用数字证书的公钥去加密这个随机值3,然后发送给服务端
  4. 服务器使用自己的私钥,将客户端发来的随机数3解密出来
  5. 两端根据约定的加密方式,使用前面的三个随机数,生成”对话密钥”,用来加密接下来的整个对话过程。

通过以上步骤可知,在 SSL/TLS 握手阶段,两端使用非对称加密的方式来通信,但是因为非对称加密速度慢,损耗性能大,所以在正式传输数据时,两端使用对称加密的方式通信。

对称加密与非对称加密的区别

对称加密:两边拥有相同的秘钥,两边都知道如何将密文加密解密。优点是速度很快
非对称加密:有公钥私钥之分,公钥所有人都可以知道,公钥用于数据的加密,但是将数据解密必须使用私钥解密,私钥只有分发公钥的一方才知道。特点是速度慢,CPU 开销大

http 2.0

采用二进制传输,引入多路复用,对Header 压缩,服务端push

TCP和UDP的区别

简答:TCP 可靠(能够知道发送的请求是否成功)、面向连接、相对 UDP 较慢;UDP 不可靠,不面向连接、相对 TCP 较快。 补充:

  • 什么叫面向连接呢?事先为所发送的数据开辟出连接好的通道,然后再进行数据发送。【像打电话,只能两人打,第三人打就显示占线。】
  • 非面向连接:是指通信双方不需要事先建立一条通信线路,而是把每个带有目的地址的包(报文分组)送到线路上,由系统自主选定路线进行传输。【就像写信,不管对方有多忙,把信放到邮筒,就与自己无关系了。】

从输入 URL 到页面展现中间发生了什么?

301和302的区别

  • 301 永久重定向,浏览器会记住
  • 302 临时重定向

HTTP缓存怎么做?

添加Cache-Control: max-age=300
通过修改查询参数来取消缓存

1
2
http://cdn.com/1.js?v=1
http://cdn.com/1.js?v=2

Cache-Control 和 Etag 的区别是什么?

  • cookie是存储在浏览器上的一小段「数据」,用来记录某些当页面关闭或者刷新后仍然需要记录的信息。
  • session是一种让服务器能识别某个用户的「机制」。session 在实现的过程中需要使用cookie
  1. 服务器响应浏览器的请求时,会种下Cookie(服务端通过在Response headers中设置set-cookie来让浏览器种下cookie)
  2. 浏览器每次访问指定域名时会自动带上Cookie
  3. Cookie 一般用来记录不敏感信息,如用户名

cookie的相关操作?

1
2
3
4
5
6
7
8
//获取cookie
document.cookie //"a=1; b=2; c=3" 以 分号+空格 的形式分割
//设置cookie,将d=4的过期时间设置为↓
document.cookie="d=4; expires=Thu, 18 Dec 2013 12:00:00 GMT";
//删除 cookie 非常简单。您只需要设置过期时间 expires 为以前的时间即可
document.cookie = "expires=Thu, 01 Jan 1970 00:00:00 GMT";

Session

当一个用户打开淘宝登录后,刷新浏览器仍然展示登录状态。服务器如何分辨这次发起请求的用户是刚才登录过的用户呢?这里就使用了session保存状态。
用户在输入用户名密码提交给服务端,服务端验证通过后会在后端创建一个session用于记录用户的相关信息的对象。
创建session后,会把关联的session_id 通过set-Cookie 添加到响应头中
浏览器在加载页面时发现响应头中有 set-cookie字段,就把这个cookie 种到浏览器。
当下次刷新页面时,发送请求会自动带上这条cookie, 服务端在接收到后根据这个session_id来识别用户。

session的使用方式:session_id存在客户端的cookie里,服务端session存用户数据;当客户端访问服务端时,自动带上cookie,根据cookie里的session_id找用户数据

  1. Session 数据放在服务器上
  2. Session 需要通过在 Cookie 里记录 SessionID 实现
  3. SessionID 一般是随机数
  • Cookie 会随请求被发到服务器上【每次请求都会带上,影响获取资源的效率】,而 LocalStorage 不会
  • Cookie 存储量为4kb【因为每次发送请求,都会自动带上cookie,所以cookie不能太大】,LocalStorage 一般5Mb 左右
  • cookie的api用起来很麻烦,不是key:value的对象形式,而是字符串”a=1; b=2; c=3”这种形式,操作起来不方便;LocalStorage是key:value的对象形式,它的api用起来方便(localStorage.getItem(key))

  • localStorage:永久存储,除非手动删除;

  • sessionStorage:数据在当前浏览器窗口关闭后自动删除。
  • cookie:数据在设置的过期时间之前一直有效(如果没有设置过期时间,则在浏览器关闭时删除)

LocalStorage 和 SessionStorage的区别

完全一样,除了【LocalStorage永久存储;SessionStorage在会话结束后清空】

Load 和 DOMContentLoaded 的区别?

页面中的 DOM,CSS,JS,图片已经全部加载完毕后才会触发Load事件。
DOMContentLoaded 事件触发代表初始的 HTML 被完全加载和解析,不需要等待 CSS,JS,图片加载完成。

Vue押题

vue面试中常问知识点整理

Vue与React的区别/为什么选择Vue而不用React

  • 我发现贵公司的主页用的是Vue,所以我也用Vue
  • 模板 —— vue的模板基于html,react基于jsx。JSX糟糕透了,因为JSX的可读性太差,它硬是把HTML和JS混合在一起
  • 开发风格:React把html和css全部写进js,即all in js;vue采用单文件组件格式,即html,css,js写在同一个文件
  • React的生命周期函数太长~~~了
  • Vue有指令、过滤器,比如v-model进行双向数据绑定,在React中,如果有10个input,那你就要绑定10个事件;而在Vue中,只需要v-model就可以了
  • 已经熟悉Vue了,没有必要再学一个定位相似的东西,React能实现的我用Vue也能做到

更多关于react与vue的区别

Vue的生命周期钩子函数

Vue 实例从创建到销毁的过程,就是生命周期。


Vue提供的可以注册的钩子都在上图片的红色框标注。 他们分别是:

  1. beforeCreate:在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用。
  2. created:实例创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算, watch/event 事件回调。然而,尚未挂载到DOM上,$el 还不可用。
  3. beforeMount:在挂载开始之前被调用:相关的 render 函数首次被调用。
  4. mounted:el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。如果 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm.$el 也在文档内。
  5. beforeUpdate:数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。 你可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。
  6. updated:由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
    6.1 ativated:keep-alive 组件激活时调用
    6.2 deactivated:keep-alive 组件停用时调用
  7. beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。
  8. destroyed:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。 该钩子在服务器端渲染期间不被调用。
    8.1 errorCaptured:当捕获一个来自子孙组件的错误时被调用。

生命周期黑白简图
| 钩子函数 | 描述 | 例子 |
| ——– | —— | ——— |
| beforeCreate | 实例初始化,$el 与 data 都未初始化 | 此时无法获取到this(data和methods) |
| created | 实例创建完成,data 配置完成但挂载还未开始 $el 仍未初始化 | 此时可以获取到this(data和methods),一般用于异步数据的请求/初始化(将$bus绑定到this.prototype、读取localStorage等) |
| beforeMount |挂载开始前,render 函数被调用,$el 和 data 配置完成,但数据未在 DOM 上渲染 |
| mounted | 挂载完成,el 被新创建的 vm.$el 替换并挂载到实例上去,data里的初始数据在 DOM 上渲染完毕 | 此时可以获取由data里初始数据渲染后的DOM节点;可以获取ref |
| beforeUpdate | 数据更新时调用,但不进行 DOM 重新渲染 |
| updated | 数据更新并且 DOM 重新渲染,此时可执行依赖于 DOM 的操作 | 此时可以获取到异步请求的数据替换初始数据渲染后的DOM节点 |
| beforeDertroy | 实例销毁前 |
| destroyed | 实例销毁后 |

日期new Date()不能放在data:{time:new Date()},这样日期不会实时变化,可以放在created或者mounted这两个生命周期函数内

1
2
3
4
5
mounted(){
timer = setInterval(()=>{
this.time = new Date();
},1000);
}

【时间与生命周期函数】

第一次页面加载会触发哪几个钩子?
答:会触发 下面这几个beforeCreate, created, beforeMount, mounted 。

DOM 渲染在 哪个周期中就已经完成?
答:DOM 渲染在 mounted 中就已经完成了。

updated和this.$nextTick(callback)

api:this.$nextTick(callback)
在数据改变之后立即使用this.$nextTick,待 DOM 渲染完成之后,会执行nextTick里的callback

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
created:{
//异步请求
axios().then(()=>{
this.lists = [4,5,6];
this.$nextTick(()=>{
console.log('第1次更新');
});
});
axios().then(()=>{
this.lists = [7,8,9,10];
this.$nextTick(()={
console.log('第2次更新');
});
});
},
updated:{
console.log(1);
//每次数据有变化,都会执行updated
//由于lists更新了2次,所以会打印2次1
//如果不希望每次数据变化都执行,或者想要执行不同的方法,可以用this.$nextTick
}

如果在数据变化并重新渲染DOM完成之后,要操作DOM,就用this.$nextTick
如果不操作DOM,那就用watch侦听器
updated:只要data里的任何一个数据发生变化,就会触发;watch可以侦听data里具体某个数据的变化

父子组件的生命周期

父子组件的加载渲染过程,
父组件beforeCreated ->父组件created ->父组件beforeMounted ->子组件beforeCreated ->子组件created ->子组件beforeMounted ->子组件mounted -> 父组件mounted。
可以看出,父组件的mounted最后

子组件更新过程:
父beforeUpdate->子beforeUpdate->子updated->父updated

父组件更新过程:
父beforeUpdate->父updated

销毁过程:
父beforeDestroy->子beforeDestroy->子destroyed->父destroyed

继承组件的生命周期

继承组件的生命周期是交替执行的

Vue的组件通信

  • 父子通信(使用 Prop 传递数据、使用 v-on 绑定自定义事件)
  • 爷孙通信(通过两次父子通信,爷爷先传给爸爸,爸爸再传给儿子)
  • 兄弟通信(new Vue() 作为 eventBus)

Vue-router

利用hash做前端路由,根据对应的路由渲染对应的组件,实现无刷新页面
hash虽然在URL中,但不被包括在HTTP请求中;改变hash不会重加载页面。

Vuex 的作用是什么?

Vuex是状态管理中心,它方便组件之间进行状态的传递,任何组件都能获取到vuex中的状态,并能通过触发行为来改变状态
作用:用于状态管理,相当于本地数据库,响应式地处理数据
https://vuex.vuejs.org/zh-cn/intro.html

Vuex的实现原理

Vue 的双向绑定是如何实现的?有什么缺点?

https://www.cnblogs.com/libin-1/p/6893712.html
https://segmentfault.com/a/1190000006599500#articleHeader6
view更新 -> data也更新
data更新 -> view跟着更新

view更新 -> data也更新,这个简单,可以通过事件监听即可,比如给input输入框添加onchange事件
关键点在于data更新时,如何让view跟着更新?vue.js采用Object.defineProperty()来实现数据劫持,通过Object.defineProperty()来劫持各个属性的setter,getter,
结合发布-订阅模式的方式,在数据变动时发布消息给订阅者,触发相应的监听回调(来改变视图),这样就可以实现data驱动view更新了。

当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。
对于Object.defineProperty,主要是靠将普通属性变成访问器属性。访问器属性内部拥有两个方法,getter方法与setter方法。当用户读取对象的属性时,就调用其getter方法,当用户为此属性赋值时,就会调用setter方法。

https://github.com/huangchucai/MVVM-

Computed 计算属性的用法?跟 Methods 的区别。

https://zhuanlan.zhihu.com/p/33778594

数据结构与算法押题

排序算法

二分查找法

  1. 先将数组从大到小排序
  2. 从有序数组的中间开始查找,如果正好是要找的值,就return,否则
  3. 如果大了,就去左半边查找;如果小了,就去右半边查找;
  4. 重复第1步

翻转二叉树

安全押题

XSS跨站请求攻击

向页面注入js脚本:输入框里的value不是文本,而是一段js代码,此时这段恶意js代码被执行了,就是XSS攻击

1
<img src="xxx.png" onload="alert(1)">
  1. hacker写了一篇博客,同时偷偷插入一段<script>
  2. 攻击代码可以获取cookie,并发送到hacker的服务器中
  3. 别人看hacker的博客,就会把查看者的cookie发送到hacker的服务器

预防:

  • 方法0:将cookie设置为httpOnly,让js无法操作cookie
  • 方法1:不要使用innerHTML,使用textContent
  • 方法2:如果一定要用innerHTML,需要进行字符过滤http://www.cnblogs.com/xianshenglu/p/8324573.html
    1
    2
    3
    4
    5
    6
    7
    function html2Text(str) {
    let div = document.createElement('div');
    div.textContent = str;
    return div.innerHTML;
    }
    html2Text('<p>123</p>') // &lt;p&gt;123&lt;/p&gt;

或者

1
2
3
4
5
6
7
8
9
10
function escape(str) {
str = str.replace(/&/g, "&amp;");
str = str.replace(/</g, "&lt;");
str = str.replace(/>/g, "&gt;");
str = str.replace(/"/g, "&quto;");
str = str.replace(/'/g, "&##39;");
str = str.replace(/`/g, "&##96;");
str = str.replace(/\//g, "&##x2F;");
return str
}

CSRF跨站请求伪造

Cross Site Request Forgery

用户已经登录某网站后,攻击者构造某网站后台某个功能接口的请求地址,诱导用户去点击或者用特殊方法让该请求地址自动加载(比如在img中插入该接口的请求<img src="weibo.com/add_friend">)。
简单点说,CSRF 就是利用用户已经登录的状态发起恶意请求。

  • 过程
    • 用户登录新浪微博 weibo.com
    • 用户切换到 hacker.com(恶意网站)
    • hacker.com 发送一个 weibo.com/add_friend 请求,让当前用户关注陌生账号 。
    • 用户在不知不觉中关注了很多营销号。

用户没有想发这个请求,但是 hacker 伪造了用户发请求的假象。

CSRF原理.PNG

  • 避免
    • 验证 Referer:通过验证 Referer 来判断该请求是否为第三方网站发起的,weibo.com 可以拒绝来自 hacker.com 的请求
    • 验证 Token :服务器下发一个随机 Token,每次发起请求时将 Token 携带上,服务器验证 Token 是否有效。
    • 增加密码、短信验证码的验证

模块化押题

AMD规范

AMD是RequieJS的模块定义规范,它通过Require.js实现

AMD会异步加载(按需加载)js,依赖了什么模块就加载这个模块

CMD

CMD是SeaJS定义的模块化规范

CommonJS规范

CommonJs是nodejs的模块化规范

CommonJs不会异步加载JS,而是一次性加载出来
CommonJs可以配合npm使用
webpack基于CommonJs规范

ES6 module

在没有标准的时代,弄一些非标准的标准作为过渡
如今,已经有了标准的规范,从即刻起开始用 ES6 module

webpack押题

作用:

  • 处理模块化(因为浏览器不支持module)
  • 编译语法(将Less转成Css,将ES6转成ES5)
  • 代码压缩

有哪些loader和plugin?

loader的规则从右往左执行

转译出的文件过大怎么办?

  1. 使用code split(代码拆分)实现按需异步加载模块

在vue单页应用中,若不做任何处理,所有vue文件会打包为一个js文件,这个js文件非常的大,造成网页在首次进入时比较缓慢。利用code split(代码拆分),会将代码分离到不同的js中,然后进行按需加载这些js文件,能够提高页面首次进入的速度,网站性能也能够得到提升。

1
2
3
4
import page1 from '@/page/page1.vue'
//将上述代码改成
const page1 = () => import('@/page/page1.vue')
  1. 生产环境,压缩混淆并移除console
  2. 通过CND从外部引入,再配置 externals, 使webpack可以处理使之不参与打包

转译速度慢什么办?

使用HappyPack 和 DllPlugin 来提升你的 Webpack 构建速度

写过 webpack loader 吗?

http://www.alloyteam.com/2016/01/webpack-loader-1/

技术选型

  • 流行:目前流行的技术有哪些,能否用到我的项目中
  • 团队:团队对某种技术的熟悉程度,学习成本
  • 业务需求:能否套用现在的成熟解决方案/库来快速解决业务需求
  • 维护成本

对前端前景的展望,前端未来会怎么发展

前端社区

掘金,segmentfault,github,官方文档,v2ex,知乎(尤雨溪也在上面)

谈谈内容,样式,行为的分离

html负责内容
css负责样式
js负责行为
为什么这三者要分离?(反过来回答:如果不分离会导致……)
如果用html负责样式

1
2
3
4
5
<div bgcolor=green>
<center>
<font color=red size=20>文字居中</font>
</center>
</div>

这样会使得html的标签有些是用来表示内容的,有些是用来表示样式的,导致html结构很复杂,很难区分这些标签的逻辑结构

如果用css负责内容

1
2
3
4
5
6
7
8
9
div:after{
content:'我是内容';
}
```
这样会导致用户无法选中内容;js也无法取到
如果用js负责样式
```javascript
$('#div1').show(); // 这会导致原本的display修改为block

建议设置css,然后通过addClass和removeClass来切换样式

关于性能优化

性能优化及技巧分享

如果在图片下载下来之前就知道img的宽高,那么就把宽高写进img标签内;否则浏览器会先用一个小宽度的占位符,等到大图下载下来后,由于宽高撑大了原本的占位符,于是之后的元素的位置要往后退,这会导致重排(reflow),重排非常的浪费性能。

异步与回调

异步

异步:不等结果,直接进行下一步
同步 vs 异步:
同步:定时器的结果出来后,才进行下一步
异步:不等定时器的结果,马上进行下一步

1
2
3
4
5
6
7
8
9
10
11
12
console.log(1);
/*
这是个异步任务↓
setTimeout(function(){
console.log(2);
},2000);
*/
console.log(3);
先打印出1,再打印出3,两秒后打印出2

回调

可以通过callback回调拿到异步结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
console.log(1);
(function xxx(){
setTimeout(function(){
console.log(2);
callback();
},2000);
})();
function callback(){
console.log('endFn');
}
console.log(3);

this

this的指向

1
2
3
4
5
6
7
8
9
10
11
window.n = 'window name'
let obj = {
n: 'obj name',
sayN(){
console.log(this.n)
}
}
let fn = obj.sayN
fn() //window name,因为是window.fn(),fn这个方法属于window,所以this指向window
obj.sayN() // obj name,因为sayN这个方法属于obj,所以this指向obj

当前方法属于谁,this就是谁

箭头函数的this

1
2
3
4
5
6
7
8
9
10
window.n = 'window name'
let obj = {
n: 'obj name',
sayN: () => {
console.log(this.n)
}
}
obj.sayN()
//window name

箭头函数是和父级上下文绑定在一起的!!比如这里的箭头函数中的this.n,箭头函数本身与sayN平级,sayN在对象obj中,而obj的父级上下文就是window,因此这里箭头函数的this指向window

new一个实例对象,this指向这个实例对象

用new命令时,默认给构造函数的起始位置加上var this = {},this指向这个空对象

1
2
3
4
5
function x(){
console.log(this);
}
x(); //window
new x(); //对象{}

1
2
3
4
5
6
7
8
9
10
var a=11
function test1(){
this.a=22;
let b=function(){
console.log(this.a);
};
b();
}
test1(); //22
var x = new test1(); //11

分析:
test1()等同于window.test1(),这个方法属于window,因此调用时,this指向window

1
2
3
4
5
6
7
8
9
var a=11
function test1(){
this.a=22; //这里的this指向window,所以window.a从11变成了22
let b=function(){
console.log(this.a); // window.a此时是22
};
b(); //最终输出22
}


new test1()会在构造函数内部生成var this = {}

1
2
3
4
5
6
7
8
9
10
11
12
var a=11
function test1(){
//var this = {}
this.a=22;
let b=function(){
console.log(this.a);
};
b();
}
var x = new test1(); //最终结果11
//此时实例对象x={a: 22},方法b不在这个实例对象里,所以this无法指向x,只能指向window

要想最终结果是22,则改写成

1
2
3
4
5
6
7
8
9
10
11
var a=11
function test1(){
this.a=22;
this.b=function(){
console.log(this.a);
};
this.b();
}
var x = new test1();
//此时x={a:22,b:方法}
因此this指向x,this.a的结果是22

定时器

1
2
3
4
5
6
7
8
9
function getSomething(){
setTimeout(function(){
return 'hello'
})
}
let something = getSomething()
console.log(something)
//undefined

由于定时器是异步的,所以优先执行同步代码,因此something是undefined

-------------本文结束感谢您的阅读-------------