概述
本文对html中的重要内容做一下梳理,主要来源html spec,较为完整的介绍可以参考这篇。
Event
js使用异步、事件驱动的编程模型。 一个完整的事件需包含
- 一个事件对象,即Event
- 一个事件目标对象,即EventTarget,一般是一个dom元素,也可以是window或其他,比如XMLHttpRequest
- 一个事件监听器 eventHandler,是一个回调函数,当监听到事件
- 事件分发动作
事件对象
浏览器中的各种事件都是基于Event class,比如MouseEvent。 除了这些内置的事件,还可以自定义事件,可以直接实例化Event,也可以实例化CustomEvent,后者可以添加自定义数据。
// add an appropriate event listener
obj.addEventListener("cat", function(e) { process(e.detail) });
// create and dispatch the event
var event = new CustomEvent("cat", {
detail: {
hazcheeseburger: true
}
});
obj.dispatchEvent(event);
事件目标对象
EventTarget是一个对象,可以绑定事件处理器和分发事件。
浏览器中的事件目标对象都是EventTarget及其字类,比如常见的Element, Document, and Window,另外还有XMLHttpRequest, AudioNode, AudioContext等。
事件监听器
事件监听器有三种使用方式
- 设置事件处理器属性
el.onclick=(e)=>{}
- html内联事件,内容是字符串
onclick="console.log('Thank you');"
- addEventListener()
三个参数,
第一个是事件类型,第二个是事件处理器函数
第三个是options或useCapture。当是布尔值时指定是否在捕获阶段处理,默认false.当是对象时包含属性
1.capture 布尔值,在捕获阶段处理
2.once 布尔值,是否只触发一次
3.passive 布尔值,如果为true,表明永远不会调用preventDefault(),从而不必等待主线程取消默认事件,直接生成对应帧,chrome等浏览器将文档级别的节点默认设为true
4.signal,是AbortSignal,其abort()方法调用时监听器移除,参考下一章
事件分发动作
一个事件可以是用户的一个行为触发的,比如鼠标单击,也可以是编程触发的,比如HTMLElement.click() ,类似的还有EventTarget.dispatchEvent()
其他细节
事件传播
事件传播包含三个阶段
- capturing
- target
- bubble
注意“focus,” “blur,” and “scroll” 事件不会冒泡,除了整个文档以外的load事件会冒泡到document截至。
在body元素直接添加事件处理器会处理window上的事件,因此冒泡时会在document之后。
事件取消
调用preventDefault()取消默认行为 用stopPropagation()停止传播
MutationObserver
提供了监测dom树变化的能力,当dom树变化会触发相关事件处理器
const targetNode = document.querySelector("#someElement");
const observerOptions = {
childList: true,
attributes: true,
// Omit (or set to false) to observe only changes to the parent node
subtree: true
}
const observer = new MutationObserver(callback);//创建一个观察者,其中包含事件处理器的回调
observer.observe(targetNode, observerOptions);//添加监测
Aborting ongoing activities
用来提供一个api来取消promise等请求,比如可以用在fetch或前面介绍的addEventListener()。
AbortController实例有一个属性,signal,和一个abort()实例方法,比如
var controller = new AbortController();
var signal = controller.signal;
var downloadBtn = document.querySelector('.download');
var abortBtn = document.querySelector('.abort');
downloadBtn.addEventListener('click', fetchVideo);
abortBtn.addEventListener('click', function() {
controller.abort();
console.log('Download aborted');
});
function fetchVideo() {
...
fetch(url, {signal}).then(function(response) {
...
}).catch(function(e) {
reports.textContent = 'Download error: ' + e.message;
})
}
Node及其子类
Node继承自EventTarget,并提供了一系列对node实例增删改查的api,即dom api。
node type
Node.nodeType属性是一个整数,来表示具体的node类型,包括
- Node.ELEMENT_NODE (1) 元素
- Node.ATTRIBUTE_NODE (2) 元素属性
- Node.TEXT_NODE (3) 元素或属性中的文本
- Node.CDATA_SECTION_NODE (4) such as .
- Node.PROCESSING_INSTRUCTION_NODE (7) such as .
- Node.COMMENT_NODE (8) 注释
- Node.DOCUMENT_NODE (9) document节点
- Node.DOCUMENT_TYPE_NODE (10) such as
- Node.DOCUMENT_FRAGMENT_NODE (11) 片段
这些节点的类别我们会关注1和9。
Document
用来表示一个html页面,其中可以通过document.documentElement and document.body分别获取html元素和body元素
还可以用来访问文档级别的很多属性,比如document.domain。
Element
在html文档中的元素都是Element的子类,更准确地说,是HTMLElement的子类,比如每个div元素都是一个HTMLDivElement类的实例,其中可用的方法和属性除了自身的,还有继承自其他类的。
在html中,多数类的实例化需要用提供的工厂函数
document.createElement(tagName[, options])
也有部分元素可以直接new调用,比如
new Image()
//等效于
document.createElement('img')
除了浏览器提供的元素,我们还可以封装自己的元素,即Web Components
元素分类
元素是按照content model分类的,content model定义了元素可以包含什么内容。
- inline inline元素只能包含文本和其他inline元素,在渲染时不需要另取一行,比如span
- block-level,block-level元素可以包含其他block-level元素或inline-element,渲染时需要另起一行,比如div。
另外还有些元素可以作为两种中的任意一种,当作为inline元素时,比如button元素在另一个inline元素内部时,就不能再包含block-level。
因为这些概念容易和css中display属性相关的内容弄混,于是在html5被重新分类。
新版的分类包括七种
- Metadata content 用来描述文档信息,包括
- Flow content body的大部分子元素都是
- Sectioning content
- Heading content
- Phrasing content
- Embedded content 用来嵌套别的内容
- Interactive content 用于交互
这一块我们只关注一下script标签的defer和async属性对于普通script和model的影响。
另外还关注一下crossorigin属性,用来向window.error中传递较少的错误信息。
Web application APIs
web页面一般运行在浏览器中,浏览器中除了根据ecma规范实现的js引擎,还提供了其他api,比如输入输出。
有多种方式可以使js在文档的上下文执行,包括但不限于:
- script元素的处理
- 使用javascript: URLs导航
- Event handlers,比如通过设置元素属性
- svg的script功能
realm
ecma介绍了realm概念,表示script运行的全局环境,每个realm都有一个全局对象(作为[[GlobalObject]]),全局对象和它的属性上挂载者规范定义的东西。
Event loops
为了协调事件、用户交互、scripts、渲染、网络请求等,用户代理一定要使用本节介绍的event loop
event loop不对应于每个线程,比如多个event loop可以对应于一个线程。
一个event loop有一个或多个task queues,即一系列tasks,注意这里task queue并不是queue,而是sets。
tasks封装的算法对应于以下works
- Event
- parsing html parser解析
- callback 回调
- Using a resource 当一个算法fetch一个资源,一旦资源可用就会被task处理
- Reacting to DOM manipulation 响应dom操作,比如插入元素
微任务包括 不同宿主统一的
- promise
html
- queueMicrotask
queueMicrotask(callback)可以将一个回调加入微任务队列,一旦调用栈为空就会调用,类似于setTimeout(f, 0)
当然不要添加太多到微任务队列,可以考虑使用 requestAnimationFrame() or requestIdleCallback()
- MutationObserver
node
- process.nextTick
当一个task完成把控制权交给用户代理进行下一次task前(如果是当前task生成的微任务也会等当前任务执行完再处理),会检查microtasks queue,微任务也会产生微任务添加到微任务队列,直到微任务为空,在这里要先执行完当前的微任务,再去执行新产生的微任务,然后进行必要的渲染,再进行下一次任务。
执行渲染之前会调用requestAnimationCallback,如果这帧还有剩余时间就会执行requestIdleCallback,否则跳过,执行真正渲染。
这节后半部分参考这里
通信
MessageEvent表示一个消息事件,在各种web通信方式中使用。
Server-sent events
Server-sent events可以用来使服务器随时向web页面推送数据。 使用步骤可以分为
- 实例化EventSource,指定接收事件的url
- 添加事件监听 = 服务端发送text/event-stream类型的事件流
具体实现参考sse.js
Web sockets
Web sockets可以用来服务端和客户端相互发送信息
当前规范只规定了客户端的用法,即创建一个WebSocket实例,然后调用对应方法向服务器发送事件,并监听事件,服务端可以搭配ws。
或者直接使用连客户端也封装了的socket.io
Cross-document messaging
这个是指的跨文档通信,比如文档和iframe,或者和一个window.open打开的文档。
这种方式可以实现跨域,案例可参考这里
Channel messaging
Channel messaging是前一种的升级版,通信双方在第一次通信验证安全性后可以建立一个管道。
Broadcasting to other browsing contexts
这是一种广播形式的通信,实现了一对多。
不能跨域
Web workers
web worker提供了main线程以外的其他线程,可以用来处理ui以外的计算(和ui相关的一些浏览器api无法访问)。worker线程和main线程使用Cross-document messaging方法通信。
普通的worker,即Worker的实例,只能被实例化它的脚本使用,这被称为dicated worker。
另外还提供了SharedWorker,可以被同源的多个脚本使用.