《用户体验要素:以用户为中心的产品设计(原书第2版)》笔记
核心:转换率(conversion rate)
通过跟踪有百分之多少的用户被你“转化”到了下一个步骤,就能衡量你的网站在达到“商业目的”方面的效率有多高。
任何在用户体验上所做的努力,目的都是为了提高效率。这基本上是以两种主要形式体现出来的:“帮助人们工作得更快”和“减少他们犯错的几率”。
5 Plans
图片源于作者博客,根据《用户体验要素:以用户为中心的产品设计(原书第2版)》添加说明。
通过跟踪有百分之多少的用户被你“转化”到了下一个步骤,就能衡量你的网站在达到“商业目的”方面的效率有多高。
任何在用户体验上所做的努力,目的都是为了提高效率。这基本上是以两种主要形式体现出来的:“帮助人们工作得更快”和“减少他们犯错的几率”。
图片源于作者博客,根据《用户体验要素:以用户为中心的产品设计(原书第2版)》添加说明。
根据课程《人机交互与用户体验设计》确定,每个部分后面数字表示在教材《人机交互:软件工程视角》豆瓣链接中对应的章节。
什么是 HCI
“HCI is a discipline concerned with the design, evaluation and implementation of interactive computing systems for human use and with the study of major phenomena surrounding them.” — ACM SIGCHI
什么是用户体验
“User experience encompasses all aspects of the end-user’s interaction with the company, its services, and its products.”—— Donald Norman
💡不能够设计用户体验,只能为用户体验而设计
用户体验设计
用户体验 UX vs. 人机交互 HCI
User Experience | HCI |
---|---|
* 广泛的用户基础 * 受业界青睐 * 常见于产品设计、工业设计领域 |
* 较为学术化 * 常见于计算机专业研究员 * 如今也包含体验的诸多方面 |
交互设计 IxD
包括:
💡 孤立地从一个学科出发永远不可能设计出一个具有良好用户体验的交互式系统
💡经常和可用性交替使用,但是两者的关注点有明显的不同
~ 可用性和可用性工程关注的是与任务相关的方面(即完成工作)
~ 用户体验与体验设计则将用户的感觉、心情、价值观等放在首位
⚠️ 注意
- 要直接与潜在用户进行接触,不要满足于间接的接触和道听途说
- “你”不是用户
- 不要“闭门造车”
⚠️ 注意:过分依赖用户或者怀疑用户都不会带来最好的设计
⚠️ 注意:要特别关注使角色之间彼此相区别的那些特征
⚠️ 注意
- 问题和前景综述直接从研究和用户模型中获得
- 用户目标和需求应该从焦点和次要人物角色中推导出
- 商业目标应该从利益相关人的访谈中获得
删除:(最明显的简化设计方法)删除不必要的
删除什么?:
⚠️ 注意
- 问题和前景综述直接从研究和用户模型中获得
- 用户目标和需求应该从焦点和次要人物角色中推导出
- 商业目标应该从利益相关人的访谈中获得
组织:组织要提供的
按照有意义的标准对界面组件进行分组:最快捷的简化设计方式
⚠️ 注意:千万不要被自己规划图中清晰的线条和整洁的布局所迷惑
转移:各司其职
💡 交互设计模式“是从过去已证明成功的实例中学习的”,是“获得以及重新应用已有知识的一种方案”。 ——Alan Dix
⚠️ 注意:
- 尽早测试、经常测试
- 快速但存在瑕疵的测试好过什么都没有做的测试
评估范型(Evaluation Paradigm)
在使用具体的评估方法前通常需要先考虑最适宜的评估范型,同时需要注意到每种范型都有自身的长处和短处,且并不一定适用于所有类型的系统。
分类
评估范型 | 快速评估 | 可用性测试 | 现场研究 | 预测性评估 |
---|---|---|---|---|
用户角色 | 自然行为 | 执行测试任务集 | 自然行为 | 通常不参与 |
控制权 | 评估人员实施最低限度控制 | 评估人员密切控制 | 评估人员与用户合作 | 评估人员为专家 |
评估地点 | 自然工作环境或实验室 | 实验室 | 自然工作环境 | 类似实验室的环境,通常在客户处进行 |
适用情形 | 快速了解设计反馈。可使用其他交互范型的技术,如启发式评估 | 测试原型或产品 | 常用于设计初期,以检查设计是否满足用户需要 ,发现问题,发掘应用契机。 | 使用启发式评估的原型测试,可在任何阶段进行;模型可用于评估潜在设计的特定方面 |
数据类型 | 通常是定性的非正式描述 | 定量数据,有时是统计数据。可采用问卷调查或访谈搜集用户意见 | 应用草图、场景、例证等的定性描述 | 专家们列出问题清单,由模型导出量化数据(如两种设计的任务执行时间) |
反馈到设计 | 通过草图、例证、报告 | 通过性能评估、错误统汁报告等为未来版本提供设计标准 | 通过描述性的例证、草图、场景和工作日志 | 专家列出一组问题,通常附带解决方案建议。为设计人员提供根据模型计算出的时间值 |
基本思想 | 以用户为中心,非常实用 | 基于试验的实用方法,即可用性工程 | 可以是客观观察或现场研究 | 专家检查以实用的启发式原则和实践经验为基础,采用基于理论的分析模型 |
评估技术
常用组合
尤其适用于客观上较难度量的与用户主观满意度和可能的忧虑心情相关的问题。
问卷与访谈
问卷调查 | 搜集大量用户的意见从中找出普遍性的意见 |
---|---|
访谈 | 方便、快捷,若用户没有时间完成问卷,就可以考虑使用访谈方法 |
在设计问卷的过程应考虑
为了减轻回答问题人员的负担鼓励用户提高回答率,应尽可能应用有限的问题
注意问题的先后顺序
对征求用户意见的问题
访谈
数据处理
焦点小组是集体访谈的一种形式
理想情况下
实际过程中
询问专家
启发式评估是分析界面可用性的一种相对非正式的方法
具体方法
步骤
阶段 | 步骤 |
---|---|
准备(项目指导) | a)确定可用性准则。 |
b)确定由3~5个可用性专家组成的评估组。 | |
c) 计划地点、日期和每个可用性专家评估的时间。 | |
d) 准备或收集材料,让评估者熟悉系统的目标和用户。将用户分析、系统规格说明、用户任务和用例情景等材料分发给评估者。 | |
e)设定评估和记录的策略。是基于个人还是小组来评估系统?指派一个共同的记录员还是每个人自己记录? | |
评估(评估者活动) | a) 尝试并建立对系统概况的感知。 |
b)温习提供的材料以熟悉系统设计。按评估者认为完成用户任务时所需的操作进行实际操作。 | |
c)发现并列出系统中违背可用性原则之处。列出评估注意到的所有问题,包括可能重复之处。确保已清楚地描述发现了什么?在何处发现? | |
结果分析(组内活动) | a) 回顾每个评估者记录的每个问题。确保每个问题能让所有评估者理解。 |
b)建立一个亲和图(又称KI法“或A型图解法),把相似的问题分组。 | |
c) 根据定义的准则评估并判定每个问题。 | |
d)基于对用户的影响,判断每组问题的严重程度。 | |
e)确定解决问题的建议,确保每个建议基于评估准则和设计原则。 | |
报告汇总 | a) 汇总评估组会议的结果。每个问题有一个严重性等级,可用性观点的解释和修改建议。 |
b)用一个容易阅读和理解的报告格式,列出所有出处、目标、技术、过程和发现。评估者可根据评估原则来组织发现的问题。一定要记录系统或界面的正面特性。 | |
c)确保报告包括了向项目组指导反馈的机制,以了解开发团队是如何使用这些信息的。 | |
d) 让项目组的另一个成员审查报告,并由项目领导审定。 |
关于评估专家的人数
启发式评估的优点
启发式评估的缺点
目标/任务 — > 子目标/子任务
采用层次图结构或表格文本格式表示
每个目标以及子目标都有一个描述如何实现该目标的计划
细分的终止规则
不能细分的任务
用途
⚠️ 注意:
- 层次化任务分析非常耗时,且不完善
- 会给出一个有用的、有代表性的描述
- 但没有说明如何去收集那些我们希望表达的任务信息
优点
缺点
~ CS2023
全球范围内最权威、最系统的计算机科学本科教育指南,也是企业招聘和人才标准的“背后模型”。参考此文件的“Human-Computer Interaction (HCI)”进行查缺补漏。
~ Recommended Reading List for First-year HCI Ph.D. students
香港城市大学(CityU)教授写给博士生的入门清单,不仅有 HCI 相关书籍、文章还有更基础的如何阅读、写作论文的建议。
~ 想加入老师的课题组,有没有相关的阅读清单推荐?
北大教授写给对 HCI 方向感兴趣的学生的阅读列表,包括书籍、文章、研究方法等。
~ What books are required reading for HCI students and practitioners?
~ 人机交互与用户体验设计
MOOC 上由南京大学软件学院出品的基础课程。 教材 笔记
~ Interaction Design
coursera 一门经典课程
~ 《人机交互:软件工程视角》 豆瓣链接
~ 总体
~ Xiaojuan Ma
这位老师在教学的第一线,有相关课程材料可作参考。
~ Jesse James Garrett
~ jjg.net
“Ajax 之父”博客,后一个为老版本
~ Edward Tufte
Edward Tufte 数据可视化
~ Degree Programs
Reddit 全球关于 HCI 项目,绝大多数为硕士课程。
~ CSRankings: Computer Science Rankings
根据发 paper 对学校进行排名。不仅有 HCI 方向,也包括 CS 其他研究方向。
动态语言是有用的工具。脚本允许用户快速高效的将复杂的系统连接在一起,并且在不需要关心诸如内存管理或者构建系统等细节中表达想法。最近几年,像 Rust 以及 Go 这样的语言,使机器代码更易生成;这些项目使得计算机基础架构获得了极大的发展。然而,我们认为拥有一个强大的脚本环境来处理各种问题依旧重要。
JavaScript 是最广泛使用的动态语言,运行于各种浏览器中。大量程序员精通 JavaScript,并且已经在优化其执行方面投入了大量精力。通过如 ECMA 这样的组织的建立,JavaScript 得到了谨慎并且持续的进步。我们相信 JavaScript 是动态语言工具的必然选择;无论是在浏览器环境还是作为独立进程。
我们是这块实践的先行者,Node.js 被证明是非常成功的平台。Node.js 足以构建 Web 开发工具、创建独立进程以及无数其他案例。在 2009 设计 Node 时,JavaScript 是截然不同的。出于必要,Node 发明了一些概念,这些概念随后由标准组织采用,并以不同的方式添加到语言中。在 《Node 的设计问题》 (译者注:安利这个演讲,一定要看哦。)演讲中,提供了更多细节。由于 Node 已经有大量的使用,使得这一系统发展变得困难并且缓慢。
随着 JavaScript 的更新,以及 TypeScript 的加入,构建 Node 项目可能会变得很艰难,涉及到管理构建系统和其他繁重的工具,这使动态语言丧失了乐趣。除此之外,通过 NPM 存储库从根本上集中了链接到外部库的机制,与 web 的理念并相冲突。
我们认为 JavaScript 和相关的软件基础架构已经发生了足够的变化,值得进行简化。我们寻求一种有趣且高效的脚本环境,可用于多种任务。
Deno 是一种在 web 浏览器之外用于执行 JavaScript 和 TypeScript 的全新运行时(runtime)。
Deno 试图提供一个独立的工具来快速编写复杂功能的脚本。Deno 是(并且永远是)一个单一的可执行文件。正如 web 浏览器,它知道如何获取外部代码。在 Deno 中,单个文件无需任何其他工具即可定义任意复杂的行为。
1 | import { serve } from "https://deno.land/std@0.50.0/http/server.ts"; |
在这里,只需一行代码,一个完整的 HTTP 服务器作为一个模块即添加完成。不需要额外的配置文件,也不要提前安装,只需要:
1 | deno run example.js |
与浏览器一致,代码默认运行于安全的沙盒中。代码无法访问硬件、打开网络连接或者是在没有许可的情况下进行任何潜在的恶意操作。浏览器提供了访问摄像头和麦克风的 API,但前提是通过用户的认证。Deno 在终端中提供类似的行为。上面的示例在没有标明 –allow-net 下会失败。
Deno 小心的不要偏离标准的浏览器 JavaScript API。当然,并不是每个浏览器 API 都与 Deno 相关,但无论它们在哪里,Deno 都不会偏离标准。
我们希望 Deno 适用于广泛的问题领域:从简单的一行代码,到复杂的服务端商业逻辑。随着程序变得越来越复杂,有一定的类型检测变得越来越重要。TypeScript 是 JavaScript 语言的扩展,允许用户选择性地提供类型信息。
Deno 在不需要任何外部工具下支持 TypeScript,内置 TypeScript 是其设计理念。deno types 命令是 Deno 提供的所有内容提供类型声明。Deno 的所有标准模块由 TypeScript 编写而成。
在 javascript 之前 Node 设计时就引入了 Promises 或者是 async / await 的概念。Node 的 Promises 对应的是 EventEmitter,所有重要的 API 都基于这个,比如 sockets 和 HTTP。抛去对编程时使用 async / await 的好处来看,EventEmitter 存在背压(back-pressure)问题。拿 TCP 套接字(socket)来举例,套接字在接收到传入数据包时将发出“ 数据(data) ”事件。这些“ 数据 ”的回调将以不受限制的方式发出,从而使事件(events)充满整个过程。由于 Node 会持续接受数据事件,基础 TCP 套接字没有适当的背压,远程发送端并不知道服务端已经过载而继续发送数据。为了降低此风险,添加了 pause() 方法。解决了这个问题,但需要额外代码。由于只有在进程非常忙碌的时候,这个问题才会出现,导致 Node 程序充满了数据。结果是系统严重的尾延迟(tail latency)。
在 Deno 中,套接字依旧是异步的,但在接受新数据时,需要用户明确使用 read()。正确的构造接收套接字而不需要额外的暂停语义。这不是 TCP 套接字所独有的。系统的最底级别绑定层从根本上与 Promise 相关联,我们称之为“ ops ”。Deno 中所有的绑定是 Promises 的一种实现或提升。
Rust 有自己类似于 Promise 的抽象,称之为 Futures。通过 “ op ” 抽象,Deno 使基于 future 的 Rust API 巧妙的与 JavaScript 的 Promises 绑定在一起。
我们提供的主要组件是 Deno 命令行界面(CLI)。今天(5 月 13日)CLI 正式发布了 1.0 版本。 Deno 并不是一个单一的程序,而是设计为 Rust 的集合,允许在不同层级的集成。
deno_core 是 Deno 的基础,它不依赖 TypeScript 和 Tokio。它简单的提供了 Op 和 Resource 模块。deno_core 提供了绑定 Rust futures 和 JavaScript promises 的组织方式。因此 CLI 完全建立在 deno_core 之上。
rusty_v8 为 Rust 和 V8 的 C++ API 提供了高质量的绑定。这个 API 试着还原所有原先 C++ API。这是零成本的绑定,Rust 暴露出的对象正是 C++ 的操作的对象(举个🌰,之前 Rust V8 的绑定尝试强制使用持久句柄(handles))。这个模块提供了建立于 Github Actions CI 之上的二进制代码,同样也允许用户从头编译 V8 并调整它的构建配置。所有 V8 源代码均在此模块进行分发。最后,rusty_v8 试着成为一个安全的接口。现在还没有 100% 完成,但我们在逐步接近于此。能够以一种安全的方式与像 V8 这样复杂的 VM 交互是非常令人惊奇的,这让我们发现 Deno 本身存在许多困难的bug。
我们承诺在 Deno 中维护一个稳定的 API。Deno 有大量的接口和模块,所以对我们来说的”稳定”保持透明是很重要的。我们创建用于与操作系统交互的 JavaScript API 都可以在“ Deno ”命名空间中找到(例如 Deno.open())。这些已经被仔细检查过了,我们定会对它们做向后兼容的更改。
在使用 –unstable 命令行标志之前,所有还没有稳定的方法将会被隐藏。你可以在这查看没有稳定的接口。在接下来的时间中,其中的 API 也会逐渐稳定下来。
在全局命名空间中,你可以发现各种其他对象(例如 setTimeout() 和 fetch())。我们努力保持这些接口和浏览器中一致,但是如果发现意外的不兼容性,我们将进行更正。浏览器标准定义了自身的接口,而不是我们。任意错误的更正只会是修复问题,而不是更新接口。如果与浏览器的标准 API 不兼容,可能会在大版本发布之前进行修复。
Deno 还有很多 Rust API,比如 deno_core 和 rusty_v8 模块。这些 API 都还没有达到1.0。我们会继续对它们进行迭代。
需要了解的是 Deno 并不是 Node 的一个翻版,它是一种全新的实现。Deno 只是开发了两年,而开发 Node 已经超过十年。基于到对 Deno 的兴趣,我们希望它能继续发展和成熟。
对于一些应用来说,Deno 是一种好的选择,对于其他的则不是。这取决于需求。我们想说明 Deno 的局限性来帮助人们选择 Deno 。
不幸的是,令人沮丧是许多用户会发现与现有 JavaScript 工具缺乏兼容性。Deno 通常与 Node(npm)包不兼容。初步建立在 https://deno.land/std/node/,但还远远没有完善。尽管 Deno 采取了强硬的方法来简化模块系统,最终 Deno 和 Node 是有着相似的目标和相似的系统。随着时间的推移,我们希望 Deno 可以开箱即用的运行比 Node 更多的系统。
我们持续追踪 Deno HTTP 服务器的性能。每秒 25000 次使用 Deno HTTP 服务器请求 hello-world 最多延迟 1.3 毫秒。类似的 Node 程序每秒请求 34000 次,而最大的延迟在 2 ~ 300 毫秒之间。
Deno 的 HTTP 服务器是在原生 TCP 套接字之上的 TypeScript 中实现的。Node 的 HTTP 服务器是由 C 语言编写的,并暴露给 JavaScript 的高级绑定。我们阻止了将本地 HTTP 服务器绑定到 Deno 的冲动,因为我们想优化 TCP 套接字层,以及更普遍的 op 接口。
Deno 是适当的异步服务器,能应付每秒 25000 次的请求,可以满足大多数的需求。(如果不满足,JavaScript 可能并不是最好的选择。)不仅如此,我们预计,由于普遍使用了Promise (之前已经讨论过了),Deno 在尾延迟方面表现得更好。综上所述,我们的确相信该系统还有更多的性能优势,希望在将来的版本中实现这一目标。
Deno 在内部使用微软的 TypeScript 编译器来进行类型检测和生成 JavaScript 。与使用 V8 直接编译 JavaScript 相比,这样做非常缓慢。之前,我们想使用“ V8 快照(V8 Snapshots)”来大幅度提升性能。快照当然有帮助,但它仍然很慢。我们认为可以在现有 TypeScript 编译器的基础上进行一些改进,但是很明显,类型检查最终需要在Rust中实现。这是个艰巨的任务,可能在短时间内不会发生;但它可以让开发人员在经历关键路径中提供数量级上的性能改进。TSC 需要移植到 Rust上。如果你对这个问题感兴趣,请联系我们。
我们有一个使用自定义 ops 来对 Deno 运行时进行初步扩展的插件系统。这个接口还在开发中,已经标记为非稳定版。因此,访问 Deno 提供的系统以外的本地系统是很困难。
Deno v1.0.0 计划于 5 月 13 号发布。这里有一些有趣的事实,可能对确定 Deno 起作用。
确定的是,(显然)太早了来讨论这个事情,但这些事实可能可以确定 Deno。
对于初学者来说,Deno 由创建过 Node.js 的 Ryan Dahl 编写,是不是听起来很熟?这是不是意味着 Deno 是 Node 的替代方案并且可以计划我们对重构进行冲刺了?当然不是!但如果你想了解更多,请继续读下去!
在 2018 年,Ryan 讲了他认为做 Node.js 时犯的错误的前十名,并且在演讲的最后,他揭示了 Deno,在那个时候,这只是一个可以称为 Node.js 第二的小项目,提供了更多的安全方案。
两年后,Deno 1.0 官方发布日期确定了:5 月 13 号。为后端开发准备的全新的 JavasSript 运行时,使用 Rust 而不是 C++ 编写而成,基于 Tokio 平台(为 JavaScript 提供必备的异步运行时),依旧运行于 Google V8 引擎之上。
我们讨论的不仅仅是与当前的 Node.js 完全兼容的新的 JavaScript 运行时,取而代之的是,Ryan 为 Deno 添加了在创建 Node.js 时没有考虑到的特性。
一般来说,Node.js 允许你具有访问所有内容,意思是你可以在文件系统中进行读写操作,向外发布请求,访问环境变量等等。尽管对于开发者来说,获取这些权限是有益的,但代码可能不小心留下安全隐患。
取而代之的是,Deno 使用命令行参数用于是否开启不同的安全措施。比如你的代码需要访问 /etc 目录,则需要:
1 | deno --allow-read=/etc myscript.ts |
这允许代码读取该目录,除此之外会报安全错误。这与其他平台的安全措施类似。如果你是安卓用户,许多应用在使用时会向你询问是否能获取系统权限(比如联系人,通行记录,文件夹等),同样的原理也应用于此。通过使用这些选项来执行你的代码,提供了代码所需的权限。
自从第一个 Node 版本开始,JavaScript 改进了其标准库,但与其他语言相比,它还有很长的路要走。Deno 试图为开发者提供完善标准库用于完成基础任务,而使用外部库(也就是 NPM)用于更复杂的任务。
实质上,开箱即用的是,Deno 为终端文字增加了颜色,使用外部数据结构(比如 binary, csv, yaml 等),生成 UUIDs 甚至是编写 websockets。还有些其他的,更多可使用的基础模块,比如访问文件系统,日期助手(date helper)方法,http 关联方法以及其他库。
你没看错,如果你是 TypeScript 粉丝,那么 Deno 帮你解决了,而不需要额外工具,默认在内部完成编译为 JavaScript 。
尽管在默认情况下,Deno 会解决许多问题,你可以使用 tsconfig.json 进行设定:
1 | deno run -c tsconfig.json [your-script.ts] |
默认情况下使用严格模式,因此,任何不良的编码将立即收到警告。
这是一个问题,因为每个包和其父类的都有依赖。这是不是显得太臃肿了?这是分发依赖关系的错误方式吗?这无疑是Node 最具争议的方面之一,而 Deno 确定彻底摆脱这种方式?
所以 Deno 是如何处理依赖的?自此,允许你可从任意地方引用。换句话说,你可以这样做:
1 | import * as log from "https://deno.land/std/log/mod.ts"; |
自身不再需要的集中式存储库,但需要小心进行实践,无法控制第三方导入的模块,导致处于暴露状态。
事实上,我们也没有 package.json,使用包含模块列表以及其依赖的 URL 的 deps.ts 文件用于模块管理。但版本管理呢?我知道你会提问的。你可以在 URL 上标明版本号,这样并不优雅,但的确可行。
deps.ts 文件示例如下:
1 | export { assert } from "https://deno.land/std@v0.39.0/testing/asserts.ts"; |
只需要简单的更改 URL ,你就可以重新导出模块并改变其版本。
随便说一下,导入的代码会被缓存,第一次运行代码时,需要增加 –reload 标签进行重新构建。
Deno 还包括了另一些模块,比如开箱即用的工具,例如测试运行器(test runner)、调试、文件监听等等。但话又说过来,其中一些只是语言提供的 APIs,你需要编写自己的工具进行使用。
比如文件监听 API,由 Deno.watchFs 提供,你可以使用类似于 nodemon 进行构建。这里仅仅使用 23 行代码就能解决类似的问题:
这段代码是 Caesar2011 发布的其中一段,你可以在这里看到完整代码。
实话实说,并不会,这里有些标题党了。我们使用 Node.js 可以追溯到 0.10 版本,并且已经用在实际产品中了!告诉你真相可能有些恐怖,但我们这么做是因为当时没有其他可选方案。无论是 PHP、Python 还是 Ruby (更不用说 Java 或 . net ),都无法与后端同时使用 JavaScript 和异步 I/O 模型相提并论。这些年来,Node(和 JavaScript )已经成为工业的必备。这是不是很完美?当然不是!与生活类似,当涉及到编程语言时,没有是完美的。
Deno 并没有区别,只是因为现在,它是花了两年时间在一个想法上的成果。其还没有在实际产品中进行编码和测试。尚未对 Deno 进行检测,也没有放入怪异和意外的用例中,以了解是如何处理边界情况的。直到这些完成,Deno 只是尝鲜者把玩的玩具。可能过了一年,我们开始听到公司是使用 Deno 的经历,他们是如何解决发现的问题,其背后的社区将对其进行怎样的调整,使其能够发挥作用。Deno 是否能代替 Node 呢?谁知道呢?只有时间才能给出答案。
所以,你是怎样认为的呢?
这段时间在研究 HTTP/2 的相关知识,没想到 HTTP/3 也在酝酿中,于是乎找到一篇介绍 HTTP/3 的文章,学习并在此进行记录。
HTTP-over-QUIC 这个实验性的协议会被命名为 HTTP/3,IETF 的一位成员披露了这条消息。
从 HTTP/1.1(发布于1999年)到 HTTP/2 已经过了许多年,而在 2019 年将大大推动 HTTP/3 的发展。
HTTP/3 由 Google 的 QUIC 协议发展而来。它开始于 Mark Nottingham 的建议。
所以啥是 QUIC ?
QUIC(Quick UDP Internet Connections / 快速 UDP 网络连接)是一种新的传输模式,与 TCP 相比减少了延迟。大体来说,QUIC 类似于 UDP 上实现 TCP + TLS + HTTP/2。由于 TCP 在操作系统内核和中间件之上实现的,导致对于 TCP 进行重大更新几乎是不可能的。然而,QUIC 基于 UDP 之上实现,所以就没有这些限制。
QUIC 于现有 TCP + TLS + HTTP/2 上的主要功能包括:
Google说,从 Chrome 到 Google 服务器的所有请求中,大约有一半是通过 QUIC 提供的,并且他们还在继续增加 QUIC 的占比,最终使其成为Google 客户端——Chrome 以及移动 apps 到 Google 服务器之间的默认传输方式。Google 计划正式向 IETF 提出 QUIC,使其成为一个网络标准,但首先还需要做一些工作,比如更新线路标准并将其实现从 SPDY-over-QUIC 更新为 HTTP2-over-QUIC(当前的 HTTP-over-QUIC 协议草案 使用最新发布的 TLS 1.3 协议)。在接下来的几个月内,Google 打算减少握手开销以便用于更好的服务器扩展,提升前向纠错和改善拥塞避免,同时增加对多路径链接(multipath connections)的支持。
reddit 的一位用户对 TCP 和 QUIC 的区分有个很好的解释:
开发 TCP 的时候需要通过比现在更大丢包率的网络进行数据包传递,并且当时的计算机系统需要更长的时间来响应 TCP 消息。例如,即便在 5 秒内无法完成 TCP 握手,主机的链接超时仍为 20 秒,即使在你不太可能获得响应的情况下。长时间的延迟是网络应用存在停滞时间过长的原因。我们无法更改这个 70 年代产生的协议,尽管在看到可靠性以及速度有很大提升的情况下。
在最大限度的与当前 TCP 兼容,而不是最终减少不会改变包的默认值的情况下,协议开发者开始使用 UDP 并在此基础上实现自己 TCP。 向 IPv6 的转变是修改 TCP 诸多问题的理想时间,例如大部分的超时,窗口尺寸(window size)以及 TCP 的慢启动。有些值可以在你的操作系统中调整,但是最烦人的超时是不能调整的。如果你终止已经有 5 秒超时的一条 TCP 套接字(TCP socket), 操作系统依旧需要为此保持打开状态,直到 20 秒以后结束,这会消耗系统资源。
参考资料:
前不久,在微博上看到貘吃馍香在吐槽
于是去搜索和查阅了相关资料,也做了一些复习,加上 W3C Plus 相关文章收费,因此在此做个记录。
CSSOM 是页面渲染不可缺少的部分。
CSSOM 是 关键路径渲染(critical rendering path)的基础和关键,理解 CSSOM 做了什么是对页面优化(页面加载更快)的重要部分。
CSSOM 将样式表中的规则映射到页面上需要样式化的内容。
为此,CSSOM 做了许多复杂的操作,但是 CSSOM 的最终是将样式映射到这些样式需要去的地方。
(更精确的说,CSSOM 定义了令牌(token)并将其隐藏的链接到树结构的节点中。页面中节点及其样式的所有映射就是 CSS Object Mode)。
为了渲染页面,浏览器必须执行一些操作。此时,我们将其简化一下并讨论四个主要步骤,这些步骤将说明 CSSOM 的重要性。
如上所示,CSSOM 确实是展示 web 页面的重要部分。
为了优化 web 页面,其实你不需要了解 CSSOM 到底是如何工作的。
你只需要知道 CSSOM 的几个关键点就可以对页面进行非常实际的优化。
让我们快速的了解这几点,然后为了优化页面我们能做什么。
所有 CSS 是阻塞式渲染(意味着直到 CSS 处理完成,将不会展示任何东西)。
这样做的原因是,如果在 CSS 执行之前就展现 web 页面,那你看到的 web 页面是没有样式的,然后过了一会,页面有了样式。如果这样做的话,体验是相当糟糕的。
由于 CSSOM 用来帮助创建渲染树,如果在页面中的 CSS 不能高效的完成,那么将会产生严重的问题。
主要问题是加载 web 页面时的白屏。
对你来说,这个可能不是相当明显,但确实是非常重要的一点。
意思是尽管你的 CSS 被缓存了,并不意味着 CSSOM 是针对每个页面构建的。
用户访问其他页面时,CSSOM 必须重新进行构建(尽管浏览器可能已经缓存了所需要的所有 CSS 文件)。
换句话说,如果你的 CSS 是劣质和庞大并且编写相当糟糕的,那么在渲染页面时会带来副作用。
在 web 页面中使用 javascript 可能(或者说经常)会阻塞 CSSOM 的构建。
简而言之—页面展现需要 CSSOM。如果 CSSOM 没有完成,页面将不会展现任何内容。如果阻塞了 CSSOM 的构建,那么 CSSOM 将会花费更长的时间,意思是页面展现将会花费更长的时间。如果 javascript 阻塞了 CSSOM 的构建,那么用户将会花费比原本更多时间停留在空白页面。
这里有些最佳实践用于加快 CSSOM 的构建(因此页面能加载的更快)。
如果经常写 JavaScript 代码,大概率你会遇到一个既有用又经常引起困惑的概念—闭包。但什么是闭包?
闭包是函数和声明该函数的词法环境(lexical environment)的组合。
但这到底是什么意思呢?词法环境 由函数创建时的所有局部变量组成。通过闭包,可以引用函数下的所有局部变量。这实际上是通过在一个函数中定义另一个函数来实现的,在函数中的这一函数称为闭包。一旦父函数被调用,一个包含所有局部变量的全新拷贝的执行上下文将创建。在全部变量中引用局部变量可以通过与全局变量进行链接,或者是返回父函数中的闭包。
一个简单的示例将采用与此类似的:
1 | function closuredFunc (){ |
同样也可以通过以下方式让同一闭包返回多个方法:
1 | function closure(){ |
为了引用这些方法,我们将闭包分配给一个全局变量,然后将其指向一组公开的方法。如下所示,每个方法分配了唯一的变量名称,以将它们带入全局范围。现在,可以调用它们了。
1 | let f = closure() |
你可能想知道为什么要花这么多时间来写闭包。然而,闭包具有许多用途与优点。
先前介绍了 ES6 中的 Class,闭包提供了类似于面向对象编程方法用于创建类似于类的隐私方法,允许我们遍历私有方法。这个也可称为 模块模式(module pattern),其允许我们更容易的维护代码,减少命名空间污染(namespace pollution)的同时增加可重用性。
让我们看一个这样做的栗子。
1 | var makeCounter = function() { |
在上面的例子中, 我们定义了一个公共函数同时也可以访问一些私有变量如 privateCounter 和其中的函数的方法 makeCounter。创建 makeCounter 模仿了类的行为,其具有内置的功能和变量。这可以在创建两个不同的计数器 counter1 和counter2 时看到。每个计数器(counter)是独立于其他计数器,指向不同状态下的变量。
闭包还允许我们使用函数来创建其他函数,这些函数会为其参数添加特定的值。在这种情况下,允许这种行为的父函数被称为 函数工厂 (function factory),因为它实际上会创建其他函数。
使用函数工厂,我们实现的这样行为称为 柯里化 (Currying)。我们将在下个部分详细描述。
柯里化是一种函数模式用于立即执行并且返回其他函数。这使得 JavaScript 函数返回其他函数的表达式成为可能。
柯里化函数通过定义并立即返回其内部函数来链接闭包来构造。
这里有柯里化的例子。
1 | let greeting = function (a) { |
从 greeting 创建了两个函数(hello and morning),每个返回函数处理提供的输入用来生成问候语。它们还接受了一个参数用于要打招呼的人的名字。
在上面的例子中,greeting 也作为函数工厂使用,hello 和 morning 由此生成。
在第一次调用之后,还可以继续调用内部函数,如下所示:
1 | greeting('Hello There')('General Kenobi') |
柯里化认为是函数式编程的一部分,同时柯里化函数在使用 ES6 的箭头函数语法和新版本的 JavaScript 时,能更容易的书写清晰,优雅的代码。
1 | let greeting = (a) => (b) => a + ' ' + b |
自从在 ES6 有了 Class,闭包可能不会经常使用,在编写清晰的可重用代码,闭包依旧占有一席之地。在本质上与面向对象编程中的私有方法具有相似用途的函数式编程中,闭包和柯里化是其重要的概念。
map, foreach, 及 filter, reduce 为典型的函数式编程实践,均为高阶函数(Higher-Order Functions)。
高阶函数指的是一个函数能作为值传递到另一个函数中,或者,一个函数可以作为另一个函数的输出。
map filter 和 reduce 的区别如图所示。
map 是将数组的每一项通过同样的操作进行变换,得到一个新的数组,且新数组与原数组的长度是一致的。
1 | var array1 = [1, 4, 9, 16]; |
filter 是保留数组中符合条件的数据,得到一个新的数组,且新数组的长度不大于原数组的长度。
1 | var array1 = [1, 4, 9, 16]; |
reduce 是合并数组的每一项数据,最终返回一个新的数据。
1 | var array1 = [1, 2, 3, 4]; |
注意:三个例子中,原来的数组是没有发生任何改变的。