文章目录
- 1. 常见状态码
- 2. 数据请求在created和mounted的区别
- 3. 在项目里有遇到过跨域的问题吗?怎么解决的?
- 4. 项目里有实现登录功能吗?是用什么实现的身份验证?用户信息和token是存在哪里?你的退出登录是怎么实现的?
- 5. 手机端的适配是怎么实现的?在实际使用过程中有没有出现bug?
- 6. 除了懒加载,还有哪些图片性能优化方式?
- 7. 精灵图的优缺点
- 8. 字体图标的优缺点?
- 9. 字体图标可以实现任意图形吗?(目前真不清楚)
- 10. 说一下对Promise的理解
- 11. 为什么Promise的状态一旦变化就无法改变?(暂时不清楚)
- 12. 有什么办法可以让老版本的浏览器可以跑ES6语法?
- 13. 说一下Babel的原理
- 14. Babel可以解析所有的新语法吗?(不清楚)
- 15. TCP和UDP的区别?
- 16. 说一下对HTTP的理解
- 17. vue响应式是如何实现的,vue2中的响应式有什么问题和不足点;为什么数组下标复制和对象新增属性不会被拦截到。为什么vue3中换proxy实现了。
- 18. vue中几种传值方式
- 19. EventLoop,在nodejs中的EventLoop是如何什么样的
- 20. 你说你的项目用了流式布局,流式布局有什么特点?它的优点是什么?
- 21. 解释下Ajax的五个步骤这就是Ajax典型的五部曲
- 22. js数组操作---改变原数组和不改变原数组的方法
- 23. 浏览器常见的兼容性问题
1. 常见状态码
2. 数据请求在created和mounted的区别
3. 在项目里有遇到过跨域的问题吗?怎么解决的?
3.1 cors:目前最常用的一种解决办法,通过设置后端允许跨域实现。
res.setHeader(‘Access-Control-Allow-Origin’, ‘*’);
res.setHeader(“Access-Control-Allow-Methods”, “GET, PUT, OPTIONS,POST”);
3.2 node中间件、nginx反向代理:
跨域限制的时候浏览器不能跨域访问服务器,node中间件和nginx反向代理,都是让请求发给代理服务器,静态页面面和代理服务器是同源的,然后代理服务器再向后端服务器发请求,服务器和服务器之间不存在同源限制。
3.3 JSONP:
利用的原理是script标签可以跨域请求资源,将回调函数作为参数拼接在url中。后端收到请求,调用该回调函数,并将数据作为参数返回去,注意设置响应头返回文档类型,应该设置成javascript。
3.4 postmessage:
H5新增API,通过发送和接收API实现跨域通信。
4. 项目里有实现登录功能吗?是用什么实现的身份验证?用户信息和token是存在哪里?你的退出登录是怎么实现的?
1)Cookie + Session 实现流程
Cookie + Session 的登录方式是最经典的一种登录方式,现在仍然有大量的企业在使用。
用户访问a.com/pageA,并输入密码登录。
- 服务器验证密码无误后,会创建 SessionId,并将它保存起来。
- 服务器端响应这个 HTTP 请求,并通过 Set-Cookie 头信息,将 SessionId 写入 Cookie 中。 服务器端的 SessionId 可能存放在很多地方,例如:内存、文件、数据库等。
第一次登录完成之后,后续的访问就可以直接使用 Cookie 进行身份验证了:
用户访问a.com/pageB页面时,会自动带上第一次登录时写入的 Cookie。
- 服务器端比对 Cookie 中的 SessionId 和保存在服务器端的 SessionId 是否一致。
- 如果一致,则身份验证成功。
Cookie + Session 存在的问题
虽然我们使用 Cookie + Session 的方式完成了登录验证,但仍然存在一些问题:
• 由于服务器端需要对接大量的客户端,也就需要存放大量的 SessionId,这样会导致服务器压力过大。
• 如果服务器端是一个集群,为了同步登录态,需要将 SessionId 同步到每一台机器上,无形中增加了服务器端维护成本。
• 由于 SessionId 存放在 Cookie 中,所以无法避免 CSRF 攻击。
2)Token 登录
为了解决 Session + Cookie 机制暴露出的诸多问题,我们可以使用 Token 的登录方式。
Token 是服务端生成的一串字符串,以作为客户端请求的一个令牌。当第一次登录后,服务器会生成一个 Token 并返回给客户端,客户端后续访问时,只需带上这个 Token 即可完成身份认证。
Token 机制实现流程
用户首次登录时:
- 用户输入账号密码,并点击登录。
- 服务器端验证账号密码无误,创建 Token。
- 服务器端将 Token 返回给客户端,由客户端自由保存。
后续页面访问时:
- 用户访问a.com/pageB时,带上第一次登录时获取的 Token。
- 服务器端验证 Token ,有效则身份验证成功。
Token 机制的特点
根据上面的案例,我们可以分析出 Token 的优缺点:
• 服务器端不需要存放 Token,所以不会对服务器端造成压力,即使是服务器集群,也不需要增加维护成本。
• Token 可以存放在前端任何地方,可以不用保存在 Cookie 中,提升了页面的安全性。
• Token 下发之后,只要在生效时间之内,就一直有效,如果服务器端想收回此 Token 的权限,并不容易。
Token 的生成方式
最常见的 Token 生成方式是使用 JWT(Json Web Token),它是一种简洁的,自包含的方法用于通信双方之间以 JSON 对象的形式安全的传递信息。
1.清理当前用户缓存数据
2.清理权限相关配置
3.返回登录页
5. 手机端的适配是怎么实现的?在实际使用过程中有没有出现bug?
1.媒体查询
2.rem
3.vw/vh(混用导致盒子变形)
4.flex布局
6. 除了懒加载,还有哪些图片性能优化方式?
懒加载是一种在页面加载时延迟加载一些非关键资源的技术,换句话说就是按需加载。对于图片来说,非关键通常意味着离屏。
我们之前看到的懒加载一般是这样的形式:
- 浏览一个网页,准备往下拖动滚动条
- 拖动一个占位图片到视窗
- 占位图片被瞬间替换成最终的图片
网页首先用一张轻量级的图片占位,当占位图片被拖动到视窗,瞬间加载目标图片,然后替换占位图片。
1.图片压缩算法
2.将图片转Base64格式来节约请求
3.图片预加载
4.精灵图
7. 精灵图的优缺点
优点:
减少HTTP请求数,提高页面加载速度;增加图片信息重复度,提高压缩比,减少图片大小;更换风格方便,只需在一张或几张图片上修改颜色或样式即可实现。
缺点:
图片合并麻烦;维护麻烦,修改一个图片可能需要从新布局整个图片,样式。
8. 字体图标的优缺点?
优势
轻量级:一个图标字体要比一系列的图像要小。一旦字体加载了,图标就会马上渲染出来,不需要下载一个个图像。这样可以减少HTTP的请求数量,而且和HTML5的离线存储配合,可以对性能做出优化。
灵活性:不调字体可以像页面中的文字一样,通过font-size属性来对其进行大小的设置,而且还可以添加各种文字效果,如color、hover、filter、text-shadow、transform等效果。灵活的简直不像话!
兼容性:图标字体支持现代浏览器,甚至是低版本的IE浏览器,所以可以放心的使用它。
相比于位图放大图片会出现失真、缩小又会浪费掉像素点,图标字体不会出现这种情况。
劣势
图标字体只能被渲染成单色,或者是CSS3的渐变色 版权上也有着对应的限制,当然还是有很多免费的图标字体可以供我们下载。
当自己创作图标字体的时候,是比较耗费时间的,重构人员的后期维护成本也比较高
9. 字体图标可以实现任意图形吗?(目前真不清楚)
10. 说一下对Promise的理解
Promise是异步微任务,解决了异步多层嵌套回调的问题,让代码的可读性更高,更容易维护
Promise使用:
Promise是ES6提供的一个构造函数,可以使用Promise构造函数new一个实例,Promise构造函数接收一个函数作为参数,这个函数有两个参数,分别是两个函数
resolve
和reject
,resolve
将Promise的状态由等待变为成功,将异步操作的结果作为参数传递过去;reject
则将状态由等待转变为失败,在异步操作失败时调用,将异步操作报出的错误作为参数传递过去。实例创建完成后,可以使用then
方法分别指定成功或失败的回调函数,也可以使用catch捕获失败,then和catch最终返回的也是一个Promise,所以可以链式调用。
Promise的特点:
- 对象的状态不受外界影响(Promise对象代表一个异步操作,有三种状态)。 - pending(执行中) - Resolved(成功,又称Fulfilled) - rejected(拒绝)
其中pending为初始状态,fulfilled和rejected为结束状态(结束状态表示promise的生命周期已结束)。- 一旦状态改变,就不会再变,任何时候都可以得到这个结果。 Promise对象的状态改变,只有两种可能(状态凝固了,就不会再变了,会一直保持这个结果): - 从Pending变为Resolved -
从Pending变为Rejected- resolve 方法的参数是then中回调函数的参数,reject 方法中的参数是catch中的参数
- then 方法和 catch方法 只要不报错,返回的都是一个fullfilled状态的promise
加分回答
Promise的其他方法:
Promise.resolve() :返回的Promise对象状态为fulfilled,并且将该value传递给对应的then方法。
Promise.reject():返回一个状态为失败的Promise对象,并将给定的失败信息传递给对应的处理方法。
Promise.all():返回一个新的promise对象,该promise对象在参数对象里所有的promise对象都成功的时候才会触发成功,一旦有任何一个iterable里面的promise对象失败则立即触发该promise对象的失败。
Promise.any():接收一个Promise对象的集合,当其中的一个 promise 成功,就返回那个成功的promise的值。
Promise.race():当参数里的任意一个子promise被成功或失败后,父promise马上也会用子promise的成功返回值或失败详情作为参数调用父promise绑定的相应句柄,并返回该promise对象。
11. 为什么Promise的状态一旦变化就无法改变?(暂时不清楚)
12. 有什么办法可以让老版本的浏览器可以跑ES6语法?
webpack 或者 babel 或者 typescript
13. 说一下Babel的原理
Babel 内部原理是将 JS 代码转换为 AST,对 AST 应用各种插件进行处理,最终输出编译后的 JS 代码。
14. Babel可以解析所有的新语法吗?(不清楚)
15. TCP和UDP的区别?
TCP三次握手过程
第一次握手:主机A通过向主机B 发送一个含有同步序列号的标志位的数据段给主机B,向主机B 请求建立连接,通过这个数据段, 主机A告诉主机B
两件事:我想要和你通信;你可以用哪个序列号作为起始数据段来回应我。 第二次握手:主机B
收到主机A的请求后,用一个带有确认应答(ACK)和同步序列号(SYN)标志位的数据段响应主机A,也告诉主机A两件事:我已经收到你的请求了,你可以传输数据了;你要用那个序列号作为起始数据段来回应我
第三次握手:主机A收到这个数据段后,再发送一个确认应答,确认已收到主机B
的数据段:"我已收到回复,我现在要开始传输实际数据了,这样3次握手就完成了,主机A和主机B 就可以传输数据了。
TCP建立连接要进行3次握手,而断开连接要进行4次
第一次: 当主机A完成数据传输后,将控制位FIN置1,提出停止TCP连接的请求 ; 第二次:
主机B收到FIN后对其作出响应,确认这一方向上的TCP连接将关闭,将ACK置1; 第三次: 由B 端再提出反方向的关闭请求,将FIN置1 ;
第四次: 主机A对主机B的请求进行确认,将ACK置1,双方向的关闭结束.。
小结TCP与UDP的区别:
1、基于连接与无连接;
2、对系统资源的要求(TCP较多,UDP少);
3、UDP程序结构较简单;
4、流模式与数据报模式 ;
5、TCP保证数据正确性,UDP可能丢包;
6、TCP保证数据顺序,UDP不保证。
16. 说一下对HTTP的理解
HTTP全称:
超文本协议传输
HTTP工作流程:
首先客户机与服务器需要建立连接。只要单击某个超级链接,HTTP的工作开始。
1.收线客户机与服务器需要建立连接,只要单击某个超级连接,http的工作开始
2.建立连接后,客户机发送一个请求给服务器,请求方式的格式为:统一资源标识符(URL)、协议版本号、后边是MIME信息包括请求修饰符、客户机信息和可能的内容。
3.服务器接到请求后,给与相应的相应信息,其格式为一个状态行,包括信息的协议版本号、一个成功或错误的代码,后边是MIME信息包括服务器信息、实体信息和可能的内容。
4.客户端接收服务器所返回的信息通过浏览器显示在用户的显示屏上,然后客户机与服务器断开链接。
HTTP协议的主要特点
简单快速,灵活,无连接,无状态
HTTP报文的组成部分
请求报文:请求行:请求方法 URL地址 协议名称或版本号
请求头: 键值对 服务端据此获取客户端的信息 空行: 分隔请求头和请求体 请求体: 通过请求体传值
响应报文: 状态行: 说明所请求的资源情况
响应头 : 描述服务器基本信息 空行 : 分隔响应头和响应体 响应体:服务端返回的数据
HTTP方法
POST: 传输资源
GET: 获取资源
PUT:更新资源
DELETE:删除资源
HEAD:获得报文首部
POST和GET的区别
GET在浏览器回退时是无害的,而POST会再次提交请求(★)
GET产生的URL地址可以被收藏,而POST不可以
GET请求会被浏览器主动缓存,而POST不可以(★)
GET请求只能进行url编码,而POST支持多种编码方式
GET请求参数会被完整保留在浏览器的历史记录里,而POST中的参数不会被保留(★)
GET请求在URL中传送的参数是有长度限制的,而POST没有限制(★)
对参数的数据类型,GET值接收ASCII字符,而POST没有限制
对参数的数据类型,GET只接收ASCII字符,而POST没有限制
GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息
GET参数通过URL传递,POST放在Request body中(★)
什么是持久连接:(1.1版本才支持)
HTTP协议采用请求-应答模式,当使用普通模式,即非Keep-Alive模式时,每个请求、应答客户和服务器都要新建一个连接,完成之后立即断开连接(HTTP协议为无连接的协议)
当使用Keep-Alive模式,(又称持久连接,连接重用)时,Keep-Alive功能使客户端到服务器端的连接持续有效,当出现对服务器的后续请求时,Keep-Alive功能避免了建立或者重新建立连接
什么是管线化
管线化机制通过持久连接完成,仅HTTP/1.1支持此技术
只有GET和HEAD请求可以进行管线化,而POST则有所限制
初次创建连接时不应启动管线机制,因为对方(服务器)不一定支持HTTP/1.1版本的协议
HTTP/1.1要求服务器端支持管线化,但并不要求服务器端也对响应进行管线化处理,只是要求对于管线化的请求不失败即可
由于上面提到的服务器端问题,开启管线化很可能并不会带来大幅度的性能提升,而且很多服务器端和代理程序对管线化的支持并不太好,因此现代浏览器默认并为开启管线化支持
17. vue响应式是如何实现的,vue2中的响应式有什么问题和不足点;为什么数组下标复制和对象新增属性不会被拦截到。为什么vue3中换proxy实现了。
Vue响应式指的是:
组件的data发生变化,立刻触发试图的更新 原理: Vue
采用数据劫持结合发布者-订阅者模式的方式来实现数据的响应式,通过Object.defineProperty来劫持数据的setter,getter,在数据变动时发布消息给订阅者,订阅者收到消息后进行相应的处理。
通过原生js提供的监听数据的API,当数据发生变化的时候,在回调函数中修改dom 核心API:Object.defineProperty
Object.defineProperty API的使用 作用: 用来定义对象属性 特点: 默认情况下定义的数据的属性不能修改
描述属性和存取属性不能同时使用,使用会报错 响应式原理: 获取属性值会触发getter方法 设置属性值会触发setter方法
在setter方法中调用修改dom的方法 加分回答 Object.defineProperty的缺点 1.
一次性递归到底开销很大,如果数据很大,大量的递归导致调用栈溢出 2. 不能监听对象的新增属性和删除属性 3. 无法正确的监听数组的方法
Vue3.0
是通过Proxy实现的数据双向绑定,Proxy是ES6中新增的一个特性,实现的过程是在目标对象之前设置了一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
用法: ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。 var proxy = new Proxy(target,
handler) target: 是用Proxy包装的被代理对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
handler: 是一个对象,其声明了代理target 的一些操作,其属性是当执行一个操作时定义代理的行为的函数。 加分回答
Object.defineProperty
的问题:在Vue中,Object.defineProperty
无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实时响应。目前只针对以上方法做了hack处理,所以数组属性是检测不到的,有局限性Object.defineProperty只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。Vue里,是通过递归以及遍历data对象来实现对数据的监控的,如果属性值也是对象那么需要深度遍历,显然如果能劫持一个完整的对象,不管是对操作性还是性能都会有一个很大的提升。
Proxy的两个优点:可以劫持整个对象,并返回一个新对象,有13种劫持
18. vue中几种传值方式
Vue2: props,自定义组件,插槽,全局事件总线,消息订阅发布
Vue3: provide/inject
19. EventLoop,在nodejs中的EventLoop是如何什么样的
浏览器的事件循环:
执行js代码的时候,遇见同步任务,直接推入调用栈中执行,遇到异步任务,将该任务挂起,等到异步任务有返回之后推入到任务队列中,当调用栈中的所有同步任务全部执行完成,将任务队列中的任务按顺序一个一个的推入并执行,重复执行这一系列的行为。
异步任务又分为宏任务和微任务。 宏任务:任务队列中的任务称为宏任务,每个宏任务中都包含了一个微任务队列。
微任务:等宏任务中的主要功能都完成后,渲染引擎不急着去执行下一个宏任务,而是执行当前宏任务中的微任务
宏任务包含:执行script标签内部代码、setTimeout/setInterval、ajax请、postMessageMessageChannel、setImmediate,I/O(Node.js)
微任务包含:Promise、MutonObserver、Object.observe、process.nextTick(Node.js)
加分回答
浏览器和Node 环境下,microtask 任务队列的执行时机不同 - Node端,microtask 在事件循环的各个阶段之间执行 -
浏览器端,microtask 在事件循环的 macrotask 执行完之后执行
20. 你说你的项目用了流式布局,流式布局有什么特点?它的优点是什么?
1.流式布局是基于标准的文档流的布局模式,是页面默认的布局方式,除了对页面上的元素做了特殊的位置处理之外,任何元素都将默认为流式布局模式,即:从上到下,从左到右的方式进行排版。
2.流式布局又称为百分比布局,是移动端开发中经常使用的布局方式之一。其具有以下的几个特征:宽度自适应,高度写死;并不是所有的内容都自适应
经典的流式布局结构
流式布局中总是需要有一端固定,而另一端则自适应,下面介绍几种常用的经典的流式布局结构。
(1)、等分布局
(2)、左侧固定(右侧固定),右侧自适应(左侧自适应)
(3)、两端固定,中间自适应。
瀑布流布局
瀑布流布局是流式布局中比较流行的网站页面布局,我们在日常使用的APP中如手机淘宝、视频、图片显示的网站中都可以发现它的身影,因为在这一些网站页面中我们可以不断的刷新添加数据信息。如果说单纯的使用CSS样式去实现瀑布流布局是一个比较麻烦的事情,为了解决这一个问题,我们可以使用javascript提供的插件:masonary,它可以帮助我们快速的实现瀑布流布局。
21. 解释下Ajax的五个步骤这就是Ajax典型的五部曲
22. js数组操作—改变原数组和不改变原数组的方法
一、改变原始数组的方法:
1、pop()
删除 arrayObject 的最后一个元素,把数组长度减 1,并且返回它删除的元素的值。如果数组已经为空,则 pop() 不
改变数组,并返回 undefined 值。arrayObject.pop() 。2、push()
push() 方法可把它的参数顺序添加到 arrayObject 的尾部。它直接修改
arrayObject,而不是创建一个新的数组,arrayObject.push(newelement1,newelement2,….,newelementX)
。3、reverse()
该方法会改变原来的数组----将原来的数组倒序,而不会创建新的数组。arrayObject.reverse()。
4、shift()
删除数组的第一个元素,并返回第一个元素的值,如果数组是空的,那么 shift() 方法将不进行任何操作。
5、unshift()
unshift()
方法可向数组的开头添加一个或更多元素,并返回新的长度。arrayObject.unshift(newelement1,newelement2,….,newelementX)返回arrayObject
的新长度。6、sort()
对数组的引用。请注意,数组在原数组上进行排序,不生成副本。arrayObject.sort(sortby)
(如果调用该方法时没有使用参数,将按字母顺序对数组中的元素进行排序,说得更精确点,是按照字符编码的顺序进行排序。要实现这一点,首先应把数组的元素都转换成字符串(如有必要),以便进行比较。
如果想按照其他标准进行排序,就需要提供比较函数,该函数要比较两个值,然后返回一个用于说明这两个值的相对顺序的数字。比较函数应该具有两个参数
a 和 b,其返回值如下:若 a 小于 b,在排序后的数组中 a 应该出现在 b 之前,则返回一个小于 0 的值。 若 a 等于 b,则返回 0。 若 a 大于
b,则返回一个大于 0 的值。)7、splice()
splice() 方法可删除从 index 处开始的零个或多个元素,并且用参数列表中声明的一个或多个值来替换那些被删除的元素。 如果从
arrayObject 中删除了元素,则返回的是含有被删除的元素的数组
arrayObject.splice(index,howmany,item1,……,itemX) 。
二、不改变原始数组的方法:
1、concat()
用于连接两个或多个数组,仅会返回被连接数组的一个副本,arrayObject.concat(arrayX,arrayX,……,arrayX)
2、join()
返回一个字符串。该字符串是通过把 arrayObject
的每个元素转换为字符串,然后把这些字符串连接起来,arrayObject.join(separator) 。3、slice()
arrayObject.slice(start,end)返回一个新的数组,包含从 start 到 end (不包括该元素)的
arrayObject 中的元素。4、JSON.parse(JSON.stringify(arry))
这种方式会重新复制一个数组。也是实现深拷贝的一种方式。
23. 浏览器常见的兼容性问题
参考:常见兼容性问题