1、解释一下重绘和回流,它们的区别是什么?怎么预防?
- 重绘:由于节点的几何属性发生改变或者由于样式发生改变,例如改变元素背景色时,屏幕上的部分内容需要更新,表现为某些元素的外观被改变
- 背景有关属性
- 边界圆角 颜色 阴影 渐变
- 回流(reflow):当render 树中的一部分(或者全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建,
- 页面布局和几何属性改变时就需要回流
- 定位属性及浮动也会触发重新布局
- 盒子模型相关属性会触发重新布局
- 改变节点内部文字结构也会触发重新布局
- 频繁的触发重绘和回流,会导致UI频繁渲染,最终导致JS变慢
- 区别就是回流一定会引起重绘 重绘不一定会回流
预防
- 避免使用触发重绘,回流的css属性
- 将重绘,回流的影响返回限制在单独的图层之间
优化
- opacity替代visibility
- 不要一条条修改dom样式 预先定义好class 修改dom的className
- 不要把dom结点的属性值放在一个循环里当成循环里的变量
- 对于动画新建图层
- 不要使用table布局 可能很小的一个改动都会造成整个table的重新布局
- 对于网络性能的调试 可以通过chrome
2、什么是函数防抖,函数节流?
let timer只能在setTimeout的父级作用域中,这样才是同一个timer,并且为了方便防抖函数的调用和回调函数fn的传参问题,我们应该用闭包来解决这些问题。
闭包:
函数嵌套,小函数内部使用大函数的变量,大函数就形成了闭包。
优点:
- 作用域空间不销毁,所以变量也不会被销毁,增加了变量的生命周期
- 在函数外部可以访问函数内部的变量
- 保护私有变量,将变量定义在函数内,不会污染全局
缺点:
因为全局可以访问函数内部的变量 创建的空间一直存在,如果一直调用内部的变量,内存一直被占用,滥用会造成内存泄露
防抖:
防抖就是当某些回调函数一直被触发的时候,只在最后一次触发之后的某一个间隔时间之后执行
function Debounce(fn,delay){
let timer;
//闭包实现
return function(){
let that=this;
let _args=args;
if(timer){
clearTimeout(timer)
},
timer=setTimeout(()=>{
fn.apply(that,args)
},delay)
}
}
function debounce(fn, delay) {
let timer; // 维护一个 timer
return function () {
// let _this = this; // 取debounce执行作用域的this
let args = arguments;
if (timer) {
clearTimeout(timer);
}
// timer = setTimeout(function () {
// fn.apply(_this, args); // 用apply指向调用debounce的对象,相当于_this.fn(args);
// }, delay);
timer = setTimeout(()=> {
fn.apply(this, args); // 用apply指向调用debounce的对象,相当于this.fn(args);
}, delay);
};
}
// test
function testDebounce(e, content) {
console.log(e, content);
}
var testDebounceFn = debounce(testDebounce, 1000); // 防抖函数
document.onmousemove = function (e) {
testDebounceFn(e, 'debounce'); // 给防抖函数传参
}
节流:
节流就是在函数一直被触发的一定时间内,间隔一定时间执行一次
function throttle(fn,delay){
let previous=0;
return function(){
let timer=0
let _args=args
let now=new Date()
if(now-previous>timer){
setTimeout(()=>({
fn.call(that,args)
previous = now;
},dalay)
}
}
}
function throttle(fn, delay) {
let timer;
return function () {
let _this = this;
let args = arguments;
if (timer) {
return;
}
timer = setTimeout(function () {
fn.apply(_this, args);
timer = null; // 在delay后执行完fn之后清空timer,此时timer为假,throttle触发可以进入计时器
}, delay)
}
}
//测试
function testThrottle(e, content) {
console.log(e, content);
}
let testThrottleFn = throttle(testThrottle, 1000); // 节流函数
document.onmousemove = function (e) {
testThrottleFn(e, 'throttle'); // 给节流函数传参
}
函数防抖的应用场景
连续的事件,只需触发一次回调的场景有:
搜索框搜索输入。只需用户最后一次输入完,再发送请求
手机号、邮箱验证输入检测
窗口大小Resize。只需窗口调整完成后,计算窗口大小。防止重复渲染。
函数节流的应用场景
间隔一段时间执行一次回调的场景有:
滚动加载,加载更多或滚到底部监听
谷歌搜索框,搜索联想功能
高频点击提交,表单重复提交
3、浏览器跨域请求用过什么?
用过jsonp Nginx代理 服务器反向代理 cors
4、解释一下js的事件循环机制?
重点
所有的代码都要通过函数调用栈中调用执行。
当遇到前文中提到的APIs的时候,会交给浏览器内核的其他模块进行处理。
任务队列中存放的是回调函数。
等到调用栈中的task执行完之后再回去执行任务队列之中的task。
特点
event loop实现了js引擎的非阻塞
同步任务 在主线程处理
异步任务 刚开始会被挂起放在webApi中 待整个异步事件返回一个结果之后 会将该事件放入一个新的队列中 --》 在事件队列处理
当主线程的任务处理完成之后 会去事件队列找一个任务塞进主线程去处理
也就是
js引擎遇到一个异步事件后并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。当一个异步事件返回结果后,js会将这个事件加入与当前执行栈不同的另一个队列,我们称之为事件队列。被放入事件队列不会立刻执行其回调,而是等待当前执行栈中的所有任务都执行完毕, 主线程处于闲置状态时,主线程会去查找事件队列是否有任务。如果有,那么主线程会从中取出排在第一位的事件,并把这个事件对应的回调放入执行栈中,然后执行其中的同步代码…,如此反复,这样就形成了一个无限的循环。这就是这个过程被称为“事件循环(Event Loop)”的原因。
更重要的一点就是
前面我们介绍过,在一个事件循环中,异步事件返回结果后会被放到一个任务队列中。然而,根据这个异步事件的类型,这个事件实际上会被对应的宏任务队列或者微任务队列中去。并且在当前执行栈为空的时候,主线程会 查看微任务队列是否有事件存在。如果不存在,那么再去宏任务队列中取出一个事件并把对应的回到加入当前执行栈;如果存在,则会依次执行队列中事件对应的回调,直到微任务队列为空,然后去宏任务队列中取出最前面的一个事件,把对应的回调加入当前执行栈…如此反复,进入循环。
我们只需记住当当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件。同一次事件循环中,微任务永远在宏任务之前执行。
5、浏览器缓存有什么,区别呢?
浏览器缓存策略分为两种:强缓存和协商缓存
概念
简单来说,浏览器缓存就是把一个已经请求过的Web资源(如html页面,图片,js,数据等)拷贝一份副本储存在浏览器中。缓存会根据进来的请求保存输出内容的副本。当下一个请求来到的时候,如果是相同的URL,缓存会根据缓存机制决定是直接使用副本响应访问请求,还是向源服务器再次发送请求。比较常见的就是浏览器会缓存访问过网站的网页,当再次访问这个URL地址的时候,如果网页没有更新,就不会再次下载网页,而是直接使用本地缓存的网页。只有当网站明确标识资源已经更新,浏览器才会再次下载网页。
为什么要用
为什么使用缓存:
(1)减少网络带宽消耗
无论对于网站运营者或者用户,带宽都代表着金钱,过多的带宽消耗,只会便宜了网络运营商。当Web缓存副本被使用时,只会产生极小的网络流量,可以有效的降低运营成本。
(2)降低服务器压力
给网络资源设定有效期之后,用户可以重复使用本地的缓存,减少对源服务器的请求,间接降低服务器的压力。同时,搜索引擎的爬虫机器人也能根据过期机制降低爬取的频率,也能有效降低服务器的压力。
(3)减少网络延迟,加快页面打开速度
带宽对于个人网站运营者来说是十分重要,而对于大型的互联网公司来说,可能有时因为钱多而真的不在乎。那Web缓存还有作用吗?答案是肯定的,对于最终用户,缓存的使用能够明显加快页面打开速度,达到更好的体验。
浏览器端的缓存规则:
对于浏览器端的缓存来讲,这些规则是在HTTP协议头和HTML页面的Meta标签中定义的。他们分别从新鲜度和校验值两个维度来规定浏览器是否可以直接使用缓存中的副本,还是需要去源服务器获取更新的版本。
新鲜度(过期机制):也就是缓存副本有效期。一个缓存副本必须满足以下条件,浏览器会认为它是有效的,足够新的:
-
含有完整的过期时间控制头信息(HTTP协议报头),并且仍在有效期内;
-
浏览器已经使用过这个缓存副本,并且在一个会话中已经检查过新鲜度;
满足以上两个情况的一种,浏览器会直接从缓存中获取副本并渲染。
校验值(验证机制):服务器返回资源的时候有时在控制头信息带上这个资源的实体标签Etag(Entity Tag),它可以用来作为浏览器再次请求过程的校验标识。如过发现校验标识不匹配,说明资源已经被修改或过期,浏览器需求重新获取资源内容。
强缓存
-
cache-control: max-age=xxxx,public
客户端和代理服务器都可以缓存该资源;
客户端在xxx秒的有效期内,如果有请求该资源的需求的话就直接读取缓存,statu code:200 ,如果用户做了刷新操作,就向服务器发起http请求 -
cache-control: max-age=xxxx,private
只让客户端可以缓存该资源;代理服务器不缓存
客户端在xxx秒内直接读取缓存,statu code:200 -
cache-control: max-age=xxxx,immutable
客户端在xxx秒的有效期内,如果有请求该资源的需求的话就直接读取缓存,statu code:200 ,即使用户做了刷新操作,也不向服务器发起http请求 -
cache-control: no-cache
跳过设置强缓存,但是不妨碍设置协商缓存;一般如果你做了强缓存,只有在强缓存失效了才走协商缓存的,设置了no-cache就不会走强缓存了,每次请求都回询问服务端。 -
cache-control: no-store
不缓存,这个会让客户端、服务器都不缓存,也就没有所谓的强缓存、协商缓存了。
协商缓存
etag: '5c20abbd-e2e8'
last-modified: Mon, 24 Dec 2018 09:49:49 GMT
比如在vue中应该这样:
我的做法是:
index.html文件采用协商缓存,理由就是要用户每次请求index.html不拿浏览器缓存,直接请求服务器,这样就保证资源更新了,用户能马上访问到新资源,如果服务端返回304,这时候再拿浏览器的缓存的index.html,切记不要设置强缓存!!!
async和await
其实就是generate函数的语法糖,想搞清楚用同步的方式实现异步只要搞清generate函数内部的机制就好了,async/Await就是一个自执行的generate函数。利用generate函数的特性把异步的代码写成“同步”的形式。