[关闭]
@hylerrix 2019-04-09T14:30:53.000000Z 字数 85322 阅读 3176

大前端核心技术详尽篇

P.S: 本文暂时维护在作业部落上
P.S: 更多请及时关注 https://github.com/hylerrix
P.S: 转载请联系微信号:CorvoH

技术碎片


正则表达式、LaTeX

计算机网络

HTTP 协议

URL、URI(待做)

URL 一般包括几大部分:

URL = 协议 + Web 服务器名(域名或 IP) + 目录名 + 文件名(可选)

URL = 协议 + Web 服务器名(域名或 IP) + CGI 程序接口名

其中,可选文件名由服务器配置,常见默认为 index.htmldefault.htm 等。

URL 根据访问目标不同,可以包括:

HTTP 版本

HTTP 1.0
HTTP 2.0

HTTP 2.0不是 HTTPS,它相当于是 HTTP 的下一代规范。使用 HTTP 2.0 时,不需要雪碧图、域名拆分等优化技术。

HTTP 2.0 与 HTTP 1.1 的不同点

HTTP 2.0 新特性

HTTPS

HTTPS 就是安全版本的 HTTP,HTTP 请求的安全系数太低。

升级为 HTTPS

一般来说,如果要将网站升级成 HTTPS ,需要后端支持(后端需要申请证书等),然后 HTTPS 的开销也比 HTTP 要大(因为需要额外建立安全链接以及加密等),所以一般来说 HTTP 2.0 配合 HTTPS 的体验更佳(因为 HTTP2.0 更快)

HTTPS 与 HTTP 的区别
SSL/TLS的握手流程
  1. 浏览器请求建立 SSL 链接,并向服务端发送一个随机数,Client random 和客户端支持的加密方法,比如 RSA 加密,此时是明文传输。
  2. 服务端从中选出一组加密算法与 Hash 算法,回复一个随机数,Server random,并将自己的身份信息以证书的形式发回给浏览器(证书里包含了网站地址,非对称加密的公钥,以及证书颁发机构等信息)
  3. 浏览器收到服务端的证书后
    • 验证证书的合法性(颁发机构是否合法,证书中包含的网址是否和正在访问的一样),如果证书信任,则浏览器会显示一个小锁头,否则会有提示。
    • 用户接收证书后(不管信不信任),浏览会生产新的随机数–Premaster secret,然后证书中的公钥以及指定的加密方法加密 Premaster secret,发送给服务器。
    • 利用 Client random、Server random和 Premaster secret 通过一定的算法生成 HTTP 链接数据传输的对称加密 key- session key
    • 使用约定好的 HASH 算法计算握手消息,并使用生成的 session key 对消息进行加密,最后将之前生成的所有信息发送给服务端。
  4. 服务端收到浏览器的回复
    • 利用已知的加解密方式与自己的私钥进行解密,获取 Premaster secret
    • 和浏览器相同规则生成 session key
    • 使用 session key 解密浏览器发来的握手消息,并验证Hash是否与浏览器发来的一致
    • 使用 session key 加密一段握手消息,发送给浏览器
  5. 浏览器解密并计算握手消息的HASH,如果与服务端发来的HASH一致,此时握手过程结束,

HTTP 主要方法

PUT 和 DELETE 常用于 RESTful API 设计中,App 和后端交互时常用

HTTP 请求消息(待做)

方法<空格>URI<空格>HTTP 版本
字段名:字段值
...
空行
消息体

消息头,从 字段名:字段值 开始,一直延伸到空行。
消息体可以包含 POST 方法要发送的内容等。

HTTP 响应消息(待做)

HTTP 版本<空格>状态码<空格>响应短语
字段名:字段值
...
空行
消息体

消息体均为二进制:

HTTP 主要头字段

一般来说,请求头部和响应头部是匹配分析的。

CRLF 是回车换行,一般作为分隔符存在。请求头和实体消息之间有一个 CRLF 分隔,响应头部和响应实体之间用一个 CRLF 分隔。

通用头 HTTP 1.0 HTTP 1.1 功能
Date 定义 定义 表示请求和响应生成的日期
Pragma 定义 定义 表示数据是否允许缓存的通信选项
Cache-Control 定义 控制缓存的相关信息,告诉浏览器或其他客户,什么环境可以安全的缓存文档
Connection 定义 设置发送响应之后 TCP 连接是否继续保持的通信选项
Transfer-Encoding 定义 表示消息主体的编码格式
Via 定义 记录途中经过的代理和网关
请求头 HTTP 1.0 HTTP 1.1 功能
Authorization 定义 定义 身份认证数据
From 定义 定义 请求发送者的邮件地址
If-Modified-Since 定义 定义 如果希望仅当数据在某个日期之后有更新时才执行请求,可以在这个字段指点希望的日期。一般来说,这个功能的用途在于判断客户端缓存的数据是否已经过期,如果已经过期则获取新的数据
Origin 最初的请求时从哪里发起的,只会精确到端口,比 Referer 更尊重隐私
Referer 定义 定义 当通过点击超级链接进入下一个页面时,在这里会记录下上一个页面的 URI
User-Agent 定义 定义 客户端软件的名称和版本号等相关信息
Accept 非正规 定义 客户端可支持的字符集
Accept-Charset 非正规 定义 客户端可支持的数据类型 Content-Type,以 MIME 类型来表示
Accept-Encoding 非正规 定义 客户端可支持的编码格式 Content-Encoding,一般来说表示数据的压缩格式
Accept-Language 非正规 定义 客户端可支持的语言,汉语为 zh,英语为 en
Host 定义 接收请求的服务器 IP 地址和端口号
If-Match 定义 参见 Etag
If-None-Match 定义 参见 Etag
If-Unmodified-Since 定义 当指定日期之后数据未更新时执行请求
Range 定义 当需要只获取部分数据而不是全部数据时,可通过这个字段指定要获取的数据范围
响应头 HTTP 1.0 HTTP 1.1 功能
Location 定义 定义 表示信息的准确位置。当请求的 URI 为相对路径时,这个字段用来返回绝对路径
Server 定义 定义 服务器程序的名称和版本号等相关信息
WWW-Authenticate 定义 定义 当请求的信息存在访问控制时,返回身份认证用的数据
Accept-Ranges 定义 当希望仅请求部分数据时,服务器会告诉客户端是否支持这一功能
Access-Control-Allow-Headers 服务器端允许的请求 Headers
Access-Control-Allow-Methods 服务器端允许的请求方法
Access-Control-Allow-Origin 服务器端允许的请求Origin头部(譬如为*)
Content-Type 服务端返回的实体内容的类型
Date 数据从服务器发送的时间
Set-Cookie 设置和页面关联的 cookie,服务器通过这个头部把 cookie 传给客户端
Keep-Alive 如果客户端有 Keep-Alive,服务端也会有响应如 timeout=38
实体头 HTTP 1.0 HTTP 1.1 功能
Allow 定义 定义 表示指定的 URI 支持的方法
Content-Encoding 定义 定义 当消息体经过压缩等编码处理时,表示其编码格式
Content-Length 定义 定义 表示消息体的长度
Content-Type 定义 定义 表示消息体的数据类型,以 MIME 规格定义的数据类型来表示
Expires 定义 定义 表示消息体的有效期
Last-Modified 定义 定义 数据的最后更新日期
Content-Language 定义 表示消息体的语言。汉语为 zh,英语为 en
Content-Location 定义 表示消息体再服务器上的位置 URI
Content-Range 定义 当仅请求部分数据时,表示消息体包含的数据范围
Etag 定义 在更新操作中,有时候需要基于上一次请求的响应数据来发送下一次请求。在这种情况下,这个字段可以用来提供上次响应与下次响应之间的关联信息。上次响应中,服务器会通过 Etag 向客户端发送一个唯一标识,在下次请求中客户端可以通过 If-Match、If-None-Match、If-Range 字段将这个标识告知服务器,这样服务器就知道该请求和上次的响应式相关的。这个字段的功能和 Cookie 是相同的,但 Cookie 是网景 Netscape 公司自行开发的规格,而 Etag 是将其标准化后的规格

HTTP 报文结构

HTTP 状态码

HTTP 状态码,HTTP Status Code,是用以表示网页服务器超文本传输协议响应状态的 3 位数字代码。它由 RFC 2616 规范定义的,并得到 RFC 2518、RFC 2817、RFC 2295、RFC 2774 与 RFC 4918 等规范扩展。HTTP 状态码的官方注册表由互联网号码分配局 Internet Assigned Numbers Authority,维护。

状态码 状态码英文名称 中文描述
100 Continue 继续。客户端应继续其请求.
101 Switching Protocols 切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议,例如,切换到 HTTP 的新版本协议。
102 Processing 表示服务器已经收到并正在处理请求,但无响应可用。这样可以防止客户端超时,并假设请求丢失。
状态码 状态码英文名称 中文描述
200 OK 请求成功,请求所希望的响应头或数据体将随此响应返回。一般用于 GET 与 POST 请求。
201 Created 请求已经被实现,而且有一个新的资源已经依据请求的需要而创建,且其 URI 已经随 Location 头信息返回。假如需要的资源无法及时创建的话,应当返回 '202 Accepted'。
202 Accepted 服务器已接受请求,但尚未处理。最终该请求可能会也可能不会被执行,并且可能在处理发生时被禁止。
203 Non-Authoritative Information 非授权信息。请求成功。但返回的 meta 信息不在原始的服务器,而返回转换代理服务器上的一个副本
204 No Content 服务器成功处理了请求,没有返回任何内容。在未更新网页的情况下,可确保浏览器继续显示当前文档
205 Reset Content 服务器成功处理了请求,但没有返回任何内容。与204响应不同,此响应要求请求者重置文档视图。可通过此返回码清除浏览器的表单域。
206 Partial Content 服务器已经成功处理了部分 GET 请求。HTTP 下载工具都是使用此类响应实现断点续传或者将一个大文档分解为多个下载段同时下载。
207 Multi-Status 代表之后的消息体将是一个 XML 消息,并且可能依照之前子请求数量的不同,包含一系列独立的响应代码。
208 Already Reported DAV 绑定的成员已经在(多状态)响应之前的部分被列举,且未被再次包含。
226 IM Used 服务器已经满足了对资源的请求,对实体请求的一个或多个实体操作的结果表示。
状态码 状态码英文名称 中文描述
300 Multiple Choices 多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择
301 Moved Permanently 永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的 URI,浏览器会自动定向到新 URI。今后任何新的请求都应使用新的 URI 代替
302 Found 临时移动。与 301 类似。但资源只是临时被移动。客户端应继续使用原有 URI
303 See Other 查看其它地址。与 301 类似。使用 GET 和 POST 请求查看
304 Not Modified 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源
305 Use Proxy 使用代理。所请求的资源必须通过代理访问
306 Unused 已经被废弃的 HTTP 状态码
307 Temporary Redirect 临时重定向。与 302 类似。使用 GET 请求重定向
308 Permanent Redirect
状态码 状态码英文名称 中文描述
400 Bad Request 客户端请求的语法错误,服务器无法理解
401 Unauthorized 请求要求用户的身份认证
402 Payment Required 保留,将来使用
403 Forbidden 服务器理解请求客户端的请求,但是拒绝执行此请求
404 Not Found 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面
405 Method Not Allowed 客户端请求中的方法被禁止
406 Not Acceptable 服务器无法根据客户端请求的内容特性完成请求
407 Proxy Authentication Required 请求要求代理的身份认证,与 401 类似,但请求者应当使用代理进行授权
408 Request Time-out 服务器等待客户端发送的请求时间过长,超时
409 Conflict 服务器完成客户端的 PUT 请求是可能返回此代码,服务器处理请求时发生了冲突
410 Gone 客户端请求的资源已经不存在。410 不同于 404,如果资源以前有现在被永久删除了可使用 410 代码,网站设计人员可通过 301 代码指定资源的新位置
411 Length Required 服务器无法处理客户端发送的不带 Content-Length 的请求信息
412 Precondition Failed 客户端请求信息的先决条件错误
413 Request Entity Too Large 由于请求的实体过大,服务器无法处理,因此拒绝请求。为防止客户端的连续请求,服务器可能会关闭连接。如果只是服务器暂时无法处理,则会包含一个 Retry-After 的响应信息
414 Request-URI Too Large 请求的 URI 过长(URI通常为网址),服务器无法处理
415 Unsupported Media Type 服务器无法处理请求附带的媒体格式
416 Requested range not satisfiable 客户端请求的范围无效
417 Expectation Failed 服务器无法满足 Expect 的请求头信息
状态码 状态码英文名称 中文描述
500 Internal Server Error 服务器内部错误,无法完成请求
501 Not Implemented 服务器不支持请求的功能,无法完成请求
502 Bad Gateway 充当网关或代理的服务器,从远端服务器接收到了一个无效的请求
503 Service Unavailable 由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的 Retry-After 头信息中
504 Gateway Time-out 充当网关或代理的服务器,未及时从远端服务器获取请求
505 HTTP Version not supported 服务器不支持请求的 HTTP 协议的版本,无法完成处理

HTTP 缓存

强缓存与协商缓存

缓存可以简单的划分成两种类型:强缓存(200 from cache)与协商缓存(304)。

HTTP 缓存头部

强缓存和协商缓存通过缓存头部区分。常用的缓存头部有:If-None-Match/E-tag、If-Modified-Since/Last-Modified、Cache-Control/Max-Age、Pragma/Expires。

属于强缓存控制的:

属于协商缓存控制的:

其实 HTML 页面中也有一个 meta 标签可以控制缓存方案 Pragma,不过,这种方案还是比较少用到,因为支持情况不佳,譬如缓存代理服务器肯定不支持,所以不推荐。

  1. <META HTTP-EQUIV="Pragma" CONTENT="no-cache">

Max-Age 与 Expires

E-tag 与 Last-Modified

