css中,style可以通过修改style节点的disabled属性进行禁用,而不必移除该节点
事件传播是浏览器决定哪个对象触发其事件处理的过程。对于单个对象的特定事件,必须是不能传播的。当文档元素上发生某个类型的事件时,然而,它们会在文档树向上传播或冒泡。事件处理程序能通过调用方法或设置事件对象属性来阻止事件传播,这样它就能停止冒泡且将无法在容器元素上触发处理程序。
事件传播的另一种形式成为事件捕获,在容器元素上注册的特定处理车光绪有机会在事件传播到真实目标之前拦截它。
大部分事件会冒泡到dom树根。调用目标的父元素的事件处理程序,然后调用在目标的祖先元素上注册的事件处理程序。这回一直到document对象,最后到达window对象。事件冒泡为在大量单独文档元素上注册处理程序提供了替代方案,即在共同的祖先元素上注册一个处理程序来处理所有的事件。
事件冒泡是事件传播的第三个阶段。目标对象本身的事件处理程序调用是第二个阶段。第一个阶段甚至发生在目标处理程序调用之前,被称为捕获阶段。如果addEventListener的第三个参数为true,则这个事件处理程序为一个捕获事件处理程序,它会在事件传播的第一个阶段调用。事件捕获阶段类似反向的冒泡阶段,是从上到下的捕获,以此类推。
click事件触发连击的时候,event.detail会返回点击次数,我们可以通过这个来判断是否双击或者三击
addEventlistener接受第三个参数。第三个参数通常是false,但是如果传递了true,那么函数将注册为捕获事件处理程序,并在事件不同的调度阶段调用
事件绑定的返回值,当返回为false的时候,会阻止浏览器的默认行为,比如表单的submit按钮的onclick事件,返回false将不会提交,但是仅对on开头的注册事件有效,通过addEventlListener注册的事件,需要通过preventDefault()或设置事件的returnValue
也能通过stopPropagation来进行阻止事件继续传播
关于onload,load事件会在页面所有资源都执行完成之后执行,包括图片影视频等,所有我们应该等待的是dom文本加载完,应该使用的是DOMContentLoaded,这个事件会在load事件之前调用postMessage
这种方式通常用于获取嵌入页面中的第三方页面数据。一个页面发送消息,另一个页面判断来源并接收消息
// 发送消息端window.parent.postMessage('message', 'http://test.com')// 接收消息端var mc = new MessageChannel()mc.addEventListener('message', event => { var origin = event.origin || event.originalEvent.origin if (origin === 'http://test.com') { console.log('验证通过') }})
Event Loop
js是非阻塞单线程语言,js在执行过程中会产生执行环境。这些执行环境会按照顺序加入到执行栈,如果遇到异步代码,则会挂起并加入task队列。一旦执行栈为空,event loop 就会从task队列取出需要执行的代码放入执行栈中执行。所以js本质还是单线程。
setTimeout第二个参数最小值为4,如果小于4,会自动矫正为4.所以,setTimeout(() => {}, 0)和setTimeout(() => {}, 4)一样
js存储
通常持久化通过cookie或者storage来实现,但是cookie一般是服务端管理,存储大小只有4k,storage存储大小为5m,indexDB大小为无上限
cookie在每次请求的时候,都会携带在header中,对请求性能可能有轻微的影响
一般要求cookie设置http-only以及same-site,保证js无法操作cookie以及同源请求才携带cookie
浏览器渲染机制
1.处理html并构建Dom树
2.处理css并构建cssOm树
3.将dom树和cssom树合并为渲染树
4.根据渲染树来布局,并计算每个节点的位置
5.调用gpu绘制,合成图层,显示在屏幕上
在构建cssom树的时候会阻塞渲染,直至cssom构建完成。并且构建cssom非常消耗性能,越是具体的选择越消耗,所以应该尽量保证层级扁平,减少过度层叠
当 HTML 解析到 script 标签时,会暂停构建 DOM,完成后才会从暂停的地方重新开始。也就是说,如果你想首屏渲染的越快,就越不应该在首屏就加载 JS 文件。并且 CSS 也会影响 JS 的执行,只有当解析完样式表才会执行 JS,所以也可以认为这种情况下,CSS 也会暂停构建 DOM。
重绘和回流
不会修改页面布局的样式改变会产生重绘,会影响布局调整或者几何属性改变的样式改变会产生回流,回流一定会产生重绘,但是重绘不一定会产生回流。回流性能消耗很大,改变深层次的节点很可能导致父节点的一系列回流。
以下操作会产生回流:
1.改变window大小
2.改变字体
3.添加或删除样式
4.文字改变
5.定位或浮动
6.盒模型
重绘和回流其实和 Event loop 有关
1.当 Event loop 执行完 Microtasks 后,会判断 document 是否需要更新。因为浏览器是 60Hz 的刷新率,每 16ms 才会更新一次。
2.然后判断是否有 resize
或者 scroll
,有的话会去触发事件,所以 resize
和 scroll
事件也是至少 16ms 才会触发一次,并且自带节流功能。
3.判断是否触发了 media query
4.更新动画并且发送事件
5.判断是否有全屏操作事件
6.执行 requestAnimationFrame
回调
7.执行 IntersectionObserver
回调,该方法用于判断元素是否可见,可以用于懒加载上,但是兼容性不好
8.更新界面
9.以上就是一帧中可能会做的事情。如果在一帧中有空闲时间,就会去执行 requestIdleCallback
回调。
如何减少重绘和回流
1.尽量使用tanslate替代修改top等属性
2.使用 visibility
替换 display: none
,因为前者只会引起重绘,后者会引发回流(改变了布局)
3.把 DOM 离线后修改,比如:先把 DOM 给 display:none
(有一次 Reflow),然后你修改 100 次,然后再把它显示出来
4.不要把 DOM 结点的属性值放在一个循环里当成循环里的变量
5.不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局
6.动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用 requestAnimationFrame
7.将频繁运行的动画变为图层,图层能够阻止该节点回流影响别的元素。比如对于 video
标签,浏览器会自动将该节点变为图层。
网络相关
DNS 解析也是需要时间的,可以通过预解析的方式来预先获得域名所对应的 IP。
懒加载的原理就是只加载自定义区域(通常是可视区域,但也可以是即将进入可视区域)内需要加载的东西。对于图片来说,先设置图片标签的 src
属性为一张占位图,将真实的图片资源放入一个自定义属性中,当进入自定义区域时,就将自定义属性替换为 src
属性,这样图片就会去下载资源,实现了图片懒加载。
懒加载不仅可以用于图片,也可以使用在别的资源上。比如进入可视区域才开始播放视频等等。
requestAnimationFrame
requestAnimationFrame采用系统时间间隔,保持最佳绘制效率,不会因为间隔时间过短,造成过度绘制,增加开销;也不会因为间隔时间太长,使用动画卡顿不流畅,让各种网页动画效果能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果
【1】requestAnimationFrame会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率
【2】在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的CPU、GPU和内存使用量
【3】requestAnimationFrame是由浏览器专门为动画提供的API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU开销
requestAnimationFrame的用法与settimeout很相似,只是不需要设置时间间隔而已。requestAnimationFrame使用一个回调函数作为参数,这个回调函数会在浏览器重绘之前调用。它返回一个整数,表示定时器的编号,这个值可以传递给cancelAnimationFrame用于取消这个函数的执行
//控制台输出1和0var timer = requestAnimationFrame(function(){ console.log(0);}); console.log(timer);//1
//控制台什么都不输出var timer = requestAnimationFrame(function(){ console.log(0);}); cancelAnimationFrame(timer);
可以使用requestAnimationFrame来实现页面大量数据的插入,避免一次性插入过多数据导致页面卡顿。
在不止requestAnimationFrame的浏览器环境,可以使用setTimeout来进行优雅降级,一般为16ms