面经
前端性能优化
1、抢镜(某不知名小公司)
- CSS的position的属性一共有多少种,他们对应的表现是什么?
- 你知道哪些本地存储的方案,他们各自的优缺点是什么?
- 你知道浏览器的缓存机制嘛?在你的项目中是如何设置缓存策略的?
- script标签的async属性和defer属性有什么作用?
- 从浏览器输入url到页面展示经历了什么?
- 在你的项目中用过哪些性能方面的优化?
- 你是如何保证前端应用的稳定行的?比如代码规范,数据监控?
- 你做过哪些前端提升人效的解决方案,怎么去评估它的效果?
Q:1、CSS的position的属性一共有多少种,他们对应的表现是什么?
A:
CSS position属性用于指定一个元素在文档中的定位方式。top、right、bottom、left 属性则决定了该元素的最终位置。
属性值 | 描述 |
---|---|
absolute | 生成绝对定位的元素,相对于 static 定位以外的第一个父元素进行定位。元素的位置通过 “left”, “top”, “right” 以及 “bottom” 属性进行规定。 |
fixed | 生成绝对定位的元素,相对于浏览器窗口进行定位。元素的位置通过 “left”, “top”, “right” 以及 “bottom” 属性进行规定。 |
relative | 生成相对定位的元素,相对于其正常位置进行定位。因此,“left:20” 会向元素的 LEFT 位置添加 20 像素。 |
static | 默认值。没有定位,元素出现在正常的流中(忽略 top, bottom, left, right 或者 z-index 声明)。 |
inhert | 规定应该从父元素继承 position 属性的值。 |
Q:2、你知道哪些本地存储的方案,他们各自的优缺点是什么?
A:
浏览器的本地存储主要分为Cookie、WebStorage、IndexDB
,其中WebStorage又可以分为localStorage和sessionStorage
Cookie:
存放4KB左右、每次都会携带在HTTP头中,如果使用cookie保存过多数据会带来性能问题、可设置失效时间,没有设置的话,默认是关闭浏览器后失效localStorage:
可以保存5MB的信息、仅在客户端(即浏览器)中保存,不参与和服务器的通信、除非被手动清除,否则将会永久保存。sessionStorage:
可以保存5MB的信息、仅在客户端(即浏览器)中保存,不参与和服务器的通信、 仅在当前网页会话下有效,关闭页面或浏览器后就会被清除。IndexedDB:
IndexedDB是运行在浏览器中的非关系型数据库, 本质上是数据库,绝不是和刚才WebStorage的 5M 一个量级,理论上这个容量是没有上限的。
Q:3、你知道浏览器的缓存机制嘛?在你的项目中是如何设置缓存策略的?
A:
浏览器缓存机制
浏览器缓存主要分为强缓存
和协商缓存
如何来检查是否命中缓存呢?通过相应的字段来进行,但是说起这个字段就有点门道了
强缓存
HTTP/1.0 :Expires
Expires: Wed, 22 Nov 2019 08:41:00 GMT
HTTP/1.1:Cache-Control
Cache-Control:max-age=3600
Cache-Control还有以下几个字段:
- public: 客户端和代理服务器都可以缓存。因为一个请求可能要经过不同的代理服务器最后才到达目标服务器,那么结果就是不仅仅浏览器可以缓存数据,中间的任何代理节点都可以进行缓存。
- private: 这种情况就是只有浏览器能缓存了,中间的代理服务器不能缓存。
- no-cache: 跳过当前的强缓存,发送HTTP请求,即直接进入协商缓存阶段。
- no-store:非常粗暴,不进行任何形式的缓存。
- s-maxage:这和max-age长得比较像,但是区别在于s-maxage是针对代理服务器的缓存时间。
值得注意的是,当Expires和Cache-Control同时存在的时候,Cache-Control会优先考虑。
协商缓存
强缓存失效之后,浏览器在请求头中携带相应的缓存tag来向服务器发请求,由服务器根据这个tag,来决定是否使用缓存,这就是协商缓存。
具体来说,这样的缓存tag分为两种: Last-Modified 和 ETag。这两者各有优劣,并不存在谁对谁有绝对的优势,跟上面强缓存的两个 tag 不一样。
主要有Last-Modified
和ETag
两个字段。这两个字段都是服务端响应头中的字段
Last-Modified
即最后修改时间。在浏览器第一次给服务器发送请求后,服务器会在响应头中加上这个字段。
浏览器接收到后,如果再次请求,会在请求头中携带If-Modified-Since
字段,这个字段的值也就是服务器传来的最后修改时间。
服务器拿到请求头中的If-Modified-Since的字段后,其实会和这个服务器中该资源的最后修改时间对比:
如果请求头中的这个值小于最后修改时间,说明是时候更新了。返回新的资源,跟常规的HTTP请求响应的流程一样。
否则返回304,告诉浏览器直接用缓存。
ETag
ETag 是服务器根据当前文件的内容,给文件生成的唯一标识,只要里面的内容有改动,这个值就会变。服务器通过响应头把这个值给浏览器。
浏览器接收到ETag的值,会在下次请求时,将这个值作为If-None-Match
这个字段的内容,并放到请求头中,然后发给服务器。
服务器接收到If-None-Match后,会跟服务器上该资源的ETag进行比对:
如果两者不一样,说明要更新了。返回新的资源,跟常规的HTTP请求响应的流程一样。
否则返回304,告诉浏览器直接用缓存。
Q:4、script标签的async属性和defer属性有什么作用?
A:
HTMl中的script中的async/defer有什么作用和区别
如果script标签是引用的外部js文件, 那就会有一个下载js文件这一过程, 为了不因为这个下载过程而阻塞页面解析与渲染, 我们需要一种机制来解决这一问题, 方法之一就是使用 defer和async属性
<script defer src="./test1.js"></script>
//console.log("defer是DOM载入完成后执行.")
<script async src="./test2.js"></script>
// console.log("async是新开一个进程, 下载完成后就暂停主进程的解析, 执行下载的脚本.")
-
使用async不能保证脚本的执行顺序, 而是谁先下载完, 就先执行谁, 因此async适用于脚本之间没有依赖关系的情况. 反之再用defer;
-
如果一个scritp标签同时有defer和async属性, 则defer失效, script的行为由async决定;
-
在脚本中还是不能使用document.write()方法.
总结起来, defer和async区别在于, 前者是在html解析完毕后按顺序执行, 而async是单独下载, 完成后立即执行.
绿色(解析/parsing)停止的地方就是碰到script标签的地方,js文件下载完,执行之后,html文件才再次继续进行解析;
问题在于,如果js文件比较大,这样会极大的阻碍网页页面的生成,页面出于短暂长时间空白,看起来页面像是卡在了那里,这种情况是我们需要极力避免的。
async 属性告诉我们HTML解析器(parser),它可以在后台下载这个JS文件,并且它可以继续向下解析当js文件在后台下载的时候,之后,只要js文件一下载完毕,这个时候如果解析工作还没有完成,还在解析, 那么立马停下手头的解析工作,开始执行js文件,执行完之后,再恢复解析工作,继续向下解析;如果此时解析完了,那就更不用说了,直接开始执行js文件。
问题在于,如果你header里面有多个script标签,肯定从上到下有个顺序吧,如果彼此互相没有依赖都能独立执行,那还好说,如果存在依赖关系,那么此刻先执行哪个文件,后执行哪个文件是有要求的时候,那么我们就没办法控制,因为哪个文件先执行,完全取决于哪个文件先下载完毕,先下载完毕的就先执行,后下载完毕的就后执行,所以不可避免的就会乱序执行起来。
defer 属性和async有点像,async是js文件刚下载完毕就开始执行,而defer是等待 HTML的解析所有都完毕之后,才会进行js文件的执行,并且这个js文件的执行是按照script标签定义的顺序来执行的,所以这就和在body中的最末尾定义普通没有任何属性的多个script标签一样,从上到下按顺序开始执行,并且没有阻塞HTML文件的解析,所以defer这个属性的添加,完美的解决了async的问题,还有没有属性却还定义在header中script标签,阻塞html文件解析的情况。
Q:5、从浏览器输入url到页面展示经历了什么?
A:
从输入URL到看到页面发生了什么
主要有以下几个过程
- DNS解析
- 发起TCP连接
- 发送HTTP请求
- 服务器处理请求并返回HTTP报文
- 浏览器解析
- 渲染页面
- 连接结束
域名解析:
DNS域名解析的过程
DNS解析视频讲解
如果某个用户正在用浏览器mail.baidu.com的网址,当你敲下回车键的一瞬间:
1、检查浏览器缓存中是否存在该域名与IP地址的映射关系,如果有则解析结束,没有则继续
2、到系统本地查找映射关系,一般在hosts文件中,如果有则解析结束,否则继续
3、到本地域名服务器去查询,有则结束,否则继续
4、本地域名服务器查询根域名服务器,该过程并不会返回映射关系,只会告诉你去下级服务器(顶级域名服务器)查询
5、本地域名服务器查询顶级域名服务器(即com服务器),同样不会返回映射关系,只会引导你去二级域名服务器查询
6、本地域名服务器查询二级域名服务器(即baidu.com服务器),引导去三级域名服务器查询
7、本地域名服务器查询三级域名服务器(即mail.baidu.com服务器),此时已经是最后一级了,如果有则返回映射关系,则本地域名服务器加入自身的映射表中,方便下次查询或其他用户查找,同时返回给该用户的计算机,没有找到则网页报错
8、如果还有下级服务器,则依此方法进行查询,直至返回映射关系或报错
TCP的三次握手
TCP四次挥手
数据传输完毕后,双方都可释放连接。最开始的时候,客户端和服务器都是处于ESTABLISHED状态,假设客户端主动关闭,服务器被动关闭。
浏览器的渲染流程
- 解析 HTML 文件,构建 DOM 树,同时浏览器主进程负责下载 CSS 文件
- CSS 文件下载完成,解析 CSS 文件成树形的数据结构,然后结合 DOM 树合并成
RenderObject 树- 布局 RenderObject 树 (Layout/reflow),负责 RenderObject 树中的元素的尺寸,位置等计算
- 绘制 RenderObject 树 (paint),绘制页面的像素信息
- 浏览器主进程将默认的图层和复合图层交给 GPU 进程,GPU 进程再将各个图层合成(composite),最后显示出页面
- 解析HTML生成DOM树。
- 解析CSS生成CSSOM规则树。
- 解析JS,操作 DOM 树和 CSSOM 规则树。
- 将DOM树与CSSOM规则树合并在一起生成渲染树。
- 遍历渲染树开始布局,计算每个节点的位置大小信息。
- 浏览器将所有图层的数据发送给GPU,GPU将图层合成并显示在屏幕上。
Q:6、在你的项目中用过哪些性能方面的优化?
A:
https://juejin.cn/post/6892994632968306702
- 减少http请求
- 使用http2
- 使用服务端渲染
- 静态资源使用CDN
- 将CSS放在文件头部,JS文件放在底部
- 使用字体图标iconfont代替图片图标
- 善用缓存,不重复加载相同的资源
- 压缩文件
- 图片优化(图片懒加载、响应式图片、调整图片大小、使用webp格式的图片)
- 使用webpack按需加载代码,提取第三方库代码,减少ES6转ES5的冗余代码
- 减少重绘重排
- 使用事件委托
- if-else对比switch
- 使用防抖节流函数
- 避免页面卡顿
- 降低CSS选择器的复杂性
- 使用 transform 和 opacity 属性更改来实现动画
- 使用 flexbox 而不是较早的布局模型
arr.splice()
splice() 方法通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。此方法会改变原数组。
arr.reduce()
reduce() 方法对数组中的每个元素按序执行一个由您提供的 reducer 函数,每一次运行 reducer 会将先前元素的计算结果作为参数传入,最后将其结果汇总为单个返回值。
const arr = [1, 2, 3, 4]
const initialValue = 0
const sum = arr.reduce((previousValue, currentValue, currentIndex, array) => {
/*
previousValue:
上一次调用 callbackFn 时的返回值。在第一次调用时,
若指定了初始值 initialValue,其值则为 initialValue,否则为数组索引为 0 的元素 array[0]。
currentValue:
数组中正在处理的元素。在第一次调用时,若指定了初始值 initialValue,
其值则为数组索引为 0 的元素 array[0],否则为 array[1]。
currentIndex:
数组中正在处理的元素的索引。若指定了初始值 initialValue,则起始索引号为 0,否则从索引 1 起始。
array:用于遍历的数组。
*/
return previousValue + currentValue
}, initialValue)
console.log(sum);
// expected output: 10
// arr.reduce()函数的参数
// 1、callbackFn
// 一个 “reducer” 函数,包含四个参数:
// 2、initialValue 可选
// 作为第一次调用 callback 函数时参数 previousValue 的值。若指定了初始值 initialValue,则 currentValue 则将使用数组第一个元素;
// 否则 previousValue 将使用数组第一个元素,而 currentValue 将使用数组第二个元素。
// 返回值
// 使用 “reducer” 回调函数遍历整个数组后的结果。
Q:7、你是如何保证前端应用的稳定行的?比如代码规范,数据监控?
A:
Q:你做过哪些前端提升人效的解决方案,怎么去评估它的效果?
A:
2、兆百特(某不知名小公司)
一下午面了三面,面过了感觉我不回去,然后没发offer!
3、Boss直聘
一面(1h)
- 说说近期做的项目
- 输入URL到页面展示经历了什么?
- 缓存的Cache-Control的no-store和no-catch字段有什么区别?
- 事件冒泡和事件捕获有什么区别?他俩的执行顺序?为什么是这样的顺序?
- event.dateset.curTarget和…的区别?
- 说说addeventListener形参,咋用的
- 事件委托、事件代理
- 生成一个随机数(-200 ,1)怎么用Math.random()
- Vue兄弟节点数据传递的方案
- Vue组件的动态切换
- 自己有没有写过UI组件,在UI组件里怎么取到全局Vue实例
- 表单提交,如果我要上传一个大文件,怎么整?分片、怎么分片、分片要注意啥
- 怎么取消一个请求,fetch原声AJAX请求怎么取消
- 事件循环,看题说输出顺序
- CSS的布局、flex布局
- 小程序的启动性能优化
- 项目!、项目!说出自己项目遇到的问题、难点、如何克服、有哪些成就(项目需要好好梳理梳理)
- iphone底部的小黑条适配
- H5页面上有100000万条数据,需要下拉这样去看,怎么做优化
- Vue项目怎么做性能优化?
- 反问,问部门。
1、输入URL到页面展示
2、addEventListener() 方法,事件监听
事件冒泡和事件捕获区别?
事件冒泡:
假设我要点击的是div,点击后会一层一层的往上。
事件捕获:
事件捕获与事件冒泡完全相反。是从上至下到指定元素。
事件委托
利用事件冒泡和事件源对象进行处理
优点:
1、性能 不需要循环所有的元素一个个绑定事件
2、灵活 当有新的子元素时不需要重新绑定事件
我们有下面结构的一个列表:
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
如果我们想实现当每个li点击自身时,都在控制台打印自己的内容,要怎么做呢?
按照我们之前的理解,肯定是先获取li标签,利用for循环给每个li标签添加点击事件。
但是当我们了解了事件冒泡和事件对象之后,我们有了更好的写法:
let ul = document.getElementsByTagName('ul')[0]
ul.onclick = function(e){
console.log(e.target.innerText) // 1、 2、 3、 4、 5
}
addEventListener()
方法用来做事件监听
你可以使用 removeEventListener() 方法来移除事件的监听。
window.addEventListener("resize", function(){
document.getElementById("demo").innerHTML = sometext;
});
element.addEventListener(event, function, useCapture);
第一个参数是事件的类型 (如 “click” 或 “mousedown”).
注意:不要使用 “on” 前缀。 例如,使用 “click” ,而不是使用 “onclick”。
第二个参数是事件触发后调用的函数。(回调函数)
event.target
代表的是触发事件的元素,而event.currentTarget
代表的是那个绑定了事件处理函数的元素。
当第三个参数设置为true就在捕获过程中执行,反之就在冒泡过程中执行处理函数
第三个参数是个布尔值用于描述事件是冒泡还是捕获。该参数是可选的。
可能值:
true - 事件句柄在捕获阶段执行
false false是默认值。事件句柄在冒泡阶段执行
DOM事件流如图:
由图可知捕获过程要先于冒泡过程
2、缓存的Cache-Control的no-store和no-catch字段有什么区别?
- no-store:不进行任何形式的缓存
- no-cache:每次都需要去服务端做校验,看是否已经过期,也就是绕过强缓存走协商缓存
3、如何实现上传大文件好几百兆?
大文件上传
- 前端上传大文件时使用 Blob.prototype.slice 将文件切片,并发上传多个切片,最后发送一个合并的请求通知服务端合并切片
- 服务端接收切片并存储,收到合并请求后使用流将切片合并到最终文件
- 原生
XMLHttpRequest
的upload.onprogress
对切片上传进度的监听 - 使用 Vue 计算属性根据每个切片的进度算出整个文件的上传进度
断点续传
- 使用 spark-md5 根据文件内容算出文件 hash
- 通过 hash 可以判断服务端是否已经上传该文件,从而直接提示用户上传成功(秒传)
- 通过 XMLHttpRequest 的 abort 方法暂停切片的上传
- 上传前服务端返回已经上传的切片名,前端跳过这些切片的上传
4、生成一个随机数
- Math.random //函数生成一个[0, 1)范围内的随机数
- Math.floor(n) //向下取整,返回一个n的整数部分的数
- Math.ceil(n) //向上取整,返回一个大于等于n的最小整数
function myRandom (a, b) {
console.log(Math.floor(Math.random()*(b-a+1) + a))
return Math.floor(Math.random()*(b-a+1) + a)
}
myRandom(80, 90)
5、兄弟组件之间传值
1、bus总线传值
//bus.js
import Vue from 'vue';
export default new Vue;
//使用 兄弟A 传值
import bus from '路径'
bus.$emit('自定义事件名称',输出数据)
//使用 兄弟B 接值
import bus from '路径'
bus.on('自定义事件名',(res)=>{})
eventbus原理:
VUE
中eventBus
可以用来进行任何组件之间的通信,我们可以把eventBus当成一个管道,这个管道两端可以接好多组件,两端的任何一个组件都可以进行通信。其实这个管道就是Vue实例,实例中的$on, $off, $emit
方法来实现此功能。
2、常规子1传父->父传子2
3、vuex
父子组件通信: props; $parent / $children; provide / inject ; ref ; $attrs / $listeners
兄弟组件通信: eventBus ; vuex
跨级通信: eventBus;Vuex;provide / inject 、$attrs / $listeners
6、怎么动态切换组件
有的时候,在不同组件之间进行动态切换是非常有用的,比如在一个多标签的界面里:
上述内容可以通过 Vue 的 元素加一个特殊的 is attribute 来实现:
<!-- 组件会在 `currentTabComponent` 改变时改变 -->
<component v-bind:is="currentTabComponent"></component>
7、如何取消请求
1、从ajax到fetch、axios
2、Axios 如何取消重复请求?
3、使用 AbortController 取消 Fetch 请求和事件监听
XMLHttpRequest.abort()
如果请求已被发出,则立刻中止请求。
Axios 是一个基于 Promise 的 HTTP 客户端,同时支持浏览器和 Node.js 环境。它是一个优秀的 HTTP 客户端,被广泛地应用在大量的 Web 项目中。对于浏览器环境来说,Axios 底层是利用 XMLHttpRequest 对象来发起 HTTP 请求。如果要取消请求的话,我们可以通过调用 XMLHttpRequest 对象上的 abort 方法来取消请求
axios的请求如何取消?
而对于 Axios 来说,我们可以通过 Axios 内部提供的 CancelToken 来取消请求:
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.post('/user/12345', {
name: 'semlinker'
}, {
cancelToken: source.token
})
source.cancel('Operation canceled by the user.'); // 取消请求,参数是可选的
此外,你也可以通过调用 CancelToken 的构造函数来创建 CancelToken,具体如下所示:
const CancelToken = axios.CancelToken;
let cancel;
axios.get('/user/12345', {
cancelToken: new CancelToken(function executor(c) {
cancel = c;
})
});
cancel(); // 取消请求
8、Vue中的watch如何只监听一次
当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。
watch: {
// 每当 question 发生变化时,该函数将会执行
question(newQuestion, oldQuestion) {
if (newQuestion.indexOf('?') > -1) {
this.getAnswer()
}
}
},
除了 watch 选项之外,你还可以使用命令式的 vm.$watch API。
$watch 返回一个取消侦听函数,用来停止触发回调:
const vm = app.mount('#app')
const unwatch = vm.$watch('a', cb)
// later, teardown the watcher
unwatch()
4、高途
- 闭包及其应用场景
- 问项目,最近做的哪些项目?用到了哪些技术栈?讲一讲
- v-model语法糖,及其原理。能在组件里面用v-model吗?
- 虚拟DOM,为什么要用虚拟DOM,虚拟DOM有什么好处?
- v-for循环中的key,为什么要绑定key,不绑定会有哪些问题?为什么只在循环中绑定key
- 讲一下JS的异步、异步有哪些方案?回调函数有了解吗?
- Vue中的高阶函数
- 未来职业规划(最近3-5年)
- async/await底层原理
- 最近项目中遇到哪些技术难点,你是怎么解决的?
解答:
1、闭包及其应用场景
闭包是指有权访问另一个函数作用域中变量的函数
var a = 0
function foo(){
var b =14
function fo(){
console.log(a, b)
}
fo()
}
foo()
这里的子函数 fo 内存就存在外部作用域的引用 a, b,所以这就会产生闭包
闭包的应用:
- 封装私有变量
- 防抖、节流
- 经典场景-return回一个函数
- 函数作为参数(将闭包作为函数的参数执行)
- 使用回调函数就是在使用闭包
2、v-model语法糖
3、Vue中的虚拟DOM
4、v-for中key的作用,为什么只在循环中绑定,不绑定key会有什么问题
5、js中的异步,js的异步有哪些方案?回调函数是啥?
其实回调函数就是异步编程的一种解决方案
6、async/await原理
5、Fabrique 束一科技&开果传媒
- 讲讲最近的项目和用到的技术栈
- 小程序中开发完之后用安卓和ios有没有什么不一样的地方
- Vue组件缓存,怎么在进入和离开缓存组件的时候做一些事情
- Vue.set怎么用(如果我更新了数据,发现视图没更新)
- 这么多筛选框重置,可以把对象重置为空,但是如果有默认值怎么整?
- 小程序多张图片上传怎么整
- Vue表格的分页
- H5的触底加载怎么实现的,具体的实现方式,有没有做什么优化
- Vue组件之间的通信
- 我从列表页走到详情页, 详情页更新,列表页怎么也跟着更新
- Vue菜单权限控制(怎么实现的,具体方法说一下)
- 有没有从0搭建过一个项目,除了默认的配置Vue-cli需要做哪些配置
1、项目
讲项目,讲技术栈
2、小程序开发ios和安卓的适配
兼容性问题
- 底部小黑条适配
- 页面栈不能超过十层
- 闪光灯按钮在ios和安卓显示不一样
- ios的date格式为
2022/04/15 08:37:56
,而安卓不是
3、Vue组件缓存
封装vue组件的一些小技巧
keep-alive:有activated 和 deactivated两个生命周期方法
使用方式:
// App.vue
<div class="app">
<keep-alive>
<router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive"></router-view>
</div>
4、Vue.set()的使用
在我们使用vue进行开发的过程中,可能会遇到一种情况:当生成vue实例后,当再次给数据赋值时,有时候并不会自动更新到视图上去; 当我们去看vue文档的时候,会发现有这么一句话:如果在实例创建之后添加新的属性到实例上,它不会触发视图更新。 如下代码,给 student对象新增 age 属性
data () {
return {
student: {
name: '',
sex: ''
}
}
}
mounted () { // ——钩子函数,实例挂载之后
this.student.age = 24
}
受 ES5 的限制,Vue.js 不能检测到对象属性的添加或删除。因为 Vue.js 在初始化实例时将属性转为 getter/setter,所以属性必须在 data 对象上才能让 Vue.js 转换它,才能让它是响应的。
正确写法:this.$set(this.data,”key”,value’)
mounted () {
this.$set(this.student,"age", 24)
}
13、Vue实现面包屑
14、vue.config.js配置
const webpack = require('webpack')
const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const BundleAnalyzerConfig = process.env.npm_config_report ? [new BundleAnalyzerPlugin()] : []
const path = require('path')
module.exports = {
publicPath: './',
filenameHashing: true,
devServer: {
host: 'local.intra.xiaojukeji.com',
port: '8777',
hot: true, // 开启热更新,提高开发效率
headers: {
'Access-Control-Allow-Origin': '*'
},
// 配置代理
proxy: {
'/api': {
target: 'http://czp-test.intra.xiaojukeji.com', // 想要访问接口域名
changeOrigin: true, // 开启跨域,在本地创建一个虚拟服务,然后发送请求的数据,并同时接收请求的数据,这样服务端和服务端进行数据交互就不会有问题
pathRewrite: {
'^/api': '' // 利用这个地面的值拼接上target里面的地址
}
}
}
},
configureWebpack: {
devtool: 'source-map',
plugins: [
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
new HtmlWebpackExternalsPlugin({
externals: [{
module: 'waterMark',
entry: 'https://sec-aegisfe.didistatic.com/static/aegisfe/water-mark1.0.js?v=' + Date.now(),
global: 'waterMark'
}]
}),
...BundleAnalyzerConfig
],
optimization: {
splitChunks: {
chunks: 'all',
maxInitialRequests: 10,
maxAsyncRequests: 10,
minSize: 30 * 1024,
minChunks: 1,
name: true,
cacheGroups: {
'chunk-vendors_1': {
name: 'chunk-vendors_1',
test: /[\\/]node_modules[\\/](mime-db|elliptic|lodash|asn1.js|core-js)/,
priority: -1
},
'chunk-vendors_2': {
name: 'chunk-vendors_2',
test: /[\\/]node_modules[\\/](xgplayer|minio|moment|zrender)/,
priority: -2
},
echarts: {
name: 'echarts',
test: /[\\/]node_modules[\\/]echarts/,
priority: -2
},
vue: {
name: 'vue',
test: /[\\/]node_modules[\\/](vue|vuex)/,
priority: -2
},
antd: {
name: 'antd',
test: /ant-design/,
priority: -2
},
iview: {
name: 'iview',
test: /[\\/]node_modules[\\/]iview/,
priority: -2
}
}
},
runtimeChunk: {
name: entrypoint => `runtime-${entrypoint.name}`
}
},
resolve: {
alias: {
'@src': path.resolve('./src'),
'@api': path.resolve('./src/api'),
'@assets': path.resolve('./src/assets'),
'@common': path.resolve('./src/common'),
'@components': path.resolve('./src/components'),
'@router': path.resolve('./src/router'),
'@store': path.resolve('./src/store'),
'@views': path.resolve('./src/views')
}
}
},
productionSourceMap: false,
chainWebpack: config => {
config
.plugin('html')
.tap(args => {
args[0].title = '车载屏管理系统'
return args
})
}
}
5、北京塞勒斯科技(Sailors)
- CSS实现三角形
- 输入URL到页面展示
- let、const区别
- 箭头函数和普通函数
- 组件封装的度,什么时候才会去封装组件
- Vue父子组件的传值
- Vue父子组件的挂载顺序
- hash路由和history路由的区别
- js的事件循环机制
- 移动端适配(媒体查询、如果移动端页面放到浏览器中怎么适配)
- 移动端1px解决方案
- 父元素塌陷解决方案
- http和https的区别
- post和get区别
- get请求怎么实现跳过缓存直接请求最新的数据
- Vue的优点缺点
- Vue2和Vue3
- 非数组怎么转化为数组,用哪个API(Array.from)
- 写CSS样式的时候怎么做性能优化
- Vue性能优化
- 前端有很多非常大的图片是怎么优化的?
- 前端的设计模式(单例模式、发布者模式、发布订阅者模式)
- 三栏布局(左右固定,中间自适应)
- 有没有做过组内技术分享
- 自己平常如何学习的
1、CSS实现三角形
思路:给div盒子设置宽高为0,然后设置border的宽度,然后给任意三边的颜色设置为transparent即可分别实现任一方向的三角形。
.sanjiao {
width: 0;
height: 0;
border-top: 100px solid #f00;
border-right: 100px solid #0f0;
border-bottom: 100px solid #00f;
border-left: 100px solid #ff0;
}
上述代码实现的效果如下:
然后我们可以通过给任意三边的颜色设置为 transparent 即可分别实现任一方向的三角形。通过设置某条边的宽度比其它边宽,来调整三角形的高度。
.triangle {
width: 0;
height: 0;
border: 100px solid transparent;
border-bottom: 200px solid #0ff;
}
实现效果如下:
等边三角形:
width: 0;
height: 0;
border-left: 69px solid transparent;
border-right: 69px solid transparent;
border-bottom: 120px solid skyblue;
效果如下:
2、CSS样式优化
- 使用简写
p { margin: 1px 2px 3px 4px; }
- 用CSS替换图片
- 使用颜色快捷方式
target { background-color: #ffffff; }
target { background: #fff; }
- 删除不必要的0和单位
padding: 0.2em;
margin: 20.0em;
avalue: 0px;
// 优化后
padding: .2em;
margin: 20em;
avalue: 0;
3、父子组件执行顺序
加载渲染过程
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
更新过程
父beforeUpdate->子beforeUpdate->子updated->父updated
销毁过程
父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
4、history路由和hash路由区别
- hash模式是通过改变锚点(#)来更新页面URL,并不会触发页面重新加载,我们可以通过window.onhashchange监听到hash的改变,从而处理路由。
- history模式是通过调用window.history对象上的一系列方法来实现页面的无刷新跳转。
5、移动端适配
面试官:你了解过移动端适配嘛?
6、北京红棉小冰科技有限公司
6、小冰
- 居中的方案
- CSS的定位,fixd定位有没有特殊情况
- 深拷贝、浅拷贝、深拷贝实现方式
- JSON.parse(JSON.stringfy())实现深拷贝有什么问题
- 普通函数和箭头函数有什么区别
- 提到了this,说说this的指向
- 浏览器的缓存,缓存Etag的计算规则,是根据啥生成的
- …展开运算符都可以操作哪些数据类型
1、CSS的定位
position的含义是指定位类型,取值类型可以有:static、relative、absolute、fixed、inherit和sticky,这里sticky是CSS3新发布的一个属性。
2、居中的方案
3、深拷贝、浅拷贝
- 浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
- 深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。
赋值和深/浅拷贝的区别
这三者的区别如下,不过比较的前提都是针对引用类型:
-
当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。
-
浅拷贝:重新在堆中创建内存,拷贝前后对象的基本数据类型互不影响,但拷贝前后对象的引用类型因共享同一块内存,会相互影响。
-
深拷贝:从堆内存中开辟一个新的区域存放新对象,对对象中的子对象进行递归拷贝,拷贝前后的两个对象互不影响。
浅拷贝的实现方式
- Object.assign()
- 函数库lodash的_.clone方法
- 展开运算符…
Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。
let obj1 = { person: {name: "kobe", age: 41},sports:'basketball' };
let obj2 = Object.assign({}, obj1);
obj2.person.name = "wade";
obj2.sports = 'football'
console.log(obj1); // { person: { name: 'wade', age: 41 }, sports: 'basketball' }
该函数库也有提供_.clone用来做 Shallow Copy,后面我们会再介绍利用这个库实现深拷贝。
var _ = require('lodash');
var obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3]
};
var obj2 = _.clone(obj1);
console.log(obj1.b.f === obj2.b.f);// true
展开运算符是一个 es6 / es2015特性,它提供了一种非常方便的方式来执行浅拷贝,这与 Object.assign ()的功能相同。
let obj1 = { name: 'Kobe', address:{x:100,y:100}}
let obj2= {... obj1}
obj1.address.x = 200;
obj1.name = 'wade'
console.log('obj2',obj2) // obj2 { name: 'Kobe', address: { x: 200, y: 100 } }
深拷贝实现方法
- JSON.parse(JSON.stringify())
- lodash的_.cloneDeep
- jQuery.extend()
- 手写递归方法
let arr = [1, 3, {
username: ' kobe'
}];
let arr4 = JSON.parse(JSON.stringify(arr));
arr4[2].username = 'duncan';
console.log(arr, arr4)
这也是利用JSON.stringify将对象转成JSON字符串,再用JSON.parse把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。
JSON.parse(JSON.stringify(obj))深拷贝的问题
- 如果obj里面存在时间对象,
JSON.parse(JSON.stringify(obj))
之后,时间对象变成了字符串。 - 如果obj里有
RegExp、Error
对象,则序列化的结果将只得到空对象。 - 如果obj里有函数,undefined,则序列化的结果会把函数, undefined丢失。
- 如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null。
JSON.stringify()
只能序列化对象的可枚举的自有属性。如果obj中的对象是有构造函数生成的, 则使用JSON.parse(JSON.stringify(obj))
深拷贝后,会丢弃对象的constructor
。- 如果对象中存在循环引用的情况也无法正确实现深拷贝。
function Person (name) {
this.name = 20
}
const lili = new Person('lili')
let a = {
data0: '1',
date1: [new Date('2020-03-01'), new Date('2020-03-05')],
data2: new RegExp('\\w+'),
data3: new Error('1'),
data4: undefined,
data5: function () {
console.log(1)
},
data6: NaN,
data7: lili
}
let b = JSON.parse(JSON.stringify(a))
console.log(b)
var _ = require('lodash');
var obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);// false
var $ = require('jquery');
var obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3]
};
var obj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f === obj2.b.f); // false
// 可以避免循环引用
function deepClone (obj, hash = new WeakMap()) {
if (hash.has(obj)) {
// 如果已经有这个对象,直接返回这个对象
return hash.get(obj)
}
const targetObj = Array.isArray(obj) ? [] : {}
hash.set(obj, targetObj)
for (let i in obj) {
if (obj.hasOwnProperty(i)) {
if (typeof obj[i] === 'object') {
targetObj[i] = deepClone(obj[i], hash)
} else {
targetObj[i] = obj[i]
}
}
}
return targetObj
}
4、浏览器缓存的Etag怎么生成
nginx 中 etag 由响应头的 Last-Modified
与 Content-Length
表示为十六进制组合而成。
$ curl --head 10.97.109.49
HTTP/1.1 200 OK
Server: nginx/1.16.0
Date: Tue, 10 Dec 2019 06:45:24 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 23 Apr 2019 10:18:21 GMT
Connection: keep-alive
ETag: "5cbee66d-264"
Accept-Ranges: bytes
5、三栏布局的实现方式(左右宽度固定,中间自适应)
- 使用浮动实现
- 使用绝对定位实现三栏布局
- 使用flexBox实现三栏布局
- 使用table实现解决三栏布局
- 使用grid实现三栏布局
// 1、前2种的html布局
<div class="outer">
<div class="left"></div>
<div class="right"></div>
<div class="center"> this is center</div>
</div>
// 2、后四种的html布局
<div class="outer">
<div class="left"></div>
<div class="center"> this is center</div>
<div class="right"></div>
</div>
使用float布局
左右中三个盒子,左右宽度各300,并且左右浮动
.outer div{
min-height: 200px;
}
.left {
float: left;
width: 300px;
background-color: pink;
}
.right {
float: right;
width: 300px;
background-color: skyblue;
}
.center {
background-color: #ccc;
}
使用position定位
三个盒子都绝对定位,中间盒子左右各定位300,左盒子左边0,右盒子右边0
.outer div{
min-height: 200px;
position: absolute;
}
.left {
left: 0;
width: 300px;
background-color: pink;
}
.right {
right: 0;
width: 300px;
background-color: skyblue;
}
.center {
right: 300px;
left: 300px;
background-color: #ccc;
}
使用flex布局
外层盒子flex布局,内层盒子中间flex:1,其它的固定宽度300px
.outer {
display: flex;
min-height: 200px;
}
.left {
width: 300px;
background-color: pink;
}
.right {
width: 300px;
background-color: skyblue;
}
.center {
flex: 1;
background-color: #ccc;
}
使用table布局
需要给外层盒子定义display:table,内层盒子定义diplay:table-cell,再分别给左右宽度即可,中间就可以自适应
.outer {
display: table;
width: 100%;
min-height: 200px;
}
.outer div{
display: table-cell;
}
.left {
width: 300px;
background-color: pink;
}
.right {
width: 300px;
background-color: skyblue;
}
.center {
background-color: #ccc;
}
grid布局
.outer {
display: grid;
grid-template-rows: 400px;
grid-template-columns: 300px auto 300px;
}
.left {
background-color: pink;
}
.right {
background-color: skyblue;
}
.center {
background-color: #ccc;
}
6、普通函数和箭头函数的区别
7、猿辅导
- 说一下BFC及其应用,触发BFC的方法
- 为啥0.1+0.2 !== 0.3,js浮点数采用什么格式
- 浏览器的缓存机制,Last-Modified和Etag为啥会有两个,用Last-Modified会有什么问题吗?
- script标签的defer和async有什么不同?如果是很多歌defer和很多和async标签,他们的执行顺序
- typeof究竟可以判断出哪些数据类型,不能判断出哪些数据类型
- 浏览器事件循环机制中的生命周期方法
- 事件循环机制、一道输出题
- 公司小程序MPX跟其它框架相比有哪些优点
- jsBridge的一个原理,怎么通信
- 手写深拷贝,支持基本数据类型、Array,Object、RegExp、Error、Map、Set、Symbol、
1、BFC
BFC 全称:Block Formatting Context, 名为 “块级格式化上下文”。
W3C官方解释为:BFC它决定了元素如何对其内容进行定位,以及与其它元素的关系和相互作用,当涉及到可视化布局时,Block Formatting Context提供了一个环境,HTML在这个环境中按照一定的规则进行布局。
简单来说就是,BFC是一个完全独立的空间(布局环境),让空间里的子元素不会影响到外面的布局。那么怎么使用BFC呢,BFC可以看做是一个CSS元素属性
怎么触发BFC?
- overflow:hidden
- 用 overflow:hidden 和 overflow:auto overflow:scroll overflow:overlay 都可以创建
- display: inline-block
- position: absolute
- position:fixed
- display:table-cell
- display:flex
BFC有哪些规则?
- BFC就是一个块级元素,块级元素会在垂直方向一个接一个的排列
- BFC就是页面中的一个隔离的独立容器,容器里的标签不会影响到外部标签
- 垂直方向的距离由margin决定, 属于同一个BFC的两个相邻的标签外边距会发生重叠
- 计算BFC的高度时,浮动元素也参与计算
可以用来解决哪些问题
- 清除浮动
- 避免margin重叠
margin重叠的规则?
margin同正,则取最大值;
margin同负,则取最小值;
margin一正一负,则取二者之和。
2、0.1+0.2 !== 0.3
在做算术运算时,JS 会先把十进制数转换成二进制数后再计算,十进制小数转二进制数的方式是 x 2 取整,0.1 和 0.2 的二进制数是个无限循环小数。
而 JS 中表示一个数字只有 64 位,其中精度位(有效数位)只有 52 位,所以当出现无限循环小数时,会通过 0 舍 1 入 的规则截取前 52 位(类似十进制的四舍五入),这样导致了精度位的丢失。0.1 实际参与计算的数变大了,0.2 参与计算的数变小了,所以运算结果不一定等于 0.3。
console.log( 0.1 + 0.2) //0.30000000000000004
3、Etag和Last-Modified
Etag主要为了解决Last-Modified无法解决的一些问题:
- 一些文件也许周期性的更改,但是它的内容并不改变(仅仅改变的是修改时间),这个时候我们不希望客户端认为这个文件被修改了,而重新获取资源.
- 某些文件修改非常频繁,比如在秒一下的时间内进行修改(比如1s内修改了N次),If-Modified-Since能检查到的粒度是秒级的,这种修改是无法判断的(或者说UNIX记录MTIME只能精确到秒)
4、script标签的defer和async有什么不同
defer:这个属性表示脚本在执行的时候不会改变页面的结构。也就是说,脚本会被延迟到整个页面都解析完毕后再运行。因此,在script元素上设置defer属性,相当于告诉浏览器立即下载,但延迟执行。
async:async属性与defer类似。当然,它们两者也都只适用于外部脚本,都会告诉浏览器立即开始下载。不过,与defer不同的 是,标记为async的脚本并不保证能按照它们出现的次序执行。
若有多个script含defer属性,推迟执行的脚本不一定总会按顺序执行或者在DOMContentLoaded事件之前执行,因此最好只包含一个这样的脚本。
5、typeof
typeof只能准确判断原始数据类型和函数,无法精确判断出引用数据类型(统统返回 object)。有一点需要注意,调用typeof null返回的是object,这是因为特殊值null被认为是一个对空对象的引用(也叫空对象指针)。
console.log(typeof 666); // number
console.log(typeof true); // boolean
console.log(typeof undefined); // undefined
console.log(typeof NaN) // number
console.log(typeof Symbol()); // symbol
console.log(typeof '1233') // string
console.log(typeof 1n); // bigint
console.log(typeof function name() {}); // function
console.log(typeof []); // object
console.log(typeof {}); // object
console.log(typeof new String('xxx')); // object
console.log(typeof null); // object
console.log(typeof new RegExp()) // object
console.log(typeof new Error()) // object
console.log(typeof new Map()) // object
console.log(typeof new WeakMap()) // object
console.log(typeof new Set()) // object
console.log(typeof new WeakSet()) // object
function getType (num) {
return Object.prototype.toString.call(num).toLowerCase().substring(8, Object.prototype.toString.call(num).length-1)
}
console.log('------------------')
console.log(getType([])); // array
console.log(getType({})); // object
console.log(getType(new String('xxx'))); // string
console.log(getType(null)); // null
console.log(getType(new RegExp())) // regexp
console.log(getType(new Error())) // error
console.log(getType(new Map())) // map
console.log(getType(new WeakMap())) // weakmap
console.log(getType(new Set())) // set
console.log(getType(new WeakSet())) // weakset
6、事件循环输出题
console.log('script start')
async function async1() {
await async2() // await后面是简单的输出
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
}
async1()
setTimeout(function() {
console.log('setTimeout')
}, 0)
new Promise(resolve => {
console.log('Promise')
resolve()
})
.then(function() {
console.log('promise1')
})
.then(function() {
console.log('promise2')
})
console.log('script end')
// script start
// async2 end
// Promise
// script end
// async1 end
// promise1
// promise2
// setTimeout
console.log('script start')
async function async1() {
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
return Promise.resolve().then(()=>{
console.log('async2 end1')
})
}
async1()
setTimeout(function() {
console.log('setTimeout')
}, 0)
new Promise(resolve => {
console.log('Promise')
resolve()
})
.then(function() {
console.log('promise1')
})
.then(function() {
console.log('promise2')
})
console.log('script end')
script start
async2 end
Promise
script end
async2 end1
promise1
promise2
async1 end
setTimeout
7、https中的数字证书
8、http1和http2的区别
- HTTP2解析速度快
- 多路复用
- 首部压缩
- HTTP2可以给请求设置优先级
- 流量控制
- 服务器推送
- 采用二进制协议
采用二进制协议
HTTP/1.1 版的头信息肯定是文本(ASCII编码),数据体可以是文本,也可以是二进制。HTTP/2 则是一个彻底的二进制协议,头信息和数据体都是二进制,并且统称为”帧”:头信息帧和数据帧。二进制协议解析起来更高效、“线上”更紧凑,更重要的是错误更少。
服务器解析 HTTP1.1 的请求时,必须不断地读入字节,直到遇到分隔符 CRLF 为止。而解析 HTTP2 的请求就不用这么麻烦,因为 HTTP2 是基于帧的协议,每个帧都有表示帧长度的字段。
多路复用
HTTP1.1 如果要同时发起多个请求,就得建立多个 TCP 连接,因为一个 TCP 连接同时只能处理一个 HTTP1.1 的请求。
在 HTTP2 上,多个请求可以共用一个 TCP 连接,这称为多路复用。同一个请求和响应用一个流来表示,并有唯一的流 ID 来标识。
多个请求和响应在 TCP 连接中可以乱序发送,到达目的地后再通过流 ID 重新组建。
首部压缩
HTTP 协议是没有状态,导致每次请求都必须附上所有信息。所以,请求的很多头字段都是重复的,比如Cookie,一样的内容每次请求都必须附带,这会浪费很多带宽,也影响速度。
对于相同的头部,不必再通过请求发送,只需发送一次;
HTTP/2 对这一点做了优化,引入了头信息压缩机制;
一方面,头信息使用gzip或compress压缩后再发送;
另一方面,客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,产生一个索引号,之后就不发送同样字段了,只需发送索引号。
服务器推送
HTTP2 新增的一个强大的新功能,就是服务器可以对一个客户端请求发送多个响应。换句话说,除了对最初请求的响应外,服务器还可以额外向客户端推送资源,而无需客户端明确地请求。
例如当浏览器请求一个网站时,除了返回 HTML 页面外,服务器还可以根据 HTML 页面中的资源的 URL,来提前推送资源。
http和https的区别
- HTTP 明文传输,数据都是未加密的,安全性较差,HTTPS(SSL+HTTP) 数据传输过程是加密的,安全性较好。
- 使用 HTTPS 协议需要到 CA(Certificate Authority,数字证书认证机构) 申请证书,一般免费证书较少,因而需要一定费用。证书颁发机构如:Symantec、Comodo、GoDaddy 和 GlobalSign 等。
- HTTP 页面响应速度比 HTTPS 快,主要是因为 HTTP 使用 TCP 三次握手建立连接,客户端和服务器需要交换 3 个包,而 HTTPS除了 TCP 的三个包,还要加上 ssl 握手需要的 9 个包,所以一共是 12 个包。
- HTTP和 HTTPS 使用的是完全不同的连接方式,用的端口也不一样,前者是 80,后者是 443。
- HTTPS 其实就是建构在 SSL/TLS 之上的 HTTP 协议,所以,要比较 HTTPS 比 HTTP 要更耗费服务器资源。
const a = new Promise((resolve, reject) => {
console.log('promise1')
resolve()
}).then(() => {
console.log('promise2')
})
setTimeout(() => {
console.log('timeout')
})
const b = new Promise(async (resolve, reject) => {
await a
console.log('after1')
await b
console.log('after2')
resolve()
})
console.log('end')
8、先胜业财
- Vue中diff算法(这个一定要搞懂)
- 主要用到哪些ES6新语法
- vue.$nextTick用法
- Vuex怎么做到双向绑定的
- Proxy代理怎么做到监听改变的
- webpack的打包构建过程
- webpack的热更新原理
- promise.all()
- 数组去重有哪些方案
- 本地代理跨域的原理
1、ES6的新语法
-
let、const
-
箭头函数 =>
箭头函数没有自己的arguments、没有prototype属性、不能作为构造函数、没有自己的this,箭头函数的this指向即使使用call、apply、bind也无法改变 -
Map、Set数据结构
-
iterator迭代器
对于可迭代的数据解构,ES6在内部部署了一个[Symbol.iterator]属性,它是一个函数,执行后会返回iterator对象(也叫迭代器对象),而生成iterator对象[Symbol.iterator]属性叫iterator接口,有这个接口的数据结构即被视为可迭代的
数组中的Symbol.iterator方法(iterator接口)默认部署在数组原型上:
默认部署iterator接口的数据结构有以下几个,注意普通对象默认是没有iterator接口的(可以自己创建iterator接口让普通对象也可以迭代)
Array
Map
Set
String
TypedArray(类数组)
函数的 arguments 对象
NodeList 对象
next方法返回又会返回一个对象,有value和done两个属性,value即每次迭代之后返回的值,而done表示是否还需要再次循环,可以看到当value为undefined时,done为true表示循环终止
可迭代的数据结构会有一个[Symbol.iterator]
方法
[Symbol.iterator]
执行后返回一个iterator
对象
iterator对象有一个next方法
执行一次next方法(消耗一次迭代器)会返回一个有value,done属性的对象 -
解构赋值
let { res } = { res: 'msg' }
- 剩余/扩展运算符
let arr = [1,2,3,4]
[...arr] // [1,2,3,4]
- for of循环
- Promise
- ES6的Module
- 函数默认值
function func1 (a =1) {}
- Proxy
2、Vue.$nextTick
this.$ nextTick–将回调延迟到下次DOM更新循环之后执行。
在修改数据之后立即使用它,然后等待DOM更新。官方给出的this.$nextTick的作用是:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
Vue. $nexttick主要用来获取数据改变之后的dom结构,放在其回调函数中的操作不会立即执行,而是等数据更新,DOM更新完之后才开始执行,这样拿到的是最新的数据
3、Vuex使用及其数据双向绑定原理
State
用来存储共享的数据属性或状态
可以使用mapState 辅助函数简化写法Getters
相当于计算属性,它的返回值会根据它的依赖被缓存起来,只有依赖值发生了变化才会重新计算。
Getter接受state作为第一个参数
可以使用mapGetters辅助函数简化写法Mutations
必须为同步函数
在组件中使用this.$store.commit(‘xx’)来触发
可以使用mapMutations辅助函数简化写法Actions
Action提交的是mutation,由mutation中的函数来修改state中的属性或状态
可以包含任意异步操作
在组件中使用this.$dispath(‘xx’)来触发
可以使用mapActions辅助函数简化写法Modules
允许将store分割成模块,每个模块拥有自己的state,getters,mutation,action
Vue 主要通过以下 4 个步骤来实现数据双向绑定的:
- 实现一个监听器 Observer:对数据对象进行遍历,包括子属性对象的属性,利用 Object.defineProperty() 对属性都加上 setter 和 getter。这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化。
- 实现一个解析器 Compile:解析 Vue 模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新。
- 实现一个订阅者 Watcher:Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁 ,主要的任务是订阅 Observer 中的属性值变化的消息,当收到属性值变化的消息时,触发解析器 Compile 中对应的更新函数。
- 实现一个订阅器 Dep:订阅器采用 发布-订阅 设计模式,用来收集订阅者 Watcher,对监听器 Observer 和 订阅者 Watcher 进行统一管理。
4、Vue2和Vue3数据双向绑定的原理
Vue2和Vue3的数据双向绑定
Vue数据双向绑定
Object.defineProperty
监听的一些缺点:
- 一次只能对一个属性进行监听,需要遍历来对所有属性监听。这个我们在上面已经解决了。
- 在遇到一个对象的属性还是一个对象的情况下,需要递归监听。
- 对于对象的新增属性,需要手动监听
- 对于数组通过
push、unshift
方法增加的元素,也无法监听
5、浏览器的事件循环
在有宏任务和微任务的概念加入后,JS代码的整体执行逻辑变为:
- 得到的JS代码进入主线程
- 判断遇到的代码:如果是同步代码则进入步骤 3;如果是异步代码则进入步骤 4。
- 同步代码直接进入主线程执行。
- 对异步代码进行分类:如果是promise,则new promise以及接受的函数参数立即执行,then和catch的回调函数进入 微任务的Event Queue;如果是setTimeout或者setInterval(需要达到指定时间),则将回调函数注册到 宏任务的Event Queue。
5.JS引擎判断主线程是否为空,如果为空,则读取 微任务Event Queue 中所有的消息,并依次执行。执行完毕后进入步骤 6。
主线程和微任务 Event Queue 都为空后,读取 宏任务Event Queue 中的第一个消息进入主线程执行。执行完毕后进入步骤 5。
9、汇博科技
- Vue3系统学习过吗?
- webpack的基本数据类型
- 一些数据输出题(this指向,作用域之类的)
- Vue2的数据双向绑定,有哪些问题,Vue3的数据双向绑定的原理
- TS用过吗
- webpack常用的一些配置
10、字节跳动
- 堆与栈的区别(内存是如何分配释放的)
- MPX与其它小程序框架和原生的对比,有什么优点
- 适配,小程序适配、H5的适配
- 小程序线上环境有跨域问题吗?没有,因为不是在浏览器运行的
- 小程序如何对请求的地址进行过滤?通过微信后台配置请求地址白名单
- 本地如何与服务端联调:node中间层反向代理
- 刚说MPX可以跨平台输出,了解背后的原理吗?
- MPX可以自动对字体和页面做适配,了解原理吗?
- 看你简历上写的有Sass、less、Stylus,你最喜欢用哪个?说说为啥各自有什么特点?
- CSS预处理器怎么对CSS样式做兼容的 --webkit等开头的,怎么做兼容
- 数据类型有哪些?基本数据类型、引用数据类型,延伸出堆栈区别
- 在数组原型上写一个sum,api实现数组内数字求和[1, 2, 3].sum() 返回6
- 手写promise.all
- 求二叉树路径上的数字之和
1、堆和栈
堆与栈实际上是操作系统对进程占用的内存空间的两种管理方式,主要有如下几种区别:
- 管理方式不同。栈由操作系统自动分配释放,无需我们手动控制;堆的申请和释放工作由程序员控制,容易产生内存泄漏;
- 生长方向不同。堆的生长方向向上,内存地址由低到高;栈的生长方向向下,内存地址由高到低。
- 空间大小不同。每个进程拥有的栈的大小要远远小于堆的大小。理论上,程序员可申请的堆大小为虚拟内存的大小,进程栈的大小64bits的Windows默认1M,64bits的Linux默认10M;
- (5)分配效率不同。栈由操作系统自动分配,会在硬件层级对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是由C/C++提供的库函数或运算符来完成申请与管理,实现机制较为复杂,频繁的内存申请容易产生内存碎片。显然,堆的效率比栈要低得多。
2、MPX小程序框架的优点
滴滴开源小程序框架 MPX
MPX
Mpx是一款致力于提高小程序开发体验的增强型小程序框架,通过Mpx,我们能够以最先进的web开发体验(Vue + Webpack)来开发生产性能深度优化的小程序,Mpx具有以下一些优秀特性:
- 数据响应是Mpx运行时增强的核心能力,该能力让用户在小程序开发中能够像Vue中一样使用watch和computed特性,并且用直接赋值的方式操作数据驱动视图更新,而不需要手动调用setData方法,换言之框架接管了小程序中的setData调用。
- 数据响应特性(watch/computed)
- 增强的模板语法(动态组件/样式绑定/类名绑定/内联事件函数/双向绑定等)
- 深度性能优化(原生自定义组件/基于依赖收集和数据变化的setData)
- Webpack编译(npm/循环依赖/Babel/ESLint/css预编译/代码优化等)
- 单文件组件开发
- 状态管理(Vuex规范/多实例/可合并)
- 跨团队合作(packages)
- 逻辑复用能力(mixins)
- 脚手架支持
- 小程序自身规范的完全支持
- 支付宝小程序的支持
3、MPX可以自动对字体和页面做适配,了解原理吗?
designWidth
设计稿宽度,单位为px。默认值为750px。
mpx会基于小程序标准的屏幕宽度baseWidth
750rpx,与option.designWidth
计算出一个转换比例transRatio
转换比例的计算方式为transRatio = (baseWidth / designWidth)
。精度为小数点后2位四舍五入
所有生效的rpx注释样式中的px会乘上transRatio得出最终的rpx值
/* 转换前:designWidth = 1280 */
.btn {
width: 200px;
height: 100px;
}
/* 转换后: transRatio = 0.59 */
.btn {
width: 118rpx;
height: 59rpx;
}
4、CSS预处理器
CSS预处理器
CSS预处理器Sass为主
主要有变量、常量、嵌套、混入(@mixin)、函数等功能
CSS预处理器主要有Sass、Less、Stylus
Less 的基本语法属于CSS 风格
Sass,stylus 相比之下更激进一些, 可以利用缩进,空格和换行来减少需要输入的字符
不过区别在于 Sass, stylus同时兼容CSS风格的代码
5、小程序和H5的适配
H5页面适配安全区
body {
padding: constant(safe-area-inset-top)
constant(safe-area-inset-right)
constant(safe-area-inset-bottom)
constant(safe-area-inset-left);
// constant在iOS<11.2的版本中生效
padding: env(safe-area-inset-top)
env(safe-area-inset-right)
env(safe-area-inset-bottom)
env(safe-area-inset-left);
//env在iOS>=11.2的版本中生效
}
延伸: meta标签有一个属性,叫viewport-fit,顾名思义,就是网页内容在设备可视窗口的填充方式。有两种填充方式:
cover:
网页内容完全覆盖可视窗口
contain:
可视窗口完全包含网页内容
我们做安全区的适配前,需要把我们的viewport-fit设置成cover之后,再去做padding的适配。
也就是说需要把整个网页内容撑满,在撑满的基础上再去设置上下左右的间距,这样就一步一步比较严谨的做好了我们的安全区适配。
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
12、北京智慧星光
- 路由守卫
- Vuex中mutations和actions的区别
- 路由跳转报错怎么捕获
- 路由跳转和传参数params和query,此时刷新网页,哪个数据会丢失
- 数据处理
- 前端分页怎么做
- 登录鉴权流程,token存在哪儿
- localStorage和sessionStorage、Cookie的区别和联系
- UI库有没有用过一些联动的组件
- v-for和v-if的优先级?怎么解决数据重复处理问题
1、Vue路由守卫
1、全局守卫
vue-router全局有三个守卫:
- router.beforeEach 全局前置守卫 进入路由之前
- router.beforeResolve 全局解析守卫
- router.afterEach 全局后置守卫 进入路由之后
// main.js 入口文件
import router from './router'; // 引入路由
router.beforeEach((to, from, next) => {
next();
});
router.beforeResolve((to, from, next) => {
next();
});
router.afterEach((to, from) => {
console.log('afterEach 全局后置钩子');
});
to和from是将要进入和将要离开的路由对象,路由对象指的是平时通过this.$route获取到的路由对象。
next:Function 这个参数是个函数,且必须调用,否则不能进入路由(页面空白)。
2、路由组件内的守卫:
- beforeRouteEnter 进入路由前
- beforeRouteUpdate (2.2) 路由复用同一个组件时
- beforeRouteLeave 离开当前路由时
beforeRouteEnter (to, from, next) {
// 在路由独享守卫后调用 不!能!获取组件实例 `this`,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用 可以访问组件实例 `this`
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用,可以访问组件实例 `this`
}
3、路由独享守卫
如果你不想全局配置守卫的话,你可以为某些路由单独配置守卫:
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// 参数用法什么的都一样,调用顺序在全局前置守卫后面,所以不会被全局守卫覆盖
// ...
}
}
]
})
4、路由钩子函数的错误捕获
如果我们在全局守卫/路由独享守卫/组件路由守卫的钩子函数中有错误,可以这样捕获:
router.onError(callback => {
// 2.4.0新增 并不常用,了解一下就可以了
console.log(callback, 'callback');
});
2、路由传参数
路由传参数有三种方式
- 在路由的path里面写参数
- query传参数
- params传参数
在路由的path里面写参数
this.$router.push({
path:`/home/${id}`,
})
// 路由配置
{
path:"/home/:id",
name:"Home",
component:Home
}
在Home组件中获取参数值
this.$route.params.id
params传参数
通过name来匹配路由,通过param来传递参数
this.$router.push({
name:'Home',
params:{
id:id
}
})
用params传递参数,不使用:/id
{
path:'/home',
name:Home,
component:Home
}
Home组件中获取参数
this.$route.params.id
query传参数
this.$router.push({
path:'/home',
query:{
id:id
}
})
路由配置
{
path:'/home',
name:Home,
component:Home
}
获取参数的方法
this.$route.query.id
params传参,必须使用命名路由的方式传参;
params传参,不会显示在地址栏上,会保存在内存中,刷新会丢失,可以配合本地存储进行使用;
query的参数会显示在地址栏上,不会丢失;
3、前端分页实现
computed: {
// 计算属性对数据进行处理
frontEndPageChange() {
let start = (currentPage - 1) * pageSize;
if (start >= thistableData.length) start = 0;
let end = currentPage * pageSize;
if (end >= thistableData.length) end = thistableData.length;
return thistableDataslice(start, end);
}
}
4、前端登录鉴权逻辑
5、v-for和v-if的优先级
1.v-for的优先级高于v-if
原因:v-for比v-if优先级高,所以使用的话,每次v-for都会执行v-if,造成不必要的计算,影响性能,尤其是当前需要渲染很小一部分的时候。
<ul>
<li v-for="user in users" v-if="user.isActive" :key="user.id">
{{ user.name }}
</li>
</ul>
如上情况,即使有很多user 但是只要有一个需要使用v-if,也需要循环整个数组,这在性能上是极大的浪费。
但是我们可以使用computed计算属性解决:
<div>
<div v-for="(user,index) in activeUsers" :key="user.index" >
{{ user.name }}
</div>
</div>
data () { // 业务逻辑里面定义的数据
return {
users,: [{
name: '111111',
isShow: true
}, {
name: '22222',
isShow: false
}]
}
}
computed: {
activeUsers: function () {
return this.users.filter(function (user) {
return user.isShow;//返回isShow=true的项,添加到activeUsers数组
})
}
}
13、拼多多
- 说说小程序登录鉴权的逻辑,如果用sso同时共享小程序和H5的登录态
- 小程序微信支付的流程
- 小程序如何控制安全性(前端安全)
- Proxy拦截数组push,拦截对象改变
- Promise输出
- 让每行只放三个块元素,然后多余的换行处理
- H5和小程序适配
- webpack常用的plugin,有没有手写过loader、plugin
14、领岳科技
- 元素隐藏的几种方式
- 三栏布局以及水平垂直居中的方式
- Vue文件中的template中为什么有且仅有一个根容器
- webpack的loader和plugin有什么区别
- 有哪些常见的loader和plugin
- H5如何与native进行交互
- 斐波那契数列第n项
- 最长不重复子串
- 给出先序遍历和中序遍历还原二叉树
- this指向输出题
- async,await异步输出题
1、this指向的输出题
// 1:全局环境下this默认指向window
console.log(this) // window
// 2:函数独立调用,函数中的this也默认指向window
function getThis () {
console.log(this) // window
}
getThis()
//3:被嵌套的函数独立调用的时候,this默认指向了window
let obj1 = {
a: 2,
foo: function () {
console.log(this) // obj1
function test () {
console.log(this) // window
}
test()
},
foo2: () => {
console.log(this) // window
},
// 闭包函数中的this默认指向window
foo3: function () {
return function () {
console.log(this) // window
}
},
foo4: function () {
return () => {
console.log(this) // obj1
}
}
}
obj1.foo()
obj1.foo2()
obj1.foo3()()
obj1.foo4()()
// 4:IIFE自执行函数内的this默认指向window
let a = 10
function myFoo () {
(function test(){
console.log(this) // window
})()
}
let obj2 = {
a: 2,
foo: myFoo
}
obj2.foo()
let obj1 = {
name: 'obj1',
fun1: function () {
console.log(this.name)
},
fun2: () => {
console.log(this.name)
},
fun3: function () {
return function () {
console.log(this.name)
}
},
fun4: function () {
return () => {
console.log(this.name)
}
}
}
let obj2 = {
name: '2323232'
}
obj1.fun1() // obj1
obj1.fun2() // undefine
obj1.fun3()() // undefine
obj1.fun4()() // obj1
console.log('--------------------------------------')
obj1.fun1.call(obj2) // 2323232
obj1.fun2.call(obj2) // undefine
obj1.fun3().call(obj2) // 2323232
obj1.fun4().call(obj2) // obj1
2、异步输出顺序问题
async、await会阻塞其后面内容的输出
promise不会阻塞其后面内容的输出
async function test1 () {
console.log('1111111111')
const res = await test2()
console.log('res', res)
console.log('22222222222')
}
async function test2 () {
console.log('test2')
}
console.log('scri1')
setTimeout(() =>{
console.log('123')
}, 1000)
test1()
new Promise((resolve, reject) => {
console.log('before Promise')
resolve('promise')
console.log('after Promise')
}).then(res => {
console.log(res)
})
console.log('scri2')
15、百度
- 深拷贝和浅拷贝的方式
- 说说Vue父子组件通信
- 父子组件执行顺序
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
- Commonjs和es6的modules区别
前端模块化 - webpack的hash参数的三种取值方式
- 浏览器重绘重排以及触发的方式
- 强缓存和协商缓存的状态码
- 根据id和pid构造出树形结,手写代码
1、浏览器重绘重排
重排:
当DOM的变化影响了元素的几何信息(DOM对象的位置和尺寸大小),浏览器需要重新计算元素的几何属性,将其安放在界面中的正确位置,这个过程叫做重排。
回流就好比向河里(文档流)扔了一块石头(dom变化),激起涟漪,然后引起周边水流受到波及,所以叫做回流
引起浏览器重排的方法:
- 添加或者删除可见的DOM元素
- 元素的尺寸、大小改变
- 内容变化,比如在input框中输入内容
- 浏览器窗口大小发生改变resize
- 计算 offsetWidth 和 offsetHeight 属性
- 设置style属性的值
重绘:
2、深拷贝和浅拷贝以及其实现方式
深浅拷贝
js的数据类型主要有基本数据类型
和引用数据类型
两种
基本数据类:
String、Number、Boolean、Null、Undefined、Symbol
引用数据类型:
Object(Object、Array、Function)
javascript的变量的存储方式–栈(stack)和堆(heap)
栈:自动分配内存空间,系统自动释放,里面存放的是基本类型的值和引用类型的地址
堆:动态分配的内存,大小不定,也不会自动释放。里面存放引用类型的值。
浅拷贝—浅拷贝是指复制对象的时候,只对第一层键值对进行独立的复制,如果对象内还有对象,则只能复制嵌套对象的地址深拷贝—深拷贝是指复制对象的时候完全的拷贝一份对象,即使嵌套了对象,两者也相互分离,修改一个对象的属性,也不会影响另一个。其实只要递归下去,把那些属性的值仍然是对象的再次进入对象内部一 一进行复制即可。
let a = 1
let b = a
a = 2
console.log(b) // 1
let obj1 = {
a: 123,
b: {
dataB: 'kell'
}
}
let obj2 = obj1
obj1.a = 456
console.log(obj2.a) // 456
let obj3 = {
a: 123,
b: {
dataB: 'kell',
olo: {
po: 'okoko'
}
}
}
let obj4 = Object.assign({}, obj3)
obj3.a = 90
console.log(obj4.a) // 123
obj3.b.dataB = 'koko'
console.log(obj4.b.dataB) // koko
let obj5 = {...obj3}
console.log(obj5) // { a: 90, b: { dataB: 'koko', olo: { po: 'okoko' } } }
对象浅拷贝的方式:
let obj4 = Object.assign({}, obj3)
。Object.assign()
方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。- 拓展运算符
let obj5 = {...obj3}
数组的浅拷贝方式:
- arr.concat()
- arr.slice()
let arr = ['one', 'two', 'three'];
let newArr = arr.concat();
let newArr = arr.slice();
正则匹配所有域名及其子域名
const regStr = /^([a-zA-Z\d-_\*@]+\.)*baidu\.com$/
let str = 'wenku.baidu.com'
let str2 = 'baidu.com'
console.log(regStr.test(str))
console.log(regStr.test(str2))
3、根据id和pid构造出树形结构
比如:
const arr = [{
id: 0,
pid: -1
}, {
id: 1,
pid: 0
}, {
id: 2,
pid: 0
}, {
id: 3,
pid: 1
}, {
id: 4,
pid: 2
}];
经过处理后转化成:
[{
id: 0,
pid: -1,
children: [{
id: 1,
pid: 0,
children: [{
id: 3,
pid: 1,
children: []
}]
}, {
id: 2,
pid: 0,
children: [{
id: 4,
pid: 2
}]
}]
}]
处理的方法如下:
function toTree(data) {
let result = []
if (!Array.isArray(data)) {
return result
}
data.forEach(item => {
delete item.children;
});
let map = {};
data.forEach(item => {
map[item.id] = item;
});
console.log(map)
data.forEach(item => {
let parent = map[item.pid];
console.log('parent', parent)
if (parent) {
(parent.children || (parent.children = [])).push(item);
} else {
result.push(item);
}
});
console.log(result)
return result;
}
toTree(arr)
16、米可世界
- CORS解决跨域
- 跨域的请求到底有没有发出去(分复杂和简单跨域,复杂跨域首先会发送option预检请求)
- webpack的hash、chunkHash、contentHash
- 有没有自己定义过Plugin和Loader
- 箭头函数的this指向,箭头函数的this是在定义时候就确定了,且无论怎样都不会改变
- H5的触底加载,怎么实现
- webpack的文件监听
- Vue3有了解过吗?
- async、await除了用try、catch还可以用啥进行包裹
17、优贝在线(音乐、K歌、社交APP)
- CSS有哪些可继承的属性
- 移动端适配
- 怎么判断触底加载下一页
- 防抖和节流区别及其联系
- ajax发请求,fetch发请求、axios发请求(怎么拦截)
- 解决跨域的方案。CORS需要设置哪些字段
- 正则表达式
1、CSS有哪些可继承属性
CSS的可继承属性
- 字体系列属性:
font、font-size、font-family、font-style、font-variant、font-stretch、font-size-adjust
- 文本系列属性:
text-algin、text-indent、line-height、word-spacing、letter-spacing、text-transform、color、direction
- 可见属性:visibility
- 光标属性:cursor