Last-Modified:

E-tag:

HTTP 各版本缓存控制头部

HTTP 1.0 中的缓存控制头部:

HTTP 1.1 中的缓存控制头部:

TCP/IP

IP 地址主体
子网掩码,决定 IP 地址哪部分是主机号

主机号为 0 时,表示整个子网
主机号为 1 时,表示向子网上所有设备发送包

TCP/IP 的三次握手四次挥手

TCP 将 HTTP 长保温划分为短报文,通过三次握手与服务端建立连接,进行可靠传输。

【问题1】为什么连接的时候是三次握手,关闭的时候却是四次握手?
答:因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。

三次握手
四次挥手

主动方经历的状态:

【注意】 在TIME_WAIT状态中,如果TCP client端最后一次发送的ACK丢失了,它将重新发送。TIME_WAIT状态中所需要的时间是依赖于实现方法的。典型的值为30秒、1分钟和2分钟。等待之后连接正式关闭,并且所有的资源(包括端口号)都被释放。
【问题2】为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?

答:虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。

被动方经历的状态:

TCP/IP 长连接与短连接

TCP/IP 层面的定义:

HTTP 层面的定义:

Keep-Alive 不会永远保持,它有一个持续时间,一般在服务器中配置(如 Apache),另外长连接需要客户端和服务器都支持时才有效

服务器类别(待做)

Web 服务器(待做)

CGI 程序

CGI:对 Web 服务器程序调用其它程序的规则所做的定义。

Apache(待做)
Nginx(待做)
DNS 服务器(待做)

DNS 查询得到 IP 的过程:

DNS 查询优化

解析域名过多时,会让首屏加载速度变得过慢,可以考虑 dns-prefetch 优化。

  1. // gethostbyname 返回 IP 地址
  2. <内存地址> = gethostbyname("要查询的服务器域名");
  3. gethostbyname {
  4. 生成发送给 DNS 服务器的查询信息;
  5. // 发送查询消息,需要委托给操作系统内部协议栈
  6. DNS 服务器发送查询消息;
  7. 接收 DNS 服务器返回的响应消息;
  8. 从响应消息中取出 IP 地址,存放到 <内存地址中>;
  9. 返回应用程序;
  10. }
DNS 预解析

“Google Chrome 浏览器”等现代浏览器还采用了一种称为“DNS 预解析”的技术,可以更快地抓取和载入网页。通过 DNS 查找将网址转换为 IP 地址的过程称为“解析”,反之则称为“反向解析”。“Chrome 浏览器”会通过 DNS 预解析同时查找网页上的其他所有链接,并在后台将这些链接预解析为 IP 地址。因此,当您实际点击网页上的某个链接后,浏览器就能立即将您转到新的网页。

FTP 服务器(待做)

经典问题

浏览器输入网址到显示页面发生了什么

关键字
* level1: http、后台、浏览器渲染、js 引擎
* level2: http 报文通信、render 树构建流程、layout、paint、复合层与简单层、常用优化方案、JS引擎的变量提升、执行上下文、VO、AO、作用域链、回收机制、http 报文结构、缓存机制、http2.0、https、跨域与安全
* level3: 键盘输入、操作系统交互、屏幕显示原理、网卡等硬件交互、浏览器的多进程和浏览器内核的多县城、网络线程、dns 查询、tcp/ip 链接、五层因特尔协议栈、DNS 优化方案、负载均衡、安全拦截、http 场景头部、cookie、跨域、web 安全、http 缓存、http2..0、https、解析 HTML 词法分析成 DOM 树、解析 CSS 为 CSS 规则树、合并 render 树、layout/painting 渲染、复合层的合成、GPU 绘制、外链处理、加载顺序
* level4: 架构、整体把握、大型工程构建

第一种(需要整合)

  1. Web 浏览器
  2. TCP/IP
  3. 网卡驱动
  4. 集线器
  5. 路由器
  6. 接入网
  7. 电话局
  8. 网络运营商
  9. 电话局
  10. 接入网
  11. 防火墙
  12. 缓存服务器
  13. 网卡驱动
  14. TCP/IP
  15. Web 服务器程序

第二种

第三种

  1. 从浏览器接收url到开启网络请求线程(这一部分可以展开浏览器的机制以及进程与线程之间的关系)
  2. 开启网络线程到发出一个完整的http请求(这一部分涉及到dns查询,tcp/ip请求,五层因特网协议栈等知识)
  3. 从服务器接收到请求到对应后台接收到请求(这一部分可能涉及到负载均衡,安全拦截以及后台内部的处理等等)
  4. 后台和前台的http交互(这一部分包括http头部、响应码、报文结构、cookie等知识,可以提下静态资源的cookie优化,以及编码解码,如gzip压缩等)
  5. 单独拎出来的缓存问题,http的缓存(这部分包括http缓存头部,etag,catch-control等)
  6. 浏览器接收到http数据包后的解析流程(解析html-词法分析然后解析成dom树、解析css生成css规则树、合并成render树,然后layout、painting渲染、复合图层的合成、GPU绘制、外链资源的处理、loaded和domcontentloaded等)
  7. CSS的可视化格式模型(元素的渲染规则,如包含块,控制框,BFC,IFC等概念)
  8. JS引擎解析过程(JS的解释阶段,预处理阶段,执行阶段生成执行上下文,VO,作用域链、回收机制等等)
  9. 其它(可以拓展不同的知识模块,如跨域,web安全,hybrid模式等等内容)

当初始的 HTML 文档完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像和子框架的完成加载。

window 的 load 事件仅在 DOM 和所有相关资源全部完成加载后才会触发。

客户端和服务器之间收发数据操作的情形

内部分为创建套接字、连接 Web 服务器、发送数据、接受数据、断开连接几个阶段

  1. <内存地址> = gethostbyname("要查询的服务器域名");
  2. ...
  3. // 同一计算机上可能同时存在多个套接字,通过描述符识别。
  4. <描述符> = socket(<使用 IPv4>, <流模式>, ...);
  5. ...
  6. // 客户端套接字通过协议栈调用 Socket 库中的 connect 组建与服务端套接字连接
  7. connect(<描述符>, <服务器的 IP 地址和端口号>);
  8. ...
  9. // 程序无法直接控制套接字,需要使用 write 组件
  10. write(<描述符>, <发送数据>, <发送数据长度>);
  11. ...
  12. // 接收时通过 Socket 库中的 read 程序组件委托协议栈来完成
  13. <接收数据长度> = read(<描述符>, <接收缓冲区>, ...);
  14. ...
  15. // HTTP 协议规定,当 Web 服务器发送完响应消息后,应主动断开连接
  16. close(<描述符>);
  17. ...

从单机至亿级流量大型网站系统架构的演进过程(待做)

从单机至亿级流量大型网站系统架构的演进过程

操作系统

进程和线程

Web 浏览器

浏览器的多进程与内核的多线程

浏览器是多进程的

浏览器多进程的优势
Browser 进程和 Renderer 进程的通信过程

浏览器内核是多线程的

浏览器内核多线程列表
浏览器内核中线程之间的关系

GUI 渲染线程与 JavaScript 引擎线程互斥

由于 JavaScript 是可操纵 DOM 的,如果在修改这些元素属性同时渲染界面(即 JavaScript 线程和 UI 线程同时运行),那么渲染线程前后获得的元素数据就可能不一致了。

因此为了防止渲染出现不可预期的结果,浏览器设置 GUI 渲染线程与 JavaScript 引擎为互斥的关系,当 JavaScript 引擎执行时 GUI 线程会被挂起,GUI 更新则会被保存在一个队列中等到 JavaScript 引擎线程空闲时立即被执行。

JavaScript 阻塞页面加载

JavaScript 如果执行时间过长就会阻塞页面。譬如,假设 JavaScript 引擎正在进行巨量的计算,此时就算 GUI 有更新,也会被保存到队列中,等待 JavaScript 引擎空闲后执行。然后,由于巨量计算,所以 JavaScript 引擎很可能很久很久后才能空闲,自然会感觉到巨卡无比。

WebWorker,JavaScript 的多线程

MDN 官方解释如下:

Web Worker 为 Web 内容在后台线程中运行脚本提供了一种简单的方法。线程可以执行任务而不干扰用户界面。
一个 Worker 是使用一个构造函数创建的一个对象(e.g. Worker()) 运行一个命名的 JavaScript 文件
这个文件包含将在工作线程中运行的代码 Workers 运行在另一个全局上下文中,不同于当前的 Window
因此,使用 Window 快捷方式获取当前全局的范围(而不是 self) 在一个 Worker 内将返回错误

JavaScript 引擎是单线程的,这一点的本质仍然未改变。如果有非常耗时的工作,请单独开一个 Worker 线程。

WebWorker 与 SharedWorked

网络线程静态资源下载

这里将遇到的静态资源分为一下几大类(未列举所有):CSS样式资源、JS脚本资源、img图片类资源。

遇到 CSS 样式资源

遇到 JS 脚本资源

注意,defer 和 async 是有区别的:

遇到 img 图片类资源

遇到图片等资源时,直接就是异步下载,不会阻塞解析,下载完毕后直接用图片替换原有 src 的地方。

loaded 和 domcontentloaded

浏览器样式兼容性(待做)

浏览器样式兼容性问题解决方案

为功能受限的浏览器提供页面的技术与流程

浏览器该具备的功能

浏览器两轮大战概要

第一轮概要,Netscape 对抗新 IE

第二轮概要,旧 IE 对抗各大新浏览器

总结,使用尽可能新的浏览器

原因:

浏览器内核版本(排版引擎)

浏览器的内核通常仅指排版引擎,相符的有 JavaScript 引擎。

常见的浏览器内核生命周期如下。

常见的浏览器内核如下。

Trident 内核,运行在 IE 上

Trident 内核运行在 IE 上,又称 IE 内核,是 IE 的排版引擎的名称。Trident 曾因其市场占有量庞大而不思进取,一度与 W3C 标准脱节(05 年),给了运行在 Safari、Chrome、Firefox 和 Opera 等浏览器 上的内核提供了很大的发展空间。随着微软逐步放弃 IE 浏览器品牌,Trident 内核版本也不再更新。

EdgeHTML 内核,运行在 Microsoft Edge 上

EdgeHTML 内核开启了 Trident 内核的分支,成为了替代 IE 浏览器的 Microsoft Edge 浏览器的主要排版引擎。EdgeHTML 移除所有旧版 IE 浏览器遗留下来的代码,并通过尊重网页标准、重写主要的代码以和其他现代浏览器的设计精神互通有无。

KHTML 内核,早期内核,Webkit 前身

KHTML 是由 KDE 自由软件社区所开发的 HTML 排版引擎,由 C++ 语言编写。这里提到 KHTML 是因为其是早起 Safari 的内核最终选型。Safari 开发团队因对 KHTML 作出大量的改动,逐步从 KHTML 中脱离出来,KHTML 逐渐淡出主流浏览器中。

Webkit 内核,运行在 Safari 及更多浏览器上

Webkit 的前身是 KHTML 引擎,属于其一个开源分支,是 Safari 及早期 Chromium 、Amazon Kindle 等浏览器的默认内核。通常所说的 Webkit 不仅仅是排版引擎,其包括用来渲染 HTML 和 CSS 的 Webcore 引擎和用来解析 JS 的 JSCore。Webcore 便用来处理排版。

Chromium/Blink,运行在 Chrome 上

Chrome 浏览器的内核来源于 Webkit 的 Webcore,最终用谷歌公司自主开发的开源排版引擎 Blink 所代替;同时 Chrome 浏览器对于 JavaScript 代码的解析也使用了自己的 V8 引擎。

Blink 同样来自于 Webkit,据说 Blink 删除了 880w 行 webkit 代码。Blink 引擎问世后,国产各种 chrome 系的浏览器也纷纷投入 Blink 的怀抱,可以在浏览器地址栏输入 chrome://version 进行查看。

Gecko 内核,运行在 Firefox 上

Gecko 是 Netscape6 和 Firefox 的内核。Gecko 代码公开,使用该内核浏览器很多。其诞生来源于 IE 的不思进取。微软内部人员不满,与一停止更新 Netscape 的员工一起在创办 了 Mozila 后开发,常被称为 Firefox 内核,跨平台使用。

Presto 内核,Opera 浏览器早期内核

Opera 浏览器早期使用的内核,Opera 在 Blink 引擎推出之后转用 Blink,其中原因包括毫无推广上的优势(主要原因)和使用 Webkit 内核的 Opera 可以兼容谷歌 Chrome 浏览器等。但换内核代价惨痛,从快速轻量化与稳定到异常卡顿与不稳定,书签同步都困难,很多用户流失。Presto 内核最终停留在了 12.17。

浏览器内核渲染机制

  1. 解析 HTML,构建 DOM 树
  2. 解析 CSS,生成 CSS 规则树
  3. 合并 DOM 树和 CSS 规则,生成 render 树
  4. 布局 render 树(Layout/reflow),负责各元素尺寸、位置的计算
  5. 绘制 render 树(paint),绘制页面像素信息
  6. 浏览器会将各层的信息发送给 GPU,GPU 会将各层合成(composite),显示在屏幕上

浏览器解析 HTML,构建 DOM 树

解析HTML到构建出DOM当然过程可以简述如下:

  1. Bytes characters tokens nodes DOM

譬如假设有这样一个 HTML 页面

  1. <html>
  2. <head>
  3. <meta name="viewport" content="width=device-width,initial-scale=1">
  4. <link href="style.css" rel="stylesheet">
  5. <title>Critical Path</title>
  6. </head>
  7. <body>
  8. <p>Hello <span>web performance</span> students!</p>
  9. <div><img src="awesome-photo.jpg"></div>
  10. </body>
  11. </html>

浏览器的处理如下

