整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

这里归纳总结全了:前端开发必须掌握的DOM对象操作

这里归纳总结全了:前端开发必须掌握的DOM对象操作

OM是什么

  • DOM(Document Object Model 文档对象模型)是W3C的标准,是所有浏览器公共遵守的标准。

  • DOM的设计是以对象管理组织(OMG)的规约为基础的,因此可以用于任何编程语言。最初人们把它认为是一种让JavaScript在浏览器间可移植的方法,不过DOM的应用已经远远超出这个范围。

  • DOM定义了访问 HTML 和 XML 文档的标准,是HTML和XML的应用程序接口(API),它允许程序和脚本动态地访问和更新文档的内容、结构和样式。Dom技术使得用户页面可以动态地变化,如可以动态地显示或隐藏一个元素,改变它们的属性,增加一个元素等,Dom技术使得页面的交互性大大地增强。

HTML DOM

当网页被加载时,浏览器会创建页面的文档对象模型(Document Object Model)。HTML DOM 模型被构造为对象的树,这种结构被称为节点树:

通过 HTML DOM,树中的所有节点均可通过 JavaScript 进行访问。所有 HTML 元素(节点)均可被修改,也可以创建或删除节点。

节点父、子和同胞

节点树中的节点彼此拥有层级关系。

父(parent)、子(child)和同胞(sibling)等术语用于描述这些关系。父节点拥有子节点。同级的子节点被称为同胞(兄弟或姐妹)。

  • 在节点树中,顶端节点被称为根(root)

  • 每个节点都有父节点、除了根(它没有父节点)

  • 一个节点可拥有任意数量的子

  • 同胞是拥有相同父节点的节点

下面的图片展示了节点树的一部分,以及节点之间的关系:

编程接口

可通过 JavaScript (以及其他编程语言)对 HTML DOM 进行访问。所有 HTML 元素被定义为对象,而编程接口则是对象方法和对象属性。

HTML DOM Document 对象

当浏览器载入 HTML 文档, 它就会成为 document 对象。document 对象是HTML文档的根节点与所有其他节点(元素节点,文本节点,属性节点, 注释节点)。Document 对象使我们可以从脚本中对 HTML 页面中的所有元素进行访问。提示:Document 对象是 Window 对象的一部分,可通过 window.document 属性对其进行访问。

  • document.activeElement 属性返回文档中当前获得焦点的元素

  • document.addEventListener() 函数用于向文档添加事件句柄

  • document.adoptNode(node) 函数从另外一个文档返回 adapded 节点到当前文档

  • document.anchors 属性集合返回了当前页面的所有超级链接数组

  • document.applets返回对文档中所有 Applet 对象的引用

  • document.baseURI 属性返回 HTML 文档的基础URI

  • document.body 属性返回文档的body元素

  • document.close()用于关闭浏览器窗口

  • document.cookie 属性返回当前文档所有 键/值 对的所有 cookies

  • document.createAttribute() 函数用于创建一个指定名称的属性,并返回Attr 对象属性

  • document.createComment() 函数可创建注释节点

  • document.createDocumentFragment() 函数创建了一虚拟的节点对象,节点对象包含所有属性和方法

  • document.createElement() 函数通过指定名称创建一个元素

  • document.createTextNode() 函数可创建文本节点

  • document.doctype 属性可返回与文档相关的文档类型声明(Document Type Declaration)

  • document.documentElement 属性以一个元素对象返回一个文档的文档元素

  • document.documentMode 属性返回浏览器渲染文档的模式

  • document.documentURI 属性可设置或返回文档的位置

  • document.domain 属性可返回下载当前文档的服务器域

  • document.domConfig 属性 返回normalizeDocument()被调用时所使用的配置

  • document.embeds 集合 返回文档中所有嵌入的内容(embed)集合

  • document.forms 集合返回当前页面所有表单的数组集合

  • document. getElementsByClassName() 函数返回文档中所有指定类名的元素集合,作为 NodeList 对象

  • document.getElementById() 函数可返回对拥有指定 ID 的第一个对象的引用

  • document.getElementsByName() 函数可返回带有指定名称的对象的集合

  • document.getElementsByTagName() 函数可返回带有指定标签名的对象的集合

  • document.images 集合返回当前文档中所有图片的数组

  • document.implementation 属性可返回处理该文档的 DOMImplementation 对象

  • document.importNode() 函数把一个节点从另一个文档复制到该文档以便应用

  • document.inputEncoding 属性可返回文档的编码(在解析时)

  • document.lastModified 属性可返回文档最后被修改的日期和时间

  • document.links 集合返回当期文档所有链接的数组

  • document.normalize() 函数合并相邻的文本节点并删除空的文本节点

  • document.normalizeDocument() 函数可以移除空文本节点 , 并合并相邻节点

  • document.open() 函数打开一个输出流来收集 document.write() 或 document.writeln() 方法输出的内容

  • document.querySelector() 函数返回文档中匹配指定 CSS 选择器的一个元素

  • document.querySelectorAll() 函数 document.querySelectorAll() 是 HTML5中引入的新方法,返回文档中匹配的CSS选择器的所有元素节点列表

  • document.readyState 属性返回当前文档的状态(载入中……)

  • document.referrer 属性返回载入当前文档的来源文档的URL

  • document.removeEventListener() 函数用于移除由 document.addEventListener() 方法添加的事件句柄

  • document.scripts 集合返回页面中所有脚本的集合

  • document.strictErrorChecking 属性可设置或返回是否强制进行错误检查(error-checking)

  • document.title 属性可返回当前文档的标题( HTML title 元素中的文本)

  • document.URL 属性可返回当前文档的 URL

  • document.write() 函数可向文档写入 HTML 表达式或 JavaScript 代码

  • document.writeln() 函数与 write() 方法作用相同,外加可在每个表达式后写一个换行符

