暑期实习给我带来了美好的实习体验,作为一个刚走出校园踏入社会的学生,熟悉了职场环境,认识了优秀的同事,有了不同的经历。也让我得以把在学校和自身所学的理论知识,运用到实际工作中去,提升了自己的经验和能力,这是在企业环境之外无法接触到的,让我对以后的职业生涯有了更清晰的认识。也让我明白了工作是一个不断在实践中学习,在学习中实践的过程。总结和积累是进步的阶梯。
秋招已然开始,为了能更进一步,也投递和面试了许多公司的岗位,每次都会遇到新问题和新知识,在这里也进行一个总结,查漏补缺的同时增强记忆,希望在秋招后也能拿到理想的offer!
一、前端基础
1、浏览器事件循环
JavaScript 引擎是单线程
JavaScript 引擎是单线程,也就是说每次只能执行一项任务,其他任务都得按照顺序排队等待被执行,只有当前的任务执行完成之后才会往下执行下一个任务。
HTML5 中提出了 Web-Worker API,主要是为了解决页面阻塞问题,但是并没有改变 JavaScript 是单线程的本质。了解 Web-Worker。
JavaScript 事件循环机制
JavaScript 事件循环机制分为浏览器和 Node 事件循环机制,两者的实现技术不一样,浏览器 Event Loop 是 HTML 中定义的规范,Node Event Loop 是由 libuv 库实现。这里主要讲的是浏览器部分。
Javascript 有一个 main thread 主线程和 call-stack 调用栈(执行栈),所有的任务都会被放到调用栈等待主线程执行。
-
JS 调用栈
JS 调用栈是一种后进先出的数据结构。当函数被调用时,会被添加到栈中的顶部,执行完成之后就从栈顶部移出该函数,直到栈内被清空。
-
同步任务、异步任务
JavaScript 单线程中的任务分为同步任务和异步任务。同步任务会在调用栈中按照顺序排队等待主线程执行,异步任务则会在异步有了结果后将注册的回调函数添加到任务队列(消息队列)中等待主线程空闲的时候,也就是栈内被清空的时候,被读取到栈中等待主线程执行。任务队列是先进先出的数据结构。
-
Event Loop
调用栈中的同步任务都执行完毕,栈内被清空了,就代表主线程空闲了,这个时候就会去任务队列中按照顺序读取一个任务放入到栈中执行。每次栈内被清空,都会去读取任务队列有没有任务,有就读取执行,一直循环读取-执行的操作,就形成了事件循环。
宏任务(macro-task)、微任务(micro-task)
除了广义的同步任务和异步任务,JavaScript 单线程中的任务可以细分为宏任务和微任务。
macro-task包括:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。
micro-task包括:process.nextTick, Promises, Object.observe, MutationObserver。
有了宏任务和微任务的概念后,那 JS 的执行顺序是怎样的?是宏任务先还是微任务先?
第一次事件循环中,JavaScript 引擎会把整个 script 代码当成一个宏任务执行,执行完成之后,再检测本次循环中是否寻在微任务,存在的话就依次从微任务的任务队列中读取执行完所有的微任务,再读取宏任务的任务队列中的任务执行,再执行所有的微任务,如此循环。JS 的执行顺序就是每次事件循环中的宏任务-微任务。
2、xss和csrf
2.1 CSRF(Cross-site request forgery):跨站请求伪造。要完成一次CSRF攻击,受害者必须满足两个必要的条件:
(1)登录受信任网站A,并在本地生成Cookie。(如果用户没有登录网站A,那么网站B在诱导的时候,请求网站A的api接口时,会提示你登录)
(2)在不登出A的情况下,访问危险网站B(其实是利用了网站A的漏洞)。
防御方法:
方法一、Token 验证:(用的最多)
(1)服务器发送给客户端一个token;
(2)客户端提交的表单中带着这个token。
(3)如果这个 token 不合法,那么服务器拒绝这个请求。
方法二:隐藏令牌:
把 token 隐藏在 http 的 head头中。
方法二和方法一有点像,本质上没有太大区别,只是使用方式上有区别。
方法三、Referer 验证:
Referer 指的是页面请求来源。意思是,只接受本站的请求,服务器才做响应;如果不是,就拦截。
2.2 XSS(Cross Site Scripting):跨域脚本攻击。XSS攻击的核心原理是:不需要你做任何的登录认证,它会通过合法的操作(比如在url中输入、在评论框中输入),向你的页面注入脚本(可能是js、hmtl代码块等)。
最后导致的结果可能是:盗用Cookie破坏页面的正常结构,插入广告等恶意内容D-doss攻击
XSS的攻击方式:
(1)反射型
发出请求时,XSS代码出现在url中,作为输入提交到服务器端,服务器端解析后响应,XSS代码随响应内容一起传回给浏览器,最后浏览器解析执行XSS代码。这个过程像一次反射,所以叫反射型XSS。
(2)存储型存
储型XSS和反射型XSS的差别在于,提交的代码会存储在服务器端(数据库、内存、文件系统等),下次请求时目标页面时不用再提交XSS代码。
防御方法:
(1)HttpOnly 防止劫取 Cookie:严格来说,HttpOnly 并非阻止 XSS 攻击,而是能阻止 XSS 攻击后的 Cookie 劫持攻击
(2)输入检查:不要相信用户的任何输入。 对于用户的任何输入要进行检查、过滤和转义。建立可信任的字符和 HTML 标签白名单,对于不在白名单之列的字符或者标签进行过滤或编码。
(3)输出检查:用户的输入会存在问题,服务端的输出也会存在问题。一般来说,除富文本的输出外,在变量输出到 HTML 页面时,可以使用编码或转义的方式来防御 XSS 攻击。例如利用 sanitize-html 对输出内容进行有规则的过滤之后再输出到页面中。
2.3 CSRF 和 XSS 的区别
区别一:
CSRF:需要用户先登录网站A,获取 cookie。XSS:不需要登录。
区别二:(原理的区别)
CSRF:是利用网站A本身的漏洞,去请求网站A的api。XSS:是向网站 A 注入 JS代码,然后执行 JS 里的代码,篡改网站A的内容。
3、https工作原理
3.1 什么是SSL/TSL
SSL和TSL是一种安全协议。其中SSL是早起采用的安全协议,后来TSL是在SSL的基础上进一步标准化了SSL协议。在上面的图中可以看到,SSL和TSL位于传输层之上,在数据到达传输层之前都会经过SSL/TSL协议层处理,由SSL/TSL保证数据的机密性和完整性
3.2 SSL的握手过程:
1. 客户端提交https请求
2. 服务器响应客户端,并把证书公钥发给客户端
3. 客户端验证证书公钥的有效性
4. 有效后,会生成一个会话密钥
5. 用证书公钥加密这个会话密钥后,发送给服务器
6. 服务器收到公钥加密的会话密钥后,用私钥解密,回去会话密钥
7. 客户端与服务器双方利用这个会话密钥加密要传输的数据进行通信
4、绝对定位如何确定元素相对的父级元素
设置为绝对定位的元素框从文档流完全删除,并相对于其包含块定位,包含块可能是文档中的另一个元素或者是初始包含块。元素原先在正常文档流中所占的空间会关闭,就好像该元素原来不存在一样。元素定位后生成一个块级框,而不论原来它在正常流中生成何种类型的框。
在官方定义中,并没有提到在什么情况下绝对定位相对于父元素定位,又在什么情况下相对于初始包含快也就是body定位?其实很简单,当该元素设置为绝对定位absolute时,如果父元素为相对定位relative,则该元素相对于父元素定位;反之则相对于初始包含块即body元素定位。
5、项目的登录模块的实现
5.1功能:
本模块主要用于对用户身份进行鉴别。用户通过表单提供用户名和密码信息,系统根据用户提供的登录信息对用户进行查询鉴别。 如果身份合法,则用户可进入商品页面。
5.2 对应处理:
1.输入用户的登录信息:在页面提供的表单处输入用户的用户名和密码信息,点击“登录”按钮提交表单数据信息。 也可点击“注册”按钮,注册新用户。
2.用户身份进行验证:连接数据库,打开user集合,检验用户登录信息。以输入的“用户名”数据为查询条件来查看用户是否存在,如果存在,继续检验其输入密码是否正确,密码和用户名都正确,则进入商品展示页面;如果用户名不存在或密码不正确,则给出相应的提示。
6、图片懒加载如何实现的?
页面中的img
元素,如果没有src
属性,浏览器就不会发出请求去下载图片,只有通过javascript
设置了图片路径,浏览器才会发送请求。所以懒加载基本的原理就是用dataset
自定义属性取代src
存储图片的路径,然后在检测到图片进入到可视区域的时候,再将其换为src
。
7、使用原生 XMLHttpRequest 对象封装 ajax 函数
//Get请求
function getXhr(){
if (typeof XMLHttpRequest != 'undefined') {
return new XMLHttpRequest();
}
}
var xhr = getXhr();
//GET请求
xhr.open('GET', url);
xhr.send(null);
xhr.onreadystatechange = function(res) {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
console.log(JSON.parse(xhr.responseText))
}
}
}
//POST请求
function getXhr(){
if (typeof XMLHttpRequest != 'undefined') {
return new XMLHttpRequest();
}
}
var xhr = getXhr();
var stringData = {
uname: '123',
password: '123',
code: ''
}
stringData = JSON.stringify(stringData);
//POST请求
xhr.open('POST', '/user/login');
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.send(stringData)
xhr.onreadystatechange = function(res) {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
console.log(JSON.parse(xhr.responseText))
}
}
}
8、说一下知道的排序算法的原理和时间复杂度
9、setTimeout和nextTick的区别和应用场景
nextTick:将回调延迟到下次DOM更新循环之后执行。在修改数据之后立即使用这个方法,获取更新后的DOM。
应用场景:需要在视图更新之后,基于新的视图进行操作
在Vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中,在created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,所以此处一定要将DOM操作的js代码放进Vue.nextTick()的回调函数中。与之对应的就是mounted()钩子函数,因为该钩子函数执行时所有的DOM挂载和渲染都已完成,此时在该钩子函数中进行任何DOM操作都不会有问题 。
setTimeout:将回调延迟到指定时间之后执行。
nextTick和setTimeout两者回调执行的前提条件不相同。
10、兼容性问题如何解决
1.IE6双边距问题:IE6在浮动后,又有横向的margin,此时,该元素的外边距是其值的2倍 解决办法:display:block;
2.IE6下图片的下方有空隙 解决方法:给img设置display:block;
3.IE6下两个float之间会有个3px的bug 解决办法:给右边的元素也设置float:left;
4.IE6下没有min-width的概念,其默认的width就是min-width
5.IE6下在使用margin:0 auto;无法使其居中 解决办法:为其父容器设置text-align:center;
6.被点击过后的超链接不再具有hover和active属性
解决办法:按lvha的顺序书写css样式, ":link": a标签还未被访问的状态; ":visited": a标签已被访问过的状态; ":hover": 鼠标悬停在a标签上的状态; ":active": a标签被鼠标按着时的状态;
11、伪元素和伪类差别
伪类:用于向某些选择器添加特殊的效果,伪类的效果可以通过添加实际的类来实现
伪元素:用于将特殊的效果添加到某些选择器,伪元素的效果可以通过添加实际的元素来实现
伪类只能使用“:”,而伪元素既可以使用“:”,也可以使用“::”。因为伪类是类似于添加类所以可以是多个,而伪元素在一个选择器中只能出现一次,并且只能出现在末尾
12、跨域解决方案
(1)CORS(包括里面的头部设置那些,以及服务端要开放的一些能力),服务端设置Access-Control-Allow-Origin即可,前端无须设置,若要带cookie请求,前后端都需要设置。
(2)JSONP:JSONP(JSON with Padding) 是一种跨域请求方式。主要原理是利用了script 标签可以跨域请求的特点,由其 src 属性发送请求到服务器,服务器返回 js 代码,网页端接受响应,然后就直接执行了,这和通过 script 标签引用外部文件的原理是一样的。
(3)正向代理指你本地发送的一些http请求,被你自己配置的burp suite或者webpack-dev-server等设置的代理拦截之后,把你的请求发送到其他一些代理服务器中。解决前后端分离下的跨域问题
(4)反向代理,一般是弄个nginx方向代理服务器,实现负载均衡。简单来说就是正向代理是浏览器端这边,反向代理是服务器端那边
(5)document.domain + iframe跨域:此方案仅限主域相同,子域不同的跨域应用场景。两个页面都通过js强制设置document.domain为基础主域,就实现了同域。
(6)postMessage跨域:postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:a.) 页面和其打开的新窗口的数据传递 b.) 多窗口之间消息传递
c.) 页面与嵌套的iframe消息传递 d.) 上面三个场景的跨域数据传递
13、说一下箭头函数以及使用场景
1、箭头函数没有自己的this, 它的this是继承而来; 默认指向在定义它时所处的对象(宿主对象),而不是执行时的对象, 定义它的时候,可能环境是window; 箭头函数可以方便地让我们在 setTimeout ,setInterval中方便的使用this
2、使用场景
(1)箭头函数适合于无复杂逻辑或者无副作用的纯函数场景下,例如:用在 map、reduce、filter 的回调函数定义中
(2)箭头函数的亮点是简洁,但在有多层函数嵌套的情况下,箭头函数反而影响了函数的作用范围的识别度,这种情况不建议使用箭头函数
(3)箭头函数要实现类似纯函数的效果,必须剔除外部状态。所以箭头函数不具备普通函数里常见的 this、arguments 等,当然也就不能用 call()、apply()、bind() 去改变 this 的指向
(4)箭头函数不适合定义对象的方法(对象字面量方法、对象原型方法、构造器方法),因为箭头函数没有自己的 this,其内部的 this 指向的是外层作用域的 this
14、变量提升
- 使用 var 来声明变量的时候,会提到当前作用域的顶端,而赋值操作在原处不变
- 变量提升不能跨script
- 函数提升优先于变量
- 函数提升:具名函数的声明有两种方式:1. 函数声明式 2. 函数字面量式。函数声明式的提升现象和变量提升略有不同,函数声明式会提升到作用域最前边,并且将定义内容一起提升到最上边。
- let 不会变量提升
- 所有的声明都会提升到作用域的最顶上去。
- 同一个变量只会声明一次,其他的会被忽略掉或者覆盖掉。
15、水平垂直居中布局
1. 已知盒子大小:
1) 子盒子:定位+margin(position: absolute; left:50%; top:50%; margin-top:-盒子高度一半px;margin-left:-盒子宽度一半px)
水平居中margin: 0 auto;也可以
2) 子盒子:定位:positon: absolute; top: 0; left: 0; right: 0; bottom: 0; margin: auto;
2. 未知盒子大小:定位+transform (left:50%;top:50%;transform: translate(-50%,-50%))
3. flex布局: (1)父盒子 display:flex; just-content: center; align-items: center; (2)父级display:flex;子级:margin:auto 最简单方便
4. 父盒子:display: table-cell; text-align: center; vertical-align: middle;
5. 使用js获取到盒子的宽高,按照方法1设置 定位+margin
16、
二、算法题
1.判断数组中是否有重复数
2.排序链表去重
3. 翻转二叉树
4.如何用promise实现promise.all()
5、闭包是什么?请手写一个函数,这个函数名字是a,使得a被执行之后有如下效果。
a(); // 函数返回值为1
a(); // 函数返回值为2
a(); // 函数返回值为3
// ...以此类推,每调用一次a函数,函数返回
var func = function(){
var count = 0
return{
getCount:function(){
return ++count;
}
}
}().getCount
console.log(func())
console.log(func())
console.log(func())
实现一个函数,用于输出指定字符串中的最大相同连续字符的长度function getMaxContinuousLen(str) {
function getMaxContinuousLen(str) {
//...
}
输入:abcdddefddg =》输出:3
输入:abcdd =》输出:2
输入:ab =》输出:
function maxLength(str){
var count = 1
var max = 0
var newStr = str.split('')
for(let i = 1 ; i < newStr.length ; i++){
if(newStr[i] === newStr[i-1]){
count++
}
max = count
count = 1
}
console.log(max>count?max:count)
}
maxLength('abcdddefddg')
6、递归实现n阶乘
普通递归:
int factorial(int n)
{
if(n==1 || n==0)
return 1;
else
return n*factorial(n-1);
}
尾递归:
int factorial(int n, int a)
{
if(n==1 || n==0)
return a;
else
return factorial(n-1, n*a);
}
由以上两种递归解决阶乘的例子可以看出在递归回归的时候尾递归和普通递归是不一样的,普通递归需要沿着一步一步进来的函数一步一步地出去,而尾递归则可以直接回到主函数。以上也可以说明尾递归比普通递归更能节约函数的堆栈空间。尾递归也更容易转换为与其对等的迭代控制结构。
三、开放性问题
1、为什么选择vue框架
2、前端安全怎么防范
我的理解应该是从XSS和CSRF入手,描述原理和如何防御。
3、项目中的通过什么实现性能优化