其中的重点过程

  1. Conversion 转换:浏览器将获得的 HTML 内容(Bytes)基于他的编码转换为单个字符
  2. Tokenizing 分词:浏览器按照 HTML 规范标准将这些字符转换为不同的标记 token。每个 token 都有自己独特的含义以及规则集
  3. Lexing 词法分析:分词的结果是得到一堆的 token,此时把他们转换为对象,这些对象分别定义他们的属性和规则
  4. DOM 构建:因为 HTML 标记定义的就是不同标签之间的关系,这个关系就像是一个树形结构一样。
    例如:body 对象的父节点就是 HTML 对象,然后段略 p 对象的父节点就是 body 对象

生成 CSS 规则树

CSS 规则树的生成也是类似:

  1. Bytes characters tokens nodes CSSOM

譬如 style.css 内容如下:

  1. body { font-size: 16px }
  2. p { font-weight: bold }
  3. span { color: red }
  4. p span { display: none }
  5. img { float: right }

构建渲染树

一般来说,渲染树和 DOM 树相对应的,但不是严格意义上的一一对应。因为有一些不可见的DOM元素不会插入到渲染树中,如 head 这种不可见的标签或者 display: none 等。

渲染

有了 render 树,接下来就是开始渲染,基本流程如下:

  1. 计算CSS样式
  2. 构建渲染树
  3. 布局,主要定位坐标和大小,是否换行,各种 position overflow z-index 属性
  4. 绘制,将图像绘制出来

图中的线与箭头代表通过 JS 动态修改了 DOM 或 CSS,导致了重新布局(Layout)或渲染(Repaint)。

回流和重绘

回流的成本开销要高于重绘,而且一个节点的回流往往回导致子节点以及同级节点的回流,所以优化方案中一般都包括,尽量避免回流。回流一定伴随着重绘,重绘却可以单独出现。

为什么引起回流

  1. 页面渲染初始化
  2. DOM 结构改变,比如删除了某个节点
  3. render 树变化,比如减少了 padding
  4. 窗口 resize
  5. 最复杂的一种:获取某些属性,引发回流,
    很多浏览器会对回流做优化,会等到数量足够时做一次批处理回流,
    但是除了 render 树的直接变化,当获取一些属性时,浏览器为了获得正确的值也会触发回流,这样使得浏览器优化无效,包括
    • offset(Top/Left/Width/Height)
    • scroll(Top/Left/Width/Height)
    • cilent(Top/Left/Width/Height)
    • width,height
    • 调用了 getComputedStyle() 或者IE的 currentStyle

回流优化方案

注意:改变字体大小会引发回流

普通图层与复合图层

硬件加速变成复合图层

硬件加速技术如下

经测试,除了上述可以引发硬件加速的属性外,其它属性并不会变成复合层。

absolute 和硬件加速的区别

absolute 虽然可以脱离普通文档流,但是无法脱离默认复合层。所以,就算 absolute 中信息改变时不会改变普通文档流中 render 树,但浏览器最终绘制时,是整个复合层绘制的,absolute 中信息的改变仍然会影响整个复合层的绘制。

硬件加速直接就是在另一个复合层了,所以它的信息改变不会影响默认复合层。

复合层的作用?

一般一个元素开启硬件加速后会变成复合图层,可以独立于普通文档流中,改动后可以避免整个页面重绘,提升性能。

硬件加速时请使用 index

使用硬件加速时,尽可能的使用index,防止浏览器默认给后续的元素创建复合层渲染。如果a是一个复合图层,而且b在a上面,那么b也会被隐式转为一个复合图层,这点需要特别注意。

JavaScript 引擎

JavaScript 引擎版本

KJS 引擎,与早期 KHTML 引擎相配

与早期 KHTML 排版引擎相配的,还有用来解析 JavaScript 的 KJS 引擎。KJS 同样由 KDE 社区开发。其后因为 Webkit 作为分支的诞生,逐步被可以直接将 JS 代码编译为原生机器码的 JavaScriptCore 引擎替代。JavaScriptCore 引擎成为了 Webkit 中的一个重要组件。

Chakra,运行在 IE9+ 上

Chakra 引擎(又称 JScript 引擎)是由微软为 IE9+ 版本开发的 JavaScript 引擎,在一个独立的 CPU 核心上即时编译脚本,与浏览器并行。在 2009 年 11 月 18 日举行的 SunSpider 测试展示了 IE9 的 PDC 版本对脚本的执行远快于 IE8,但是仍然慢于 Firefox 3.5、Google Chrome 4 和 Safari 4。

ChakraCore,运行在 Microsoft Edge 上

Chakra是由微软为其Microsoft Edge网页浏览器开发的JavaScript引擎。它是Internet Explorer中使用的JScript引擎的一个分支。

V8 引擎,运行在 Chrome、Node.JS 上

V8 由 Google 公司开发,是开源的 JavaScript 引擎。V8 在运行之前将 JavaScript 编译成了机器码,而非字节码或是解释执行它,以此提升性能。基于 V8 引擎对 JavaScript 的高性能解析,Node.js 也选择了 V8 引擎作为其在服务端解析 JavaScript 的首选引擎,促进了前端的蓬勃发展。

JavaScriptCore 引擎

浏览器下载的顺序是从上到下,渲染的顺序也是从上到下,下载和渲染是同时进行的。

JavaScript 引擎解析过程

JavaScript 的解析阶段

JavaScript 是解释型语音,所以它无需提前编译,而是由解释器实时运行。引擎对 JavaScript 的处理过程可以简述如下:

  1. 读取代码,进行词法分析 Lexical analysis,然后将代码分解成词元 token
  2. 对词元进行语法分析 parsing,然后将代码整理成语法树 syntax tree
  3. 使用翻译器 translator,将代码转为字节码 bytecode
  4. 使用字节码解释器 bytecode interpreter,将字节码转为机器码
  5. 总结:核心的 JIT 编译器将源码编译成机器码运行

最终计算机执行的就是机器码。为了提高运行速度,现代浏览器一般采用即时编译 JIT-Just In Time compiler,即字节码只在运行时编译,用到哪一行就编译哪一行,并且把编译结果缓存 inline cache,这样整个程序的运行速度能得到显著提升。

不同浏览器策略可能还不同,有的浏览器就省略了字节码的翻译步骤,直接转为机器码,如 Chrome 的 V8。

JavaScript 的预处理阶段

正式执行 JavaScript 前,还会有一个预处理阶段。

分号补全机制

JavaScript 解释器有一个 Semicolon Insertion 规则,它会按照一定规则,在适当的位置补充分号。

加分号规则:

经典的例子:

  1. function b() {
  2. return
  3. {
  4. a: 'a'
  5. };
  6. }

分号补全机制:

  1. function b() {
  2. return;
  3. {
  4. a: 'a'
  5. };
  6. }
变量提升机制(待做)

变量声明,函数声明,形参,实参的优先级顺序,以及 es6 中 let 有关的临时死区、var conost let 等

一般包括函数提升和变量提升。

JavaScript 的执行阶段

执行上下文

譬如,如果程序执行完毕,被弹出执行栈,然后有没有被引用,没有形成闭包,那么这个函数中用到的内存就会被垃圾处理器自动回收。

每一个执行上下文,都有三个重要属性:

VO 与 AO

VO 是执行上下文的属性(抽象概念),但是只有全局上下文的变量对象允许通过 VO 的属性名称来间接访问(因为在全局上下文里,全局对象自身就是变量对象)。AO(activation object),当函数被调用者激活,AO 就被创建了。

总的来说,VO中会存放一些变量信息(如声明的变量,函数,arguments参数等等)

作用域链

它是执行上下文中的一个属性,原理和原型链很相似,作用很重要。流程:

this 指针

this 是执行上下文环境的一个属性,而不是某个变量对象的属性。

JavaScript 的回收机制

JavaScript 有垃圾处理器,所以无需手动回收内存,而是由垃圾处理器自动处理。常用的两种垃圾回收规则是:标记清除和引用计数。

标记清除

JavaScript 引擎基础 GC 方案是(Simple GC):mark and sweep 标记清除,简单解释如下:

引用计数

跟踪记录每个值被引用的次数,当一个值被引用时,次数 +1,减持时 -1,下次垃圾回收器会回收次数为 0 的值的内存(容易出循环引用的bug)。

GC 的缺陷

GC 时,停止响应其他操作,这是为了安全考虑。Javascript 的 GC 在 100ms 甚至以上。对一般的应用还好,但对于 JavaScript 游戏,动画对连贯性要求比较高的应用,就麻烦了。

GC 优化策略

Generation GC 分代回收机制目前是通过区分“临时”与“持久”对象:

参考资料

HTML

HTML 标准

HTML5

HTML5 基本构件
HTML5 语义化标签
语义化标签 作用 示例
article 装载显示一个独立的文章内容,可以嵌套 一篇完整的论坛帖子、一则网站新闻、一篇博客文章
section 定义文档中的节/区段 章节、页眉、页脚
aside 用来装载非正文类的内容 广告、成组的链接、侧边栏
hgroup 用于对网页或区段的标题元素(h1-h6)进行组合 连续的 h 系列的标签元素可以用 hgroup 将他们括起来
header 定义文档的页面组合 一些引导和导航信息
footer 定义 section 或 document 的页脚 典型时可包含创作者的姓名、文档的创作日期以及/或者联系信息。
nav 定义显示导航链接。不是所有的成组的超级链接都需要放在nav标签里。nav标签里应该放入一些当前页面的主要导航链接 在页脚显示一个站点的导航链接(如首页,服务信息页面,版权信息页面等等)
time 定义公历的时间(24 小时制)或日期,时间和时区偏移是可选的。该元素能够以机器可读的方式对日期和时间进行编码 用户代理能够把生日提醒或排定的事件添加到用户日程表中,搜索引擎也能够生成更智能的搜索结果
mark 定义带有记号的文本 请在需要突出显示文本时使用 标签
figure 规定独立的流内容。figure 元素的内容应该与主内容相关,但如果被删除,则不应对文档流产生影响。 独立流内容如图像、图表、照片、代码等等
figcaption 定义 figure 元素的标题(caption) "figcaption" 元素应该被置于 "figure" 元素的第一个或最后一个子元素的位置
contextmenu 添加到系统右键菜单 貌似这个功能只有 firefox 支持

BootStrap HTML 模板

  1. <!doctype html>
  2. <html lang="en">
  3. <head>
  4. <!-- Required meta tags -->
  5. <meta charset="utf-8">
  6. <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  7. <meta http-equiv="X-UA-Compatible" content="ie=edge">
  8. <!-- Bootstrap CSS -->
  9. <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
  10. <title>Document</title>
  11. </head>
  12. <body>
  13. <!-- Optional JavaScript -->
  14. <!-- jQuery first, then Popper.js, then Bootstrap JS -->
  15. <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
  16. <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
  17. <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
  18. </body>
  19. </html>

常见 HTML 技术解析

参考资料:github@yangshun@front-end-interview-handbook

DOCTYPE 用途(待做)

DOCTYPE 是 document type 的缩写,是 HTML 用来区分标准模式和怪异模式的声明。

在各个 HTML 版本中的声明方法如下。

通常情况下,<link> 标签最好放在 <head></head> 中,这是规范要求的内容。这样做可以让页面逐步呈现,防止呈现给用户的是空白页面或没有样式的内容,提高了用户体验。

script 标签

通常情况下,<script> 标签最好放在 </body> 之前,这样可以保证 HTML 文档首先完成解析,将页面尽早呈现给用户。解决方案是使用 <script> 标签的 defer、async 属性,具体区别如下:

注意:没有 src 属性的脚本,defer、async 属性将会被忽略。

img 标签

srcset 属性配合 sizes 属性可以实现响应式图片。serset 定义了我们允许浏览器选择的图像集,以及每个图像的大小。sizes 定义了一组媒体条件(例如屏幕宽度)并且指明当某些媒体条件为真时,什么样的图片尺寸是最佳选择。

浏览器遇到该属性后的处理过程:

data- 属性

在 JavaScript 框架流行前,常用 data- 属性把额外数据存储在 DOM 自身中。现在鼓励将数据模型存放在 JavaScript 本身中,并利用框架提供的数据绑定,使之与 DOM 保持更新。

渐进式渲染

渐进式渲染是用于提高网页性能(尤其是提高用户感知的加载速度),以尽快呈现页面的技术。该技术在以前互联网宽带小时常常需要使用,现虽移动终端盛行,但移动网络不稳定,仍然有用武之地。

举例如下:

开发多语言网站

客户端可在发送 HTTP 请求时,使用 Accept-Language 请求头,让服务器返回相匹配语言的 HTML 文档。返回的文档还应在 <html> 标签中声明 lang 属性。后台不同的语言,多以 YML 或 JSON 格式存储,并动态的生成指定的 HTML 页面,整个过程通常需要借助后台框架实现。

在开发多语言网站时,应注意如下事项。

模板语言(待做)

HTML 标准(待做)

Canvas(待做)

设计

字体(待做)

颜色(待做)

CSS

样式表

样式表是一种将网页的内容和表示分离的网页设计形式,在网页设计中网页标记(HTML 或 XHTML)包含页面的语义内容和结构,但没有定义其可视化布局(风格)。相反,风格的定义是在一个外部的样式表文件中,使用如 CSS、XSLT 样式表语言。这种设计方法被认为是一种“分离”,因为它在很大程度上取代了以前风格和结构在一起的定义方法。 这种方法背后的哲学是一种关注点的分离。

优点:

缺点:

文档流

普通文档流

absolute 文档流

复合图层

CSS 标准(待做)

CSS 发展时间线(待做)

其它样式表语言有:

CSS 1.0(待做)

CSS 2.0(待做)

CSS 2.1(待做)

CSS 3.0(待做)

CSS 术语和概念(待做)

CSS 属性(待做)

CSS 值(待做)

整数值、数值、百分比值、长度值、颜色值、字符串值

CSS3:角度值、频率值、时间值