HTML DOM 元素 (Element) 对象

在 HTML DOM 中, 元素对象代表着一个 HTML 元素。元素对象 的 子节点可以是, 可以是元素节点,文本节点,注释节点。NodeList 对象 代表了节点列表,类似于 HTML元素的子节点集合。所有主流浏览器都支持元素对象和NodeList 对象。

  • element.accessKey 属性可设置或返回访问单选按钮的快捷键

  • element.addEventListener() 函数用于向指定元素添加事件句柄

  • element.appendChild() 函数http://techbrood.com/jsref?p=met-node-appendchild

  • element.attributes 属性返回指定节点属性的集合

  • element.childNodes 属性返回包含被选节点的子节点的 NodeList

  • element.classlist 属性返回元素的类名,作为 DOMTokenList 对象

  • element.className 属性设置或返回元素的 class 属性

  • element.clientHeight 属性在页面上返回内容的可视高度(不包括边框,边距或滚动条)

  • element.clientWidth 属性在页面上返回内容的可视宽度(不包括边框,边距或滚动条)

  • element.cloneNode() 函数可创建指定的节点的精确拷贝,拷贝所有属性和值,该方法将复制并返回调用它的节点的副本。如果传递给它的参数是 true,它还将递归复制当前节点的所有子孙节点。否则,它只复制当前节点。

  • element.compareDocumentPosition() 函数按照文档顺序,比较当前节点与指定节点的文档位置

  • element.contentEditable 属性用于设置或返回元素的内容是否可编辑

  • element.dir 属性设置或返回元素的文字方向

  • element.firstChild 属性返回被选节点的第一个子节点,如果选定的节点没有子节点则该属性返回NULL

  • element.focus() 函数用于为元素设置焦点(如果可以设置)

  • element.getAttribute() 函数通过名称获取属性的值

  • element.getAttributeNode() 函数从当前元素中通过名称获取属性节点

  • element.getElementsByTagName() 函数可返回带有指定标签名的对象的集合

  • element. getElementsByClassName() 函数返回文档中所有指定类名的元素集合,作为 NodeList 对象

  • element.getFeature() 函数返回指定特征的执行APIs对象

  • element.getUserData() 函数返回一个元素中关联键值的对象

  • element.hasAttribute() 函数通过名称获取属性的值

  • element.hasAttributes() 函数如果某节点有任何属性时返回 true,否则返回 false

  • element.hasChildNodes() 函数可在某节点用于任何子节点时返回 true,否则返回 fals

  • element.hasfocus() 函数返回布尔值,用于检测文档(或文档内的任一元素)是否获取焦点

  • element.id 属性设置或者发回元素的id

  • element.innerHTML 属性设置或返回表格行的开始和结束标签之间的 HTML

  • element.insertBefore() 函数可在已有的子节点前插入一个新的子节点

  • element.isContentEditable 属性返回元素的内容是否可编辑

  • element.isDefaultNamespace() 函数如果指定的命名空间是默认的 ,isDefaultNamespace() 方法返回 true,否则返回 false

  • element.isEqualNode() 函数用于检查两个节点是否相等

  • element.isSameNode() 函数可在某个节点与给定的节点是同一个节点时返回 true,否则返回 false

  • element.isSupported() 函数用于判断当前节点是否支持某个特性

  • element.lang 属性设置或返回元素的语言

  • element.lastChild 属性可返回文档的最后一个子节点

  • element.namespaceURI 属性为被选节点返回命名空间的 URI

  • element.nextSibling 属性可返回某个元素之后紧跟的元素(处于同一树层级中)

  • element.nodeName 属性可依据节点的类型返回其名称

  • element.nodeType 属性属性返回节点类型

  • element.nodeValue 属性根据节点的类型设置或返回节点的值

  • element.normalize() 函数合并相邻的文本节点并删除空的文本节点

  • element.offsetHeight 属性返回任何一个元素的高度包括边框和填充,但不是边距

  • element.offsetWidth 属性返回元素的宽度,包括边框和填充,但不是边距

  • element.offsetLeft 属性返回当前元素的相对水平偏移位置的偏移容器

  • element.offsetParent 属性返回元素的偏移容器

  • element.offsetTop 属性返回当前元素的相对垂直偏移位置的偏移容器

  • element.ownerDocument 属性可返回某元素的根元素

  • element.parentNode 属性可返回某节点的父节点

  • element.previousSibling 属性可返回某节点之前紧跟的节点(处于同一树层级)

  • element.querySelector() 函数返回匹配指定 CSS 选择器元素的第一个子元素

  • document.querySelectorAll() 函数返回匹配指定 CSS 选择器元素的所有子元素节点列表

  • element.removeAttribute() 函数删除指定的属性

  • element.removeAttributeNode() 函数从元素中删除指定的属性节点

  • element.removeChild() 函数可从子节点列表中删除某个节点

  • element.removeEventListener() 函数用于移除由 addEventListener() 方法添加的事件句柄

  • element.replaceChild() 函数可将某个子节点替换为另一个,新节点可以是文本中已存在的,或者是你新创建的

  • element.scrollHeight 属性返回整个元素的高度(包括带滚动条的隐蔽的地方)

  • element.scrollLeft 属性返回当前视图中的实际元素的左边缘和左边缘之间的距离

  • element.scrollTop 属性返回当前视图中的实际元素的顶部边缘和顶部边缘之间的距离

  • element.scrollWidth 属性返回元素的整个宽度(包括带滚动条的隐蔽的地方)

  • element.setAttribute() 函数创建或改变某个新属性,如果指定属性已经存在,则只设置该值

  • element.setAttributeNode() 函数用于添加新的属性节点

  • element.setIdAttribute() 函数

  • element.setIdAttributeNode() 函数

  • element.setUserData() 函数在元素中为指定键值关联对象

  • element.style 属性设置或返回元素的样式属性

  • element.tabIndex 属性可设置或返回单选按钮的 tab 键控制次序

  • element.tagName 属性返回元素的标签名

  • element.textContent 属性设置或者返回指定节点的文本内容

  • element.title 属性设置或返回元素的标题

  • element.toString() 函数 一个元素转换成字符串

  • nodelist.item() 函数返回一个节点列表中指定索引的节点

  • nodelist.length 属性返回节点集合的数目

