事件触发一般来说会按照上面的顺序进行,但是也有特例,如果给一个目标节点同时注册冒泡和捕获事件,事件触发会按照注册的顺序执行
// 以下会先打印冒泡然后是捕获
node.addEventListener('click',(event) =>{
console.log('冒泡')
},false);
node.addEventListener('click',(event) =>{
console.log('捕获 ')
},true)
addEventListener
注册事件,该函数的第三个参数可以是布尔值,也可以是对象。对于布尔值 useCapture
参数来说,该参数默认值为 false
。useCapture
决定了注册的事件是捕获事件还是冒泡事件stopPropagation
来阻止事件的进一步传播。通常我们认为 stopPropagation
是用来阻止事件冒泡的,其实该函数也可以阻止捕获事件。stopImmediatePropagation
同样也能实现阻止事件,但是还能阻止该事件目标执行别的注册事件node.addEventListener('click',(event) =>{
event.stopImmediatePropagation()
console.log('冒泡')
},false);
// 点击 node 只会执行上面的函数,该函数不会执行
node.addEventListener('click',(event) => {
console.log('捕获 ')
},true)
如果一个节点中的子节点是动态生成的,那么子节点需要注册事件的话应该注册在父节点上
<ul id="ul">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<script>
let ul = document.querySelector('##ul')
ul.addEventListener('click', (event) => {
console.log(event.target);
})
</script>
事件代理的方式相对于直接给目标注册事件来说,有以下优点
因为浏览器出于安全考虑,有同源策略。也就是说,如果协议、域名或者端口有一个不同就是跨域,Ajax 请求会失败
JSONP 的原理很简单,就是利用 <script> 标签没有跨域限制的漏洞。通过 <script> 标签指向一个需要访问的地址并提供一个回调函数来接收数据当需要通讯时
<script src="http://domain/api?param1=a¶m2=b&callback=jsonp"></script>
<script>
function jsonp(data) {
console.log(data)
}
</script>
CORS
需要浏览器和后端同时支持CORS
通信,实现CORS通信的关键是后端。只要后端实现了 CORS
,就实现了跨域。Access-Control-Allow-Origin
就可以开启 CORS
。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源a.test.com
和 b.test.com
适用于该方式。document.domain = 'test.com'
表示二级域名都相同就可以实现跨域这种方式通常用于获取嵌入页面中的第三方页面数据。一个页面发送消息,另一个页面判断来源并接收消息
// 发送消息端
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('验证通过')
}
});
众所周知 JS 是门非阻塞单线程语言,因为在最初 JS 就是为了和浏览器交互而诞生的。如果 JS 是门多线程的语言话,我们在多个线程中处理 DOM 就可能会发生问题(一个线程中新加节点,另一个线程中删除节点)
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
console.log('script end');
不同的任务源会被分配到不同的
Task
队列中,任务源可以分为 微任务(microtask
) 和 宏任务(macrotask
)。在ES6
规范中,microtask
称为 jobs,macrotask 称为 task
console.log('script start');
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 => Promise => script end => promise1 => promise2 => setTimeout
以上代码虽然
setTimeout
写在Promise
之前,但是因为Promise
属于微任务而setTimeout
属于宏任务
微任务
process.nextTick
promise
Object.observe
MutationObserver
宏任务
script
setTimeout
setInterval
setImmediate
I/O
UI rendering
宏任务中包括了 script ,浏览器会先执行一个宏任务,接下来有异步代码的话就先执行微任务
所以正确的一次 Event loop 顺序是这样的
Event loop
,执行宏任务中的异步代码通过上述的
Event loop
顺序可知,如果宏任务中的异步代码有大量的计算并且需要操作DOM
的话,为了更快的响应界面响应,我们可以把操作DOM
放入微任务中
Node
中的 Event loop
和浏览器中的不相同。Node
的 Event loop
分为6
个阶段,它们会按照顺序反复运行┌───────────────────────┐
┌─>│ timers │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ I/O callbacks │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘ ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll │<──connections─── │
│ └──────────┬────────────┘ │ data, etc. │
│ ┌──────────┴────────────┐ └───────────────┘
│ │ check │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└──┤ close callbacks │
└───────────────────────┘
timer
timers
阶段会执行 setTimeout
和 setInterval
I/O
I/O
阶段会执行除了 close
事件,定时器和 setImmediate
的回调idle, prepare idle, prepare 阶段内部实现
poll
poll
阶段很重要,这一阶段中,系统会做两件事情
poll
队列中的事件并且当 poll 中没有定时器的情况下,会发现以下两件事情
setImmediate
需要执行,poll
阶段会停止并且进入到 check
阶段执行 setImmediate
setImmediate
需要执行,会等待回调被加入到队列中并立即执行回调timer
阶段执行回调。check
check
阶段执行 setImmediate
close callbacks
close callbacks
阶段执行 close
事件Node
中,有些情况下的定时器执行顺序是随机的setTimeout(() => {
console.log('setTimeout');
}, 0);
setImmediate(() => {
console.log('setImmediate');
})
// 这里可能会输出 setTimeout,setImmediate
// 可能也会相反的输出,这取决于性能
// 因为可能进入 event loop 用了不到 1 毫秒,这时候会执行 setImmediate
// 否则会执行 setTimeout
上面介绍的都是 macrotask 的执行情况,microtask 会在以上每个阶段完成后立即执行
setTimeout(()=>{
console.log('timer1')
Promise.resolve().then(function() {
console.log('promise1')
})
}, 0)
setTimeout(()=>{
console.log('timer2')
Promise.resolve().then(function() {
console.log('promise2')
})
}, 0)
// 以上代码在浏览器和 node 中打印情况是不同的
// 浏览器中一定打印 timer1, promise1, timer2, promise2
// node 中可能打印 timer1, timer2, promise1, promise2
// 也可能打印 timer1, promise1, timer2, promise2
Node
中的process.nextTick
会先于其他microtask
执行
setTimeout(() => {
console.log("timer1");
Promise.resolve().then(function() {
console.log("promise1");
});
}, 0);
process.nextTick(() => {
console.log("nextTick");
});
// nextTick, timer1, promise1
Service workers 本质上充当Web应用程序与浏览器之间的代理服务器,也可以在网络可用时作为浏览器和网络间的代理。它们旨在(除其他之外)使得能够创建有效的离线体验,拦截网络请求并基于网络是否可用以及更新的资源是否驻留在服务器上来采取适当的动作。他们还允许访问推送通知和后台同步API
目前该技术通常用来做缓存文件,提高首屏速度
// index.js
if (navigator.serviceWorker) {
navigator.serviceWorker
.register("sw.js")
.then(function(registration) {
console.log("service worker 注册成功");
})
.catch(function(err) {
console.log("servcie worker 注册失败");
});
}
// sw.js
// 监听 `install` 事件,回调中缓存所需文件
self.addEventListener("install", e => {
e.waitUntil(
caches.open("my-cache").then(function(cache) {
return cache.addAll(["./index.html", "./index.js"]);
})
);
});
// 拦截所有请求事件
// 如果缓存中已经有请求的数据就直接用缓存,否则去请求数据
self.addEventListener("fetch", e => {
e.respondWith(
caches.match(e.request).then(function(response) {
if (response) {
return response;
}
console.log("fetch source");
})
);
});
打开页面,可以在开发者工具中的 Application 看到 Service Worker 已经启动了
在 Cache 中也可以发现我们所需的文件已被缓存
当我们重新刷新页面可以发现我们缓存的数据是从 Service Worker 中读取的
浏览器的渲染机制一般分为以下几个步骤
HTML
并构建 DOM
树。CSS
构建 CSSOM
树。DOM
与 CSSOM
合并成一个渲染树。GPU
绘制,合成图层,显示在屏幕上一般来说,可以把普通文档流看成一个图层。特定的属性可以生成一个新的图层。不同的图层渲染互不影响,所以对于某些频繁需要渲染的建议单独生成一个新图层,提高性能。但也不能生成过多的图层,会引起反作用
通过以下几个常用属性可以生成新图层
translate3d
、translateZ
will-change
video
、iframe
标签opacity
动画转换position: fixed
回流必定会发生重绘,重绘不一定会引发回流。回流所需的成本比重绘高的多,改变深层次的节点很可能导致父节点的一系列回流
所以以下几个动作可能会导致性能问题:
很多人不知道的是,重绘和回流其实和 Event loop 有关
Microtasks
后,会判断 document
是否需要更新。因为浏览器是 60Hz
的刷新率,每 16ms
才会更新一次。resize
或者 scroll
,有的话会去触发事件,所以 resize
和 scroll
事件也是至少 16ms
才会触发一次,并且自带节流功能。 media query
requestAnimationFrame
回调IntersectionObserver
回调,该方法用于判断元素是否可见,可以用于懒加载上,但是兼容性不好requestIdleCallback
回调减少重绘和回流
translate
替代 top
visibility
替换 display: none
,因为前者只会引起重绘,后者会引发回流(改变了布局)table
布局,可能很小的一个小改动会造成整个 table 的重新布局requestAnimationFrame
CSS
选择符从右往左匹配查找,避免 DOM
深度过深video
标签,浏览器会自动将该节点变为图层此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。