CSS 属性值(待做)

属性值可以由单一内容构成,也可以由“值 + 关键字 +功能符”构成。

属性值:声明、声明块、规则、规则集、选择器

CSS 选择器

通用元素选择器、标签选择器、类选择器、ID 选择器、属选择器性、伪类选择器、伪元素选择器、关系选择器(后代选择器、相邻后代选择器、兄弟选择器、相邻兄弟选择器)。

CSS 基本选择器
序号 选择器 含义 定义在版本
1. * 通用元素选择器,匹配任何元素 2
2. E 标签选择器,匹配所有使用 E 标签的元素 1
3. .info class 选择器,匹配所有 class 属性中包含 info 的元素 1
4. footer id 选择器,匹配所有 id 属性等于 footer 的元素 1
CSS 多元素的组合选择器
序号 选择器 含义 定义在版本
5. E, F 多元素选择器,同时匹配所有 E 元素或 F 元素,E 和 F 之间用逗号分隔 1
6. E F 后代元素选择器,匹配所有属于 E 元素后代的 F 元素,E 和 F 之间用空格分隔 1
7. E > F 子元素选择器,匹配所有 E 元素的子元素 F 2
8. E + F 毗邻元素选择器,匹配所有紧随 E 元素之后的同级元素 F 2
CSS 2.1 属性选择器
序号 选择器 含义 定义在版本
9. E[att] 匹配所有具有 att 属性的 E 元素,不考虑它的值。(注意:E 在此处可以省略,比如"[cheacked]"。以下同。) 2
10. E[att=val] 匹配所有 att 属性等于"val"的 E 元素 2
11. E[att~=val] 匹配所有 att 属性具有多个空格分隔的值、其中一个值等于"val"的 E 元素 2
12. E[att|=val] 匹配所有 att 属性具有多个连字号分隔(hyphen-separated)的值、其中一个值以"val"开头的 E 元素,主要用于 lang 属性,比如"en"、"en-us"、"en-gb"等等 2
CSS 2.1 中的伪类
序号 选择器 含义 定义在版本
13. E:first-child 匹配父元素的第一个子元素 2
14. E:link 匹配所有未被点击的链接 1
15. E:visited 匹配所有已被点击的链接 1
16. E:active 匹配鼠标已经其上按下、还没有释放的E元素 1
17. E:hover 匹配鼠标悬停其上的 E 元素 1
18. E:focus 匹配获得当前焦点的 E 元素 2
19. E:lang(c) 匹配 lang 属性等于 c 的 E 元素 2
CSS 2.1 中的伪元素
序号 选择器 含义 定义在版本
20. E:first-line 匹配 E 元素的第一行 1
21. E:first-letter 匹配 E 元素的第一个字母 1
22. E:before 在 E 元素之前插入生成的内容 2
23. E:after 在 E 元素之后插入生成的内容 2
CSS 3 的同级元素通用选择器
序号 选择器 含义 定义在版本
24. E ~ F 匹配任何在 E 元素之后的同级F元素 3
CSS 3 属性选择器
序号 选择器 含义 定义在版本
25. E[att^="val"] 属性 att 的值以"val"开头的元素 3
26. E[att$="val"] 属性 att 的值以"val"结尾的元素 3
27. E[att*="val"] 属性 att 的值包含"val"字符串的元素 3
CSS 3 中与用户界面有关的伪类
序号 选择器 含义 定义在版本
28. E:enabled 匹配表单中激活的元素 3
29. E:disabled 匹配表单中禁用的元素 3
30. E:checked 匹配表单中被选中的 radio(单选框)或 checkbox(复选框)元素 3
31. E::selection 匹配用户当前选中的元素 3
CSS 3 中的结构性伪类
序号 选择器 含义 定义在版本
32. E:root 匹配文档的根元素,对于 HTML 文档,就是 HTML 元素 3
33. E:nth-child(n) 匹配其父元素的第 n 个子元素,第一个编号为 1 3
34. E:nth-last-child(n) 匹配其父元素的倒数第 n 个子元素,第一个编号为 1 3
35. E:nth-of-type(n) 与 :nth-child() 作用类似,但是仅匹配使用同种标签的元素 3
36. E:nth-last-of-type(n) 与 :nth-last-child() 作用类似,但是仅匹配使用同种标签的元素 3
37. E:last-child 匹配父元素的最后一个子元素,等同于 :nth-last-child(1) 3
38. E:first-of-type 匹配父元素下使用同种标签的第一个子元素,等同于 :nth-of-type(1) 3
39. E:last-of-type 匹配父元素下使用同种标签的最后一个子元素,等同于 :nth-last-of-type(1) 3
40. E:only-child 匹配父元素下仅有的一个子元素,等同于 :first-child :last-child或 :nth-child(1) :nth-last-child(1) 3
41. E:only-of-type 匹配父元素下使用同种标签的唯一一个子元素,等同于 :first-of-type :last-of-type 或 :nth-of-type(1) :nth-last-of-type(1) 3
42. E:empty 匹配一个不包含任何子元素的元素,注意,文本节点也被看作子元素 3
CSS 3的反选伪类
序号 选择器 含义 定义在版本
43. E:not(s) 匹配不符合当前选择器的任何元素 3
CSS 3中的 :target 伪类
序号 选择器 含义 定义在版本
44. E:target 匹配文档中特定"id"点击后的效果 3
CSS 选择器优先级

一种说法:

CSS 优先级,是指 CSS 样式在浏览器中被解析的先后顺序。在比较样式的优先级时,只需统计选择符中的 ID、class 和标签名的个数,然后把相应的权值相加即可,最后根据结果排出优先级。权值较大的优先级越高;权值相同的,后定义的优先级较高。

另一种说法:

优先级通过 4 个维度指标确定,我们假定以 a、b、c、d 命名,分别代表以下含义:

优先级的结果并非通过以上四个值生成一个得分,而是每个值分开比较。a、b、c、d 权重从左到右,依次减小。判断优先级时,从左到右,一一比较,直到比较出最大值,即可停止。所以,如果b的值不同,那么 c 和 d 不管多大,都不会对结果产生影响。比如 0,1,0,0的优先级高于 0,0,10,10。

当出现优先级相等的情况时,最晚出现的样式规则会被采纳。如果你在样式表里写了相同的规则(无论是在该文件内部还是其它样式文件中),那么最后出现的(在文件底部的)样式优先级更高,因此会被采纳。

在写样式时,我会使用较低的优先级,这样这些样式可以轻易地覆盖掉。尤其对写 UI 组件的时候更为重要,这样使用者就不需要通过非常复杂的优先级规则或使用 !important 的方式,去覆盖组件的样式了。

CSS 选择器查询原则及优化准则

浏览器是如何查找元素的呢?浏览器CSS匹配不是从左到右进行查找,而是从右到左进行查找。

比如 #divBox p span.red {color: red;} 浏览器的查找顺序如下:先查找 HTML 中所有 class='red' 的 span 元素,找到后,再查找其父辈元素中是否有 p 元素,再判断 p 的父元素中是否有 ID 为 divBox 的 DIV 元素,如果都存在则匹配上。

浏览器从右到左进行查找的好处是为了尽早过滤掉一些无关的样式规则和元素。

优化目的是减少 CSS 文件大小,提高维护性和可读性。优化准则如下。

CSS 选择器参考链接

CSS 关键字(待做)

CSS 变量(待做)

CSS 长度单位(待做)

相对长度单位

绝对长度单位

CSS Hack

什么是 CSS Hack

我们把这个针对不同的浏览器或不同版本编写相应 CSS 的过程,叫做 CSS Hack。

一般情况下,我们尽量避免使用 CSS Hack,但是有些情况为了顾及用户体验实现向下兼容,不得已才使用 Hack。使用 Hack 虽然对页面表现的一致性有好处,但过多的滥用会造成 HTML 文档混乱不堪,增加管理和维护的负担。

CSS Hack 的分类

CSS Hack 大致有 3 种表现形式。实际项目中 CSS Hack大部分是针对IE浏览器不同版本之间的表现差异而引入的。

说明,在标准模式中:
1. "-" 减号是IE6专有的hack
2. "\9" IE6/IE7/IE8/IE9/IE10都生效
3. "\0" IE8/IE9/IE10都生效,是IE8/9/10的hack
4. "\9\0" 只对IE9/IE10生效,是IE9/10的hack

  1. *html *前缀只对 IE6 生效
  2. *+html *+前缀只对 IE7 生效
  3. @media screen\9{...}只对 IE6/7 生效
  4. @media \0screen {body { background: red; }}只对 IE8 有效
  5. @media \0screen\,screen\9{body { background: blue; }}只对 IE6/7/8 有效
  6. @media screen\0 {body { background: green; }} 只对 IE8/9/10 有效
  7. @media screen and (min-width:0\0) {body { background: gray; }} 只对 IE9/10 有效
  8. @media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {body { background: orange; }} 只对 IE10 有效
  9. 等等
  1. 只在 IE 下生效
  2. <!--[if IE]>
  3. 这段文字只在 IE 浏览器显示
  4. <![endif]-->
  5. 只在 IE6 下生效
  6. <!--[if IE 6]>
  7. 这段文字只在 IE6 浏览器显示
  8. <![endif]-->
  9. 只在 IE6 以上版本生效
  10. <!--[if gte IE 6]>
  11. 这段文字只在 IE6 以上(包括)版本 IE 浏览器显示
  12. <![endif]-->
  13. 只在 IE8 上不生效
  14. <!--[if ! IE 8]>
  15. 这段文字在非 IE8 浏览器显示
  16. <![endif]-->
  17. IE 浏览器生效
  18. <!--[if !IE]>
  19. 这段文字只在非 IE 浏览器显示
  20. <![endif]-->
常见的浏览器兼容问题机器 hack 技巧
  1. .bb{
  2. background-color: red;/* 所有识别 */
  3. background-color: #00deff\9; /* IE6、7、8 识别*/
  4. +background-color: #a200ff;/* IE6、7 识别*/
  5. _background-color: #1e0bd1;/* IE6 识别*/
  6. }
CSS Hack 参考资料

CSS 解析器(待做)

CSS 功能符(待做)

布局模型

布局是承上启下的中间环节,渲染树是从 DOM 树映射而来的可布局的层级关系,通过应用布局属性确定视图排版。同时布局更是性能的瓶颈所在,需要灵活运用缓存、线程切换等手段来优化性能。

把布局和视图生成两部分从整个架构中抽离出来,也可成为客户端 UI 框架。

布局模型基本属性

CSS 2.1 定义了以下 4 个布局模型。

CSS 3 引入了以下布局模型。

布局模型算法思想

一个对象的布局由位置和尺寸这两个要素唯一确定,但实际使用中很少需要为其赋予绝对值来指定排版,而是通过指定相对位置、相对宽高、相互关系来间接实现。所以布局要做的就是从这些相对信息中推算出每个对象的绝对信息,通过多次从根节点开始向下遍历,以及从子节点向上回溯,不断估计、修正,计算出树上每个节点的唯一布局。

无论何种框架,要实现通过用网页的规范或自定义的模板规范来达到动态控制原生 UI,都会包含以下过程:

React-Native 和 Weex 的核心布局算法都采用 Facebook 开源的 CSSLayout 算法,CSSLayout 基于 W3C 标准的 Flexbox 模型对页面元素排版,同时也支持相对布局和绝对布局,iOS 和 Andriod 平台都适用。

CSSLayout 对容器可应用 FlexDirectionFlexWrapJustifiyContentAlignItemsAlignContent 属性。对元素可应用 FlexAlignSelf 属性(除了 Flex 属性,还支持普通的 Position 和 Overflow 属性)。

布局算法把外部传入的计算属性先转化为对应的数组,通过下标访问具体值,而下标又是通过主轴、交叉轴构造的映射关系表来获取。

计算过程用到的样式属性如下。

计算过程中用到的布局属性如下。

CSSLayout 算法首先对内容节点、叶子节点和非布局节点这三种情况进行预处理,提前返回,减少走完整个流程的次数,尽可能的减少计算量。

CSSLayout 算法中的缓存分为两个层次,如下。

布局模型技术方案(待做)

居中布局、多列布局、全局布局、多列等高布局

水平垂直居中
  1. div {
  2. position: absolute;
  3. width: 300px;
  4. height: 300px;
  5. margin: auto;
  6. top: 0;
  7. right: 0;
  8. bottom: 0;
  9. left: 0;
  10. background-color: pink;
  11. }
  1. div {
  2. position: relative; /* 或 absolute */
  3. width: 500px;
  4. height: 300px;
  5. top: 50%;
  6. left: 50%;
  7. margin: -150px 0 0 -250px;
  8. background-color: pink;
  9. }
  1. div {
  2. position: relative; /* 或 absolute */
  3. width: 500px;
  4. height: 300px;
  5. top: 50%;
  6. left: 50%;
  7. transform: translate(-50%, -50%);
  8. margin: -150px 0 0 -250px;
  9. background-color: pink;
  10. }
  1. .container {
  2. display: flex;
  3. align-items: center;
  4. justify-content: center;
  5. height: 200px;
  6. width: 200px;
  7. background-color: yellow;
  8. }
  9. .container div {
  10. width: 100px;
  11. height: 100px;
  12. background-color: pink;
  13. }

布局模型混合应用(待做)

以下混合方案可通过多种布局技术来实现。

布局模型参考链接

相对定位、绝对定位与盒模型等(待做)

5 个 position 值与描述

描述
static 默认值。没有定位,元素出现在正常的流中(忽略 top, bottom, left, right 或者 z-index 声明)。inherit 规定应该从父元素继承 position 属性的值。
relative 生成相对定位的元素,相对于其正常位置进行定位。因此,"left:20" 会向元素的 LEFT 位置添加 20 像素。
absolute 生成绝对定位的元素,相对于 static 定位以外的第一个父元素进行定位。元素的位置通过 "left", "top", "right" 以及 "bottom" 属性进行规定。
fixed 生成绝对定位的元素,相对于浏览器窗口进行定位。元素的位置通过 "left", "top", "right" 以及 "bottom" 属性进行规定。老 IE 不支持。
inherit 规定从父元素继承 position 属性的值。