HTML DOM 属性 (Attr) 对象

在 HTML DOM 中, Attr 对象 代表一个 HTML 属性。所有主流浏览器都支持 Attr 对象和 NamedNodeMap 对象 (在HTML DOM中, NamedNodeMap对象表示一个无顺序的节点列表, 我们可通过节点名称来访问 NamedNodeMap 中的节点)

  • attr.isId 属性如果属性是 ID 类型(例如,包含了其所属的元素的标识符),则 isId 属性返回 true,否则返回 false

  • name 属性返回属性名称

  • value 属性用于设置或者返回属性的值。

  • specified 属性如果在文档中设置了属性值,则specified属性返回 true,如果是 DTD/Schema 中的默认值,则返回 false

  • length 属性可返回集合中节点的选项数目

  • getNamedItem() 函数返回节点列表中指定属性名的值

  • item() 函数可返回节点列表中处于指定索引号的节点

  • removeNamedItem() 函数可删除指定的节点

  • setNamedItem() 函数用于添加指定节点。如果节点已经存在,它将被替换,并返回替换节点的值,否则将返回 null。

HTML DOM 事件 (Event) 对象

事件允许Javascript在HTML文档元素中注册不同事件处理程序。事件通常与函数结合使用,函数不会在事件发生前被执行!

  • 鼠标事件

  • onclick事件会在元素被点击时发生

  • oncontextmenu事件在元素中用户右击鼠标时触发并打开上下文菜单

  • ondblclick事件会在对象被双击时发生

  • onmousedown事件会在鼠标按键被按下时发生。

  • onmouseenter事件在鼠标指针移动到元素上时触发

  • onmouseleave事件在鼠标移除元素时触发

  • onmousemove事件会在鼠标指针移出指定的对象时发生

  • onmouseover事件会在鼠标指针移动到指定的元素上时发生

  • onmouseout事件会在鼠标指针移出指定的对象时发生

  • onmouseup事件会在鼠标按键被松开时发生

  • 键盘事件

  • onkeydown事件会在用户按下一个键盘按键时发生

  • onkeypress事件会在键盘按键被按下并释放一个键时发生

  • onkeyup事件会在键盘按键被松开时发生

  • 框架/对象(Frame/Object)事件

  • onabort事件在用户中止加载 或提交元素时触发

  • onbeforeunload事件在即将离开当前页面(刷新或关闭)时触发

  • onerror事件在加载外部文件(文档或图像)发生错误时触发

  • onhashchange事件在当前 URL 的锚部分(以 '#' 号为开始) 发生改变时触发

  • onload事件会在页面或图像加载完成后立即发生

  • onpageshow事件在用户浏览网页时触发

  • onpagehide事件在用户离开网页时触发

  • onresize事件会在窗口或框架被调整大小时发生

  • onscroll事件在元素滚动条在滚动时触发

  • onunload事件在用户退出页面时发生

  • 表单事件

  • onblur事件会在对象失去焦点时发生

  • onchange事件会在域的内容改变时发生

  • onfocus事件在对象获得焦点时发生

  • onfocusin事件在一个元素即将获得焦点时触发

  • onfocusout事件在元素即将失去焦点时触发

  • oninput事件在用户输入时触发

  • onreset事件在表单被重置后触发

  • onsearch事件在用户按下"ENTER(回车)" 按键或点击 type="search" 的 元素的 "x(搜索)" 按钮时触发

  • onselect事件会在文本框中的文本被选中时发生

  • onsubmit事件在表单提交时触发

  • 剪贴板事件

  • oncopy事件在用户拷贝元素上的内容时触发

  • oncut事件在用户剪切元素的内容时触发

  • onpaste事件在用户向元素中粘贴文本时触发

  • 打印事件

  • onafterprint事件在页面打印后触发,或者打印对话框已经关闭

  • onbeforeprint事件在页面即将打印时触发 (在打印窗口出现前

  • 拖动事件

  • ondrag事件在元素或者选取的文本被拖动时触发

  • ondragend事件在用户完成元素或首选文本的拖动时触发

  • ondragenter事件在拖动的元素或选择的文本进入到有效的放置目标时触发

  • ondragleave事件在可拖动的元素或选取的文本移出放置目标时执触发

  • ondragover事件在可拖动元素或选取的文本正在拖动到放置目标时触发

  • ondragstart事件在用户开始拖动元素或选择的文本时触发

  • ondrop事件在可拖动元素或选取的文本放置在目标区域时触发

  • 多媒体(Media)事件

  • onabort事件在视频/音频(audio/video)终止加载时触

  • oncanplay事件在用户可以开始播放视频/音频(audio/video)时触发。

  • oncanplaythrough事件在视频/音频(audio/video)可以正常播放且无需停顿和缓冲时触发

  • ondurationchange事件在视频/音频(audio/video)的时长发生变化时触发

  • onemptied当期播放列表为空时触发

  • onended事件在视频/音频(audio/video)播放结束时触发

  • onerror事件在视频/音频(audio/video)数据加载期间发生错误时触发

  • onloadeddata事件在当前帧的数据加载完成且还没有足够的数据播放视频/音频(audio/video)的下一帧时触发

  • onloadedmetadata事件在指定视频/音频(audio/video)的元数据加载后触发

  • onloadstart事件在浏览器开始寻找指定视频/音频(audio/video)触发

  • onpause事件在视频/音频(audio/video)暂停时触发

  • onplay事件在视频/音频(audio/video)开始播放时触发

  • onplaying事件在视频/音频(audio/video)暂停或者在缓冲后准备重新开始播放时触发

  • onprogress事件在浏览器下载指定的视频/音频(audio/video)时触发

  • onratechange事件在视频/音频(audio/video)的播放速度发生改变时触发

  • onseeked事件在用户重新定位视频/音频(audio/video)的播放位置后触发

  • onseeking事件在用户开始重新定位视频/音频(audio/video)时触发

  • onstalled事件在浏览器获取媒体数据,但媒体数据不可用时触发

  • onsuspend事件在浏览器读取媒体数据中止时触发

  • ontimeupdate事件在视频/音频(audio/video)当前的播放位置发送改变时触发

  • onvolumechangehttp://techbrood.com/jsref?p=event-onvolumechange

  • onwaiting事件在视频由于要播放下一帧而需要缓冲时触发

  • 动画事件

  • animationend事件在 CSS 动画完成后触发

  • animationiteration事件在 CSS 动画重新播放时触发

  • animationstart事件在 CSS 动画开始播放时触发

  • 过渡事件

  • transitionend事件在 CSS 完成过渡后触发

  • 其他事件

  • onmessage该事件通过或者从对象(WebSocket, Web Worker, Event Source 或者子 frame 或父窗口)接收到消息时触发

  • ononline事件在浏览器开始在线工作时触发

  • onoffline事件在浏览器离线工作时触发

  • onpopstate该事件在窗口的浏览历史(history 对象)发生改变时触发

  • onshow事件当 <menu> 元素在上下文菜单显示时触发

  • onstorage该事件在 Web Storage(HTML 5 Web 存储)更新时触发

  • ontoggle事件在用户打开或关闭 &lgt;details> 元素时触发

  • onwheel事件在鼠标滚轮在元素上下滚动时触发

喽,今天是一篇HTML to PDF速食指南。

Java 转换 HTML 到PDF有许多类库,今天我们介绍一下第三方免费的类库OpenPDF。

1. OpenPDF

OpenPDF是免费的Java类库 ,遵从LGPL 和 MPL协议,所以基本上能够可以随意使用。OpenPDF是基于iTEXT的,目前来说也是维护的比较好的Java操作PDF的开源软件。

话不多说,且看所需要的依赖,

<dependency>    
    <groupId>org.jsoup</groupId>    
    <artifactId>jsoup</artifactId>   
    <version>1.13.1</version> 
</dependency>
<dependency>
    <groupId>com.openhtmltopdf</groupId>
    <artifactId>openhtmltopdf-core</artifactId>
    <version>1.0.6</version>
</dependency>
<dependency>
    <groupId>com.openhtmltopdf</groupId>
    <artifactId>openhtmltopdf-pdfbox</artifactId>
    <version>1.0.6</version>
</dependency>

jsoup可以将html文件转换成输入流等,也可以遍历html的DOM节点,提取元素及样式等。

2. 示例

本篇示例将以下html文件转换成pdf

<html>
<head>
    <style>
        .center_div {
            border: 1px solid #404e94;
            margin-left: auto;
            margin-right: auto;
            background-color: #f6d0ed;
            text-align: left;
            padding: 8px;
        }
        table {
            width: 100%;
            border: 1px solid black;
        }
        th, td {
            border: 1px solid black;
        }
        body,html,input{font-family:"msyh";}
    </style>
</head>
<body>
<div class="center_div">
    <h1>Hello java North!</h1>
    <div>
        <p>convert html to pdf.</p>
    </div>
    <div>
        <table>
            <thead>
                <th>ROLE</th>
                <th>NAME</th>
                <th>TITLE</th>
            </thead>
            <tbody>
                <tr>
                    <td>MARKSMAN</td>
                    <td>ASHE</td>
                    <td>THE FROST ARCHER</td>
                </tr>
                <tr>
                    <td>MAGES</td>
                    <td>ANNIE</td>
                    <td>THE DARK CHILD</td>
                </tr>
                <tr>
                    <td>射手</td>
                    <td>凯塔琳</td>
                    <td>皮城女警</td>
                </tr>
            </tbody>
        </table>
    </div>
</div>
</body>
</html>

以上html用浏览器打开如下,乱码是因为中文字体不识别,下面转换的时候会加载对应的字体来进行转换。

使用Java转换HTML到PDF代码如下:

public class HtmlToPDFOpenSource {
    public static void main(String[] args) throws IOException {
        HtmlToPDFOpenSource htmlToPDFOpenSource=new HtmlToPDFOpenSource();
        htmlToPDFOpenSource.generatePdfByOpenhtmltopdf();
    }

    private  void generatePdfByOpenhtmltopdf() throws IOException {
        File inputHtml=new File("E:\\javaNorth\\java-study-note\\javaOpenSource\\src\\main\\resources\\test.html");

        //加载html文件
        Document document=Jsoup.parse(inputHtml, "UTF-8");
        document.outputSettings().syntax(Document.OutputSettings.Syntax.html);
        
        //引入资源目录,可以单独引入css,图片文件等
        String baseUri=FileSystems.getDefault()
            .getPath("javaOpenSource\\src\\main\\resources")
            .toUri().toString();
       
        try (OutputStream os=new FileOutputStream("javaOpenSource\\src\\main\\resources\\testOpenLeagueoflegends1.pdf")) {
            PdfRendererBuilder builder=new PdfRendererBuilder();
            builder.withUri("javaOpenSource\\src\\main\\resources\\testOpenLeagueoflegends1.pdf");
            builder.toStream(os);
            builder.withW3cDocument(new W3CDom().fromJsoup(document), baseUri);
            
            //引入指定字体,注意字体名需要和css样式中指定的字体名相同
            builder.useFont(new File("javaOpenSource\\src\\main\\resources\\fonts\\msyh.ttf"),"msyh",1,BaseRendererBuilder.FontStyle.NORMAL, true);
            builder.run();
        }
    }
}

使用Java代码转换成PDF如下(示例中使用了微软雅黑中文字体):

上述html文件中增加如下外部样式

<link href="style.css" rel="stylesheet">

并在resources目录下添加style.css文件,重新生成PDF文件如下。

3. 总结

本片介绍了使用OpenPDF将html文件转换成PDF文件。同时也使用了自定义字体,外部样式。但是以下几点需要格外注意。

  • Java代码中加载的字体名称要和HTML引用的CSS样式中的字体名相同 ({font-family:"msyh";})。
  • HTML文件标签节点必须闭合(<xxx></xxx>).否则解析会失败。

全部示例在此:https://github.com/javatechnorth/java-study-note/tree/master/javaOpenSource/src/main/java/pdf

文章来源:Java技术指北

者 | Bruno Couriol

译者 | 马可薇

策划 | 丁晓昀

AngularJS 的创造者Misko Hevery近期宣布了新网络框架Qwik测试版本的推出,声称无论应用程序有多大,Qwik 都能够快速地构建。在多数情况下,Qwik 会先下载 1KB 的 JavaScript,在需要的时候才会懒加载或预处理程序和应用程序代码。


在一次名为《如何从主线程中移除99%的JavaScript》的演讲中,Hevery 介绍了 Qwik 背后的原理。


Qwik 的目标很简单,确保复杂的网站也能在谷歌页面速度评分项上拿到 100/100……归根究底,就是要让互动时间尽可能地缩短。

如你所见,行业中的大多数框架都能在优化图片和 CSS 上做到尽善尽美,但 JavaScript 方面却又乏善可陈。因为这对于互联网上的每个人来说都是系统性的问题,我的意思是说,问题根源在于工具而不是开发者。

用于优化 JavaScript 交付速度的工具是 Qwik 关注的问题。


Misko 将 JavaScript 在互动时间指标上负面的表现归因于水合(Hydration)作用。水合在连接服务器的渲染时出现。服务器接收到客户端对页面的请求后,做出对应查询以填充界面,并将结果返回客户端。虽然对用户来说,服务器端的页面渲染速度通常要比客户端渲染的页面要快(如更快的首次内容绘制),但页面却并不是立即就可交互的,客户端还需要下载并执行页面上的 JavaScript 脚本。


在多数框架中,这种首次交付的 HTML 与应用程序的 JavaScript 协调的过程称作水合。在水合过程中,Web 应用程序框架将事件处理程序和 DOM 元素相连接,并初始化应用程序状态。水合之后用户操作会被事件处理程序捕捉,从而使页面可交互。


Qwik 保留了服务器端的渲染,通过在服务器上运行应用程序以避免水合。它将所有相关状态信息序列化,将页面内容和序列化的状态一起以 HTML 的形式发送给客户端。这些相关的状态信息包括时间监听器、内部数据结构,以及应用状态。借助序列化的状态,客户端可以接力完成服务器端没有执行完的任务。


处理交互性的 JavaScript 加载默认是延迟进行的,一般是直到用户实际使用交互时才启动,也就是说一个按钮的事件处理程序最晚可以在用户点击按钮时加载。这种即时的 JavaScript 获取加上预取策略,利用浏览器的本地能力,在不影响页面交互性的前提下完成了文件的加载。


在 Qwik 文档中有详细的介绍:


Qwik 只会预取当前页面需要的代码,避免下载与静态组件相关的代码。最坏的情况是 Qwik 预取的代码量与现有框架的最佳情况相同,而在大多数情况下,Qwik 所预取的代码只会比现有框架要少。

除主线程之外的其他线程都可以做到代码预取,大多数浏览器甚至支持主线程之外的代码 AST 语法预分析。


如果用户在预取完成之前开始交互,浏览器会自动优先交互模块于其他预取模块。

Qwik 可以将应用程序分解成部分,这些分块可以按照用户交互的概率高低顺序进行下载。


Qwik 网站为开发者提供了教程、实例,以及学习和尝试 Qwik 的在线运行平台。以简单的计数器为例,由一个按钮和显示按钮点击次数的文本框组成,实现方法如下:


import { component$, useStore } from '@builder.io/qwik';


export const App=component$(()=> {
  const store=useStore({ count: 0 });


  return (
    <div>
      <p>Count: {store.count}</p>
      <p>
        <button onClick$={()=> store.count++}>Click</button>
      </p>
    </div>
  );
});



开发者可以通过 Qwik 的component$ API 创建可恢复组件,有状态的组件可以通过useStore API 显示其对某段状态的依赖。在处理程序的名字后附加$ 字符创建可恢复的事件处理程序(如前文例子中的onclick$ )。通过这些手动添加的提示,Qwik 可以将应用程序文件打包,以实现并优化 JavaScript 的懒加载。前文的计数器程序在服务器端渲染的 HTML 如下:


<!DOCTYPE html>
<html
  q:container="paused"
  q:version="0.11.1"
  q:render="ssr"
  q:base="/repl/21kry8ac4hl/build/"
>
  <html>
    <head q:head>
      <title q:head>Tutorial</title>
    </head>
    <body>
      <!--qv q:id=0 q:key=AkbU84a8zes:-->
      <div>
        <p>
          Count:
          <!--t=1-->0<!---->
        </p>
        <p>
          <button
            on:click="app_component_div_p_button_onclick_8dwua0cjar4.js#App_component_div_p_button_onClick_8dWUa0cJAr4[0]"
            q:id="2"
          >
            Click
          </button>
        </p>
      </div>
      <!--/qv-->
    </body>
  </html>
  <script type="qwik/json">
    {"ctx":{"#2":{"r":"0!"}},"objs":[{"count":"1"},0],"subs":[["2 #0 0 #1 data count"]]}
  </script>
  <script id="qwikloader">
    ((e,t)=>{const n="__q_context__",o=window,r=new Set,i=t=>e.querySelectorAll(t),s=(e,t,n=t.type)=>{i("[on"+e+"\\:"+n+"]").forEach((o=>l(o,e,t,n)))},a=(e,t)=>new CustomEvent(e,{detail:t}),c=(t,n)=>(t=t.closest("[q\\:container]"),new URL(n,new URL(t.getAttribute("q:base"),e.baseURI))),l=async(t,o,r,i=r.type)=>{const s="on"+o+":"+i;t.hasAttribute("preventdefault:"+i)&&r.preventDefault();const a=t._qc_,l=null==a?void 0:a.li.filter((e=>e[0]===s));if(l&&l.length>0){for(const e of l)await e[1].getFn([t,r],(()=>t.isConnected))(r,t);return}const d=t.getAttribute(s);if(d)for(const o of d.split("\n")){const i=c(t,o),s=b(i),a=performance.now(),l=u(await import(i.href.split("#")[0]),s),d=e[n];if(t.isConnected)try{e[n]=[t,r,i],f("qsymbol",{symbol:s,element:t,reqTime:a}),await l(r,t)}finally{e[n]=d}}},f=(t,n)=>{e.dispatchEvent(a(t,n))},u=(e,t)=>{if(t in e)return e[t];for(const n of Object.values(e))if("object"==typeof n&&n&&t in n)return n[t]},b=e=>e.hash.replace(/^#?([^?[|]*).*$/,"$1")||"default",d=e=>e.replace(/([A-Z])/g,(e=>"-"+e.toLowerCase())),p=async e=>{let t=d(e.type),n=e.target;for(s("-document",e,t);n&&n.getAttribute;)await l(n,"",e,t),n=e.bubbles&&!0!==e.cancelBubble?n.parentElement:null},v=e=>{s("-window",e,d(e.type))},w=()=>{var n;const s=e.readyState;if(!t&&("interactive"==s||"complete"==s)&&(t=1,f("qinit"),(null!=(n=o.requestIdleCallback)?n:o.setTimeout).bind(o)((()=>f("qidle"))),r.has("qvisible"))){const e=i("[on\\:qvisible]"),t=new IntersectionObserver((e=>{for(const n of e)n.isIntersecting&&(t.unobserve(n.target),l(n.target,"",a("qvisible",n)))}));e.forEach((e=>t.observe(e)))}},q=(e,t,n,o=!1)=>e.addEventListener(t,n,{capture:o}),y=t=>{for(const n of t)r.has(n)||(q(e,n,p,!0),q(o,n,v),r.add(n))};if(!e.qR){const t=o.qwikevents;Array.isArray(t)&&y(t),o.qwikevents={push:(...e)=>y(e)},q(e,"readystatechange",w),w()}})(document);
  </script>
  <script>
    window.qwikevents.push("click")
  </script>
</html>


注意,HTML 文件是通过以下方式强化的。


  • q: 属性,如q:baseq:idq:key
  • 包含特定框架信息的 HTML 注释,如<!--qv q:id=0 q:key=AkbU84a8zes:-->
  • 序列化状态,如<script type="qwik/json"> {"ctx": ..., "objs":[{"count":"1"},0], "subs":[["2 #0 0 #1 data count"]]} </script>
  • 用于在客户端恢复应用程序执行的 Qwik 脚本,如<script id="qwikloader"> ... </script>window.qwikevents.push("click")


Qwik 的在线代码运行平台可以让开发者了解到程序代码是如何被切割打包的,还是用前面的计数器为例,客户端的打包方式如下:



如截图所示,计数器的应用程序被分成了三个脚本。当用户点击按钮时,动态下载并执行其中两个脚本(Qwik 运行时间和 click 事件处理程序的代码)。



参考Qwik文档了解具体执行情况以及代码拆分的原理。Qwik 的网站给出了大量包括教程、示例,以及演示在内的信息,还有一个可互动的在线代码运行平台。Qwik 社区中同样也有一个非常简单的电子商务示例,一般对电子商务的厂商来说,页面加载速度提高收入也会增加。


Qwik 团队目前由 AngularJS 的创造者 Mi?ko Hevery、基于 Go 语言 Web 架构 Gin 的创造者 Manu Almeida、Web组件编译器Stencil的创造者 Adam Bradley 组成。


目前,Qwik 已推出测试版,且采用 MIT 许可开源,欢迎各位在遵循 Qwik行为准则的前提下贡献代码。


原文链接:

New Qwik JavaScript Framework Seeks Faster Web Apps with Unique Approach: Resumability