回流和重绘
浏览器渲染页面步骤:
- 处理HTML标记并构建DOM树
- 处理css标记并构建CSSOM树
- 将DOM和CSSOM合并成一个渲染树
- 根据渲染树来布局以计算每个节点的几何信息
- 将各个节点绘制到屏幕上
回流:当Render树中部分或全部元素的尺寸、布局、隐藏等改变,需要重新计算render树
当页面布局和几何属性改变时发生回流,下面一些情况会
重绘:当元素的样式(color/background-color/visibility等)发生变化时触发重绘
回流必将引起重绘,重绘不一定会引起回流
下面一些行为会引起回流: - 添加或删除可见的DOM元素
- 元素的位置发生变化
- 元素的尺寸发生变化 -边距、填充、边框、宽度、高度
- 内容改变 -比如文本改变或者图片大小改变而引起的计算值宽度和高度改变
- 页面渲染初始化
- 浏览器窗口尺寸改变 -resize事件发生时
- 一些常用且会引发回流的属性和方法:clientWidth、clientHeight、clientTop、clientLeft
offsetWidth、offsetHeight、offsetTop、offsetLeft
scrollWidth、scrollHeight、scrollTop、scrollLeft
scrollIntoView()、scrollIntoViewIfNeeded()
getComputedStyle()
getBoundingClientRect()
scrollTo()
性能影响:回流比重绘的代价更高。
如何避免
CSS - 避免使用table布局。
- 尽可能在DOM树的最末端改变class。
- 避免设置多层内联样式。
- 将动画效果应用到position属性为absolute或fixed的元素上。
- 避免使用CSS表达式(例如:calc())。
JS - 避免频繁操作样式,最好一次性重写style属性,或者将样式列表定义为class并一次性更改class属性。
- 避免频繁操作DOM,创建一个documentFragment,在它上面应用所有DOM操作,最后再把它添加到文档中。
- 也可以先为元素设置display: none,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘。
- 避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。
- 对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流。
Map和Object区别
都是键值对集合,区别在于object的键只能是字符串,但Map的键可以是任意类型。
call apply bind区别
- 都可以改变函数中this指向
- 都可以传参,参数指的是传给函数的参数,但参数形式不一样,apply只能传数组,call传列表,apply和call是一次性传参,bind可以多次传参
- apply和call立即执行,bind不会立即执行,加个括号才执行
为什么要改变函数this指向,比如下面这段代码,函数在setTimeout的回调中执行,this默认指向了window,我们要像让this指向obj,就需要通过这三个方法改变this指向:
var name="lucy";
var obj={name:"mark",say:function () {console.log(this.name)}
};
setTimeout(obj.say,0) //lucy
//bind方法改变this指向
setTimeout(obj.say.bind(obj),0) //mark
webpack 热更新原理
- 使用express启动本地server,让浏览器可以请求本地静态资源
- 启动本地server后,再启动websocket服务,通过websocket,可以建立本地服务和浏览器的双向通信,这样就实现了当本地文件发生变化后,立马告诉浏览器可以热更新代码了。
JSON.parse (JSON.stringfy())实现深拷贝的缺点
- 如果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。
- 如果对象中存在循环引用的情况也无法正确实现深拷贝。
抽奖游戏转盘的实现方案
核心是计算出中奖产品对应的指针位置。大体步骤如下:
- 前端根据奖品个数划分转盘对应的角度,如果是8个奖品那每个就是45deg,指针一般在中间位置,所以最终指向的角度是45±22.5deg(±的意思看你是顺时针还是逆时针)
- 后端读取中间产品对应的drawIndex
this.rotateAngle = this.config.circle * 360 * this.cricleAdd - (22.5 + this.drawIndex * 45)// 圈数位置解析// this.config.circle * 360 * this.cricleAdd 顺时针总圈数/累积总圈数// 22.5 + this.drawIndex * 45 ===> (奖品位置 === this.drawIndex * 45) (指针中间位置 === 22.5)