relativefixedabsolutestatic 四种定位有什么区别?

经过定位的元素,其 position 属性值必然是 relativefixedabsolutestatic

19 个 display 值与描述

描述
none 此元素不会被显示。
block 此元素将显示为块级元素,此元素前后会带有换行符。
inline 默认。此元素会被显示为内联元素,元素前后没有换行符。
inline-block 行内块元素。(CSS2.1 新增的值)
list-item 此元素会作为列表显示。
run-in 此元素会根据上下文作为块级元素或内联元素显示。
compact CSS 中有值 compact,不过由于缺乏广泛支持,已经从 CSS2.1 中删除。
marker CSS 中有值 marker,不过由于缺乏广泛支持,已经从 CSS2.1 中删除。
table 此元素会作为块级表格来显示(类似 <table>),表格前后带有换行符。
inline-table 此元素会作为内联表格来显示(类似 <table>),表格前后没有换行符。
table-row-group 此元素会作为一个或多个行的分组来显示(类似 <tbody>)。
table-header-group 此元素会作为一个或多个行的分组来显示(类似 <thead>)。
table-footer-group 此元素会作为一个或多个行的分组来显示(类似 <tfoot>)。
table-row 此元素会作为一个表格行显示(类似 <tr>)。
table-column-group 此元素会作为一个或多个列的分组来显示(类似 <colgroup>)。
table-column 此元素会作为一个单元格列显示(类似 <col>
table-cell 此元素会作为一个表格单元格显示(类似 <td><th>
table-caption 此元素会作为一个表格标题显示(类似 <caption>
inherit 规定应该从父元素继承 display 属性的值。

其中,inlineinline-blockblock 的区别是:

  block inline-block inline
大小 填充其父容器的宽度。 取决于内容。 取决于内容。
定位 从新的一行开始,并且不允许旁边有 HTML 元素(除非是float) 与其他内容一起流动,并允许旁边有其他元素。 与其他内容一起流动,并允许旁边有其他元素。
能否设置 width 和 height 不能。设置会被忽略。
可以使用 vertical-align 对齐 不可以 可以 可以
边距(margin)和填充(padding) 各个方向都存在 各个方向都存在 只有水平方向存在。垂直方向会被忽略。尽管 border 和 padding 在 content 周围,但垂直方向上的空间取决于 'line-height'
浮动(float) - - 就像一个 block 元素,可以设置垂直边距和填充。

盒模型

CSS盒模型描述了以文档树中的元素而生成的矩形框,并根据排版模式进行布局。每个盒子都有一个内容区域(例如文本,图像等)以及周围可选的padding、border和margin区域。

CSS盒模型负责计算:

盒模型有以下规则:

.png)

Flexbox

时代背景:随着终端设备的多样化发展,需要拥有足够的定义来支持那些必须随用户代理(user agent)不同或设备方向从水平转为垂直等各种变化而变换方向、调整大小、拉伸、收缩的应用程序组件。

flexbox 是一种布局机制。分为弹性容器(Flex container)和弹性项目(Flex item),分别有自己的属性。在 flex 中,弹性容器的子元素可以在任何方向上排布,也可以“弹性伸缩”其尺寸,既可以增加尺寸以填满未使用的空间,也可以收缩尺寸以避免父元素溢出。

flexbox 被期望设计为:

优点
* 告别使用浮动的 <div> 元素、绝对定位和一些 JavaScript hacks,使用仅仅几行 CSS 就可以构建出水平或垂直方向的布局。

缺点
* 一个 flexbox 一次只能处理一个维度上的元素布局,一行或者一列。

Flexbox 容器属性

  1. flex-direction: row | row-reverse | column | column-reverse;
  2. <div class="md-section-divider"></div>
  1. flex-wrap: nowrap | wrap | wrap-reverse
  2. <div class="md-section-divider"></div>
  1. flex-flow: <flex-direction> || <flex-wrap>;
  2. <div class="md-section-divider"></div>
  1. justify-content: flex-start | flex-end | center | space-between | space-around;
  2. <div class="md-section-divider"></div>
  1. align-items: flex-start | flex-end | center | baseline | stretch;
  2. <div class="md-section-divider"></div>
  1. align-content: flex-start | flex-end | center | space-between | space-around | stretch;
  2. <div class="md-section-divider"></div>

Flexbox 项目属性

具体在下节 “flexbox 相关词汇” 有所拓展。

  1. order: <integer>;
  2. <div class="md-section-divider"></div>
  1. flex-grow: <number>; /* default 0 */
  2. <div class="md-section-divider"></div>
  1. flex-shrink: <number>; /* default 1 */
  2. <div class="md-section-divider"></div>
  1. flex-basis: <length> | auto; /* default auto */
  2. <div class="md-section-divider"></div>
  1. flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]
  2. <div class="md-section-divider"></div>
  1. align-self: auto | flex-start | flex-end | center | baseline | stretch;
  2. <div class="md-section-divider"></div>

Flexbox 相关词汇

需要我们从诸如水平/行内轴和垂直/块级轴这些术语中解放出来,用一套新的术语来正确描述此模型。

Flexbox 要点须知

Flexbox 浏览器兼容性

早期 W3C 草案为 《Flexible Box Layout Module》,发布于 2009 年 7 月 23 日,语法大不相同。

Flexbox 参考链接

在线试验场地

参考博客/文档

CSS Grid Layout(待做)

工程化 CSS

常见 CSS 技术解析

CSS 可视化格式模型

说到底,CSS 的可视化格式模型就是规定了浏览器在页面中如何处理文档树。

关键字:

CSS有三种定位机制:普通流,浮动,绝对定位,如无特别提及,下文中都是针对普通流中的

Containing Block 包含块

一个元素的 box 的定位和尺寸,会与某一矩形框有关,这个框就称之为包含块。元素会为它的子孙元素创建包含块,但是,并不是说元素的包含块就是它的父元素,元素的包含块与它的祖先元素的样式等有关系。

Controlling Box 控制框

块级元素和块框以及行内元素和行框的相关概念。

如果一个框里,有一个块级元素,那么这个框里的内容都会被当作块框来进行格式化,因为只要出现了块级元素,就会将里面的内容分块几块,每一块独占一行。出现行内可以用匿名块框解决。

如果一个框里,没有任何块级元素,那么这个框里的内容会被当成行内框来格式化,因为里面的内容是按照顺序成行的排列。

块框

匿名块框

  1. <DIV>
  2. Some text
  3. <P>More text</P>
  4. </DIV>
  5. <div class="md-section-divider"></div>

如果一个块框在其中包含另外一个块框,那么我们强迫它只能包含块框,因此其它文本内容生成出来的都是匿名块框(而不是匿名行内框)

行内框

匿名行内框

  1. <P>Some <EM>emphasized</EM> text</P>
  2. <div class="md-section-divider"></div>

display 属性对不同框的影响

FC 格式化上下文

FC 即格式化上下文,它定义框内部的元素渲染规则,比较抽象,譬如:

不同类型的框参与的 FC 类型不同,譬如块级框对应 BFC,行内框对应 IFC。并不是所有框都会产生 FC,而需符合特定条件。

BFC 块格式化上下文

在 BFC 中,每一个元素左外边与包含块的左边相接触(对于从右到左的格式化,右外边接触包含块的右边),即使清除浮动也是如此,除非这个元素也创建了一个新的 BFC。浮动元素一般会直接贴近它的包含块的左边,与普通元素重合。

BFC 特点:

如何触发 BFC:

注:display: table 本身不产生 BFC,但是会产生匿名框,而这个匿名框产生 BFC。匿名框包含 display: table-cell 的框

IFC 行内格式化上下文

IFC 规则:

行框的宽度由它的包含块和其中的浮动元素决定,高度的确定由行高度计算规则决定。行框的规则:

相比 BFC 规则来说,IFC 可能更加抽象——IFC 没有那么条理清晰的规则和触发条件。但总的来说,它就是行内元素自身如何显示以及在框内如何摆放的渲染规则,这样描述应该更容易理解。

行内元素与 IFC

CSS BEM 规范

BEM(Block Element Modifier)原则上建议为独立的 CSS 类命名,并且在需要层级关系时,将关系也体现在命名中,这自然会使选择器高效且易于覆盖。

CSS BNF 规范(待做)

Resetting(重置) CSS 和 Normalizing(标准化) CSS

CSS 隐藏内容的技巧

这些与元素的可访问性有关。

触发浏览器重新布局(reflow)、重绘(repaint)、合成(compositing)的 CSS 属性及其机制(待做)

响应式设计与自适应设计

响应式设计和自适应设计都以提高不同设备间的用户体验为目标,根据视窗大小、分辨率、使用环境和控制方式等参数进行优化调整。

CSS 预处理器优缺点

优点:

缺点:

CSS 自动化兼容工具 autoprefixer (待做)

响应式的分辨率切割点

BootStrap
  1. // Extra small devices (portrait phones, less than 576px)
  2. // No media query since this is the default in Bootstrap
  3. // Small devices (landscape phones, 576px and up)
  4. @media (min-width: 576px) { ... }
  5. // Medium devices (tablets, 768px and up)
  6. @media (min-width: 768px) { ... }
  7. // Large devices (desktops, 992px and up)
  8. @media (min-width: 992px) { ... }
  9. // Extra large devices (large desktops, 1200px and up)
  10. @media (min-width: 1200px) { ... }
  11. <div class="md-section-divider"></div>
Five Grid Tiers
Extra small <576px xs
Small ≥576px sm
Medium ≥768px md
Large ≥992px lg
Extra large ≥1200px xl
Material Design Viewport Breakpoints
device code types range
phone_iphoneExtra small xs small to large handset
tabletSmall sm small to medium tablet 600px > < 960px
laptopMedium md large tablet to laptop 960px > < 1264*
desktop_windowsLarge lg desktop 1264 > < 1904px*
tvExtra large xl 4k and ultra-wides
1904px*

备注: * -16px on Desktop

CSS sprites,雪碧图

HTTP 2.0 不需要

雪碧图是把多张图片整合到一张上的图片。它被运用在众多使用了很多小图标的网站上。实现方法:

好处:

JavaScript

ECMAScript 标准(待做)

ECMAScript5(待做)

'use strict'

'use strict' 是用于对整个脚本或单个函数启用严格模式的语句。严格模式是可选择的一个限制 JavaScript 的变体的一种方式。

优点:

缺点:

缺点:

ECMAScript6(待做)

JavaScript 关键字与保留字

关键字与保留字列表

ECMA-262 定义了 ECMAScript 支持的一套关键字和一套保留字。如果把关键字用作变量名或函数名,可能得到诸如 "Identifier Expected"(应该有标识符、期望标识符)这样的错误消息。其中,关键字标识了 ECMAScript 语句的开头和/或结尾,保留字在某种意思上是为将来的关键字而保留的单词,因此关键字与保留字军不能被用作变量名或函数名。

JavaScript 关键字 (待做待加入 ES6+)
break case catch continue default
delete do else finally for
function if in instanceof new
return switch this throw try
typeof var void while with
class(ES5) enum(ES5) export(ES5) import(ES5) super(ES5)
extends(ES5)
JavaScript 保留字 (待做待加入 ES6+)
abstract boolean byte char const
debugger double final float goto
implements int interface long native
package private protected public short
static synchronized throws transient volatile
typeof
constructor 与 instanceof

只要一个对象 a 的内部 prototype 属性或者它的原型链上的任意对象与 b.prototype 是同一个对象,那么 a instanceof b 就返回true。

  1. Array instanceof Object // true
  2. Object instanceof Object // true
  3. Array instanceof Array // false
  4. null instanceof Object // false
  5. NaN instanceof Number // false
  6. 'str' instanceof String // false
  7. new String('str') instanceof String // true
  8. <div class="md-section-divider"></div>

对象字面量 vs 构造函数创建对象对比

在 JavaScript 中,我们可以通过 new 关键字、Object.create() 函数创建一个对象,或者使用字面量记法(也称对象初始化器材——object initializer)。字面量记法使用花括号定义对象,对象的 Property 与值以名称 - 值对的方式组织,用冒号分隔。我们还需要在每个名称 - 值对的最后加上逗号(除了最后一个名称 - 值对)。值可以包含变量、函数,或者其他对象。
—— 《 SPA 设计与架构 》

字面量的优势:

关键字与保留字逐个深入(待做)

with 关键字(待做)

其它标识符命名避免原则

Java 关键字示例
getClass java JavaArray
javaClass JavaObject JavaPackage
HTML、Windows 对象属性和名称
alert all anchor anchors area
assign blur button checkbox clearInterval
clearTimeout clientInformation close closed confirm
constructor crypto decodeURI decodeURIComponent defaultStatus
document element elements embed embeds
encodeURI encodeURIComponent escape event fileUpload
focus form forms frame innerHeight
innerWidth layer layers link location
mimeTypes navigate navigator frames frameRate
hidden history image images offscreenBuffering
open opener option outerHeight outerWidth
packages pageXOffset pageYOffset parent parseFloat
parseInt password pkcs11 plugin prompt
propertyIsEnum radio reset screenX screenY
scroll secure select self setInterval
setTimeout status submit taint text
textarea top unescape untaint window
onblur onclick onerror
onkeydown onkeypress onkeyup
onload onmouseup onmousedown

一个实例是 const 关键字,用于定义变量。一些 JavaScript 引擎把 const 当作 var 的同义词。另一些引擎则把 const 当作只读变量的定义。Const 是 JavaScript 的扩展。JavaScript 引擎支持它用在 Firefox 和 Chrome 中。但是它并不是 JavaScript 标准 ES3 或 ES5 的组成部分。建议:不要使用它。

关键字与保留字参考链接

JavaScript 内置对象

不建议拓展内置对象,唯一使用场景是创建 polyfill,为老版本浏览器缺失的方法提供自己的实现。

JavaScript 面向对象

JavaScript 函数重载

方法一

  1. function overLoading() {
  2.   // 根据 arguments.length,对不同的值进行不同的操作
  3.   switch(arguments.length) {
  4.     case 0:
  5.       /*操作1的代码写在这里*/
  6.       break;
  7.     case 1:
  8.       /*操作2的代码写在这里*/
  9.       break;
  10.     default:
  11.      break;
  12. }
  13. }
  14. <div class="md-section-divider"></div>

方法二

  1. // addMethod
  2. function addMethod(object, name, fn) {
  3.   var old = object[name]; // 把前一次添加的方法存在一个临时变量 old 里面
  4.   object[name] = function() { // 重写了 object[name] 的方法
  5.     // 如果调用 object[name] 方法时,传入的参数个数跟预期的一致,则直接调用
  6.     if(fn.length === arguments.length) {
  7.       return fn.apply(this, arguments);
  8.     // 否则,判断 old 是否是函数,如果是,就调用 old
  9.     } else if(typeof old === "function") {
  10.       return old.apply(this, arguments);
  11.     }
  12.   }
  13. }
  14. var people = {
  15.   values: ["Dean Edwards", "Alex Russell", "Dean Tom"]
  16. };
  17. /* 下面开始通过 addMethod 来实现对 people.find 方法的重载 */
  18. // 不传参数时,返回 peopld.values 里面的所有元素
  19. addMethod(people, "find", function() {
  20.   return this.values;
  21. });
  22. // 传一个参数时,按 first-name 的匹配进行返回
  23. addMethod(people, "find", function(firstName) {
  24.   var ret = [];
  25.   for(var i = 0; i < this.values.length; i++) {
  26.     if(this.values[i].indexOf(firstName) === 0) {
  27.       ret.push(this.values[i]);
  28.     }
  29.   }
  30.   return ret;
  31. });
  32. // 传两个参数时,返回 first-name 和 last-name 都匹配的元素
  33. addMethod(people, "find", function(firstName, lastName) {
  34.   var ret = [];
  35.   for(var i = 0; i < this.values.length; i++) {
  36.     if(this.values[i] === (firstName + " " + lastName)) {
  37.       ret.push(this.values[i]);
  38.     }
  39.   }
  40.   return ret;
  41. });
  42. // 测试:
  43. console.log(people.find()); //["Dean Edwards", "Alex Russell", "Dean Tom"]
  44. console.log(people.find("Dean")); //["Dean Edwards", "Dean Tom"]
  45. console.log(people.find("Dean Edwards")); //["Dean Edwards"]
  46. <div class="md-section-divider"></div>

JavaScript this 指针(待做)

this 是 JavaScript 语言的一个关键字,函数调用的方式决定了 this 的值,this 取值符合以下标准:

  1. 在调用函数时使用 new 关键字,函数内的 this 是一个全新的对象。
  2. 如果 apply、call 或 bind 方法用于调用、创建一个函数,函数内的 this 就是作为参数传入这些方法的对象。
  3. 当函数作为对象里的方法被调用时,函数内的 this 时调用该函数的对象。比如当 obj.method() 被调用时,函数内的 this 将绑定到 obj 对象。
  4. 如果调用函数不符合上述规则,那么 this 的值指向全局对象。浏览器环境下 this 的值指向 window 对象,但是在严格模式下('use strict'),this 的值为 undefined。
  5. 如果符合上述多个规则,则较高的规则(1 号最高,4 号最低)将决定 this 的值。
  6. 如果该函数是 ES2015 中的箭头函数,将忽略上面的所有规则,this 被设置为它被创建时的上下文。

JavaScript 高阶函数

高阶函数是将一个或多个函数作为参数的函数,它用于数据处理,也可能将函数作为返回结果。高阶函数是为了抽象一些重复执行的操作。如 map、forEach、filter、reduce 等。

.apply()、.bind()、.call()

.apply() 和 .call() 都用于调用函数,第一个参数将用作函数内 this 的值。然而 .call 接受逗号分隔的参数作为后面的参数,而 .apply 接受一个参数数组作为后面的参数。

  1. function add (a, b) {
  2. return a + b;
  3. }
  4. console.log(add.call(null, 1, 2)) // 3
  5. console.log(add.apply(null, [1, 2])) // 3
  6. <div class="md-section-divider"></div>

.bind() 方法创建一个新的函数,当被调用时,将其 this 关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列。这种将 this 的值绑定到想要传递给其他函数的类的方法中是非常有用的。

JavaScript 原型链

所有 JavaScript 对象都有一个 prototype 属性,指向它的原型对象。当试图访问一个对象的属性时,如果没有在该对象上找到,它还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。这种行为是在模拟经典的继承,与其说是继承,不如说是委托。

  1. Function instanceof Object; // true
  2. Object instanceof Function; // true
  3. //①构造器Function的构造器是它自身
  4. Function.constructor=== Function;//true
  5. //②构造器Object的构造器是Function(由此可知所有构造器的constructor都指向Function)
  6. Object.constructor === Function;//true
  7. //③构造器Function的__proto__是一个特殊的匿名函数function() {}
  8. console.log(Function.__proto__);//function() {}
  9. //④这个特殊的匿名函数的__proto__指向Object的prototype原型。
  10. Function.__proto__.__proto__ === Object.prototype//true
  11. //⑤Object的__proto__指向Function的prototype,也就是上面③中所述的特殊匿名函数
  12. Object.__proto__ === Function.prototype;//true
  13. Function.prototype === Function.__proto__;//true
  14. <div class="md-section-divider"></div>

1、所有的构造器的constructor都指向Function
2、Function的prototype指向一个特殊匿名函数,而这个特殊匿名函数的 __proto__ 指向Object.prototype

JavaScript 闭包(待做)

能够在 IIFE 完成执行后任维系着内部功能的生存期。

闭包,closure,概念最早提出在 1964 年,1975 年最早实现,是函数和声明该函数的词法环境的组合。词法作用域中使用的域,是变量在代码中声明的位置所决定的。

闭包就是能够读取其他函数内部变量的函数。

为什么使用闭包:

Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量。另一方面,在函数外部自然无法读取函数内的局部变量。本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

如何从外部读取局部变量?

那就是在函数的内部,再定义一个函数。这就是 JavaScript 语言特有的"链式作用域"结构(chain scop),子对象会一级一级地向上寻找所有父对象的变量。

使用闭包的注意点

1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

JavaScript IIFE 立即执行函数

IIFE,Immediately Invoked Function Expressions,代表立即执行函数。

IIFE 的外层括号不是必须的,因为 IIFE 是一个函数表达式。

JavaScript 函数式

柯里化函数

柯里化,currying,是一种模式,其中具有多个参数的函数被分解为多个函数,当被串联调用时,将一次一个地累积所有需要的参数。这种技术帮助编写函数式风格的代码,使代码更易读、紧凑。值得注意的是,对于需要被 curry 的函数,它需要从一个函数开始,然后分解成一系列函数,每个函数都需要一个参数。

JavaScript 数据类型

JavaScript 全局对象

根据 JavaScript 的运行环境,在 JavaScript 中存在两种全局对象:JavaScript 内置全局对象和 JavaScript 宿主全局对象,后者依赖运行环境。

JavaScript 内置全局对象

参考:http://mm.devhints.cn/

没有 Web 浏览器宿主环境的条件下,对于任何 JavaScript 程序,在程序开始之前,JavaScript 解释器都会初始化一个全局对象供程序使用,通过使用该 JavaScript 全局对象,可以访问所有预定义的全局属性、全局普通函数、全局构造函数和全局对象。这些预定义的全局 XX 都是“JS全局对象”的属性。此“JS全局对象”没有名称,可以在全局作用域内使用 this 关键字或引用“JavaScript 全局对象”。

JavaScript 宿主全局对象 - 浏览器宿主

Web 浏览器这个宿主环境中特有的 JavaScript 全局对象为“window 全局对象”,“window 全局对象” 提供了与当前窗口、页面有关的诸多属性与方法。除了这些与浏览器有关的全局属性和方法,window 对象还封装了“JavaScript 内置全局对象”,并向外暴露“JavaScript 内置全局对象的属性与接口”。因此,当进行浏览器端 JavaScript 编程时,只需关心“window 全局对象”即可。

JavaScript 宿主全局对象 - Node.js 宿主(待做)

JSON 本质

JSON 对象包含两个方法: 用于解析 JavaScript Object Notation (JSON) 的 parse() 方法,以及将对象/值转换为 JSON 字符串的 stringify() 方法。除了这两个方法, JSON 这个对象本身并没有其他作用,也不能被调用或者作为构造函数调用。

JSON 风格指南

JavaScript 与 JSON 的区别

JavaScript类型 JSON 的不同点
对象和数组 属性名称必须是双引号括起来的字符串;最后一个属性后不能有逗号。
数值 禁止出现前导零( JSON.stringify 方法自动忽略前导零,而在 JSON.parse 方法中将会抛出 SyntaxError);如果有小数点, 则后面至少跟着一位数字。
字符串 只有有限的一些字符可能会被转义;禁止某些控制字符; Unicode 行分隔符 (U+2028)和段分隔符 (U+2029)被允许 ; 字符串必须用双引号括起来。请参考下面的示例,可以看到 JSON.parse() 能够正常解析,但将其当作JavaScript解析时会抛出 SyntaxError 错误:
  1. let code = '"\u2028\u2029"';
  2. JSON.parse(code); // 正常
  3. eval(code); // 错误
  4. <div class="md-section-divider"></div>

JSON.parse()

解析 JSON 字符串并返回对应的值,可以额外传入一个转换函数,用来将生成的值和其属性, 在返回之前进行某些修改。

JSON.stringify()

返回与指定值对应的JSON字符串,可以通过额外的参数, 控制仅包含某些属性, 或者以自定义方法来替换某些 key 对应的属性值。

JSON Polyfill

JSON对象可能不被老版本的浏览器支持。可以将下面的代码放到JS脚本最开始的位置,这样就可以在没有原生支持 JSON 对象的浏览器(如IE6)中使用 JSON对象。

以下算法是对原生JSON对象的模仿:

  1. if (!window.JSON) {
  2. window.JSON = {
  3. parse: function(sJSON) { return eval('(' + sJSON + ')'); },
  4. stringify: (function () {
  5. var toString = Object.prototype.toString;
  6. var isArray = Array.isArray || function (a) { return toString.call(a) === '[object Array]'; };
  7. var escMap = {'"': '\\"', '\\': '\\\\', '\b': '\\b', '\f': '\\f', '\n': '\\n', '\r': '\\r', '\t': '\\t'};
  8. var escFunc = function (m) { return escMap[m] || '\\u' + (m.charCodeAt(0) + 0x10000).toString(16).substr(1); };
  9. var escRE = /[\\"\u0000-\u001F\u2028\u2029]/g;
  10. return function stringify(value) {
  11. if (value == null) {
  12. return 'null';
  13. } else if (typeof value === 'number') {
  14. return isFinite(value) ? value.toString() : 'null';
  15. } else if (typeof value === 'boolean') {
  16. return value.toString();
  17. } else if (typeof value === 'object') {
  18. if (typeof value.toJSON === 'function') {
  19. return stringify(value.toJSON());
  20. } else if (isArray(value)) {
  21. var res = '[';
  22. for (var i = 0; i < value.length; i++)
  23. res += (i ? ', ' : '') + stringify(value[i]);
  24. return res + ']';
  25. } else if (toString.call(value) === '[object Object]') {
  26. var tmp = [];
  27. for (var k in value) {
  28. if (value.hasOwnProperty(k))
  29. tmp.push(stringify(k) + ': ' + stringify(value[k]));
  30. }
  31. return '{' + tmp.join(', ') + '}';
  32. }
  33. }
  34. return '"' + value.toString().replace(escRE, escFunc) + '"';
  35. };
  36. })()
  37. };
  38. }
  39. <div class="md-section-divider"></div>

JavaScript 关联数组(Map)

其它多数语言里,数组分为索引数组和关联数组,索引数组又分为一维数组、二维数组和多维数组。
引用:“JavaScript 里面没有关联数组和索引数组这两种不同的区分,一切对象都是键值对,数组也是对象,数组也可以看作是键值对。”
自我感悟,存疑:JavaScript 还是有索引数组和关联数组的微小差异。索引数组和 length 属性直接挂钩,关联数组其实访问的是数组上的属性及其值,length 一般都是 0(没有真正的数据内容)。

在计算机科学中,关联数组(英语:Associative Array),又称映射(Map)、字典(Dictionary)是一个抽象的数据结构,它包含着类似于(键,值)的有序对。一个关联数组中的有序对可以重复(如C++中的 multimap)也可以不重复(如 C++ 中的 map)。这种数据结构包含以下几种常见的操作:

字典问题是设计一种能够具备关联数组特性的数据结构。解决字典问题的常用方法,是利用散列表,但有些情况下,也可以直接使用二叉查找树或其他结构。
许多程序设计语言内置基本的数据类型,提供对关联数组的支持。而内容定址存储器则是硬件层面上实现对关联数组的支持。

属性值和键值的异同
  1. var a = [1,2,3]; // 定义一个数组
  2. console.log(a);
  3. console.log(a.length); // 结果为3
  4. a["name"] = "xiaoming"; // 我们再给它赋值,这是给 a 数组增加了一个属性叫 name,而不是在数组里添加数据。
  5. console.log(a); // [1, 2, 3, aaa: 1],a 依然是数组
  6. console.log(a.length); // 结果还是为 3
  7. a.push(4);
  8. console.log(a) // [1, 2, 3, 4, aaa: 1],a 依然是数组
  9. console.log(a.length); // 结果为4
  10. <div class="md-section-divider"></div>
  1. var j1 = {
  2. name: 'j1',
  3. age: 18,
  4. fun: function () {
  5. console.log(1)
  6. }
  7. }
  8. var j2 = {
  9. "name": "j2",
  10. "age": 18,
  11. "fun": function () {
  12. console.log(1)
  13. }
  14. }
  15. <div class="md-section-divider"></div>
遍历关联数组
  1. for (var key in array){
  2. console.log("键:",key);
  3. console.log("值:",array[key]);
  4. }
  5. <div class="md-section-divider"></div>
遍历对象
区分 Object 和 Array
  1. function isArrayOne (arr) {
  2. return Object.prototype.toString.call(arr) === "[object Array]"
  3. }
  4. var obj = {"k1":"v1"};
  5. var arr = [1,2];
  6. console.log("对象的结果:"+isArrayOne(obj)); // false
  7. console.log("数组的结果:"+isArrayOne(arr)); // true
  8. <div class="md-section-divider"></div>
  1. function isArray(obj) { //obj 是待检测的对象,如果返回 true 则为数组
  2. if (Array.isArray) {
  3. return Array.isArray(obj);
  4. } else {
  5. return Object.prototype.toString.call(obj)==="[object Array]";
  6. }
  7. }
  8. <div class="md-section-divider"></div>
  1. var obj = {"k1":"v1"};
  2. var arr = [1,2];
  3. console.log("Instanceof处理对象的结果:"+(obj instanceof Array));
  4. console.log("Instanceof处理数组的结果:"+(arr instanceof Array));
  5. <div class="md-section-divider"></div>
  1. Array.prototype.isPrototypeOf(arr) // true 表示是数组,false 不是数组
  2. <div class="md-section-divider"></div>
  1. var obj = {'k':'v'};
  2. var t1 = new Array(1);
  3. var t2 = t1;
  4. console.log(obj.constructor == Array); // false
  5. console.log(t1.constructor == Array); // true
  6. console.log(t2.constructor == Array); // true
  7. <div class="md-section-divider"></div>
  1. function isArrayFour (arr) {
  2. if (typeof(arr)==="object") {
  3. if (arr.concat) {
  4. return "This is Array";
  5. } else {
  6. return "This Not Array";
  7. }
  8. }
  9. }
  10. var arr = [1];
  11. var obj = {'k':'v'};
  12. console.log(typeof(arr));
  13. console.log(typeof(obj));
  14. console.log(isArrayFour(arr));
  15. console.log(isArrayFour(obj));
  16. <div class="md-section-divider"></div>
关联数组和索引数组的遍历效率问题

遍历赋值以下数组时,第一次耗费时间差不多,浏览器默认优化之后,arr2 作为 Array 而非 Object 存取速度更快。

  1. var arr = new Array(50000000);
  2. var arr2 = {};
  3. <div class="md-section-divider"></div>

JavaScript 数据类型操作

对象遍历
数组遍历
数组浅拷贝与深拷贝
  1. var a = [1, 1],
  2. b = a;
  3. console.log(a === b) // true
  4. <div class="md-section-divider"></div>
  1. function cloneObj(obj) {
  2. var tempObj = {};
  3. if (obj instanceof Array) {
  4. tempObj = [];
  5. }
  6. for (var prop in obj) {
  7. if (typeof prop === 'Object') {
  8. cloneObj(prop);
  9. }
  10. tempObj[prop] = obj[prop];
  11. }
  12. return tempObj;
  13. }
  14. var myCountry = {
  15. name: 'China',
  16. birth: 1949
  17. }
  18. var country = cloneObj(myCountry);
  19. console.log(country === myCountry); // false
  20. <div class="md-section-divider"></div>

JavaScript DOM & BOM

JavaScript DOM

DOM,文档对象模型

虚拟 DOM 见 SPA 章节

  1. var div1 = document.getElementById('div1')
  2. // 添加新节点
  3. var p1 = document.createElement('p')
  4. p1.innerHTML = 'this is p1'
  5. div.appendChild(p1)
  6. // 移动节点,而非拷贝
  7. var p2 = document.getElementById('p2')
  8. div1.appendChild(p2)
  9. <div class="md-section-divider"></div>
  1. var div1 = document.getElementById('div1')
  2. var parent = div1.parentElement
  3. <div class="md-section-divider"></div>
  1. var div1 = document.getElementById('div1')
  2. var child = div1.childNodes
  3. <div class="md-section-divider"></div>
  1. var div1 = document.getElementById('div1')
  2. var child = div1.childNodes
  3. div1.removeChild(child[0])
  4. <div class="md-section-divider"></div>
  1. previousSibling
  2. <div class="md-section-divider"></div>
  1. nextSibling
  2. <div class="md-section-divider"></div>

JavaScript BOM

BOM,浏览器对象模型,是浏览器本身的一些信息的设置和获取。

  1. var ua = navigator.userAgent
  2. var isChrome = ua.indexOf('Chrome')
  3. console.log(isChrome)
  4. <div class="md-section-divider"></div>
  1. console.log(screen.width)
  2. console.log(screen.height)
  3. <div class="md-section-divider"></div>
  1. console.log(location.href)
  2. console.log(location.protocol)
  3. console.log(location.pathname)
  4. console.log(location.search)
  5. console.log(location.hash)
  6. <div class="md-section-divider"></div>
  1. history.back()
  2. history.forward()
  3. <div class="md-section-divider"></div>

JavaScript 事件

事件冒泡、事件捕获和事件委托

事件流

事件流被分为三个阶段(1~5)捕获过程、(5~6)目标过程、(6~10)冒泡过程。

IE 提出的是冒泡流,而网景提出的是捕获流,后来在 W3C 组织的统一之下,JS 支持了冒泡流和捕获流,但是目前低版本的 IE 浏览器还是只能支持冒泡流(IE6, IE7, IE8 均只支持冒泡流),所以为了能够兼容更多的浏览器,建议大家使用冒泡流。

从事件传播的过程能够看出来,当点击鼠标后,会先发生事件的捕获

JavaScript 事件冒泡

当触发子元素时,事件会沿着 DOM 向上冒泡。事件冒泡是实现事件委托的原理。阻止冒泡示例:

  1. var btn = document.getElementById('btn')
  2. btn.addEventListener('click', function (event) {
  3. // event.preventDefault() // 阻止默认行为
  4. event.stopPropagation() // 阻止冒泡
  5. console.log('clicked')
  6. })
  7. <div class="md-section-divider"></div>

JavaScript 事件委托(事件代理)

事件委托是将事件监听器添加到父元素,而不是每个子元素单独设置事件监听器。当触发子元素时,事件会冒泡到父元素,监听器就会触发,这种技术的好处如下。

示例目标:为 div 下的每个 a 标签绑定点击事件:

  1. <div id="div1">
  2. <a href="#" id="a1">a1</a>
  3. <a href="#">a2</a>
  4. <a href="#">a3</a>
  5. <a href="#">a4</a>
  6. </div>
  7. <button>点击增加一个 a 标签</button>
  8. <div class="md-section-divider"></div>

示例原理:监听 div 下的事件触发点是不是 a 标签:

  1. function bindEvent(elem, type, selector, fn) {
  2. // 这样可以实现重载
  3. if (fn == null) {
  4. fn = selector
  5. selector = null
  6. }
  7. // 绑定事件
  8. elem.addEventListener(type, function (e) {
  9. var target
  10. // 有 selector 说明需要做事件代理
  11. if (selector) {
  12. // 获取触发事件的元素,即 e.target
  13. target = e.target
  14. // 看是否符合 selector 这个条件
  15. if (target.matches(selector)) {
  16. fn.call(target, e)
  17. }
  18. } else {
  19. // 无 selector,说明不需要事件代理
  20. fn(e)
  21. }
  22. })
  23. }
  24. <div class="md-section-divider"></div>

使用示例:

  1. // 使用代理 bindEvent 多一个 'a' 参数
  2. var div = document.getElementById('div')
  3. bindEvent(div1, 'click', 'a', function (e) {
  4. console.log(this.innerHTML)
  5. })
  6. // 不使用代理
  7. var a1 = document.getElementById('a1')
  8. bindEvent(div, 'click', function (e) {
  9. console.log(a1.innerHTML)
  10. })
  11. <div class="md-section-divider"></div>

JavaScript 事件循环

事件循环是一个单线程循环,用于监视调用堆栈并检查是否有工作即将在任务队列中完成。如果调用堆栈为空并且任务队列中有回调函数,则将回调函数出队并推送到调用堆栈中执行。

事件循环进阶

JavaScript 中分为两种任务类型:macrotask 和 microtask,在 ECMAScript 中,microtask 称为 jobs,macrotask 可称为 task。

分别什么场景会用到 macrotask 和 microtask?

在node环境下,process.nextTick 的优先级高于 Promise__,也就是可以简单理解为:在宏任务结束后会先执行微任务队列中的 nextTickQueue 部分,然后才会执行微任务中的 Promise 部分。

从线程角度重新理解:

所以,总结下运行机制:

另外,请注意下 Promise 的 polyfill 与官方版本的区别:

注意,有一些浏览器执行结果不一样,因为它们可能把 microtask 当成macrotask来执行了。

使用 MutationObserver 实现 microtask

MutationObserver 可以用来实现 microtask,它属于 microtask,优先级小于 Promise,一般是 Promise 不支持时才会这样做。

它是 HTML5 中的新特性,作用是:监听一个 DOM 变动,
当 DOM 对象树发生任何变动时,Mutation Observer 会得到通知。

像以前的 Vue 源码中就是利用它来模拟 nextTick 的,具体原理是,创建一个 TextNode 并监听内容变化,
然后要 nextTick 的时候去改一下这个节点的文本内容。

  1. var counter = 1
  2. var observer = new MutationObserver(nextTickHandler)
  3. var textNode = document.createTextNode(String(counter))
  4. observer.observe(textNode, {
  5. characterData: true
  6. })
  7. timerFunc = () => {
  8. counter = (counter + 1) % 2
  9. textNode.data = String(counter)
  10. }
  11. <div class="md-section-divider"></div>

不过,现在的 Vue 2.5+ 的 nextTick 实现移除了 MutationObserver 的方式(据说是兼容性原因),取而代之的是使用 MessageChannel
(当然,默认情况仍然是 Promise,不支持才兼容的)。

MessageChannel 属于宏任务,优先级是:MessageChannel->setTimeout,所以 Vue 2.5+ 内部的 nextTick 与 2.4 及之前的实现是不一样的,需要注意下。

JavaScript 异步请求

  • 利用 setTimout 实现异步
  • 使用 Promise 对象
  • 动态创建 script 标签
  • 利用 script 标签提供的 async
  • 还有 ES6 里面 async 函数,使得异步操作变得更加方便

同步和异步

同步函数与异步函数

Promise

Promise 是一个可能在未来某个时间产生结果的对象:操作成功的结果或失败的原因。Promise 可能处于以下三种状态之一:fulfilled、rejected、pending。用户可以对 Promise 添加回调函数来处理操作成功的结果或失败的原因。

Promise 代替回调函数的优点:

Promise 代替回调函数的缺点:

JavaScript 后端请求

CORS

跨域资源共享 CORS 详解

CORS 是一个 W3C 标准,全称是"跨域资源共享"(Cross-origin resource sharing)。它允许浏览器向跨源服务器,发出 XMLHttpRequest 请求,从而克服了 AJAX 只能同源使用的限制。

基本上目前所有的浏览器都实现了 CORS 标准,其实目前几乎所有的浏览器 ajax 请求都是基于 CORS 机制的。

CORS 实现原理

CORS 请求分类

浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。只要同时满足以下两大条件,就属于简单请求。

凡是不同时满足上面两个条件,就属于非简单请求。

JSONP

JSONP 是一种通常用于绕过 Web 浏览器中的跨域限制的方法,因为 Ajax 不允许跨域请求。JSONP 通过 <script> 标签发送跨域请求,通常使用 callback 查询参数,例如:https://example.com?callback=printData。然后服务器将数据包装在一个名为 printData 的函数中并将其返回客户端。

  1. <script>
  2. function printData (data) {
  3. console.log('My name is ${data.name}')
  4. }
  5. </script>
  6. <script src="https://example.com?callback=printData"></script>
  7. // 文件加载自 https://example.com?callback=printData
  8. printData({ name: 'Yang shun'} )
  9. <div class="md-section-divider"></div>

客户端必须在其全局范围内具有 printData 函数,并且在收到来自跨域的响应时,该函数将由客户端执行。JSONP 可能具有一些安全隐患,因此需要信任 JSONP 数据的提供者。

Ajax - XMLHttpRequest

Ajax 是创建异步 Web 应用的一种 Web 开发技术,起源于 IE 的 Active X 控件,并以 XMLHTTPRequest API 方式提供编程接口。借助 Ajax,Web 应用可以异步(在后台)向服务器发送数据和从服务器检索数据,而不会干扰现有页面的现实和行为。现在通常将 JSON 替换为 XML,因为 JavaScript 对 JSON 由原生支持优势。

优点:

缺点:

  1. var xhr = new XMLHttpRequest()
  2. xhr.open("GET", "/api", false)
  3. xhr.onreadystatechange = function () {
  4. // 这里的函数异步执行,可参考之前 JS 基础中的异步模块
  5. if (xhr.readyState == 4) {
  6. if (xhr.status == 200) {
  7. alert(xhr.responseText)
  8. }
  9. }
  10. }
  11. xhr.send(null)
  12. <div class="md-section-divider"></div>

xhr.readyState 的状态码说明:

Fetch API(待做)

Fetch 支 持headers 定义,通过 headers 自定义可以方便地实现多种请求方法(PUT、GET、POST 等)、请求头(包括跨域)和 cache 策略等;除此之外还支持 response(返回数据)多种类型,比如支持二进制文件、字符串和 formData 等。

  1. fetch('some/api/data.json', {
  2. method:'POST', // 请求类型 GET、POST
  3. headers:{}, // 请求的头信息,形式为 Headers 对象或 ByteString
  4. body:{}, // 请求发送的数据 blob、BufferSource、FormData、URLSearchParams(get 或 head 方法中不能包含 body)
  5. mode:'', // 请求的模式,是否跨域等,如 cors、 no-cors 或 same-origin
  6. credentials:'', // cookie 的跨域策略,如 omit、same-origin 或 include
  7. cache:'', // 请求的 cache 模式: default、no-store、reload、no-cache、 force-cache 或 only-if-cached
  8. }).then(function(response) { ... });...
  9. <div class="md-section-divider"></div>

JavaScript 跨域解决方案(同源策略)

同源策略可防止 JavaScript 发起跨域请求。源被定义为 URI、主机名和端口号的组合。此策略可防止页面上的恶意脚本通过该页面的文档对象模型,访问另一个网页上的敏感数据。

JavaScript 调试技巧

JavaScript 客户端存储

  • 根据不同浏览器和操作系统,localStorage、sessionStorage 的存储位置
  • IE userdata

Cookie 的交互

LocalStorage、SessionStorage、Storage

  1. localStorage.__proto__ === Storage.prototype // true
  2. localStorage.__proto__.constructor === Storage // true
  3. <div class="md-section-divider"></div>

方法及属性

存储事件(待做)

无论什么时候存储在 localStorage 或者 sessionStorage 的数据发生改变,浏览器都会在其他对该数据可见的窗口对象上触发存储事件。

Cookie、LocalStorage 与 SessionStorage异同

Cookie、LocalStorage、SessionStorage 都是客户端以键值对存储的存储机制,并且只能将值存储为字符串。

  Cookie LocalStorage SessionStorage
由谁初始化 服务器(Set-Cookie 请求头)或客户端 客户端 客户端
过期时间 手动设置 永不过期 当前页面关闭时
在当前浏览器会话中是否保持不变 取决于是否设置过期时间
是否与域名相关联
是否随着每个 HTTP 请求发送给服务器 是,Cookie 会自动设置 Cookie 请求头
每个域名容量 4kb 5mb 5mb
访问权限 任一窗口 任一窗口 当前页面窗口

JavaScript 高级语言

包括 TypeScript、PureScript、ClojureScript、Elm、CoffeeScript。

优点:

缺点:

TypeScript

静态类型检查、融合进主流框架

Node.JS

模块化、组件化

严格意义上来讲,AMD/CMD/UMD 这些都是社区的规范,官方承认的只有 commonjs 和 esm,以往的资料都把这些混为一谈

AMD

AMD 是异步的,支持异步加载模块,更适合浏览器。

CMD

UMD

CommonJS

CommonJS 是同步的,是为服务器端开发考虑的。CommonJS 语法上更接近 Node 编写模块的风格,在前后端都使用 JavaScript 开发时,语境切换开销小。

ESM

Web Component

SPA 单页面应用

SPA 对比传统网站

“网络应用”相较于“网站”,具有高度的交互性和动态性,允许用户执行操作并接收他们的操作响应。

好处:

缺点:

SPA 带来的

构建优秀的 SPA

HTML: 应用脚手架、提供占位符、规划 UI 结构、提供交互控件
CSS: 描述 UI 设计、负责外规与格式
JavaScript: 交互、逻辑

当我们谈到 SPA 的时候,我们在谈什么?

模型、模板、模块

构建方式 技术要点 其他周边
MV* 架构: MVC、MVP、MVVM 模块化编程 单元测试
Web Component 组件 客户端路由及历史管理 客户端开发
React 布局设计与视图合成 构建任务自动化
模块间通信
服务端处理: RESTful
客户端模板: 渲染指令、占位符

构建要点

MV 框架*

为什么要使用 MV 框架*

虚拟 DOM

近年来,前端的框架主要发展方向就是解放DOM操作的复杂性。虚拟的 DOM 的核心思想是:对复杂的文档 DOM 结构,提供一种方便的工具,进行最小化地 DOM 操作。

算法实现

  1. 用 JS 对象模拟 DOM 树
  2. 比较两棵虚拟 DOM 树的差异
  3. 把差异应用到真正的 DOM 树上

DOM patching,DOM 修补

DOM diff

SPA 模型

模型的绑定

SPA 模板

SPA 模块

模块化概念

模块化意义

缺点:不能测试私有部分、拓展对象

模块化编程

模块模式

  1. // 创建模块的命名空间
  2. var moduleName = (function () {
  3. var someVar;
  4. // 整体闭包构造
  5. function someFunction () {
  6. }
  7. return {
  8. // 返回 对象字面量
  9. someFunction: someFunction
  10. }
  11. })(); // IIFE
  12. <div class="md-section-divider"></div>

模块间交互方式

模块加载及依赖管理

SPA 客户端路由

客户端路由器

路由术语

路由配置

客户端路由器的工作机制

SPA 主流框架全家桶

以 san 举例

移动端(待做)

移动 Web

移动端的触摸事件

触摸事件指的是指的是用户将手指放在屏幕上,在屏幕上滑动到将手指从屏幕移开触发的事件,具体来说,有以下触摸事件的产生:

以上的这些事件都会冒泡,而且都可以取消冒泡。对于以上事件也提供了和鼠标事件中常用的属性:bubble, cancelable, view, clientX, clientY, screenX, screenY, detail, altKey, shiftKey, ctrKey 和 metaKey。

除了上面这些属性外,触摸事件还提供了下面这些属性:

每个Touch对象包含一下的属性:

前端性能优化

  • 最大限度地减少 HTTP 请求
  • 使用内容分发网络
  • headers 添加一个 Expires 或Cache-Control 头
  • 用 gzip 的压缩内容
  • 将样式表在顶部
  • JS 代码放在底部
  • 避免使用 CSS 表达式
  • JavaScript 和 CSS 放在外部
  • 减少 DNS 查找
  • 缩小 JavaScript 和 CSS
  • 避免重定向
  • 删除重复的脚本和样式
  • 减少 DOM 元素的数量

  • CSS 雪碧图

  • HTTP 缓存
  • 尽量避免回流
  • 复合层、简单层与硬件加速
  • 工程化打包
  • 复合层与性能优化

静态资源域名拆分

HTTP 2.0 不需要

压缩

GZip 压缩

图片格式

服务端

SQL

SQL 性能优化

慢查询

网络安全(待做)

CSP 内容安全策略

内容安全策略 (CSP, Content Security Policy) 是一个附加的安全层,用于帮助检测和缓解某些类型的攻击,包括跨站脚本 (XSS) 和数据注入等攻击。 这些攻击可用于实现从数据窃取到网站破坏或作为恶意软件分发版本等用途。

浏览器沙盒模型(待做)

例如,“Google Chrome 浏览器”采用了“安全浏览”技术,这种技术也应用于其他几种现代浏览
器。当您浏览网页时,浏览器会将每个网页与疑似存在网上诱骗和恶意软件的网站列表进行
快速比对。这个列表会在您的本地计算机上进行存储和维护,从而帮助您保护浏览隐私。如
果从本地列表中找到了匹配项,浏览器就会向 Google 发送一条请求,以获取更多信息。(这
条请求是完全加密的,不会以纯文本形式发送。)如果 Google 通过验证确定是匹配
项,“Chrome 浏览器”就会显示一个红色警告页面,提醒您尝试访问的网页可能存在风险。

浏览器获取和载入网站时,如何通过扩展验证证书验证网站的身份。

版本控制

Git

Git Commit Message 提交规范

每次提交,Commit message 都包括三个部分:Header,Body 和 Footer。其中,Header 是必需的,Body 和 Footer 可以省略。不管是哪一个部分,任何一行都不得超过72个字符(或100个字符)。这是为了避免自动换行影响美观。

  1. <type>(<scope>): <subject>
  2. // 空一行
  3. <body>
  4. // 空一行
  5. <footer>
  6. <div class="md-section-divider"></div>

Angular 版规范

Header部分只有一行,包括三个字段:type(必需)、scope(可选)和subject(必需)。

type

type用于说明 commit 的类别,只允许使用下面7个标识。

P.S: hylerrix 根据自身情况,新增了以下标识:
* code: 逻辑代码改动。逻辑代码包括一定的 HTML、CSS 和 JavaScript。改动模糊了一定的样式、重构、新功能。这是个模糊化用途。
* file: 上传了图片等非代码文件。

如果 type 为 feat 和 fix,则该 commit 将肯定出现在 Change log 之中。其他情况(docs、chore、style、refactor、test)由你决定,要不要放入 Change log,建议是不要。

scope

scope 用于说明 commit 影响的范围,比如数据层、控制层、视图层等等,视项目不同而不同。

subject

subject是 commit 目的的简短描述,不超过50个字符。

  1. * 以动词开头,使用第一人称现在时,比如change,而不是changedchanges
  2. * 第一个字母小写
  3. * 结尾不加句号(.)
  4. <div class="md-section-divider"></div>
Body

Body 部分是对本次 commit 的详细描述,可以分成多行。下面是一个范例。

  1. More detailed explanatory text, if necessary. Wrap it to
  2. about 72 characters or so.
  3. Further paragraphs come after blank lines.
  4. - Bullet points are okay, too
  5. - Use a hanging indent
  6. <div class="md-section-divider"></div>

有两个注意点。

Footer 部分只用于两种情况。

不兼容变动

如果当前代码与上一个版本不兼容,则 Footer 部分以 BREAKING CHANGE 开头,后面是对变动的描述、以及变动理由和迁移方法。

  1. BREAKING CHANGE: isolate scope bindings definition has changed.
  2. To migrate the code follow the example below:
  3. Before:
  4. scope: {
  5. myAttr: 'attribute',
  6. }
  7. After:
  8. scope: {
  9. myAttr: '@',
  10. }
  11. The removed `inject` wasn't generaly useful for directives so there should be no code using it.
  12. <div class="md-section-divider"></div>

关闭 Issue

如果当前 commit 针对某个issue,那么可以在 Footer 部分关闭这个 issue 。也可以一次关闭多个 issue 。

  1. Closes #123[, #245, #992]
  2. <div class="md-section-divider"></div>
Revert

还有一种特殊情况,如果当前 commit 用于撤销以前的 commit,则必须以revert:开头,后面跟着被撤销 Commit 的 Header。

  1. revert: feat(pencil): add 'graphiteWidth' option
  2. This reverts commit 667ecc1654a317a13331b17617d973392f415f02.
  3. <div class="md-section-divider"></div>

Body部分的格式是固定的,必须写成 This reverts commit <hash>.,其中的 hash 是被撤销 commit 的 SHA 标识符。

如果当前 commit 与被撤销的 commit,在同一个发布(release)里面,那么它们都不会出现在 Change log 里面。如果两者在不同的发布,那么当前 commit,会出现在 Change log 的Reverts小标题下面。

Commitizen

Commitizen 是一个撰写合格 Commit message 的工具。安装后在项目目录里,运行下面的命令,使其支持 Angular 的 Commit message 格式。

  1. npm install -g commitizen
  2. commitizen init cz-conventional-changelog --save --save-exact
  3. <div class="md-section-divider"></div>

以后,凡是用到 git commit 命令,一律改为使用 git cz。这时,就会出现选项,用来生成符合格式的 Commit message。

validate-commit-msg

validate-commit-msg 用于检查 Node 项目的 Commit message 是否符合格式。它的安装是手动的。首先,拷贝下面这个 JavaScript 文件,放入你的代码库。文件名可以取为 validate-commit-msg.js。接着,把这个脚本加入 Git 的 hook。下面是在 package.json 里面使用 ghooks,把这个脚本加为 commit-msg 时运行。

  1. "config": {
  2. "ghooks": {
  3. "commit-msg": "./validate-commit-msg.js"
  4. }
  5. }
  6. <div class="md-section-divider"></div>

然后,每次git commit的时候,这个脚本就会自动检查 Commit message 是否合格。如果不合格,就会报错。

  1. $ git commit -m "edit markdown"
  2. INVALID COMMIT MSG: does not match "<type>(<scope>): <subject>" ! was: edit markdown
  3. <div class="md-section-divider"></div>
生成 ChangeLog

如果你的所有 Commit 都符合 Angular 格式,那么发布新版本时, Change log 就可以用脚本自动生成。生成的文档包括以下三个部分。

  1. New features
  2. Bug fixes
  3. Breaking changes.
  4. <div class="md-section-divider"></div>

每个部分都会罗列相关的 commit ,并且有指向这些 commit 的链接。当然,生成的文档允许手动修改,所以发布前,你还可以添加其他内容。conventional-changelog 就是生成 Change log 的工具,运行下面的命令即可。

  1. $ npm install -g conventional-changelog
  2. $ cd my-project
  3. $ conventional-changelog -p angular -i CHANGELOG.md -w
  4. <div class="md-section-divider"></div>

上面命令不会覆盖以前的 Change log,只会在 CHANGELOG.md 的头部加上自从上次发布以来的变动。如果你想生成所有发布的 Change log,要改为运行下面的命令。

  1. conventional-changelog -p angular -i CHANGELOG.md -w -r 0
  2. <div class="md-section-divider"></div>

为了方便使用,可以将其写入package.json的scripts字段。

  1. {
  2. "scripts": {
  3. "changelog": "conventional-changelog -p angular -i CHANGELOG.md -w -r 0"
  4. }
  5. }

开源运动

参考资料

从浏览器多进程到JS单线程,JS运行机制最全面的一次梳理
从输入URL到页面加载的过程?如何由一道题完善自己的前端知识体系!
Commit message 和 Change log 编写指南

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注