Git Product home page Git Product logo

blog's People

Contributors

coconilu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

blog's Issues

CSS 布局相关

概述

  1. positioning(定位)
    1.1. display
    1.2. position
    1.3. 盒子模型和外边距合并
    1.4. 块格式化上下文
    1.5. float——圣杯布局与双飞翼布局
  2. 表格布局
  3. 栅格布局
  4. 多栏布局
  5. 弹性布局
  6. 网格布局

1. positioning

主要是下面的css样式:

  1. display,指定用于元素的呈现框的类型。在 HTML 中,默认的 display 属性取决于 HTML 规范所描述的行为或浏览器/用户的默认样式表。在 XML中,其默认值为 inline。
none,关闭一个元素的显示,该元素如同不存在。
inline,该元素生成一个或多个行内元素盒。
block,该元素生成一个块元素盒。
list-item,该元素生成一个容纳内容和单独的列表行内元素盒的块状盒。
inline-block,该元素生成一个块状盒,该块状盒随着周围内容流动,如同它是一个单独的行内盒子(表现更像是一个被替换的元素)。
inline-table,在HTML中没有对应元素。它的行为就像一个HTML中的table元素,但是作为内联框,而不是块级框。 表格框内是一个块级上下文。
table,这个元素的作用就像  <table> 元素. 它定义了一个块级盒子。
table-caption,这个元素的作用的就像<caption> 一样。
table-header-group,这个元素的作用就像<tfoot> 一样。
table-footer-group,这个元素的作用就像<thead> 一样。
table-cell,这个元素的作用就像<td> 一样。
table-row,这个元素的作用就像<tr> 一样。
table-row-group,这个元素的作用就像<tbody> 一样。
table-column,这个元素的作用就像<col> 一样。
table-column-group,这个元素的作用就像<colgroup> 一样。
flex,该元素的行为类似于块级元素,并根据Flexbox模型来描述其内容。
inline-flex,该元素的行为类似于行内块级元素,并根据Flexbox模型来描述其内容。
grid,网格布局。
inline-grid,行内网格布局。
  1. position,指定一个元素在文档中的定位方式。static | relative | absolute | fixed | sticky
  2. float,指定一个元素应沿其容器的左侧或右侧放置,允许文本和内联元素环绕它。该元素从网页的正常流动中移除,尽管仍然保持部分的流动性(与绝对定位相反)。
  3. clear,清除浮动。none | left | right | both | inherit
当应用于非浮动块时,它将非浮动块的边框边界移动到所有相关浮动元素外边界的下方。这个非浮动块的垂直外边距会折叠。
另一方面,两个浮动元素的垂直外边距将不会折叠。当应用于浮动元素时,它将元素的外边界移动到所有相关的浮动元素外边界的下方。这会影响后面浮动元素的布局,后面的浮动元素的位置无法高于它之前的元素。
  1. visibility,元素的可视性。visible | hidden | collapse | inherit
visible,元素正常显示。
hidden,隐藏元素,但是其他元素的布局不改变,相当于此元素变成透明。
collapse,用于表格行、列、列组和行组,隐藏表格的行或列,并且不占用任何空间。
  1. z-index,指定了一个具有定位属性的元素及其子代元素的 z-order。 当元素之间重叠的时候,z-order 决定哪一个元素覆盖在其余元素的上方显示。 通常来说 z-index 较大的元素会覆盖较小的一个。

还有下面的:

  1. top
当position设置为absolute或fixed时,top属性指定了定位元素上外边距边界与其包含块上边界之间的偏移。
当position设置为relative时,top属性指定了元素的上边界离开其正常位置的偏移。
当position设置为sticky时,如果元素在viewport里面,top属性的效果和position为relative等同;如果元素在viewport外面,top属性的效果和position为fixed等同。
当position设置为static时,top属性无效。
  1. bottom
  2. left
  3. right

圣杯布局与双飞翼布局

相同点:

  1. 圣杯布局和双飞翼布局解决的问题是一样的,就是两边定宽,中间自适应的三栏布局,中间栏要在放在文档流前面以优先渲染。
  2. 圣杯布局和双飞翼布局解决问题的方案在前一半是相同的,也就是三栏全部float浮动,且左右两栏加上负margin让其跟中间栏div并排,以形成三栏布局。

不同点:
于解决”中间栏div内容不被遮挡“问题的思路不一样:圣杯布局,为了中间div内容不被遮挡,将中间div设置了左右padding-left和padding-right后,将左右两个div用相对布局position: relative并分别配合right和left属性,以便左右两栏div移动后不遮挡中间div;双飞翼布局,为了中间div内容不被遮挡,直接在中间div内部创建子div用于放置内容,在该子div里用margin-left和margin-right为左右两栏div留出位置。

最重要的,圣杯里的float:left元素设置margin-left: -100%并不能达到主要展示内容的左侧,还需要加上position:relative;left:<自身宽度>;但是双飞翼的float:left元素设置margin-left:-100%就可以达到预期效果

演示:圣杯布局双飞翼布局

2. 表格布局

已经不推荐使用。
但是可以通过设置display来使用:

inline-table,在HTML中没有对应元素。它的行为就像一个HTML中的table元素,但是作为内联框,而不是块级框。 表格框内是一个块级上下文。
table,这个元素的作用就像  <table> 元素. 它定义了一个块级盒子。
table-caption,这个元素的作用的就像<caption> 一样。
table-header-group,这个元素的作用就像<tfoot> 一样。
table-footer-group,这个元素的作用就像<thead> 一样。
table-cell,这个元素的作用就像<td> 一样。
table-row,这个元素的作用就像<tr> 一样。
table-row-group,这个元素的作用就像<tbody> 一样。
table-column,这个元素的作用就像<col> 一样。
table-column-group,这个元素的作用就像<colgroup> 一样。

3. 栅格布局

一个可以无限嵌套的栅格布局,会包含:

  1. 栅格系统的容器
  2. 各列之间的空隙

设计理念如下:

以上提到的容器、行、列的盒子模型设置为box-sizing: border-box

  1. 容器的左右padding需要设置为某个特定的值,设为x
  2. 行的左右margin需要设置为-x
  3. 列的左右padding需要设置为x

注意:

需要清除浮动,因为浮动会让父元素塌陷。

栅栏系统提供的功能:

  1. 行的gutter属性可以设置各列间的间隙,间隙包含在列的宽度里
  2. 列的span属性可以设置元素的宽度大小
  3. 列的offset属性可以设置元素的水平偏移量

4. 多列布局

可以使用float达到多列布局的目的。
但是现在浏览器支持更高级的方法:
主要的特性:

1. column-count,列数
2. column-width,列宽
3. column-gap,间隙

次要的特性:

break-after,描述在生成的盒子之后的页面,列或区域中断行为(换句话说,如何以及是否中断)。
break-before,定义页面,列或区域在生成的盒子之前应如何处理中断。
break-inside,描述了在多列布局页面下的内容盒子如何中断,如果多列布局没有内容盒子,这个属性会被忽略。
column-fill
column-rule
column-rule-color,CSS 特性 column-rule-color 让你可以设置在多列布局中被画在两列之间的规则(线条)的颜色。
column-rule-style,CSS 特性 column-rule-color 让你可以设置在多列布局中被画在两列之间的规则(线条)的样式。
column-rule-width,CSS 特性 column-rule-color 让你可以设置在多列布局中被画在两列之间的规则(线条)的宽度。
column-span,设置为all的时候跨越所有列

5. 弹性布局

flex布局模型:主轴和交叉轴、父容器和子容器

主轴和交叉轴

这是相对的概念,比如设置flex容器的方向(flex-direction)为水平方向,这就是主轴的方向,那么交叉轴就是垂直方向;如果设置flex容器的方向(flex-direction)为垂直方向,那么交叉轴就是水平方向。

父容器和子容器

父容器需要设置 display: flex,才会使flex布局生效
父容器可以设置子容器排列方向:flex-direction
父容器可以设置子容器在一行里排满后是否允许换行和换行的方式:flex-wrap
父容器可以设置子容器在一行里的对齐方式,包括主轴和交叉轴:justify-content(主轴)、align-items(单行)、align-content(多行)

总结来说,父容器设置了子容器的排列方向、换行方式、对齐方式

子容器可以设置在主轴方向上的初始大小(flex-basic)、拉伸因子(flex-grow)、收缩规则(flex-shrink)
子容器可以设置在主轴方向上的顺序:order
子容器可以覆盖父元素的align-items:align-self

6. 网格布局

网格是一组相交的水平线和垂直线,它定义了网格的列和行。

CSS网格布局具有以下特点:

  1. 可以使用固定的轨道尺寸创建网格,比如使用像素单位。你也可以使用比如百分比或者专门为此目的创建的新单位 fr来创建有弹性尺寸的网格。
  2. 可以使用行号、行名或者标定一个网格区域来精确定位元素。网格同时还使用一种算法来控制未给出明确网格位置的元素。
  3. 可以使用网格布局定义一个显式网格,但是根据规范它会处理你加在网格外面的内容,当必要时它会自动增加行和列,它会尽可能多的容纳所有的列。
  4. 网格包含对齐特点,以便我们可以控制一旦放置到网格区域中的物体对齐,以及整个网格如何对齐。
  5. 多个元素可以放置在网格单元格中,或者区域可以部分地彼此重叠。然后可以CSS中的z-index属性来控制重叠区域显示的优先级。

相关概念

  1. 网格容器
display: grid;
  1. 网格轨道
显式网格:
    grid-template-columns
    grid-template-rows
隐式网格:
    grid-auto-rows
    grid-auto-columns
fr单位:等份
定义重复部分:repeat()
轨道大小:minmax()
布局算法:grid-auto-flow,[ row | column ] || dense
  1. 网格线
应该注意的是,当我们定义网格时,我们定义的是网格轨道,而不是网格线。Grid 会为我们创建编号的网格线来让我们来定位每一个网格元素。
grid-column-start
grid-column-end
grid-row-start
grid-row-end
  1. 网格单元
  2. 网格区域
grid-template-areas
grid-area
  1. 网格间距
grid-gap
grid-column-gap
grid-row-gap
  1. 嵌套网格
嵌套网格和他的父级并没有关系。
  1. 控制层级
z-index

属性

容器:
grid
grid-area
grid-gap

显示网格:
grid-template
grid-template-areas
grid-template-columns
grid-template-rows

隐式网格:
grid-auto
grid-auto-columns
grid-auto-rows

布局算法:
grid-auto-flow:控制着自动布局算法怎样运作,精确指定在网格中被自动布局的元素怎样排列。

定位:
grid-column
grid-column-end
grid-column-gap
grid-column-start
grid-row
grid-row-end
grid-row-gap
grid-row-start

参考

圣杯布局和双飞翼布局的作用和区别
块格式化上下文
CSS 盒模型
CSS display
CSS 多列布局
CSS 弹性布局
网格布局的基本概念
CSS 网格布局
跟着写一个 CSS 栅格布局
Bootstrap响应式栅格系统的设计原理

Web编程模型概述

概述

HTML,是标记语言,负责承载内容,就是一个网页可以被呈现出来的内容。
CSS,可以装饰排版这些内容,让它看起来更美观。
JS,可以为这些那内容添加一些交互元素,让人机关系更友好。

三者之间的联系

html和css是展现用户界面的关键,无论前端如何发展,多了多少框架和库,都避免不了界面就是基于html和css的事实;
而js可以操控html和css,可以达到响应用户操作改变界面的效果。

相关的内容

  1. HTML标签及属性
  2. DOM模型
  3. DOM的事件模型
  4. Web API
  5. CSS收集解析模型

Web API参考

介绍

Web API 是浏览器(也叫用户代理)需要提供给开发者的一套API。

分类

MDN维护了一份 API 参考列表
但是没有对它们分类,我简单对一些常用的API进行了分类。

1. DOM 相关

除了之前提到过的:EventTargetNodeDocumentElementWindow
还有下面的:

  1. Attr,表示一个DOM元素的属性。
  2. CSS,涵盖CSS相关的实用方法。
  3. DocumentFragment,表示一个没有父级文件的最小文档对象。
  4. DomParser,可以将存储在字符串中的XML或HTML源代码解析为一个 DOM文档。
  5. DataTransfer,在进行拖放操作时,DataTransfer 对象用来保存,通过拖放动作,拖动到浏览器的数据。
  6. MutationObserver,给开发者们提供了一种能在某个范围内的DOM树发生变化时作出适当反应的能力。
  7. TreeWalker,表示一个当前文档的子树中的所有节点及其位置。

2. 浏览器相关

  1. History,允许操作浏览器的曾经在标签页或者框架里访问的会话历史记录。
back(),回退
forward(),前进
go(step)

HTML5 新增了几个API, history.pushState() 和 history.replaceState() 方法,它们分别可以添加和修改历史记录条目。这些方法通常与window.onpopstate 配合使用,用于无刷新变更历史记录。——他们的作用非常大,可以做到改变网址却不需要刷新页面,这个特性后来用到了单页面应用中。

pushState(stateObj, title, URL)
replaceState(stateObj, title, URL)

第三个参数RUL必须是同域的。

popstate事件,window.addEventListener('popstate', cb)

调用history.pushState()或者history.replaceState()不会触发popstate事件. popstate事件只会在浏览器某些行为下触发, 比如点击后退、前进按钮(或者在JavaScript中调用history.back()、history.forward()、history.go()方法)
  1. IDBIndex,让程序可以异步存取 indexed databases。
  2. Location,表示其链接到的对象的位置(URL)。
  3. Navigator,表示用户代理的状态和标识。
  4. Notification,用于向用户配置和显示桌面通知。
  5. Screen,表示屏幕。
  6. Storage,作为 Web Storage API 的接口,Storage 提供了访问特定域名下的会话存储(session storage)或本地存储(local storage)的功能,例如,可以添加、修改或删除存储的数据项。

3. 系统相关

  1. ClipBoard,提供了一种读写操作系统剪贴板的方式。
  2. Console,提供对浏览器控制台的接入。
  3. File,提供有关文件的信息,并允许网页中的 JavaScript 访问其内容。
通常情况下, File 对象是来自用户在一个   <input> 元素上选择文件后返回的 FileList 对象;
也可以是来自由拖放操作生成的 DataTransfer 对象;
或者来自 HTMLCanvasElement 上的 mozGetAsFile() API。

File 对象是特殊类型的 Blob,且可以用在任意的 Blob 类型的 context 中。
比如说, FileReader, URL.createObjectURL(), createImageBitmap(), 及 XMLHttpRequest.send() 都能处理 Blob  和 File。
  1. FileReader,允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用 File 或 Blob 对象指定要读取的文件或数据。
  2. Performance,可以获取到当前页面中与性能相关的信息。

4. 网络请求

  1. Fetch,提供了一个获取资源的接口(包括跨域)。
  2. FormData,利用FormData对象,我们可以通过JavaScript用一些键值对来模拟一系列表单控件。
  3. XMLHttpRequest,为客户端提供了在客户端和服务器之间传输数据的功能。

4.1 Fetch

Fetch 的核心在于对 HTTP 接口的抽象,包括 Request,Response,Headers,Body,以及用于初始化异步请求的 global fetch。Fetch 还利用到了请求的异步特性——它是基于 Promise 的。

使用方式:

Promise<Response> fetch(input[, init]);

?input,定义要获取的资源。这可能是:
一个 USVString 字符串,包含要获取资源的 URL。一些浏览器会接受 blob: 和 data: 作为 schemes.
一个 Request 对象。

init(可选),一个配置项对象,包括所有对请求的设置。可选的参数有:
method: 请求使用的方法,如 GET、POST。
headers: 请求的头信息,形式为 Headers 的对象或包含 ByteString 值的对象字面量。
body: 请求的 body 信息:可能是一个 Blob、BufferSource、FormData、URLSearchParams 或者 USVString 对象。注意 GET 或 HEAD 方法的请求不能包含 body 信息。
mode: 请求的模式,如 cors、 no-cors 或者 same-origin。
credentials: 请求的 credentials,如 omit、same-origin 或者 include。为了在当前域名内自动发送 cookie , 必须提供这个选项, 从 Chrome 50 开始, 这个属性也可以接受 FederatedCredential 实例或是一个 PasswordCredential 实例。
cache:  请求的 cache 模式: default 、 no-store 、 reload 、 no-cache 、 force-cache 或者 only-if-cached 。
redirect: 可用的 redirect 模式: follow (自动重定向), error (如果产生重定向将自动终止并且抛出一个错误), 或者 manual (手动处理重定向). 在Chrome中,Chrome 47之前的默认值是 follow,从 Chrome 47开始是 manual。
referrer: 一个 USVString 可以是 no-referrer、client或一个 URL。默认是 client。
referrerPolicy: Specifies the value of the referer HTTP header. May be one of no-referrer、 no-referrer-when-downgrade、 origin、 origin-when-cross-origin、 unsafe-url 。
integrity: 包括请求的  subresource integrity 值 ( 例如: sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=)。

相关接口

Body(由Request 和Response实现):
Body.bodyUsed,指示body是否被读取过的 Boolean 值。
Body.arrayBuffer(),使用一个buffer数组来读取 Response流中的数据,并将bodyUsed状态改为已使用。
Body.blob(),使用一个Blob对象来读取 Response流中的数据,并将bodyUsed状态改为已使用。
Body.formData(),使用一个 FormData 对象来读取 Response流中的数据,并将bodyUsed状态改为已使用。
Body.json(),使用一个 JSON 对象来读取 Response流中的数据,并将bodyUsed状态改为已使用。
Body.text(),使用一个USVString (文本) 对象来读取 Response流中的数据,并将bodyUsed状态改为已使用。

Headers(Request.headers 和Response.headers):
Headers.append(),给现有的header添加一个值, 或者添加一个未存在的header并赋值.
Headers.delete(),从Headers对象中删除指定header.
Headers.entries(),以 迭代器 的形式返回Headers对象中所有的键值对.
Headers.get(),从Headers对象中返回指定header的第一个值.
Headers.getAll(),以数组的形式从Headers对象中返回指定header的全部值.
Headers.has(),以布尔值的形式从Headers对象中返回是否存在指定的header.
Headers.keys(),以迭代器的形式返回Headers对象中所有存在的header名.
Headers.set(),替换现有的header的值, 或者添加一个未存在的header并赋值.
Headers.values(),以迭代器的形式返回Headers对象中所有存在的header的值.

Response:
Response.type(只读),包含Response的类型 (例如, basic, cors).
Response.url(只读),包含Response的URL.
Response.useFinalURL,包含了一个布尔值来标示这是否是该Response的最终URL.
Response.status (只读),包含Response的状态码 (例如, 200 成功).
Response.ok (只读),包含了一个布尔值来标示该Response成功(状态码200-299) 还是失败.
Response.redirected (只读),表示该Response是否来自一个重定向,如果是的话,它的URL列表将会有多个.
Response.statusText (只读),包含了与该Response状态码一致的状态信息 (例如, OK对应200).
Response.headers (只读),包含此Response所关联的Headers 对象.

Request:
new Request(input, init),跟fetch(input[, init]);一样

4.2 XMLHttpRequest

LEVEL 1:
受同源策略的限制,不能发送跨域请求;
不能发送二进制文件(如图片、视频、音频等),只能发送纯文本数据;
在发送和获取数据的过程中,无法实时获取进度信息,只能判断是否完成;

LEVEL 2:
可以发送跨域请求,在服务端允许的情况下;
支持发送和接收二进制数据;
新增formData对象,支持发送表单数据;
发送和获取数据时,可以获取进度信息;
可以设置请求的超时时间;

实例属性

readyState:

0:UNSENT(代理被创建,但尚未调用 open() 方法。);
1:OPENED(open() 方法已经被调用。);
2:HEADERS_RECEIVED(send() 方法已经被调用,并且头部和状态已经可获得。);
3:LOADING(下载中; responseText 属性已经包含部分数据。);
4:DONE(下载操作已完成。)

onreadystatechange:只要 readyState 属性发生变化,就会调用相应的处理函数。
timeout:默认值为 0,意味着没有超时。
status:http响应数字状态码。
statusText:stateState为3、4时,赋值为"OK"
response:返回响应的正文。可以是 ArrayBuffer, Blob, Document, JavaScript对象 或 一个 DOMString 类型
responseType:default:DOMString;"arraybuffer":ArrayBuffer;"blob":Blob;"document":Document;"text":“DOMString”
responseURL:当URL被返回的时候,任何包含在URL # 后面的fragment都会被删除。
upload:返回一个 XMLHttpRequestUpload对象,表示上传的进度。
withCredentials:一个Boolean类型,它指示了是否该使用类似cookies,authorization headers(头部授权)或者TLS客户端证书这一类资格证书来创建一个跨站点访问控制(cross-site Access-Control)请求。

实例方法

open(method, url, async, user, password):初始化一个请求。
send([data]):用于发送 HTTP 请求。
abort():终止该请求。readyState 属性将被置为0( UNSENT )。
getAllResponseHeaders():返回所有的响应头.
getResponseHeader():返回包含指定头文本的字符串。
setRequestHeader():设置HTTP请求头部的方法。此方法必须在 open() 方法和 send() 之间调用。

5. 多线程

  1. Worker,代表一个可以轻松创建的后台任务,并可以将消息发送回其创建者。
  2. ShareWork,代表一种特定类型的工作者,可以从几个浏览上下文中访问,例如几个窗口,内联框架或甚至工作者。
  3. ServiceWorker,提供一个对一个服务工作者的引用。 多个浏览上下文(例如页面,工作者等)可以与相同的服务工作者相关联,每个都通过唯一的ServiceWorker对象。

6. 通信

  1. MessageChannel,允许我们创建一个新的消息通道,并通过它的两个MessagePort 属性发送数据。
  2. RTCDataChannel,代表在两者之间建立了一个双向数据通道的连接。
  3. WebSockets,是一个可以创建和服务器间进行双向会话的高级技术。
  4. Transferable,代表一个能在不同可执行上下文中相互传递的对象,列如主线程和 Worker 间。它也没有定义任何方法和属性;它只是一个标签,用来指示对象在特定场合可用。
  5. URL,一个包含若干静态方法的对象,用来创建 URLs。URL.createObjectURL():返回一个DOMString ,包含一个唯一的blob链接

7. 数据结构

  1. TypeArray,描述一个底层的二进制数据缓存区的一个类似数组(array-like)视图。
  2. Blob,表示一个不可变、原始数据的类文件对象。Blob 表示的不一定是JavaScript原生格式的数据。File 接口基于Blob,继承了 blob 的功能并将其扩展使其支持用户系统上的文件。从Blob中读取内容的唯一方法是使用 FileReader。
  3. ArrayBuffer,用来表示通用的、固定长度的原始二进制数据缓冲区。

8. 安全

  1. Crypto,提供了基本的加密功能,可用于当前的上下文中。它允许访问一个密码强度的随机数生成器和 cryptographic primitives。

9. 优化

  1. requestAnimationFrame,告诉浏览器您希望执行动画并请求浏览器在下一次重绘之前调用指定的函数来更新动画。该方法使用一个回调函数作为参数,这个回调函数会在浏览器重绘之前调用。

  2. DocumentFragment,表示一个没有父级文件的最小文档对象。它被当做一个轻量版的 Document 使用,用于存储已排好版的或尚未打理好格式的XML片段。最大的区别是因为DocumentFragment不是真实DOM树的一部分,它的变化不会引起DOM树的重新渲染的操作(reflow) ,且不会导致性能等问题。

  3. requestIdleCallback,会在浏览器空闲时期依次调用函数, 这就可以让开发者在主事件循环中执行后台或低优先级的任务,而且不会对像动画和用户交互这样延迟触发而且关键的事件产生影响。函数一般会按先进先调用的顺序执行,除非函数在浏览器调用它之前就到了它的超时时间。

  4. Performance,可以获取到当前页面中与性能相关的信息。它是 High Resolution Time API 的一部分,同时也融合了 Performance Timeline API、Navigation Timing API、 User Timing API 和 Resource Timing API。

9.1 window.requestAnimationFrame

细节:

  1. requestAnimationFrame的优势,在于充分利用显示器的刷新机制,比较节省系统资源。
  2. 设置这个API的目的是为了让各种网页动画效果(DOM动画、Canvas动画、SVG动画、WebGL动画)能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果。
  3. 此外,使用这个API,一旦页面不处于浏览器的当前标签,就会自动停止刷新。这就节省了CPU、GPU和电力。

API:

window.requestAnimationFrame(callback);

callback:
一个指定函数的参数,该函数在下次重新绘制动画时调用。这个回调函数只有一个传参,DOMHighResTimeStamp,指示requestAnimationFrame() 开始触发回调函数的当前时间(performance.now() 返回的时间)。

返回值:
一个 long 整数,请求 ID ,是回调列表中唯一的标识。是个非零值,没别的意义。你可以传这个值给 window.cancelAnimationFrame() 以取消回调函数。

window.cancelAnimationFrame(),取消requestAnimationFrame注册的回调。

9.2 window.requestIdleCallback

var handle = window.requestIdleCallback(callback[, options])

callback:
一个即将被调用的函数的引用。函数会接收到一个名为 deadline 的参数,它具有如下属性 :
    timeRemaining:一个返回DOMHighResTimeStamp的函数的引用
    didTimeout: 布尔型,如果 callback 在空闲时间被客户端执行,它的值为 false,其他情况它的值为 true(例如:options 中给了超时时间,并且在超时过期时仍没有足够的空闲时间)。

options:
包括可选的配置参数。具有如下属性:
    timeout:timeout 值被指定为正数时,当做浏览器调用 callback 的最后期限。它的单位是毫秒。

返回值:
一个无符号长整数,可以把它传入 Window.cancelIdleCallback() 方法,来结束回调

9.3 window.performance

属性:
navigation,对象,提供了在指定的时间段里发生的操作相关信息,包括页面是加载还是刷新、发生了多少次重定向等等。
onresourcetimingbufferfull,事件处理器,当触发 resourcetimingbufferfull 事件的时候会被调用。
timeOrigin,数值,返回性能测量开始时的时间的高精度时间戳。
timing,对象,包含延迟相关的性能信息。

方法:
clearMarks()
clearMeasures()
clearResourceTimings() 
getEntries()
getEntriesByName() 
getEntriesByType()
mark(),标记
measure(name, startMark, endMark),测量
now()
setResourceTimingBufferSize()
toJSON()

参考

Web API接口
使用 Fetch
分时函数/requestAnimationFrame优化页面数据渲染

CSS 基础

CSS基础包含的内容

  1. 函数
  2. 值和单位
  3. 指令解析

1. 函数

css支持的函数不多,仅6个。

函数 描述 CSS版本
attr() 返回选择元素的属性值 2
calc() 允许计算 CSS 的属性值,比如动态计算长度值。 3
linear-gradient() 创建一个线性渐变的图像 3
radial-gradient() 用径向渐变创建图像。 3
repeating-linear-gradient() 用重复的线性渐变创建图像。 3
repeating-radial-gradient() 类似 radial-gradient(),用重复的径向渐变创建图像。 3

当然还有URL()、rgb()、hsl()、repeat()、minmax()等等

2. 值和单位

  1. 数字:整数和实数(小数)。
  2. 百分数:相对另一个值,可能是同一元素的另一个属性的值,也可以是从父元素继承的一个值,或者是祖先元素的一个值。
  3. 颜色:命名颜色;十六进制表示颜色;函数式:RGB、RGBA、HSL、HSLA。
  4. URL:绝对URL、相对URL。
  5. 角度值:deg(度)、grad(梯度)、rad(弧度)。
  6. 时间值:ms、s。
  7. 频率值:Hz、MHz。
  8. 关键字:none、inherit。
  9. 长度单位:绝对长度单位,相对长度单位。

绝对长度单位:

单位 描述
cm 厘米
mm 毫米
in 英寸 (1in = 96px = 2.54cm)
px * 像素 (1px = 1/96th of 1in)
pt point,大约1/72英寸; (1pt = 1/72in)
pc pica,大约6pt,1/6英寸; (1pc = 12 pt)

相对长度单位:

单位 描述
em 它是描述相对于应用在当前元素的字体尺寸,所以它也是相对长度单位。一般浏览器字体大小默认为16px,则2em == 32px;
ex 依赖于英文子母小 x 的高度
ch 数字 0 的宽度
rem 根元素(html)的 font-size
vw viewpoint width,视窗宽度,1vw=视窗宽度的1%
vh viewpoint height,视窗高度,1vh=视窗高度的1%
vmin vw和vh中较小的那个。
vmax vw和vh中较大的那个。

px有争议,在《CSS权威指南》里它被定义为相对长度单位,但是在RUNOOB里它是绝对长度。

3. 指令解析

  1. @support,检查
  2. @media,媒体查询,print、screen
  3. @import,导入
  4. @charset,指定字符编码

参考

《CSS 权威指南》
RUNOOB的CSS参考手册
MDN的CSS参考
@规则

CSS 收集解析模型

概述

CSS解析模型大概分为两部分:

  1. 收集分解CSS样式
  2. 权重确定CSS样式
  3. 应用CSS样式

1. 收集分解CSS样式

首先,接收所有可以接收的css样式,包括内联(属性样式)、内部样式(style标签)、引用样式(link标签)、浏览器默认样式、浏览器用户自定义样式表。
然后,把所有CSS样式分解成最小单元,比如:

下面的样式:
.oneClass .twoClass {
    width: 100px;
    margin: 20px;
}

会被分解如下:
.oneClass{
    width: 100px;
}
.oneClass{
    margin-left: 20px;
}
.oneClass{
    margin-right: 20px;
}
.oneClass{
    margin-top: 20px;
}
.oneClass{
    margin-bottom: 20px;
}
.twoClass{
    width: 100px;
}
.twoClass{
    margin-left: 20px;
}
.twoClass{
    margin-right: 20px;
}
.twoClass{
    margin-top: 20px;
}
.twoClass{
    margin-bottom: 20px;
}

2. 权重确定CSS样式

这里很简单,只有如下的规则:

  1. 重要声明的特殊性冲突会在重要声明内部解决(带有!important),遵守最近原则(覆盖),不会与非重要声明相混。
  2. 非重要声明会使用权重来解决冲突,解析并计算它们的权重,权重高的会获胜;权重相同的话,根据来源优先级:浏览器用户自定义>内联>内部>引用>浏览器默认;如果来源相同,则遵守最近原则。
1. 重要声明,!important,放在声明的最后
2. 内联样式特殊性,1, 0, 0, 0
3. ID选择器,0, 1, 0, 0
4. 类选择器、属性选择器或伪类,0, 0, 1, 0
5. 元素选择器和伪元素,0, 0, 0, 1
  1. 额外原则
1. 为目标元素直接添加样式,永远比继承样式的优先级高,无视优先级的遗传规则
2. 无视DOM树中的距离
3. 使用更具体的规则。在您选择的元素之前,增加一个或多个其他元素,使选择器变得更加具体,并获得更高的优先级

3. 应用CSS样式

通过第一步确定页面上所有元素的样式后,开始计算并确定每个元素的样式,包括位置、大小、颜色等等,内容规则都很多。

参考

《CSS 权威指南》

链接

优先级

完结

完结

重新梳理自己曾经的笔记确实收获不少,把很多知识点系统化整理并放入自己的思维宫殿里面。
原来我们访问的每一个网页背后有这么多东西在支撑着,真的很感谢前人的智慧与努力。
尝试用一张图来描述它们:
default

碎片化的知识点

  • 先占个位,等我收集好这些碎片化知识后,会在这里放上链接。

科技黑箱

人类的知识体系到目前为止已经庞大到没有人能穷其一生全掌握了,哪怕是其中一条分支也不能办到。
而人类文明能继续稳健发展的基础就是——“科技黑箱”。
大家各司其职,把内部复杂的东西封装起来,并提供简单的接口给其他人,这就是现代社会合作的基础。
所以,IOS开发者不用去理会iPhone内部运行原理,也可以开发IOS应用。

知识的维度

我把知识分为两个维度:盲点、难点。
盲点是比较简单的,不难理解,只需要接触多了,就会减少盲点;
难点因为跟你的先有知识体系不匹配,所以很难理解,需要花跟多的时间去分析、琢磨。

最后

感谢你能坚持看到这里,对于话痨的我,已经努力把文字精简、篇幅缩短。
如果这些知识能在未来对你的事业有所帮助的话,我将感到很荣幸。

Web 安全专题

概述

Web 安全不可谓不重要。附一张思维导图:
default

XSS

1. 描述

XSS (Cross Site Script)是一个术语,用来描述一类允许攻击者通过网站将客户端脚本代码注入到其他用户的浏览器中的攻击手段。主要分两种:

1. 反射型XSS

本质:

带有恶意代码的URL被打开时,恶意代码被浏览器解析、执行。

经典场景:

网页脚本会渲染URL的某一段,而攻击者会在URL上放入一段攻击代码。比如攻击者会发邮件诱导你点击,当你访问的时候,攻击代码会被顺利执行。

措施:
  1. Web 页面渲染的所有内容或者渲染的数据都必须来自于服务端。
  2. 尽量不要从 URL,document.referrerdocument.forms 等这种 DOM API 中获取数据直接渲染。
  3. 尽量不要使用 eval, new Function()document.write()document.writeln()window.setInterval()window.setTimeout()innerHTMLdocument.creteElement() 等可执行字符串的方法。
  4. 如果做不到以上几点,也必须对涉及 DOM 渲染的方法传入的字符串参数做 escape 转义。
  5. 前端渲染的时候对任何的字段都需要做 escape 转义编码。

概况起来就是,1. 不要从敏感地方获取渲染资源,2. 转义编码。

escape()除了 ASCII 字母、数字和特定的符号外,对传进来的字符串全部进行转义编码,因此如果想对URL编码,最好不要使用此方法。而encodeURI() 用于编码整个URI,因为URI中的合法字符都不会被编码转换。encodeURIComponent方法在编码单个URIComponent(指请求参数)应当是最常用的,它可以讲参数中的中文、特殊字符进行转义,而不会影响整个URL。

2. 持久型XSS

本质:

恶意代码被攻击者顺利提交到服务器,服务器把攻击代码返回给受害者。

经典场景:

攻击者经过论坛提交表单内容(带有攻击代码)到服务器,但是没有经过安全过滤,顺利存储到服务器上,当另一用户访问论坛,服务器返回的内容中包含攻击代码,受害者顺利被攻击。

措施:
  1. 后端在入库前应该选择不相信任何前端数据,将所有的字段统一进行转义处理。
  2. 后端在输出给前端数据统一进行转义处理。
  3. 前端在渲染页面 DOM 的时候应该选择不相信任何后端数据,任何字段都需要做转义处理。

3. 基于字符集的XSS

本质:浏览器在meta没有指定 charset 字符集的时候有自动识别编码的机制。而攻击代码可以通过罕见的字符编码躲避转义编码。
措施:
  1. 指定<meta charset="utf-8">

CSRF

1. 描述

CSRF(Cross-Site Request Forgery),跨站请求伪造攻击。攻击者在用户不知情的情况下利用其身份信息执行操作。
完成CSRF攻击需要具备三个条件:

  1. 用户已经登录了站点 A,并在本地记录了 cookie
  2. 在用户没有登出站点 A 的情况下(也就是 cookie 生效的情况下),访问了恶意攻击者提供的引诱危险站点 B (B 站点要求访问站点A)
  3. 站点 A 没有做任何 CSRF 防御

2. 措施

在服务器端要求每个 POST 请求都包含一个用户特定的由站点生成的密钥( 这个密钥值可以由服务器在发送用来传输数据的网页表单时提供)。

SQL注入

1. 描述

SQL 注入漏洞使得攻击者能够通过在数据库上执行任意SQL代码,从而允许访问、修改或删除数据,而不管该用户的权限如何。

2. 典型案例

用户登录名设置成xxx ' OR 1 = 1 --,如果没有做校验的话,下次用户可以不用登录密码就可以登录了。

3. 措施

  1. 对进入数据库的特殊字符(',",\,<,>,&,*,; 等)进行转义处理,或编码转换。
  2. 后端代码检查输入的数据是否符合预期
  3. 所有的查询语句建议使用数据库提供的参数化查询接口
  4. 严格限制Web应用的数据库的操作权限

命令行注入

1. 描述

本质上就是服务器可以接受用户传入的数据,并作为执行命令行的参数。

2. 措施

  1. 后端对前端提交内容需要完全选择不相信,并且对其进行规则限制
  2. 在调用系统命令前对所有传入参数进行命令行参数转义过滤
  3. 不要直接拼接命令语句,借助一些工具做拼接、转义预处理

DDoS攻击

学名叫分布式拒绝服务(DDoS:Distributed Denial of Service)。

本质上就是利用大量的请求造成资源过载,导致服务不可用。

1. 网络层 DDoS

针对TCP / UDP协议原理进行的攻击。

2. 应用层 DDoS

主要针对TCP成功握手后,DNS和HTTP协议攻击。

流量劫持

本质上就是,攻击者操控用户的网络请求还有返回给用户的网络响应。

1. DNS 劫持

攻击者把用户导向虚假的网站。

2. HTTP 劫持

攻击者截获用户的网络响应结果,并篡改其中的内容。

方案

使用HTTPS就可以解决流量劫持的问题。

服务器漏洞

主要跟服务器系统的健壮性有关。主要包括目录遍历漏洞、物理路径泄漏、源码暴露漏洞。

参考

书籍

《Web前端 黑客技术解密》

链接

MDN的网站安全
常见Web安全攻防总结

VueJS 运行过程

了解生命周期的目的

通过了解Vue对象的生命周期,可以加深对Vuejs的了解,知道哪些步骤具体发生在哪个阶段,进而可以快速找到业务的最佳解决方案;当然了,最重要的,人类毕竟是求知欲望很强的生物。

对于单个Vue对象的生命周期

借用官方一张图:

Vue生命周期

我喜欢把Vue的生命周期分为:

  1. 初始化阶段
  2. 编译阶段
  3. 挂载阶段
  4. 监听阶段
  5. 注销阶段

初始化阶段

这个阶段主要是把普通对象转化为响应式对象。

比如options.data、options.computed、options.watch等将会通过API:Object.defineProperty()转化为响应式的属性。期间options.data还会被挂载到vm._data,并通过代理的方式映射到vm的属性上,所以我们可以访问和设置this[key]的值。

被初始化的顺序:

  1. props
  2. methods
  3. data
  4. computed
  5. watch

编译阶段

编译阶段会把options.template编译成render函数。

编译阶段分三步:

  1. parse,将 template 模板中进行字符串解析,得到指令、class、style等数据,形成 AST
  2. optimize,这个阶段用于优化patch阶段,标记节点的 static 属性是否是静态的
  3. generate,将 AST 转化成 render funtion 字符串,最终得到 render 的字符串以及 staticRenderFns 字符串,这些字符串将来会在render函数里被执行——通过使用eval(string)

如果使用webpage等构件工具的话,预编译阶段将会在打包过程中已经执行过了

挂载阶段

这个阶段会执行render函数以获取vnode。

然后模板引擎根据vnode去生成真实DOM,如果解析到组件vnode(component-vnode)将会实例化组件——也就是初始化、编译、挂载组件。

监听阶段

挂载阶段之后,模板引擎已经渲染好网页,这时就进入了监听阶段。

这个阶段主要响应用户的行为,并触发重新执行render函数,patch函数还会对比新旧vnode,并计算出更新DOM需要的操作。最后由框架更新到网页上。

注销阶段

注销过程会先触发beforeDestroy,然后注销掉watchers、child components、event listeners等,之后是destroyed钩子函数。

对于多个Vue对象的生命周期

下面我们看看父子组件和兄弟组件的生命周期之间的关系。

假设Parent组件是Root组件,child1和child2是其子组件。

它们的生命周期运行顺序如下:

beforeCreate Parent
created Parent
beforeMount Parent
beforeCreate child1
created child1
beforeMount child1
beforeCreate child2
created child2
beforeMount child2
mounted child1
mounted child2
mounted Parent

由于每个组件都是独立更新的,所以一个组件的更新仅会触发该组件的beforeupdate、update、updated阶段。

参考

Vue.js 技术揭秘

并发与并行

并发与并行的区别

打个比喻,你左手拿着炸鸡翅,右手拿着冰淇淋。并发是你吃一口炸鸡翅,然后再舔一下冰淇淋;并行是嚼着鸡肉还舔着冰淇淋。

记忆小卡片:
并发指的是,任务一起发起抢占CPU的请求,但是同一时刻内只有一个任务可以占用CPU,其它任务必须等到CPU轮询执行。
并行指的是,任务一起运行在CPU上,同一时刻内能运行多少个任务取决于CPU的能力(线程多少)。

参考

并发与并行的区别

position: sticky的polyfill

概述

position: sticky是新增的css属性,粘性定位可以被认为是相对定位和固定定位的混合。元素在跨越特定阈值前为相对定位,之后为固定定位。

polyfill

其实这样的效果可以使用JS来实现,下面的链接就是我写的polyfill

position: sticky 的兼容代码

/**
* position: sticky的polyfill
* 推荐在文档加载完成之后(‘DOMContentLoaded’事件)调用
* @param {String} selectors 选择器
* @param {String} top 距离顶部的偏移量
*/
function sticky(selectors, top = 0) {
  let elements = document.querySelectorAll(selectors);
  for (let i = 0; i < elements.length; ++i) {
    elements[i].dataset['originOffsetTop'] = elements[i].getBoundingClientRect().top + ((window.pageYOffset !== undefined) ? window.pageYOffset : (document.documentElement || document.body.parentNode || document.body).scrollTop)
  }
  window.addEventListener('scroll', onScroll(elements, top));

  function onScroll(elements, top) {
    let shouldRun = true
    return () => {
      let scrollY = window.pageYOffset;
      if (shouldRun) {
        // 节流代码
        shouldRun = false
        for (let i = 0; i < elements.length; ++i) {
          let element = elements[i];
          console.log(element)
          // 判断是relative条件还是fixed条件
          if (element.dataset['originOffsetTop'] - scrollY <= top && !element.$isSticky) {
            // fixed条件
            fixed(element, top)
          }
          if (element.dataset['originOffsetTop'] - scrollY > top && element.$isSticky) {
            // relative条件
            element.$unFixed && element.$unFixed()
          }
        }
        setTimeout(() => {
          shouldRun = true
        }, 100)
      }
    }
  }

  function fixed(element, top) {
    let originCss = element.style.cssText;
    element.style.cssText += ";position: fixed; top: " + top + "px;";
    element.$isSticky = true
    element.$unFixed = () => {
      element.$isSticky = false
      element.style.cssText = originCss;
    }
  }
}

思路

  1. 获取元素初始化时候的offsetTop,并存储在元素的dataset中
  2. 监听滚动条事件,并使用节流方式处理滚动事件处理器
  3. 在事件处理器里,获取浏览器的纵轴的滚动偏移量——window.pageYOffset,然后跟元素的纵轴偏移量作比较,判断是否需要对元素做position: fixed处理,或者还原positon

因为需要把元素设置为 position: fixed;所以元素的width和height需要制定确切的值

API

该库仅对外提供一个接口:

/**
* position: sticky的polyfill
* 推荐在文档加载完成之后(‘DOMContentLoaded’事件)调用
* @param {String} selectors 选择器
* @param {String} top 距离顶部的偏移量
*/
function sticky(selectors, top = 0)

NPM地址

案例

可以在CodePen上看到效果。

参考

position

WebAPI 概述

概述

JavaScript是浏览器支持的内置的脚本语言。它的语法遵循ECMAScript规范,但是不同的浏览器厂商可能会有自己的JS引擎,除此之外,浏览器厂商还会实现一些API,提供给开发者使用,诸如操作DOM、与服务端交互数据、画图、视频音频、客户端存储等等。

虽然JS是单线程语言,但是浏览器也会提供了Worker API,用以发挥多核CPU的性能。

所以我们在使用JavaScript的时候,有如下的API可以使用:

  1. JS语言内置的库,如Math、Date
  2. 浏览器厂商提供的Web API
  3. 第三方API,可选,如jQuery、React、Vue

思维导图

附一张自己整理的关于Web API的思维导图:
webapi

参考

MDN的Web API

CSS 进阶内容

1. 3D相关

  1. 层叠上下文
  2. perspective

2. 动画

设置帧:@Keyframes {animation-name}
设置动画参数:animation: animation-name duration timing-function delay iteration-count direction fill-mode;

iteration-count:循环次数,默认为1

infinite:无限循环
<number>:设置次数

direction:指示动画是否反向播放

normal:每个循环内动画向前循环,换言之,每个动画循环结束,动画重置到起点重新开始,这是默认属性
alternate:动画交替反向运行,反向运行时,动画按步后退
reverse:反向运行动画,每周期结束动画由尾到头运行
alternate-reverse:反向交替, 反向开始交替

fill-mode:指定在动画执行之前和之后如何给动画的目标应用样式。

none:动画执行前后不改变任何样式
forwards:目标保持动画最后一帧的样式
backwards:动画采用相应第一帧的样式
both:动画将会执行 forwards 和 backwards 执行的动作。

fill-mode演示

3. 变形(transform)

transform仅对块元素有影响,如display为block、inline-block等

transition:<property> <duration> <timing-function> <delay>;

4. 高级图像

  1. canvas
  2. svg

5. CSS 盒子对齐方式

块元素

  1. 借助margin: auto达到居中对齐的效果
  2. 父元素设置position为非static,子元素设置为absolute进行对齐(margin+top、left,translate)
  3. 绝对定位可以在水平方向上设置left: 0;right: 0;margin: auto;达到水平居中效果;垂直方向同理
  4. display: table-cell; vertical-align: middle;

行内元素

  1. 父元素可以设置text-align达到水平对齐效果
  2. 子元素可以设置vertical-align达到垂直对齐效果

6. 开启硬件加速的方式

那我们怎样才可以切换到GPU模式呢,很多浏览器提供了某些触发的CSS规则。比较常见的方式是,我们可以通过3d变化(translate3d属性)来开启硬件加速。

7. 浮动元素的特性

由于float意味着使用块布局,所以它会修改元素的display值(block)。

浮动的本意:

让文字像流水一样环绕浮动元素。

特性:

  1. 包裹性
  2. 高度欺骗

规则:

  1. 不会超越前面的块元素,仅在本行浮动
  2. 脱离文档流后,下分的块元素会填充
  3. 两个浮动元素的垂直外边距将不会折叠
  4. 浮动后的元素不会影响其他块元素的布局,仅会影响被它覆盖的行内元素
  5. 浮动盒子的顶部不会超出在html文档中早出现的的块级元素(block)或者是浮动元素的顶部

可以从浮动元素的容器、兄弟块元素、兄弟行内元素角度看问题

clear

指定一个元素是否可以在它之前的浮动元素旁边,或者必须向下移动(清除浮动) 到这些浮动元素的下面。

这个规则只能影响使用清除的元素本身,不能影响其他元素。

容器解决高度塌陷的标准写法:

.clearfix::after {
    content: "";
    display: table;
    clear: both;
    overflow: hidden;
    visibility: hidden;
}

8. 导致回流、重绘的操作

回流(reflow)是需要消耗大量cpu或gpu资源的,重绘(repaint)相对而言会少一些。

回流:

  1. 调整浏览器窗口大小
  2. 改变字体型号
  3. 访问引擎浏览器flush队列的属性(如offsetTop等)
  4. 其他引起浏览器重新计算所有节点位置大小的操作

重绘:

  1. fixed元素在滚动条滚动的时候
  2. 仅改变背景样式

9. 减少回流、重绘的方案

  1. 在动画里,使用transform的translate代替left、top等操作
原因是在重绘操作中:Recalculate Style -> Layout -> Paint Setup and Paint -> Composite Layers,
而transform是位于Composite Layers层,而width、left、margin等则是位于Layout层
  1. 在需要添加大量DOM的场景可以借助DocumentFragment对象;也可以先cloneNode,操作完成后对父元素进行replaceChild操作
  2. 如果需要大量复杂的DOM操作,可以先display: none,操作完成后还原display
  3. 不要经常访问会引起浏览器flush队列的属性(如下),如果你确实要访问,就先读取到变量中进行缓存,以后用的时候直接读取变量就可以了
offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、
scrollHeight、clientTop、clientLeft、clientWidth、clientHeight、getComputedStyle()
  1. 添加移动动画时,尽量把元素设置为position: absolute,绝对定位仅会引起重绘,不会引起回流
  2. 千万不要使用 table 布局。因为可能很小的一个小改动会造成整个 table 的重新布局

10. CSS 命名规范

使用连字符分隔字符串
BEM:
    B:用一个连字符连接单词表示一个独立的区块
    E:两个下划线表示子组件
    M:两个连字符表示修饰符
JS-类名:表示使用在js代码里,请谨慎修改

11. 超链接的伪类样式顺序

为了正确渲染超链接的样式,一般它的伪类元素排序(LVHA)应该如下:

a:link{} /* 未访问链接 */ 
a:visited{} /* 已访问链接 */ 
a:hover{} /* 用户鼠标悬停,当鼠标悬浮在链接上面。 */ 
a:active{} /* 激活链接,鼠标按下和松开链接期间。 */ 

12. 对齐方式

水平居中

行内元素:容器使用text-align: center;
块元素:元素本身使用margin: auto;,绝对定位本身使用left: 50%; margin-left: -width/2或者left: 50%; transform: translateX(-50%)或者left: 0;right: 0; margin: auto

垂直居中

行内元素:容器使用line-height: height;
块元素:元素本身使用margin: auto;,绝对定位本身使用top: 50%; margin-top: -height/2或者top: 50%; transform: translateY(-50%)或者top: 0;bottom: 0; margin: auto

使用flex box布局

容器使用:

display: flex;
flex-direction: row;
justify-content: center;
align-items: middle;

13. 百分比

margin-(left、right、top、bottom)的百分比:相对于其最近的父级容器的宽度。

width、height:相对于其最近的父级容器的宽度。

left、right、top、bottom的百分比:代表元素包含块的宽度的百分比 。

transform: translate()的百分比:相对于自身的宽高。

14. 隐藏滚动条

两个方法:

  1. 伪元素::-webkit-scrollbar
.hidden-scrollbar::-webkit-scrollbar {
  display: none;
}
  1. 借助父元素的overflow: hidden,一般滚动条的长度为17px

15. 混合模式

  1. mix-blend-mode
mix-blend-mode: normal;          //正常
mix-blend-mode: multiply;        //正片叠底
mix-blend-mode: screen;          //滤色
mix-blend-mode: overlay;         //叠加
mix-blend-mode: darken;          //变暗
mix-blend-mode: lighten;         //变亮
mix-blend-mode: color-dodge;     //颜色减淡
mix-blend-mode: color-burn;      //颜色加深
mix-blend-mode: hard-light;      //强光
mix-blend-mode: soft-light;      //柔光
mix-blend-mode: difference;      //差值
mix-blend-mode: exclusion;       //排除
mix-blend-mode: hue;             //色相
mix-blend-mode: saturation;      //饱和度
mix-blend-mode: color;           //颜色
mix-blend-mode: luminosity;      //亮度

mix-blend-mode: initial;         //初始
mix-blend-mode: inherit;         //继承
mix-blend-mode: unset;           //复原
  1. background-blend-mode

只能是background属性中的背景图片和颜色混合,而且只能在一个background属性中。

  1. isolation

该属性的主要作用是当和background-blend-mode属性一起使用时,可以只混合一个指定元素栈的背景:它允许使一组元素从它们后面的背景中独立出来,只混合这组元素的背景。

16. CSS匹配方式和优化方案

事实上,CSS 选择符是从右到左进行匹配的。

  1. 避免使用通配符,只对需要用到的元素进行选择
  2. 少用标签选择器
  3. 不要画蛇添足,id 和 class 选择器不应该被多余的标签选择器拖后腿
  4. 减少嵌套。后代选择器的开销是最高的,因此我们应该尽量将选择器的深度降到最低(最高不要超过三层),尽可能使用类来关联每一个标签元素。

17. JS操作样式的途径

  1. 通过style
element.style.height
  1. 通过设置attribute
element.setAttribute(key, value)
element.setAttribute("style", "key: value")
  1. 通过class
element.className += ""
  1. 通过cssText
element.style.cssText += "key: value"
  1. 通过JS插入新的style节点

  2. 通过JS操作document对象的styleSheets

document.styleSheets[0].addRule("css_name", "key: value")
document.styleSheets[0].insertRule("css_name", "key: value")

参考:

浮动元素有什么特征?对父容器、其他浮动元素、普通元素、文字分别有什么影响?
CSS Box Alignment
JavaScript——浏览器的重绘与回流
如何减少回流、重绘
[译]这些 CSS 命名规范将省下你大把调试时间
Float元素的9条特性
CSS浮动float详解
Compositing and Blending
mix-blend-mode
CSS3混合模式mix-blend-mode/background-blend-mode简介

GraphQL

介绍GraphQL

GraphQL是一种标准,或者说是一种规范。
为什么这么说呢?
因为它定义了前端如何请求后端,后端要如何才能回应请求,还有怎样回应其请求这一系列的规范。但是没有去实现它,也没有规定只有某些语言才能使用它,而是任何语言都可以根据这个规范来实现自己的GraphQL库。
打个比方的话,就像JavaScript的EcmaScript,EcmaScript 也是一个标准,JavaScript根据这份标准来实现一些API提供给使用者。

称呼

对于前端开发者来说,GraphQL是一种针对 API 的查询语言;
对于后端开发者来说,GraphQL是类型系统。

特性

  1. 匹配请求的数据,不多不少
  2. 获取多个资源,只用一个请求
  3. 描述所有的可能,类型系统
  4. API演进,无须划分版本
  5. 高解耦,不需要修改现有的代码和数据

使用步骤

使用步骤可以大致分为三步:

  1. 描述数据,在后端实现
  2. 请求数据,前端使用http / https协议请求数据
  3. 获得数据,一般是返回JSON格式的结果

解决了什么行业痛点?

跟Restful风格的API有什么区别?

Restful风格的 API,是根据网络资源划分api的,
比如说一个用户资源:

http://www.xxx.com/users

我们可以通过http的方法(get、post、put、delete等等)来操作这个资源。

而GraphQL推荐只有一个网络请求入口,所有的操作都通过这个接口来完成,
比如

get方法:
http://www.xxx.com/graphql?query={}
post方法(在请求体里写查询语句):
http://www.xxx.com/graphql

所以,Restful风格对于后端开发者来说比较友好,GraphQL标准对于前端开发者来说更友好。
因为使用GraphQL来操作数据是很方便的,但是需要后端开发者来支持,工作量从前端转到后端。
这或许也是阻碍GraphQL发展的一个原因吧,如果你是后端开发者不会主动升级到GraphQL。

参考链接

https://leetcode-cn.com/articles/%E9%98%BB%E7%A2%8D%E4%BD%A0%E4%BD%BF%E7%94%A8-graphql-%E7%9A%84%E5%8D%81%E4%B8%AA%E9%97%AE%E9%A2%98/
https://www.zhihu.com/question/264629587

README

为什么会有这系列的博客?

NodeJS 网络模块

概述

网络相关类的关系如下:

nodejs

当然了,还有内部生成的:

  1. http.ClientRequest,该对象在 http.request() 内部被创建并返回。 它表示着一个正在处理的请求,其请求头已进入队列。
  2. http.ServerResponse,该对象在 HTTP 服务器内部被创建。 它作为第二个参数被传入 'request' 事件。
  3. http.IncomingMessage,IncomingMessage 对象由 http.Server 或 http.ClientRequest 创建,并作为第一个参数分别递给 'request' 和 'response' 事件。

1. dgram

dgram模块提供了 UDP 数据包 socket 的实现。
事件:

  1. close,'close'事件将在使用close()关闭一个 socket 之后触发。
  2. error,当有任何错误发生时,'error'事件将被触发。
  3. listening,当一个 socket 开始监听数据包信息时,'listening'事件将被触发。
  4. message,当有新的数据包被 socket 接收时,'message'事件会被触发。

api:

dgram.createSocket(options[, callback]),创建一个 dgram.Socket 对象. 一旦创建了套接字,调用 socket.bind() 会指示套接字开始监听数据报消息。
dgram.createSocket(type[, callback]),同上。

socket.addMembership(multicastAddress[, multicastInterface])
socket.address(),返回一个包含 socket 地址信息的对象。
socket.bind([port][, address][, callback]),对于 UDP socket,该方法会令dgram.Socket在指定的port和可选的address上监听数据包信息。
socket.bind(options[, callback]),对于 UDP socket,该方法会令dgram.Socket在指定的port和可选的address上监听数据包信息。
socket.close([callback]),关闭该 socket 并停止监听其上的数据。
socket.dropMembership(multicastAddress[, multicastInterface])
socket.getRecvBufferSize(),socket 接收到的字节大小。
socket.getSendBufferSize(),socket 发送的字节大小。
socket.ref(),默认情况下,绑定一个 socket 会在 socket 运行时阻止 Node.js 进程退出。socket.unref() 方法用于将 socket 从维持 Node.js 进程的引用列表中解除。 socket.ref() 方法用于将 socket 重新添加到这个引用列表中,并恢复其默认行为。
socket.send(msg, [offset, length,] port [, address] [, callback]),msg参数包含了要发送的消息。
socket.setBroadcast(flag),设置或清除 SO_BROADCAST socket 选项。
socket.setMulticastInterface(multicastInterface)
socket.setMulticastLoopback(flag)
socket.setMulticastTTL(ttl),设置IP_MULTICAST_TTL套接字选项。
socket.setRecvBufferSize(size),设置 SO_RCVBUF 套接字选项。设置最大的套接字接收缓冲字节。
socket.setSendBufferSize(size),设置 SO_SNDBUF 套接字选项。设置最大的套接字发送缓冲字节。
socket.setTTL(ttl),设置 IP_TTL 套接字选项。 
socket.unref(),参考socket.ref()

2. net

net 模块提供了创建基于流的 TCP 或 IPC 服务器(net.createServer())和客户端(net.createConnection()) 的异步网络 API。

API:

net.connect()
net.createConnection(options[, connectListener]),一个用于创建 net.Socket 的工厂函数,立即使用 socket.connect() 初始化链接,然后返回启动连接的 net.Socket。
net.createConnection(path[, connectListener]),同上。
net.createConnection(port[, host][, connectListener]),同上。
net.createServer([options][, connectionListener]),创建一个新的TCP或IPC server。
net.isIP(input),测试 input 是否是 IP 地址。
net.isIPv4(input),如果 input 是 IPv4 地址则返回 true,否则返回 false。
net.isIPv6(input),如果 input 是 IPv6 地址则返回 true,否则返回 false。

2.1. net.server

这个类用于创建 TCP 或 IPC server。
事件:

close,当server关闭的时候触发。
connection,当一个新的connection建立的时候触发。
error,当错误出现的时候触发。
listening,当服务被绑定后触发。

API:

server.address(),如果在IP socket上监听,则返回绑定的ip地址,一个有 port, family, 和 address 属性: { port: 12346, family: 'IPv4', address: '127.0.0.1' }的对象。
server.close([callback]),停止 server接受建立新的connections并保持已经存在的connections。
server.getConnections(callback),异步获取服务器的当前并发连接数。当 socket 被传递给子进程时工作。
server.listen(handle[, backlog][, callback]),为 connections 启动一个 server 监听. 一个 net.Server 可以是一个 TCP 或者 一个 IPC server,这取决于它监听什么。
server.listen(options[, callback]),同上。
server.listen(path[, backlog][, callback]),同上。
server.listen([port[, host[, backlog]]][, callback]),同上。
server.listening,一个布尔值, 表明 server 是否正在监听连接。
server.maxConnections,设置该属性使得当 server 连接数过多时拒绝连接。
server.ref()
server.unref()

2.2. net.Socket

这个类是 TCP 或 UNIX Socket 的抽象(在Windows上使用命名管道,而UNIX使用域套接字)。net.Socket 实例实现了一个双工流接口。 他们可以在用户创建客户端(使用 connect())时使用, 或者由 Node 创建它们,并通过 connection 服务器事件传递给用户。
事件:

close,一旦 socket 完全关闭就发出该事件。
connect,当一个 socket 连接成功建立的时候触发该事件。 
data,当接收到数据的时触发该事件。
drain,当写入缓冲区变为空时触发。可以用来做上传节流。
end,当 socket 的另一端发送一个 FIN 包的时候触发,从而结束 socket 的可读端。
error,当错误发生时触发。
lookup,在找到主机之后创建连接之前触发。
timeout,当 socket 超时的时候触发。该事件只是用来通知 socket 已经闲置。

API:

socket.address()
socket.bufferSize,net.Socket 具有该属性,socket.write() 工作时需要。
socket.bytesRead,接收的字节数量。
socket.bytesWritten,发送的字节数量。
socket.connect(options[, connectListener])
socket.connect(path[, connectListener])
socket.connect(port[, host][, connectListener])
socket.connecting
socket.destroy([exception]),确保在该 socket 上不再有 I/O 活动。
socket.destroyed,一个布尔值,用来指示连接是否已经被销毁。
socket.end([data][, encoding]),半关闭 socket。例如发送一个 FIN 包。服务端仍可以发送数据。
socket.localAddress,远程客户端连接的本地 IP 地址字符串。
socket.localPort,用数字表示的本地端口。
socket.pause(),暂停读写数据。
socket.ref()
socket.remoteAddress,用字符串表示的远程 IP 地址。
socket.remoteFamily,用字符串表示的远程 IP 协议族。
socket.remotePort,用数字表示的远程端口。
socket.resume(),在调用 socket.pause() 之后恢复读取数据。
socket.setEncoding([encoding]),设置作为可读流(Readable Stream)的编码。
socket.setKeepAlive([enable][, initialDelay]),启用/禁用长连接功能, 并且在第一个长连接探针被发送到一个空闲的 socket 之前可选则配置初始延迟。
socket.setNoDelay([noDelay]),禁止 Nagle 。默认情况下 TCP 连接使用 Nagle 算法,在发送之前缓冲数据。将 noDelay 设置为 true 将会在每次 socket.write() 被调用的时候立即发送数据。noDelay默认是 true。
socket.setTimeout(timeout[, callback]),当 socket 在 timeout 毫秒不活动之后将其设置为超时状态。
socket.unref()
socket.write(data[, encoding][, callback]),在 socket 上发送数据。第二个参数制定了字符串的编码 - 默认是 UTF8 编码。

3. http

要使用 HTTP 服务器与客户端,需要 require('http')。

http模块是对HTTP协议的描述,简化了使用方式。包括通信的双方的API,其中:

  1. http.ClientRequest代表通信的客户端
  2. http.Server代表通信的服务端
  3. http.Agent是服务器的配置,表示服务器是否支持keepAlive、支持keepAlive多久、最多支持多少个socket/空闲socket、超时时间
  4. http.IncomingMessage代表一次通信的请求报文
  5. http.ServerResponse代表一次通信的响应报文

3.1. http.Agent

Agent 负责为 HTTP 客户端管理连接的持续与复用。

API:

new Agent([options])
agent.createConnection(options[, callback]),创建一个用于 HTTP 请求的 socket 或流。
agent.keepSocketAlive(socket)
agent.reuseSocket(socket, request)
agent.destroy()
agent.freeSockets
agent.getName(options)
agent.maxFreeSockets
agent.maxSockets
agent.requests
agent.sockets

3.2. http.ClientRequest

该对象在 http.request() 内部被创建并返回。 它表示着一个正在处理的请求,其请求头已进入队列。 请求头仍可使用 setHeader(name, value)、getHeader(name) 和 removeHeader(name) API 进行修改。 实际的请求头会与第一个数据块一起发送或当调用 request.end() 时发送。

事件:

abort
connect
continue
information
response
socket
timeout
upgrade

API:

request.abort(),标记请求为终止。
request.aborted
request.connection
request.end([data[, encoding]][, callback]),结束发送请求。 如果部分请求主体还未被发送,则会刷新它们到流中。 如果请求是分块的,则会发送终止字符 '0\r\n\r\n'。
request.flushHeaders()
request.getHeader(name)
request.maxHeadersCount
request.removeHeader(name)
request.setHeader(name, value)
request.setNoDelay([noDelay])
request.setSocketKeepAlive([enable][, initialDelay])
request.setTimeout(timeout[, callback])
request.socket
request.write(chunk[, encoding][, callback]),发送请求主体的一个数据块。 通过多次调用该方法,一个请求主体可被发送到一个服务器,在这种情况下,当创建请求时,建议使用 ['Transfer-Encoding', 'chunked'] 请求头。

3.3. http.Server

该类继承自 net.Server。
事件:

checkContinue
checkExpectation
clientError
close
connect,每当客户端发送 HTTP CONNECT 请求时触发。
connection,当新的 TCP 流被建立时触发。
request,每次接收到一个请求时触发。 
upgrade

API:

server.close([callback])
server.listen(),开启HTTP服务器监听连接。
server.listening
server.maxHeadersCount
server.setTimeout([msecs][, callback])
server.timeout
server.keepAliveTimeout

3.4. http.ServerResponse

该对象在 HTTP 服务器内部被创建。 它作为第二个参数被传入 'request' 事件。

3.5. http.IncomingMessage

IncomingMessage 对象由 http.Server 或 http.ClientRequest 创建,并作为第一个参数分别递给 'request' 和 'response' 事件。 它可以用来访问响应状态、消息头、以及数据。

3.6. http.METHODS

返回解析器支持的 HTTP 方法的列表。

3.7. http.STATUS_CODES

返回标准的 HTTP 响应状态码的集合,以及各自的简短描述。

3.8. http.createServer([options][, requestListener])

返回一个新的 http.Server实例。

3.9. http.get(options[, callback])

因为大多数请求都是 GET 请求且不带请求主体,所以 Node.js 提供了该便捷方法。 该方法与 http.request() 唯一的区别是它设置请求方法为 GET 且自动调用 req.end()。 返回一个 http.ClientRequest 类的实例。

callback 被调用时只传入一个参数,该参数是 http.IncomingMessage 的一个实例。

3.10. http.globalAgent

Agent 的全局实例,作为所有 HTTP 客户端请求的默认 Agent。

3.11. http.request(options[, callback])

Node.js 为每台服务器维护多个连接来进行 HTTP 请求。 该函数允许显式地发出请求。

4. https

我们都知道HTTPS是建立在TLS/SSL之上的HTTP。在NodeJS中,它被分成另外一个模块,但是它的API和http模块几乎一样,只是用法有些差别,比如在创建https服务器的时候需要注入key和cert,来一段官网例子:

// curl -k https://localhost:8000/
const https = require('https');
const fs = require('fs');

const options = {
  key: fs.readFileSync('test/fixtures/keys/agent2-key.pem'),
  cert: fs.readFileSync('test/fixtures/keys/agent2-cert.pem')
};

https.createServer(options, (req, res) => {
  res.writeHead(200);
  res.end('hello world\n');
}).listen(8000);

5. http2

http2模块提供了对HTTP2协议的实现。

5.1 核心API

提供了更底层的接口来支持HTTP/2协议特征。

关键类:

  1. Http2Session:Http2Session类实例表示一个通信双方建立的会话
  2. ServerHttp2Session
  3. ClientHttp2Session
  4. Http2Stream:Http2Stream类的每个实例表示会话上的通信流,一次通信包括请求报文和响应报文
  5. ClientHttp2Stream
  6. ServerHttp2Stream
  7. Http2Server,普通的http2
  8. Http2SecureServer,基于TSL/SSL的http2

虽然HTTP2协议并没有要求必须基于TSL/SSL,但是很多浏览器都不支持普通的http2

5.2 兼容API

为了兼容HTTP1,可以在创建HTTP2服务器的时候设置:allowHTTP1: true,可以参考ALPN negotiation

关键类:

  1. http2.Http2ServerRequest,表示请求对象
  2. http2.Http2ServerResponse,表示响应对象

参考

Node.js Net 模块

基础模块

1. events

类似于DOM的事件模型,如果你想要为你的对象能够触发事件的话,可以继承NodeJS提供的EventEmitter类。
NodeJS中许多内置的类都继承自EventEmitter类,比如net.Serverfs.ReadStreamStream
API:

两个全局事件,‘newListener’和‘removeListener’分别表示`EventEmitter`的子类添加或移除监听器的时候触发的事件。
EventEmitter.defaultMaxListeners,全局设置`EventEmitter`的单个事件的监听器数量。

addListener(eventName, listener),添加 listener 函数到名为 eventName 的事件的监听器数组的末尾。
emit(eventName[, ...args]),按监听器的注册顺序,同步地调用每个注册到名为 eventName 事件的监听器,并传入提供的参数。
eventNames(),返回一个列出触发器已注册监听器的事件的数组。
getMaxListeners(),返回 EventEmitter 当前的最大监听器限制值。
listenerCount(eventName),返回正在监听名为 eventName 的事件的监听器的数量。
listeners(eventName),返回名为 eventName 的事件的监听器数组的副本。
off(eventName, listener),removeListener的别名。
on(eventName, listener),addListener的别名。
once(eventName, listener),添加一个单次 listener 函数到名为 eventName 的事件。
prependListener(eventName, listener),添加 listener 函数到名为 eventName 的事件的监听器数组的开头。 
prependOnceListener(eventName, listener),添加一个单次 listener 函数到名为 eventName 的事件的监听器数组的开头。
removeAllListeners([eventName]),移除全部或指定 eventName 的监听器。
removeListener(eventName, listener),从名为 eventName 的事件的监听器数组中移除指定的 listener。
setMaxListeners(n),修改指定的 EventEmitter 实例的限制。
rawListeners(eventName),返回一个监听某具名为eventName事件的数组的拷贝,包括任何被包装的事件(例如由.once()创建的事件)。

2. global

全局变量在所有模块中均可使用。

Buffer,用于处理二进制数据。
console,用于打印 stdout 和 stderr。
global,全局的命名空间对象。
process,进程对象。
URL,URL对象。
URLSearchParams,URLSearchParams对象。
Timer,定时器:
    setImmediate(callback[, ...args])
    setInterval(callback, delay[, ...args])
    setTimeout(callback, delay[, ...args])
    clearImmediate(immediateObject)
    clearInterval(intervalObject)
    clearTimeout(timeoutObject)

3. module

以下变量虽然看起来像全局变量,但实际上不是,它们是属于模块作用域的:

1. __dirname,当前模块的文件夹名称。
2. __filename,当前模块的文件名称---解析后的绝对路径。
3. exports,这是一个对于 module.exports 的更简短的引用形式。
4. module,对当前模块的引用。
5. require(),引入模块。

A. require()

1. require.cache
2. require.main
3. require.resolve,使用内部的 require() 机制查询模块的位置, 此操作只返回解析后的文件名,不会加载该模块。
4. require.resolve.paths,返回一个数组,其中包含解析 request 过程中被查询的路径。 如果 request 字符串指向核心模块(例如 http 或 fs),则返回 null。

通过 require.resovle 可以提前在文件中作为常量定义, 那么在应用启动时就可以抛异常, 而不是等到具体操作文件的时候才抛异常。

B. module

module.children,被该模块引用的模块对象。
module.exports,导出的对象。
module.filename,模块的完全解析后的文件名。
module.id,模块的标识符。 通常是完全解析后的文件名。
module.loaded,模块是否已经加载完成,或正在加载中。
module.parent,最先引用该模块的模块。
module.paths,模块的搜索路径。
module.require(id)

每一个模块开始,都会执行exports = module.exports = {},真正导出的对象是module.exports

参考

events
globals
module
Node.js 的 require.resolve 简介

V8 引擎

概述

V8引擎编译相关类:
compile

V8引擎运行相关类:
run

上面的图片摘自《WebKit 技术内部》

V8 引擎作为JS引擎中的佼佼者,主要有如下的方面改进:

1. 延迟编译

V8 有一个重要的特点就是延迟(deferred)**,使得很多 JavaScript 代码的编译直到运行的时候才会进行,这样可以减少很多时间开销。比如很多回调(callback)都是依赖异步框架和事件循环的,它们都是等到时机成熟了之后才会被执行。

2. 函数优化和优化回滚

V8 有两个编译器:

一个非常简单并且非常快的编译器用于将 js 编译成简单但是很慢的机械码,叫做 full-codegen
另一个是非常复杂的实时优化编译器,编译高性能的可执行代码,叫做 Crankshaft

最开始执行你的代码的时候,V8 开始使用 full-codegen,full-codegen 直接将 JS 代码解释成机械码,没有做任何转化。这可以让 V8 快速执行机械码。注意,V8 并不使用中间字节码,因此也就不再需要转译处理。当你的代码被执行的时候,profiler 线程有足够的数据来找出哪些方法需要被优化。

编译器通常会做比较乐观和大胆的预测,那就是认为这些代码比较稳定,比如函数的入参的类型是不会改变的,所以生成高效的本地代码。但是如果某次调用该函数的时候,入参的类型改变了,那么就会把本地代码回滚到原代码,这就是优化回滚。

3. 数据表示

V8把JS代码里的每一个定义的变量分为三部分,第一部分是变量名;第二部分是数据的句柄,变量名指向的是数据的句柄,句柄的大小是固定的;第三部分是数据的实际内容。这样做是因为,JS是动态类型的,由句柄存储数据的实际类型,并指向实际内容的地址,也利于V8进行垃圾回收。

句柄的后两位用来表示数据是整数(00)或其它(01)。如果句柄是指向对象的,在V8中对象内部包含3个成员。第一个是指向隐藏类的指针,第二个是指向属性值表的指针,第三个是指向元素表的指针。

4. 隐藏类和内嵌缓存

大多数 JavaScript 解释器使用类似字典的结构 (基于散列函数) 去存储对象属性值在内存中的位置,查找对象属性的位置效率很低。

V8使用类和偏移位置**,将本来需要通过字符串匹配来查找属性值的算法改进为使用类似C++编译器的偏移位置的机制来实现,这就是隐藏类(Hidden Class)。

隐藏类将对象划分成不同的组,它存储着同组内的对象的属性名和对应的偏移位置,对于同组(该组内的对象拥有相同的属性名和属性值的类型)内的所有对象共享这些信息。

就算通过隐藏类已经可以提高访问属性值的效率,但是还可以做得更好,就是把查找过的结果信息缓存起来,如果当前对象和之前的对象是同一个隐藏类,那么就可以省去在隐藏类里查找信息的功夫。这就是内嵌缓存。

V8维护一个对象类型的缓存;这些对象在最近的方法调用中被当做传参,然后V8根据这个缓存信息来推断将来什么样类型的对象会再次被当成传参。如果V8能够准确推断出接下来被传入的对象类型,那么它就能绕开获取对象属性的计算步骤,而只是使用先前查找该对象的隐藏类时所存储的信息。

5. 内存管理

1. Zone对象

Zone对象首先自己申请一块内存,然后管理和分配一些小内存。当一块小内存被分配之后,不能够被Zone回收,只能一次性回收Zone分配的所有小块内存。

2. 堆

V8使用堆来管理JS使用的数据,以及生成的代码、哈希表等。

为了实现垃圾回收,V8将堆分为三个部分,第一个是年轻分代,第二个是年老分代,第三个是大对象空间。

对于年轻分代,主要是为新创建的对象分配内存空间,因为年轻分代总的对象比较轻易被要求回收,为了方便垃圾回收,可以使用复制方式,将年轻分代分为两半,一半用来分配,另外一半在回收的时候负责将之前还需要保留的对象复制过来。

对于年轻分代,经常需要进行垃圾回收。
对于年老分代,主要是根据需要将年老的对象、指针、代码等数据使用的内存较少地做垃圾回收。
对于大对象空间,主要是用来为那些需要使用较多内存的大对象分配内存。

3. 垃圾回收算法

V8使用的是“标记-清除”垃圾回收算法,该算法比“引用计数”垃圾回收算法更好,可以避免循环引用导致无法回收垃圾的情况。

“标记-清除”算法假定设置一个叫做根(root)的对象(在Javascript里,根是全局对象)。垃圾回收器将定期从根开始,找所有从根开始引用的对象,然后找这些对象引用的对象……从根开始,垃圾回收器将找到所有可以获得的对象和收集所有不能获得的对象。

标记阶段会阻止 JavaScript 的运行。为了控制垃圾回收的成本,并且使 JavaScript 的执行更加稳定,V8 使用增量标记:与遍历全部堆去标记每一个可能的对象的不同,取而代之的是它只遍历部分堆,然后就恢复正常执行。下一次垃圾回收就会从上一次遍历停下来的地方开始,这就使得每一次正常执行之间的停顿都非常短。就像前面说的,清理的操作是由独立的线程的进行的。

6. 快照机制

快照机制是将内置的对象和函数加载之后的内存保存并序列化。序列化之后的结果很容易被反序列化,经过快照机制的启动时间,可以大幅缩减。

7. 题外话:JavaScriptCore

JavaScriptCore和V8最大差别在于,把源代码转变成抽象语法树(AST)之后,JavaScriptCore会将AST转化为字节码,该字节码是运行在解释器上面的,并由JIT不断的改进优化。而V8则通过全代码生成器(full code generator)把抽象语法树直接生成本地代码,为了性能考虑,还会通过数据分析器(Profiler)来采集一些信息,来帮助决策哪些本地代码需要优化,以生成效率更高的本地代码,这是一个逐步改进的过程。

参考

书籍

《WebKit 技术内幕》

链接

V8基础学习一:从编译流程学习V8优化机制
[译] JavaScript 如何工作:在 V8 引擎里 5 个优化代码的技巧
Google V8 引擎工作原理(翻译)

类相关

前言

在JS的世界中,是没有严格的类这个概念的,但它确实是一门面向对象语言。

  1. 封装,JS的对象就是由一些键值对数据组成,封装了数据和方法
  2. 继承,虽然没有类这个概念,但是可以通过new和原型链实现继承
  3. 多态,由于JS是动态弱类型语言,所以是很容易实现的

鸭子类型(duck typing)如果它走起路来像鸭子,叫起来也是鸭子,那么它就是鸭子。

prototype

前面的文章说过,JS引擎线程用堆来存储执行过程中创建的对象(函数也是对象),每个函数对象都有prototype属性,每个prototype默认有两个属性:constructor(指向函数自己)和__proto__指向下一个原型。
如果一个函数对象被当做构造函数使用,也就是使用new语法糖调用函数对象,那么它的prototype会称为新生成的对象的原型(proto)。

new的原理

1. 构造函数

JS没有严格的类概念,它是通过一个(构造)函数加上new语法糖去生成一个新对象的。
大致过程如下:

  1. 创建一个新的空对象
  2. 把新对象的__proto__指向构造函数的prototype(默认的prototype就有constructor指向构造函数,还有__proto__指向下一个原型链);也可以描述成使用setPrototypeOf()方法设置新对象的原型
  3. 对该构造函数使用call或apply方法,传入该新对象(作为this)和构造函数的参数
  4. 如果构造函数没有返回对象(普通对象、函数对象、数组),则返回这个新对象

如果构造函数返回了非对象(!result instanceof Object),并不影响返回this指向的新对象。

new

2. instanceof

借用MDN的一句话

instanceof 运算符用来测试一个对象(A)在其原型链中是否存在一个构造函数(B)的 prototype 属性。

用法:

A instanceof B

1. 这里的A是对象,如果是基本类型的话,是找不到它的原型的

0 instanceof Number // false
new Number(0) instanceof Number // true

2. 对象A会查看自己的__proto__属性指向的是不是B的prototype,如果不是则继续往下——查看A的__proto____proto__是不是指向B的prototype, 遍历直到找到返回true,或者遍历结束返回false。

我们用原型链继承的例子解释这个过程:

function Parent () {};
function Child () {};
Child.prototype = new Parent();
var child = new Child();

child instanceof Child; // true
child instanceof Parent; // true

isPrototypeOf() 方法允许你检查一个对象是否存在于另一个对象的原型链上。

3. 即使一个构造函数的prototype属性中的constructor属性没有指向自己也不会影响instanceof的结果

3. 防止构造函数被直接调用

由于构造函数和普通函数一样都是可以直接调用的。
所以为了防止构造函数被当做普通函数使用,下面有两种方案可以选择。

  1. 上面的描述中,第三步执行构造函数之前就已经把新对象的__proto__设置成了构造函数的prototype,所以可以借助instanceof判断新对象的原型链上是不是有构造函数的prototype
function Bar(params) {
    if (!(this instanceof Bar)) {
      return new Bar(params);
    }
}
  1. 还可以借助new.target来判断构造函数是不是通过new被调用的
function Foo() {
  if (!new.target) throw "Foo() must be called with new";
  console.log("Foo instantiated with new");
}

继承的几种方式

1. 原型链继承

描述:将父类的实例作为子类的原型

function Parent () {};
function Child () {};
Child.prototype = new Parent();
var child = new Child();

缺点:

  1. 来自原型对象的所有属性被所有实例共享
  2. 创建子类实例时,无法向父类构造函数传参

2. 构造继承

描述:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)

function Parent () {};
function Child () {
    Parent.call(this);
};
var child = new Child();

缺点:

  1. 实例并不是父类的实例,只是子类的实例
  2. 只能继承父类的实例属性和方法,不能继承原型属性/方法
  3. 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

3. 实例继承

描述:为父类实例添加新特性,作为子类实例返回

function Parent () {};
function Child (prop) {
    var instance = new Parent ();
    instance.prop = prop || 'default_prop';
    return instance;
}

缺点:

  1. 实例是父类的实例,不是子类的实例

4. 拷贝继承

描述:每次构造新对象的时候,给新对象的原型添加同名的父类可枚举属性(包括原型链)

function Parent () {};
function Child (prop) {
    var instance = new Parent ();
    for (var p in Parent) {
        Child.prototype[p] = Parent[p];
    }
    instance.prop = prop || 'default_prop';
    return instance;
}

缺点:

  1. 效率较低,内存占用高
  2. 无法获取父类不可枚举的方法

5. 组合继承(原型链+构造函数)

描述:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用

function Parent () {};
function Child () {
    Parent.call(this);
};
Child.prototype = new Parent();
var child = new Child();

缺点:

调用了两次父类构造函数,生成了两份实例

6. 寄生组合继承

描述:子类实例通过构造函数继承父类的属性,子类实例的原型是以父类原型作为原型的空对象

function Parent () {};
function Child () {
    Parent.call(this);
};
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
var child = new Child();

对比组合继承,寄生组合继承避免创建了一些不必要的、多余的属性。因为组合继承使用new Parent()创建的新对象设置为子类的prototype,该新对象还包括一些冗余的属性。

7. 总结几种继承方式

所以,寄生组合式继承是目前业内公认最高效整洁的继承方式。

可能原型式继承和寄生式继承太受欢迎了吧,ES5实现了一个api:Object.create(proto, propertiesObject)

// Polyfill
if (typeof Object.create !== "function") {
    Object.create = function (proto, propertiesObject) {
        // 这里省略一些安全检查
        var o;
        function F() {};
        F.prototype = proto;
        o = new F();
        o.defineProperties(propertiesObject)
        return o
    }
}

行为委托

JavaScript 本身就没有类似“类”的抽象机制。基于原型的方式相对于面向类编程,更适合叫“对象关联”(OLOO,objects linked to other objects)。

摘一段来自《你不知道的JavaScript》上卷里“行为委托”章节里的代码:

Task = {
    setID: function (ID) { this.id = ID; },
    outputID: function () { console.log(this.id); }
};
// 让XYZ 委托Task
XYZ = Object.create(Task);
XYZ.prepareTask = function (ID, Label) {
    this.setID(ID);
    this.label = Label;
};
XYZ.outputTaskDetails = function () {
    this.outputID();
    console.log(this.label);
};

代理模式

可以代理处理对某个对象的操作。跟设计模式里的代理模式类似,可以设置拦截属性查找,赋值,枚举,函数调用等。

下面的代码展示使用Proxy来实现前面的行为委托:

let person = {
    name: 'Bar'
}

let handler = {
    get: function (target, name) {
        if (name === 'getName') {
            return () => target['name']
        }
    }
}

let p = new Proxy(person, handler)

console.log(p.getName()) // Bar

当然Proxy还可以实现更多的功能,推荐MDN的权威链接

VueJs的代理

关键源码(2.5.27版本)

我把关键源码抽取出来:

// initData 方法里有如下代码,说明我们传给Vue构造函数的options.data被绑定到了vm._data
let data = vm.$options.data
data = vm._data = typeof data === 'function' ?
    getData(data, vm) :
    data || {}

// 通关代理的方式,使vm[key] === vm._data[key]
const sharedPropertyDefinition = {
    enumerable: true,
    configurable: true,
    get: noop,
    set: noop
}

export function proxy(target: Object, sourceKey: string, key: string) {
    sharedPropertyDefinition.get = function proxyGetter() {
        return this[sourceKey][key]
    }
    sharedPropertyDefinition.set = function proxySetter(val) {
        this[sourceKey][key] = val
    }
    Object.defineProperty(target, key, sharedPropertyDefinition)
}

proxy(vm, `_data`, key) // 遍历设置代理

借助Object.defineProperty就可以把vm[key]的getter和setter绑定到vm._data[key]上,而options.data和vm._data是指向同一个对象的。
官网有如下一个例子指出了这一点:

// 我们的数据对象
var data = { a: 1 }
// 该对象被加入到一个 Vue 实例中
var vm = new Vue({
  data: data
})
// 它们引用相同的对象!
vm.a === data.a // => true

ES6

即使在ES6里,有了class、extend关键字,但是JS的本质还是没有改变。

1. class

语法糖class,规定必须有一个constructor(如果没有系统会自动添加一个默认的constructor)和若干个函数声明。
其中的constructor就是构造函数;
若干个函数声明会转变为构造函数的prototype上的函数。

class A {
  constructor () {/**constructor code**/}

  foo () {/**foo code**/}
}

会被转化为下面:

var A = function () {
  function A () {/**constructor code**/}

  A.prototype.foo = function () {/**foo code**/}

  return A
}

2. extend

也是一个语法糖,原理是上面提到的寄生组合式继承。
可以通过一段代码来验证:

class Father {
    constructor() {
        this.fatherSelf = function () { }
    }

    fatherPrototype() { }
}

class Child extends Father {
    constructor() {
        super()
        this.childSelf = function () { }
    }

    childPrototype() { }
}

var child = new Child()

上面的代码可以直接在chrome的开发者工具里运行,然后查看c的详细信息。
extend

写在最后

博客如有哪里不对,请不吝赐教。

参考链接

new.target
mqyqingfeng/Blog#16
Object.create
ES6 入门,阮一峰
JS实现继承的几种方式

深究Function.prototype.bind

前言

在读这篇文章之前,希望你对Function.prototype.bind有所了解。
如果还没有的话,强烈推荐去看看MDN上关于它的介绍,飞机票

主要有以下两个特征:

  1. 多次bind,仅第一次的bind传入的绑定this生效
  2. 使用new 操作bind返回的构造函数,曾经绑定的this会失效

bind的polyfill

MDN上为了向下兼容给出了bind的polyfill,先把代码贴出来:

if (!Function.prototype.bind) {
  Function.prototype.bind = function(oThis) {
    if (typeof this !== 'function') {
      // closest thing possible to the ECMAScript 5
      // internal IsCallable function
      throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
    }

    var aArgs   = Array.prototype.slice.call(arguments, 1),
        fToBind = this,
        fNOP    = function() {},
        fBound  = function() {
          return fToBind.apply(this instanceof fNOP
                 ? this
                 : oThis,
                 aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    if (this.prototype) {
      // Function.prototype doesn't have a prototype property
      fNOP.prototype = this.prototype; 
    }
    fBound.prototype = new fNOP();

    return fBound;
  };
}

一段示例代码

var o1 = { a: 1 }
var o2 = { b: 2 }
var f = function () {
    console.log(this)
    console.log([].slice.call(arguments))
}

var f1 = f.bind(o1, 1, 2) // A行
var f2 = f1.bind(o2, 3, 4) // B行

f2(5, 6) // C行

学习方法有正向也有反向,我们从运行代码来解释这段polyfill

分析

接下来将会从执行上下文栈来解析这段代码运行的整个过程。
如果对“执行上下文栈”还不了解的话,推荐看我的另一篇文章——执行上下文

1. 刚开始时的全局执行上下文:

  1. 变量对象:o1,o2,f,f1,f2
  2. 作用域链:目前为空
  3. this,指向window

2. A行执行时加入的执行上下文:

  1. 变量对象:oThis === o1,aArgs === [1, 2],fToBind === f,fNOP,fBound
  2. 作用域链:全局执行上下文
  3. this,指向f
  4. 返回的f1,指向变量对象的fBound,它的原型链:fBound.prototype.proto === f.prototype

3. B行执行时加入的执行上下文:

  1. 变量对象:oThis === o2,aArgs === [3, 4],fToBind === f1,fNOP,fBound
  2. 作用域链:全局执行上下文
  3. this,指向f1
  4. 返回的f2,指向变量对象的fBound,它的原型链:fBound.prototype.proto === f1.prototype

4. C行执行时加入的执行上下文:

  1. 变量对象:arguments
  2. 作用域链:比较复杂,看下面说明
  3. this,指向window

C行其实会执行两次函数
第一次:

  1. 变量对象:arguments === [5, 6]
  2. 作用域链:B行的执行上下文(闭包)、全局执行上下文
  3. this,指向window
f2(5, 6) === return f1.apply(o2, [3, 4, 5, 6])

第二次:

  1. 变量对象:arguments === [3, 4, 5, 6]
  2. 作用域链:A行的执行上下文(闭包)、全局执行上下文
  3. this,指向o2
return f1.apply(o2, [3, 4, 5, 6])  === return f.apply(o1, [1, 2, 3, 4, 5, 6]

5. 结果

所以f2(5, 6)的打印的结果就是

{a: 1}
[1, 2, 3, 4, 5, 6]

可以直接放到chrome的开发者工具里运行得到结果。

两处亮点

1. 维护原型关系

这里使用的是“原型式继承”,可以参考我的另一篇文章——类相关
在这里的作用是,把原函数(f)的原型保留下来,以供第二个亮点使用。

2. bind不影响new

我想你一定很疑惑fBound里的这段代码

this instanceof fNOP ? this : oThis

其实这里的作用就是为了bind返回的函数不影响new操作符创建对象(也就是this被忽略)。
如果再执行以下语句,再上门的基础上修改f:

var f = function () {
    this.c = 3
    console.log(this)
    console.log([].slice.call(arguments))
}

var f2Obj = new f2(5, 6);

// 运行过程,下面的this指将要创建的新对象:
f2(5, 6) === return f1.apply(this, [3, 4, 5, 6] === return f.apply(this, [1, 2, 3, 4, 5, 6]

// 结果(在chrome上执行)
打印:
f {c: 3}
[1, 2, 3, 4, 5, 6]

并且 f2Obj.c === 3

总结

总结一下polyfill的思路:

bind的函数体维护5个变量——绑定的this(oThis)、当前函数(也就是调用bind的函数)、参数、返回的函数、用于维护原型关系的空函数

返回的函数

返回的函数的函数体里:使用apply调用当前函数,先通过this判断返回的函数是不是维护原型关系的空函数的实例(被new操作符调用),如果是的话,使用this作为apply绑定的this,如果不是的话,使用oThis作为apply绑定的this,参入的参数为拼接的。

因为直接调用返回函数的话,this会指向undefined或者window,所以需要维护原型,接着往下。

用于维护原型关系的空函数

过程是:把当前函数的原型赋值给维护原型关系的空函数的原型,然后使用原型链继承的方式返回一个实例并赋值给返回的函数,这样一来,返回的函数被new操作符调用的时候,可以通过this来判断是不是维护原型关系的空函数的实例。

这个逻辑是适合嵌套的。

Web交互的关键:提交表单

概述

  1. 表单提交的原理
  2. 表单提交的方式

1. 表单提交的原理

前端与后端的交互方式,除了http协议,还有websocket协议等等。
但是表单的提交几乎都是遵循http(https)协议。
换句话说,客户端和服务端交互的媒介就是请求报文和响应报文。

2. 表单提交的方式

不依靠JS

1. 传统方法

这种方式就是点击Form表单的submit按钮,让浏览器根据Form表单的action和method把表单数据,还有其他一些相关的数据(比如请求头)合并成请求报文;而接受响应报文则是通过Form表单的target属性来接收。

2. target为iframe

步骤为:

  1. 新建一个iframe,设置属性为不可见。
visibility: hidden;
  1. 还有设置load事件,并用如下方式接收来自服务器的响应
iframe.addListener('', function() {
  iframe.contentDocument.body.textContent || iframe.contentWindow.document.body.textContent;
})
  1. 最后把form的target设置为该iframe
  2. 当表单submit的时候就不会重定向了
this.form.submit();

依靠JS、XHR

这种方式借助XMLHTTPRequest对象或Fetch API,优点是更灵活(可以自定义校验表单的细节),可以异步发起请求等等。

请求头常用的Content-Type:

  1. application/x-www-form-urlencoded
  2. application/json
  3. multipart/form-data

上面的Content-Type其实只是一个标识,主要给服务器参考,具体要怎么解析数据由服务器决定。毕竟HTTP协议是纯文本的,请求体的内容是不受限制,但更多的是约定俗成。

根据上面的描述,构建请求体的方式有:

1. 用字符串拼接的方式

“application/x-www-form-urlencoded”:通过“=”、“&”把表单数据串联起来。
“application/json”:把json对象序列化。
“multipart/form-data”:比较复杂,需要分隔符分割每部分的内容,包括上传的内容。

2. 借助FormData

这就比较强大了。通过传入键值对来模拟表单控件,甚至是二进制文件。该对象可以直接被XHR对象传送到服务器,当然了期间构建请求体的过程是由XHR对象来完成。

new FormData (form? : HTMLFormElement)

formData.append(name, value[, filename]);FormData.set 和 append() 的区别在于,如果指定的键已经存在, FormData.set 会使用新值覆盖已有的值,而 append() 会把新值添加到已有值集合的后面。
formData.set(name, value[, filename[);对 FormData 对象里的某个 key 设置一个新的值,如果该 key 不存在,则添加。
formData.get(name);返回FormData对象中和指定的键关联的第一个值,如果你想要返回和指定键关联的全部值,那么可以使用getAll()方法。
formData.getAll(name);返回该 FormData 对象指定 key 的所有值。
formData.has(name);
formData.delete(name);从 FormData 对象中删除指定 key 和它对应的 value(s)

formData.entries();
formData.keys();
formData.values();

参考

发送表单数据
使用JavaScript发送表单
XMLHttpRequest
FormData
FormData 对象的使用

HTML标签及属性

追求的目标

  1. 分类标签,并记住常用的(80%)标签
  2. 了解常用标签的常用(80%)独有特性
  3. 了解全局特性

HTML模型

指令
根元素
  头部
    元数据
    外部资源
  网页结构
    网页内容
      多媒体
        文本,有很多相关的文本标签,分为块元素和行内元素
        图片
        音频
        视频
      内嵌内容
        iframe
        object
        canvas
        svg
        script
        table
        form
        交互元素:dialog
        web component:slot、shadow、template

1. 分类标签

标签有很多,但是可以把它们分类出来便以回忆,推荐到MDN查看完整的列表。

指令

<!DOCTYPE html>

根元素

<html>:
    lang,指定语言。

文档元数据和外部资源

元数据(Metadata)含有页面的相关信息,包括样式、脚本及数据,能帮助一些软件 (如搜索引擎, 浏览器等等)更好地运用和渲染页面。

<link>,链接外部资源:
    rel,指明被链接文档对于当前文档的关系——链接类型:stylesheet、shortcut icon、icon、manifest、preload
    type,用于定义链接的内容的类型。text/css
    href,指定被链接资源的URL。

<meta>,元数据信息:
    charset,声明当前文档所使用的字符编码
    http-equiv,允许站点管理者在指定的页面控制用户代理的资源,refresh(秒)
    name,author、description、generator、keywords、viewport
    content,为 http-equiv 或 name 属性提供了与其相关的值的定义

<style>,包含文档的样式信息或者文档的部分内容:
    type,MIME类型(不应该指定字符集)定义样式语言。默认为 text/css。
    media,用于媒体查询,默认值为 all。
    scoped,如果该属性存在,则样式应用于其父元素;如果不存在,则应用于整个文档。

<title>,义文档的标题,显示在浏览器的标题栏或标签页上。

链接类型

内容分区

内容分区元素允许你将文档内容从逻辑上进行组织划分。使用包括页眉(header)、页脚(footer)、导航(nav)和标题(h1~h6)等分区元素,来为页面内容创建明确的大纲,以便区分各个章节的内容。

文本内容

文本内容标签是块元素

<main>,呈现了文档<body>或应用的主体部分。

<blockquote>,引用内容:
    cite,标注引用的信息的来源文档或者相关信息的URL。

<dir>,废弃,表示目录。

<div>,通用型的流内容容器。

<dl>,术语定义列表。
<dt>,声明术语。
<dd>,术语描述。

<figure>,独立内容。
<figcaption>,figure的描述。

<hr>,水平分割线。

<p>,段落。
<pre>,预定义格式文本。

<ol>,有序列表:
    reversed,规定了列表的条目采用倒序。
    start,规定了列表得条目序号的开始的值。
    type,数字类型。
<ul>,无序列表。
<li>,列表条目:
    value,表明了列表的当前序号值。

内联文本语义

内联文本语义的标签是行内元素

<a>,创建一个到其他网页、文件、同一页面内的位置、电子邮件地址或任何其他URL的超链接:
    download,指示浏览器下载URL而不是导航到URL,因此将提示用户将其保存为本地文件。
    href,链接来源,URL、#
    rel,指定了目标对象到链接对象的关系。
    target,指定在何处显示链接的资源。_self(默认)、_blank(新窗口)、_parent、_top
    type,媒体类型。仅提供建议,并没有内置的功能。
<abbr>,缩写:
    title,定义对缩写的完整描述。
<dfn>,术语。
<cite>,表示一个作品的引用。
<code>,呈现一段计算机代码。经常和pre标签一起使用。
<time>,表示24小时制时间或公历日期。

<span>,短语内容的通用行内容器。
<b>,提醒注意。
<em>,着重阅读,可以嵌套。
<i>,斜体,常用在术语。
<mark>,高亮突出显示的文字。
<q>,短引用,会被双引号包括:
cite,标注引用的信息的来源文档或者相关信息的URL。
<br>,文本内的换行。
<small>,字体变小一号。
<strong>,十分重要,粗体显示。
<sub>,下标。
<sup>,上标。
<u>,下划线。
<del>,表示删除的文字内容:
    cite,指向一个文档的 URL
    datetime,指示的此修改发生的时间和日期

<ins>,表示插入的文字内容:
    cite,指向一个文档的 URL
    datetime,指示的此修改发生的时间和日期

// 下面是不常用的
<data>,兼容性不好
<ruby>
<rt>
<rtc>
<rp>
<s>
<kbd>
<samp>
<tt>
<var>
<wbr>

图片和多媒体

<img>,图像:
    alt,替代文本。
    ismap,表示图像是否是服务器端map的一部分。
    src,图像的URL。
    height,width。

<map>,<map> 属性 与 <area> 属性一起使用来定义一个图像映射(一个可点击的链接区域):
    name,查询用。
<area>,定义一个热点区域,可以关联一个超链接。
alt,替代文本。
    coords,坐标值。
    download,下载资源。
    shape,形状。
    href、rel、target、type,参考a标签。

<audio>,音频:
    autoplay,指定后,音频会马上自动开始播放。
    buffered,获取已缓冲的资源的时间段信息。
    controls,控制面板。
    loop,布尔属性;如果指定,将循环播放音频。
    muted,是否静音的布尔值。默认值为false,表示有声音。
    played,表示所有已播放的音频片段。
    preload,预加载,none、metadata、auto。
    src,音频URL。
    volume,音量。

<video>,视频:
    和audio标签有很多相同的属性;
    poster,一个海报帧的URL。

<track>,字幕,被当作媒体元素—<audio> 和 <video>的子元素来使用:
    default,默认的字母。
    kind,subtitle、captions、descriptions、chapters、metadata
    label,标题
    src,URL

内嵌内容

<iframe>,嵌套的浏览上下文:
    name,嵌入的浏览上下文(框架)的名称。该名称可以用作<a>标签,<form>标签的target属性值,或<input> 标签和 <button>标签的formtaget属性值。
    sandbox,可选值有:allow-forms、allow-modals、allow-orientation-lock、allow-pointer-lock、allow-popups、allow-popups-to-escape-sandbox、allow-presentation、allow-same-origin、allow-scripts、allow-top-navigation。
    seamless,指示浏览器将iframe渲染成容器页面文档的一部分。
    src,URL。
    width、height、frameborder、marginheight、marginright、scrolling。

<source>,声明多媒体资源,picture、audio、video

<picture>,是一个容器,用来为其内部特定的 <img> 元素提供多样的 <source> 元素。
<applet>,不推荐使用。
<embed>,不明智的。
<noembed>不明智的。
<object>、<param>。

脚本

<canvas>,通过脚本绘制图形:
    width、height。

<noscript>,定义脚本未被执行时的替代内容。
<script>,用于嵌入或引用可执行脚本。通常是JS:
    async,指示浏览器是否在允许的情况下异步执行该脚本。该属性对于内联脚本无作用 (即没有src属性的脚本)。
    defer,设定用来通知浏览器该脚本将在文档完成解析后,触发 DOMContentLoaded 事件前执行。该属    性对于内联脚本无作用 (即没有src属性的脚本)。
    src,URL。
    type,支持的MIME类型包括text/javascript, text/ecmascript, application/javascript, 和application/ecmascript。
    text、crossorigin。
    crossorigin,跨域相关,可以使用本属性来使那些将静态资源放在另外一个域名的站点打印错误信息。

表格内容

用在td:colspan,跨列;rowspan,跨行。

<table>,表格
<caption>,标题
<tr>,行
<th>
<td>
<colgroup>
<col>
<thead>
<tbody>
<tfoot>

表单

<form>,表单
<fieldset>,分组
<legend>,fieldset的标题 
<button>
<input>
<datalist>,id关联input的list属性
<label>
<textarea>
<select>,选项菜单
<optgroup>,选项分组
<option>,选项
<progress>,进度条
<meter>,显示已知范围的标量值或者分数值
<output>

交互元素

<dialog>,表示一个对话框或其他交互式组件。open属性、showModal()
<summary>、<details>
<menu>、<menuitem>,兼容性差

Web组件

<content>,废弃,被slot代替
<element>,废弃
<shadow>,废弃
<slot>,slot是web组件的一个占位符,可以用来插入自定义的标记文本。
<template>,用于保存客户端内容的机制,该内容在页面加载时不被渲染,但可以在运行时使用JavaScript进行实例化。

2. 全局属性

除了我们常见的:

id、class、style、title

还有下面的:

accesskey,访问元素的键盘快捷键
contenteditable,是否可以编辑元素的内容
data-*,自定义数据
dir,元素内文本的方向
draggable,是否可以拖动
dropzone,拖动动作的结果
hidden,隐藏元素
lang,设置语言
spellcheck,是否检查拼写错误
tabindex,tab键的控制次序
translate,页面加载时是否需要翻译

当然了,除了上面的属性,还有下面的(包括但不止)事件处理器(可以参考EventTarget接口)——格式是on+<event-name>

onabort, 当一个资源的加载已中止时,将触发 abort事件。
onautocomplete
onautocompleteerror
onblur,当一个元素失去焦点的时候 blur 事件被触发。
oncancel
oncanplay
oncanplaythrough
onchange
onclick, 当一个设备功能按钮(通常是鼠标左键)被按下和释放,且在同一个元素上时被触发。
onclose
oncontextmenu
oncuechange
ondblclick
ondrag
ondragend
ondragenter
ondragexit
ondragleave
ondragover
ondragstart
ondrop
ondurationchange
onemptied
onended
onerror
onfocus
oninput
oninvalid
onkeydown, 当一个键被按下后会触发keydown 事件
onkeypress
onkeyup
onload
onloadeddata
onloadedmetadata
onloadstart
onmousedown
onmouseenter
onmouseleave
onmousemove
onmouseout
onmouseover
onmouseup
onmousewheel
onpause
onplay
onplaying
onprogress
onratechange
onreset
onresize
onscroll
onseeked
onseeking
onselect
onshow
onsort
onstalled
onsubmit
onsuspend
ontimeupdate
ontoggle
onvolumechange
onwaiting

参考

HTML 元素参考
HTML 属性参考
HTML 元素全局属性
EventTarget API

NodeJS 编程模型

API 分类

我把NodeJS的API大致分为如下四个类别:

  1. 基础模块
  2. 网络模块
  3. 系统模块
  4. 工具模块

nodejs

CSS 样式检索

1. 文本

color:rgb | rgba | hsl | hsla
direction: ltr | rtl
line-height:设置多行元素的空间量,比如文本。
text-align:定义行内内容(例如文字)如何相对它的块父元素对齐。start | end | left | right | center | justify | justify-all | match-parent
text-justify:只有 text-align: justify; 才会生效
vertical-align:指定行内元素(inline)或表格单元格(table-cell)元素的垂直对齐方式
text-indent:规定了 一个元素首行文本内容之前应该有多少水平空格。
text-decoration:用于设置文本排版(下划线、顶划线、删除线或者闪烁)。
text-decoration-[color, line, style]:line——none、underline、overline、line-through;style——solid、double、dotted、dashed、wavy
text-shadow:为文字添加阴影。
text-transform,大小写。none | capitalize | uppercase | lowercase | full-width

white-space:设置如何处理元素中的空白。normal | pre | nowrap | pre-wrap | pre-line
word-spacing:声明标签和单词直接的间距行为。normal | <length> | <percentage>
letter-spacing:明确了文字的间距行为。normal | <length>

换行相关:
word-break:单词内断行。normal | break-all | keep-all | break-word
overflow-wrap(word-wrap):用来说明当一个不能被分开的字符串太长而不能填充其包裹盒时,为防止其溢出,浏览器是否允许这样的单词中断换行。normal | break-word
hyphens:连字符。none | manual | auto
line-break:指定如何断行拉丁文字。 auto | loose | normal | strict

与word-break不同的是,overflow-wrap只会在整个单词没有溢出的情况下才会创建一个断行。

2. 字体

font
font-family:<family-name>, <generic-name>。serif——带衬线字体;sans-serif——无衬线字体;monospace——等宽字体;cursive——草书字体;fantasy——艺术字体。
font-size:xx-small, x-small, small, medium, large, x-large, xx-large, larger, smaller
font-size-adjust:定义字体大小应取决于小写字母,而不是大写字母。
font-stretch
font-style:normal | italic | oblique
font-variant:设置或检索对象中的文本是否为小型的大写字母。normal | small-caps
font-weight:粗细程度。normal | bold | lighter | bolder

3. 列表

list-style
list-style-image:指定一个能用来作为列表元素标记的图片。<url> | none
list-style-position:inside | outside
list-style-type:指定一个列表元素的外观。none | disc | circle | square | decimal(默认) | decimal-leading-zero

4. 表格

border-collapse:决定表格的边框是分开的还是合并的。 separate | collapse
border-spacing:指定相邻单元格边框之间的距离。
caption-side:将表格的标题<caption> 放到规定的位置。top | bottom | block-start | block-end | inline-start | inline-end
empty-cells:定义了用户端 user agent 应该怎么来渲染表格 <table> 中没有可见内容的单元格的边框和背景。show | hide
table-layout:定义了用于布局表格单元格,行和列的算法。auto | fixed
vertical-align:指定行内元素(inline)或表格单元格(table-cell)元素的垂直对齐方式。baseline | sub | super | text-top | text-bottom | middle | top | bottom | <percentage> | <length>

5. 轮廓

outline:<outline-color> | <outline-style> | <outline-width>
outline-color:<color> | invert
outline-style:none | dotted | dashed | solid | double | groove | ridge | inset | outset | inherit 
outline-width:thin | medium | thick | <length>

6. 定位

position:static | relative | absolute | fixed | sticky
top、bottom、left、right

7. 行内元素

vertical-align:指定行内元素(inline)或表格单元格(table-cell)元素的垂直对齐方式。初始值是baseline。baseline | sub | super | text-top | text-bottom | middle | top | bottom | <percentage> | <length>
text-align:定义行内内容(例如文字)如何相对它的块父元素对齐,并不控制块元素自己的对齐,只控制它的行内内容的对齐。start | end | left | right | center | justify | match-parent

8. 背景

background:
background-attachment:如果指定了background-image,那么该属性决定背景是在视窗中固定还是随包含它的区块滚动。scroll(相对于元素本身固定) | fixed(相对视窗固定) | local(相对于元素的内容固定) | inherit
background-color:在指定的图像的下面
background-image:先指定的图像会在之后指定的图像上面绘制。url("RUL")
background-repeat:属性定义背景图像的重复方式。repeat-x | repeat-y | repeat | space | round | no-repeat
background-size:设置背景图片大小。auto | cover | contain | <length> | <percentage>
background-clip:裁剪边界,设置元素的背景(背景图片或颜色)是否延伸到边框下面。border-box | padding-box | content-box | text
background-origin:图像起始位置,规定了指定背景图片background-image 属性的原点位置的背景相对区域。border-box | padding-box | content-box
background-position:指定背景图片的初始位置,以origin为基准,可以给多背景设置。<position> | [水平偏移,垂直偏移]

9. 边框

border
border-[top, bottom, left, right]
border-[top, bottom, left, right]-[color, style, width]
border-[top, bottom]-[left, right]-radius
border-color
border-style
border-width
border-image
border-image-[outset, repeat, slice, source, width]

10. 图片

同行内元素;
但是作为盒模型的背景,会更灵活;
过滤器(filter)产生特效。
object-fit,替换元素该如何调整大小来适应容器

11. 其他

cursor:光标属性
quotes:引号,open-quote、close-quote、no-close-quote
counter-reset:重置
counter-increment:递增

12. 伪类

:active,当用鼠标交互时,它代表的是用户按下按键和松开按键之间的时间。
:empty,代表没有子元素的元素。子元素只可以是元素节点或文本(包括空格)。注释或处理指令都不会产生影响。
:focus
:focus-within,元素自身或者它的某个后代匹配:focus伪类。
:hover
:link
:not()
:visited
:target,匹配URL的一个片段 (以#标识的) 
:root,匹配文档树的根元素。对于HTML来说是<html>元素

表单相关:
:checked
:default
:disabled
:enabled
:in-range
:out-of-range
:invalid
:valid
:required
:read-only
:read-write

定位元素:
:first-child
:first-of-type
:last-child
:last-of-type
:nth-child()
:nth-last-child()
:nth-last-of-type()
:nth-of-type()
:only-child
:only-of-type

13. 伪元素

::before
::after
::first-letter
::first-line
::selection,应用于文档中被用户高亮的部分

14. 盒模型

overflow:visible | hidden | scroll | auto | overlay

遮盖和裁剪

clip-path:创建一个只有元素的部分区域可以显示的剪切区域
mask:遮盖

参考

MDN的CSS参考

实现缓慢回到顶部

介绍

回到顶部效果对于网页纵向篇幅很长的场景比较适合。让我由浅入深介绍一下这个特效。

1. 回到顶部

直接调用web api的window.scrollTo(0, 0)便可以立即回到顶部。

2. 缓慢回到顶部

可以简单通过一段css来实现:

scroll-container, html {
    scroll-behavior: smooth;
}

scroll-behavior可以设置的值有两个:auto | smooth。默认的auto表示立即滚动;smooth表示平滑滚动。
让人沮丧的是,截至今天,该css属性并没有被所有浏览器支持,换句话说,兼容性并不是很好

3. 兼容性较强的缓慢回到顶部

好吧,既然你不仁我不义,我可以用JS来实现缓慢回到顶部效果。
分为两个步骤:

  1. 获取页面已经滚动的距离(offset),这里有兼容性代码
var y = (window.pageYOffset !== undefined) ? window.pageYOffset : (document.documentElement || document.body.parentNode || document.body).scrollTop;
  1. 设置整个动画的时间,并借助window.requestAnimationFrameAPI实现平滑滚动

当然了,除了window.requestAnimationFrameAPI,还可以使用setTimeoutsetInterval实现,只是动画会有些僵硬。

Warning,测试下面的代码的使用,请把scroll-behavior: smooth;注释掉。

代码如下:

/**
 * 缓慢置顶动画
 * @param {Number} duration 动画持续时间
 */
function scrollToTop(duration) {
    /**
     * 获取滚动条与页面顶部的距离,注意兼容性
     */
    function getOffSetY() {
        return document.body.scrollTop || document.documentElement.scrollTop || window.pageYOffset;
    }
    /**
     * 计算每一帧移动的距离(1秒60帧)
     * @param {Number} duration 动画持续时间
     */
    function getDistancePerFrame(duration) {
        var time = duration || 0.5;
        return getOffSetY() / (time * 60)
    }
    /**
     * 根据计算的距离来滚动
     */
    function scrollByDistance() {
        window.scrollBy(0, -distancePerFrame);
        console.log(-distancePerFrame, document.body.scrollTop);
        if (getOffSetY() > 0) {
            rAF(scrollByDistance)
        }
    }
    var distancePerFrame = Math.floor(getDistancePerFrame());
    var rAF = requestAnimationFrame || webkitRequestAnimationFrame || mozRequestAnimationFrame ||
        msRequestAnimationFrame || oRequestAnimationFrame;// 兼容性代码
    rAF(scrollByDistance);// 开始动画
}

附上CodePen的演示链接。

参考

缓慢回到顶部

浏览器运行原理概述

前言

这个系列的文章将会理清整个浏览器运行过程及原理。目的是,把浏览器的内核的各个部分描述清楚,职责分明利于思路清晰,也便于查找问题所在,也便于找到优化的方案。

全栈工程师终其一生都要了解的东西,应该就是浏览器的运行原理了。

WebKit才不是个黑盒。它是个白盒。并且,它是个打开的白盒。

概述

浏览器运行原理,指的是用户请求网站,服务器返回所有资源(包括HTML、CSS、JavaScript、图像等等)给浏览器,由浏览器构建界面,并开始响应用户操作的一系列过程。

市面上有一些浏览器内核:

  1. Trident,
  2. Edge
  3. Gecko
  4. KHTML
  5. WebKit
  6. Blink

其中最经典的是由Apple开源的WebKit浏览器内核,所以接下来的内容将会基于WebKit,它与其它浏览器内核的工作原理大致一样。

WebKit组成部分

借用网上的一张图:
webkit

从上面的图中可以看出,WebKit是依赖第三方库(如:图形库、网络库、存储等等)来与操作系统交互的。

图中的JavaScriptCore是WebKit的默认JS引擎,但是在Google的系列产品中被替换为V8 引擎

重要概念区别

  1. 浏览器内核,是浏览器最重要的部分,连接开发者和用户。
  2. 渲染引擎,是浏览器内核最重要的部分,负责展示页面给用户。
  3. JS 引擎,也是浏览器内核最重要的部分,负责JS代码的编译和运行。
  4. V8 引擎,是众多JS 引擎中的一种,JavaScript Core是另外的一种。

浏览器大致运行原理

  1. 加载资源和渲染页面
  2. 执行JS脚本并注册响应用户操作的回调

参考

1. 书籍:

《WebKit 技术内幕》

2. 博客:

Inside look at modern web browser (part 1)
浏览器工作原理-webkit内核研究
认识 V8 引擎
深入剖析 WebKit
开发者需要了解的WebKit
webkit架构和模块

资源加载和页面渲染

概述

下面的描述有一个前提,就是你的网页结构是把CSS放在<head>标签里,<body>标签里没有JS代码需要执行,全部<script>都放在<body>标签后面。这是一个很好的约定俗成的规范。

1. 资源加载

当用户通过用户代理(浏览器)请求网站的时候,经过DNS解析、建立TCP连接,根据HTTP/HTTPS协议服务器会返回相关的资源给用户代理。其中的下载资源都是异步的,每个资源一个请求,比如HTML文件、CSS文件、JS文件、图片。当然了链接数是有上限的,这是用户代理的限制,比如同一时间只能下载资源上限为10个,那么第11个只能等到前面的资源下载完了才能进行。

2. 页面渲染

Talking is cheap. Let's see the piture below.

default

1. 构建RenderTree

根据 HTML 生成 DOM 树;根据 CSS 生成 CSSOM;将 DOM 和 CSSOM 整合形成渲染树(RenderTree)。

渲染树(RenderTree)只有构建好了,页面才能被渲染呈现。构建好的条件是解析完整个HTML文件,包括执行script脚本。

1. JS相关

嵌入JS脚本的方式有两种,一种是内联的方式(写在script标签里),一种是引入方式(使用src指定路径)。

所以思考JS的问题会从两个角度:

A. 加载JS

内联的方式是不用考虑网络问题的;如果是通过引入的方式加载JS,那么会等待JS下载好了才会执行。

但是有优化的地方:

script标签有async和defer属性的话:

  1. async指示浏览器是否在允许的情况下异步执行该脚本
  2. defer,设定用来通知浏览器该脚本将在文档完成解析后,触发 DOMContentLoaded 事件前执行
B. 执行JS

JS的执行会阻塞RenderTree的构建,所以会阻塞页面渲染,导致页面空白。

当遇到<script>标签时,会触发网页的渲染,因为JS有可能需要操作DOM和CSSOM,在执行JS的过程中会阻塞DOM的解析——也就会阻塞RenderTree的渲染和网页的呈现。

JS线程与GUI渲染线程是互斥的关系

2. 解析CSS

CSS所在的style标签本质上也是一个节点。

跟JS一样,可以从两个角度:

A. 加载CSS

一般而言,是通过同步加载的方式加载link标签里的css,整个过程会阻塞RenderTree的解析。

但是可以通过媒体查询的方式避免阻塞RenderTree的解析。比如:media="print"media="(min-width: 40em)"

B. 解析CSS

每当解析CSS的时候,会阻塞渲染RenderTree,但是不会阻塞DOM的解析。

CSS 是阻塞渲染的资源。需要将它尽早、尽快地下载到客户端,以便缩短首次渲染的时间。

所以一般会把样式放在head标签里。也可以通过 CSS“媒体类型”和“媒体查询”来解决这类用例。比如使用:

<link href="style.css" rel="stylesheet">
<link href="print.css" rel="stylesheet" media="print">
<link href="other.css" rel="stylesheet" media="(min-width: 40em)">
内容样式短暂失效(FOUC——Flash Of Unstyled Content)

场景:浏览器采用的是windows版本的IE浏览器,且版本在5.0以上(也就是现在所有的IE浏览器)。
解决方案:把样式link放在head里。

3. 小结

  1. JS语句的执行会阻塞DOM的解析和渲染,并让浏览器计算出当前的DOM(不完整,没有该script标签后面的标签)和CSSOM
  2. css加载不会阻塞DOM的解析,但是会阻塞RenderTree的渲染
  3. css加载会阻塞后面Script标签里的JS语句的执行

从浏览器优化性能角度去思考这个问题。

2. 布局(Layout)

结合浏览器的一些属性,计算RenderTree的几何信息,包括位置和大小等。

3. 绘制(Paint)

把RenderTree传给WebKit,由它调用相关的第三方库绘制到浏览器上展示。

4. 回流(reflow)和重绘(repaint)

用户和页面交互过程,如果发生页面DOM位置和大小变化则会触发回流(reflow);如果仅仅是样式的变化,比如背景颜色,那么仅会触发重绘(repaint)。

媒体查询由媒体类型以及零个或多个检查特定媒体特征状况的表达式组成。例如,上面的第一个样式表声明未提供任何媒体类型或查询,因此它适用于所有情况,也就是说,它始终会阻塞渲染。第二个样式表则不然,它只在打印内容时适用---或许您想重新安排布局、更改字体等等,因此在网页首次加载时,该样式表不需要阻塞渲染。最后,最后一个样式表声明提供由浏览器执行的“媒体查询”:符合条件时,浏览器将阻塞渲染,直至样式表下载并处理完毕。

参考

浏览器渲染页面过程与页面优化
深入剖析 WebKit
阻塞渲染的 CSS

DOM模型

介绍

DOM(Document Object Model——文档对象模型)是用来呈现以及与任意 HTML 或 XML 交互的API文档。DOM 是载入到浏览器中的文档模型,它用节点树的形式来表现文档,每个节点代表文档的构成部分(例如: element——页面元素、字符串或注释等等)。

DOM就是JS能识别的HTML结构,一个普通的JS对象或者数组

作用

DOM 将 web 页面与到脚本或编程语言连接起来,是Web——万维网上使用最为广泛的API之一,因为它允许运行在浏览器中的代码访问文件中的节点并与之交互。节点可以被创建,移动或修改。事件监听器可以被添加到节点上并在给定事件发生时触发。

DOM模型

DOM相关接口继承关系

大致介绍图中接口的用途:

  1. EventTarget,主要提供事件处理器注册、事件触发等功能
  2. Node,主要提供操作(插删改查、比较)节点的功能
  3. Element,表示页面标签元素,可以操作标签元素的属性和获取位置相关的信息,还可以细分成HTMLElement和SVGElement。
  4. Document,表示网页的整个文档,它提供了获取一些文档相关属性(比如URL、Location、加载状态)的方法,还有获取HTMLCollection(比如form、image)对象
  5. Window,表示客户端代理,可以注册自定义元素,操作history、数据库(indexedDB、localStorage、sessionStorage)、滚动条、定时器,跨源通信。

EventTarget

请参考另一篇博客

Node

属性:

baseURI,表示base URL的DOMString。
childNodes,返回一个包含了该节点所有子节点的实时的NodeList。
firstChild,返回该节点的第一个子节点,如果该节点没有子节点则返回null。
innerText,表示一个节点及其后代的“渲染”文本内容的属性。
isConnected,如果该节点与 DOM 树连接则返回 true , 否则返回 false。
lastChild,返回该节点的最后一个子节点,如果该节点没有子节点则返回null。
nextSibling,只读属性,返回其父节点的 childNodes 列表中紧跟在其后面的节点,如果指定的节点为最后一个节点,则返回 null。
nodeName,返回当前节点的节点名称。
nodeType,只读属性 Node.nodeType 表示的是该节点的类型。
nodeValue,返回或设置当前节点的值。
outerText
ownerDocument,只读属性会返回当前节点的顶层的 document 对象。
parentElement,返回当前节点的父元素节点,如果该元素没有父节点,或者父节点不是一个元素节点.则 返回null.
parentNode,返回指定的节点在DOM树中的父节点。
previousSibling,返回当前节点的前一个兄弟节点,没有则返回null。
textContent,表示一个节点及其后代的文本内容。

方法:

appendChild(),将一个节点添加到指定父节点的子节点列表末尾。如果节点已经存在,则是移动到父节点。
cloneNode(),返回调用该方法的节点的一个副本。
compareDocumentPosition(),比较当前节点与任意文档中的另一个节点的位置关系。
contains(),返回的是一个布尔值,来表示传入的节点是否为该节点的后代节点。
hasChildNodes(),返回一个布尔值,表明当前节点是否包含有子节点。
insertBefore(),在参考节点之前插入一个节点作为一个指定父节点的子节点。
isDefaultNamespace(),接受一个命名空间URI作为参数,如果该命名空间是当前节点的默认命名空间,则返回true,否则返回false。
isEqualNode(),判断两个节点是否相等。当两个节点的类型相同,定义特征(defining characteristics)相同(对元素来说,即 id,孩子节点的数量等等),属性一致等,这两个节点就是相等的。
lookupNamespaceURI()
lookupPrefix()
normalize(),将当前节点和它的后代节点”规范化“(normalized)。在一个"规范化"后的DOM树中,不存在一个空的文本节点,或者两个相邻的文本节点。
removeChild(),从DOM中删除一个子节点。返回删除的节点。
replaceChild(),用指定的节点替换当前节点的一个子节点,并返回被替换掉的节点。

ParentNode.append 方法在 ParentNode的最后一个子节点之后插入一组 Node 对象或 DOMString 对象。被插入的 DOMString 对象等价为 Text 节点。
而parentNode.appendChild()的入参只能接收一个节点,且不直接支持传字符串(document.createTextElement可以把字符串转化为字符节点),返回追加的Node节点。

Element

属性:

accessKey,设置了这样一个按键——用户通过敲击这个键把焦点跳转到这个元素上。
attributes,返回该元素所有属性节点的一个实时集合。
childElementCount,读属性返回一个无符号长整型数字,表示给定元素的子元素数。
children,只读属性,返回 一个Node的子elements ,是一个动态更新的 HTMLCollection。
classList,只读属性,返回一个元素的类属性的实时 DOMTokenList集合。但是可以通过add、remove操作这个classList对象。
className,获取或设置指定元素的class属性的值。由空格分隔的多个class属性值。
clientHeight,它是元素内部的高度(单位像素),包含内边距,但不包括水平滚动条、边框和外边距。
clientLeft,表示一个元素的左边框的宽度,以像素表示。
clientTop,一个元素顶部边框的宽度(以像素表示)。不包括顶部外边距或内边距。
clientWidth,内部宽度,该属性包括内边距,但不包括垂直滚动条(如果有)、边框和外边距。
firstElementChild,返回对象的第一个孩子 Element, 如果没有子元素,则为null。
id,获取和设置一个元素的标识符(id属性)。
innerHTML,设置或获取HTML语法表示的元素的后代。
lastElementChild,读属性返回对象的最后一个孩子Element ,如果没有子元素,则返回null。
localName,只读属性,返回本地名称的。
name,
namespaceURI,
nextElementSibling,返回当前元素在其父元素的子元素节点中的后一个元素节点,如果该元素已经是最后一个元素节点,则返回null,该属性是只读的.
ongotpointercapture,事件处理器
onlostpointercapture,事件处理器
outerHTML,包括元素本身和后代元素的HTML语法。要求:必须有父元素;原变量依旧指向旧元素。
prefix,返回指定元素的命名空间前缀。
previousElementSibling,返回当前元素在其父元素的子元素节点中的前一个元素节点,如果该元素已经是第一个元素节点,则返回null,该属性是只读的。
scrollHeight,只读属性是一个元素内容高度的度量,包括由于溢出导致的视图中不可见内容。不包括元素的border和margin
scrollLeft,可以读取或设置元素滚动条到元素左边的距离。
scrollTop,获取或设置一个元素的内容垂直滚动的像素数。
scrollWidth,只读属性以px为单位返回元素的内容区域宽度或元素的本身的宽度中更大的那个值。
tagName,当前元素的标签名。

方法:

getAttribute(),返回元素上一个指定的属性值。如果指定的属性不存在,则返回  null 或 "" (空字符串)。
getAttributeNames(),返回一个Array,该数组包含指定元素(Element)的所有属性名称,如果该元素不包含任何属性,则返回一个空数组。
getAttributeNode(),返回指定元素的指定属性, 返回值是 Attr 节点类型。
getBoundingClientRect(),返回元素的大小及其相对于视口的位置。获得页面中某个元素的左,上,右和下分别相对浏览器视窗的位置。
getClientRects(),返回一个指向客户端中每一个盒子的边界矩形的矩形集合。 
getElementsByClassName(),返回一个即时更新的(live) HTMLCollection,包含了所有拥有指定 class 的子元素。
getElementsByTagName(),返回一个动态的包含所有指定标签名的元素的HTML集合HTMLCollection。
hasAttribute(),返回一个布尔值,指示该元素是否包含有指定的属性(attribute)。
insertAdjacentElement(),将一个给定的元素节点插入到相对于被调用的元素的给定的一个位置。
insertAdjacentHTML(),将指定的文本解析为HTML或XML,并将结果节点插入到DOM树中的指定位置。
insertAdjacentText(),将一个给定的文本节点插入在相对于被调用的元素给定的位置。
matches(),如果元素被指定的选择器字符串选择,Element.matches()  方法返回true; 否则返回false。
querySelector(),返回与指定的选择器组匹配的元素的后代的第一个元素。
querySelectorAll(),返回一个 NodeList 表示元素的列表,把当前的元素作为根与指定的选择器组相匹配。
removeAttribute(),指定的元素中删除一个属性。
removeAttributeNode(),从当前的 element(元素节点) 删除指定的属性。
requestFullscreen(),用于发出异步请求使元素进入全屏模式。
setAttribute(),设置指定元素上的一个属性值。
setAttributeNode(),为指定的 Element 添加属性节点.
setCapture(),在处理一个 mousedown 事件过程中调用这个方法来把全部的鼠标事件重新定向到这个元素,直到鼠标按钮被释放或者 document.releaseCapture() 被调用。

HTMLElement

属性:

contentEditable,用于表明元素是否是可编辑的。
dataset,HTMLElement.dataset属性允许无论是在读取模式和写入模式下访问在 HTML或 DOM中的元素上设置的所有自定义数据属性(data-*)集。
dir,dir属性用于获取或设置当前元素的元素内容的文本书写方向。
hidden,如果想要隐藏文档,值设置为 true,否则值设置为false。
isContentEditable,只读属性返回一个布尔值:如果当前元素的内容为可编辑状态,则返回 true,否则返回 false。
lang,用来获取或设置元素属性值或文本内容的基语言(base language)。
offsetHeight,只读属性,它返回该元素的像素高度,高度包含该元素的垂直内边距和边框,且是一个整数。包括元素的边框、内边距和元素的水平滚动条(如果存在且渲染的话),不包含:before或:after等伪类元素的高度。
offsetLeft,只读属性,返回当前元素左上角相对于  HTMLElement.offsetParent 节点的左边界偏移的像素值。
offsetParent,只读属性,返回一个指向最近的(closest,指包含层级上的最近)包含该元素的定位元素。
offsetTop,只读属性,它返回当前元素相对于其 offsetParent 元素的顶部的距离。
offsetWidth,只读属性,返回一个元素的布局宽度。
on + [event + name]
outerText
style,返回一个 CSSStyleDeclaration 对象,表示元素的 内联style 属性(attribute),但忽略任何样式表应用的属性。
tabIndex,获取或设置当前元素的tab键激活顺序。
title,属性表示元素的 title。当鼠标移到节点上时,会以“工具提示”(tool tip)的弹出形式显示该属性的属性值文本。

方法:

blur(),blur方法用来移除当前元素所获得的键盘焦点。
click(),click 方法可以用来模拟鼠标左键单击一个元素。
focus(),可以设置指定元素获取焦点。

Document

属性:

body,返回当前文档中的<body>元素或者<frameset>元素。
characterSet,只读属性返回当前文档的字符编码。
childElementCount,只读属性返回一个无符号长整型数字,表示给定元素的子元素数。
children,只读属性,返回 一个Node的子elements ,是一个动态更新的 HTMLCollection。
compatMode,表明当前文档的渲染模式是混杂模式还是"标准模式"。
contentType,返回当前文档的Content-Type(MIME)类型。
cookie,设置或读取cookie。
curreentScript,返回其所包含的脚本中正在被执行的 <script> 元素。
defaultView,在浏览器中,该属性返回当前 document 对象所关联的 window 对象,如果没有,会返回 null。
designMode,document.designMode 控制整个文档是否可编辑。有效值为 “on”和 “off”。
dir,代表了文档的文字朝向,是从左到右(默认)还是从右到左。
doctype,返回当前文档关联的文档类型定义(DTD)。
documentElement,返回文档对象(document)的根元素的只读属性(如HTML文档的 <html> 元素)。
documentURI,返回文档地址字符串。
domain,获取/设置当前文档的原始域部分, 用于 同源策略。
embeds,返回一个HTMLCollection对象,包含了嵌入到当前文档中的所有的OBJECT对象。
firstElementChild,只读属性,返回对象的第一个孩子 Element, 如果没有子元素,则为null。
forms,返回当前文档中的 <form>元素的一个集合(一个 HTMLCollection)。
head,返回当前文档中的 <head> 元素。如果有多个 <head> 元素,则返回第一个。
hidden,返回布尔值,表示页面是(true)否(false)隐藏。
images,返回当前文档中所有 image 元素的集合。
implementation,返回一个和当前文档相关联的DOMImplementation对象。
lastElementChild,只读属性返回对象的最后一个孩子Element ,如果没有子元素,则返回null。
lastModified,返回一个字符串,其中包含了当前文档的最后修改日期和时间。
lastStyleSheetSet,返回最后一个启用的样式表集合。
links,links 属性返回一个包含文档中所有具有 href 属性值的 <area> 元素 <a> 元素的集合。
location,本页面的Location对象。
on + [event-name]
pointerLockElement
readyState,一个document 的 Document.readyState 属性描述了文档的加载状态。loading、interactive、complete。
referrer,返回跳转或打开到当前页面的那个页面的URI。
scripts,返回一个HTMLCollection对象,包含了当前文档中所有<script>元素的集合。
scrollingElement,返回滚动文档的 Element 对象的引用。 在标准模式下, 这是文档的根元素, document.documentElement。
styleSheets,表示当前使用的样式表集合的名称。
selectedStyleSheetSet,表示当前使用的样式表集合的名称。
styleSheetSets,返回一个所有当前可用样式表集的实时列表。
title,获取或设置文档的标题。
tooltipNode
URL,返回当前文档的URL地址
visibilityState,返回document的可见性, 即当前可见元素的上下文环境. 由此可以知道当前文档(即为页面)是在背后, 或是不可见的隐藏的标签页,或者(正在)预渲染.

方法:

adoptNode(),从其他的document文档中获取一个节点。
close(),用来关闭向当前文档的数据写入。
createAttribute(),创建并返回一个新的属性节点。
createComment(),创建并返回一个注释节点。
createDocumentFragment(),创建一个新的空白的文档片段。
createElement(),创建由tagName 指定的HTML元素,或一个HTMLUnknownElement,如果tagName不被识别。
createEvent(),使用new Event()代替。
createExpression(),编译生成一个 XPathExpression ,可以用来多次的执行。
createNodeIterator(),该函数的作用是用来筛选元素(节点)的。
createProcessingInstruction(),创建一个新的处理指令节点,并返回。
createRange()
createTextNode()
createTreeWalder()
elementFromPoint(),返回坐标点的顶端元素。
evaluate(),根据传入的 XPath 表达式以及其他参数,返回一个 XPathResult 对象。
execCommand(),操作当前focus元素,指令有参考。
exitFullscressn(),让当前文档退出全屏模式。全屏元素栈。
getElementById(),根据id属性返回节点列表集合。
getElementsByClassName(),根据css类属性返回节点列表集合。
getElementsByName(),根据name属性返回节点列表集合。
getElementsByTagName(),根据标签名字返回节点列表集合。
getSelection(),返回一个  Selection 对象,表示用户选择的文本范围或光标的当前位置。
hasFocus(),回一个 Boolean,表明当前文档或者当前文档内的节点是否获得了焦点。
importNode(),将外部文档的一个节点拷贝一份,然后可以把这个拷贝的节点插入到当前文档中。
open(),打开一个要写入的文档。如果目标中存在文档,则此方法将清除它。
queryCommandEnabled(),查询浏览器中指定的编辑指令是否可用。
queryCommandSupported(),确定浏览器是否支持指定的编辑指令。
querySelector(),返回与指定的选择器组匹配的元素的后代的第一个元素。
querySelectorAll(),返回一个 NodeList 表示元素的列表,把当前的元素作为根与指定的选择器组相匹配。
releaseCapture()
write(),将一个文本字符串写入由 document.open() 打开的一个文档流。
writeIn(),向文档中写入一串文本,并紧跟着一个换行符。

Window

也是浏览器JavaScript运行时的全局对象。

属性:

applicationCache,应用缓存对象的一个引用。
closed,表示所引用的窗口是否关闭。
console,控制台对象的引用。
crypto,返回与全局对象关联的 Crypto对象。
customElements,返回当前窗口的自定义元素注册表的CustomElementRegistry对象的实例。
devicePixelRatio,此属性返回当前显示设备的物理像素分辨率与CSS像素分辨率的比值。
document,返回指向当前窗口内的文档节点
frameElement,返回嵌入当前window对象的元素
frames,返回当前窗口,一个类数组对象,列出了当前窗口的所有直接子窗口。
fullScreen,这个属性表明了窗口是否处于全屏模式下。
history,返回History 对象的引用,History 对象提供了操作浏览器会话历史(浏览器地址栏中访问的页面,以及当前页面中通过框架加载的页面)的接口。
indexedDB,返回indexedDB的引用。
innerHeight,只读,浏览器窗口的视口(viewport)高度(以像素为单位),如果存在水平滚动条,则包括它。
innerWidth,只读,浏览器视口(viewport)宽度(单位:像素),如果存在垂直滚动条则包括它。
length,返回当前窗口中包含的框架数量(框架包括frame和iframe两种元素)。
localStorage,返回localStorage的引用。
location,只读属性,返回一个 Location  对象,其中包含有关文档当前位置的信息。可以赋给它一个 DOMString。只要赋给 location 对象一个新值,文档就会使用新的 URL 加载。
locationbar,返回一个可以检查visibility属性的 locationbar 对象。
menubar,返回一个可以检测visibility属性的 menubar 对象。
name,窗口的名字主要用于为超链接和表单设置目标(targets)。窗口不需要有名称。
navigator,返回一个navigator对象的引用,可以用它来查询一些关于运行当前脚本的应用程序的相关信息。
on + [event-name],各种事件类型。
opener,返回打开当前窗口的那个窗口的引用。
outerHeight,Window.outerHeight 获取整个浏览器窗口的高度(单位:像素),包括侧边栏(如果存在)、窗口镶边(window chrome)和窗口调正边框(window resizing borders/handles)。
outerWidth,Window.outerWidth 获取浏览器窗口外部的宽度。表示整个浏览器窗口的宽度,包括侧边栏(如果存在)、窗口镶边(window chrome)和调正窗口大小的边框(window resizing borders/handles)。
pageYOffset,这是 scrollY 的别名。
parent,返回当前窗口的父窗口对象,如果一个窗口没有父窗口,则它的 parent 属性为自身的引用。
performance,Web Performance API允许网页访问某些函数来测量网页和Web应用程序的性能,包括 Navigation Timing API和高分辨率时间数据。
personalbar,返回一个可以检测visibility属性的 personalbar 对象。
screen,返回当前window的screen对象。screen对象实现了Screen接口,它是个特殊的对象,返回当前渲染窗口中和屏幕有关的属性。
screenX,返回浏览器左边界到操作系统桌面左边界的水平距离。
screenY,返回浏览器顶部距离系统桌面顶部的垂直距离。
scrollbars,返回可以检查其可见性的滚动条对象。
scrollX,返回文档/页面水平方向滚动的像素值。
scrollY,返回文档在垂直方向已滚动的像素值。
self,返回一个指向当前 window 对象的引用。
sessionStorage,sessionStorage 属性允许你访问一个 session Storage 对象。
top,返回窗口体系中的最顶层窗口的引用。
URL,Window.URL 属性返回一个对象,它提供了用于创建和管理对象URLs的静态方法。
window,指向自己。

方法:

alert(),显示一个警告对话框,上面显示有指定的文本内容以及一个"确定"按钮。
atob(),对用base-64编码过的字符串进行解码 。
blur(),将焦点移出顶层窗口。
btoa(),编码。
cancelIndleCallback(),用于取消之前调用window.requestIdleCallback() 的回调。
clearImmediate(),清除 window.setImmediate
clearInterval(),取消用setInterval设置的重复定时任务。
clearTimeout(),取消了先前通过调用setTimeout()建立的定时器。
close(),关闭当前窗口或某个指定的窗口。
confirm(),显示一个具有一个可选消息和两个按钮(确定和取消)的模态对话框。
createImageBitmap()
fetch()
focus(),请求将窗口显示在前(靠近屏幕),这可能由于用户设置而失败,并且该窗口在方法返回之前不能保证会显示在最前。
getComputedStyle(),返回一个对象,该对象在应用活动样式表并解析这些值可能包含的任何基本计算后报告元素的所有CSS属性的值。
getSelection(),返回一个  Selection 对象,表示用户选择的文本范围或光标的当前位置。
matchMedia(),返回一个新的MediaQueryList 对象,表示指定的媒体查询字符串解析后的结果。
minimize(),让当前浏览器窗口最小化。
moveBy(),根据指定的值,移动当前窗口。备注:不能实现的原因。
moveTo(),将当前窗口移动到指定的坐标位置。备注:不能实现的原因。
open(),根据指定的参数,将一个资源加载到一个新的浏览上下文(如一个窗口)或一个已经存在的浏览上下文中。
postMessage(message, targetOrigin, [transfer]),可以安全地实现跨源通信。
print(),打开打印对话框打印当前文档。
prompt(),显示一个对话框,对话框中包含一条文字信息,用来提示用户输入文字。
requestAnimationFrame(),告诉浏览器您希望执行动画并请求浏览器在下一次重绘之前调用指定的函数来更新动画。该方法使用一个回调函数作为参数,这个回调函数会在浏览器重绘之前调用。
requestIdleCallback(),会在浏览器空闲时期依次调用函数, 这就可以让开发者在主事件循环中执行后台或低优先级的任务,而且不会对像动画和用户交互这样延迟触发而且关键的事件产生影响。函数一般会按先进先调用的顺序执行,除非函数在浏览器调用它之前就到了它的超时时间。
resizeBy(),调整窗口大小。备注:不能实现的原因。
resizeTo(),动态调整窗口的大小。备注:不能实现的原因。
scroll(),滚动窗口至文档中的特定位置。
scrollBy(),在窗口中按指定的偏移量滚动文档。
scrollTo(),滚动到文档中的某个坐标。
setImmediate(),仅IE10支持。
setInterval(),重复调用一个函数或执行一个代码段,在每次调用之间具有固定的时间延迟。
setTimeout(),设置一个定时器,该定时器在定时器到期后执行一个函数或指定的一段代码。
stop(),此方法用于阻止页面资源加载。

参考:

MDN上介绍的DOM:https://developer.mozilla.org/zh-CN/docs/Web/API/Document_Object_Model

JS 编程技巧

概述

下面会收集一些经常用到且有用的小技巧

1. 数组

  • 合并数组
[].push.apply(arr1, arr2)

Array.prototype.push.apply(arr1, arr2)

展开语法
  • 转换类数组对象为数组
[].slice.apply(arr)
  • isArray的polyfill
Array.isArray = function(arr){
    return Object.prototype.toString.call(arr) === "[object Array]"
}
  • 数据去重
  1. 使用Array.prototype.reduce
  2. 使用对象key唯一的特性
  3. 使用Set对象的唯一特性,不论是原始值还是对象引用

2. 浅拷贝与深拷贝

浅方式有下:

  1. 使用JSON语法
  2. 展开语法
  3. Object.assign()

Object.assign() 函数会触发 setters,而展开语法则不会。这一点很重要,特别是在使用vuejs这类依靠setter通知视图更新的MVVM库。

3. 模块化兼容性导出

支持AMD、commonJS、ES6三种风格

const NeedExported

if (typeof define === 'function' && define.amd) {
    // AMD
    define(() => {
        return {
            NeedExported: NeedExported
        }
    });
} else if (typeof exports === 'object' && typeof module.exports === "object") {
    // Node, CommonJS之类的
    module.exports.NeedExported = NeedExported
} else {
    // ES6
    export default NeedExported
}

UMD库

UMD模块会检查是否存在模块加载器环境。

let outputName = '';

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['b'], factory);
    } else if (typeof module === 'object' && module.exports) {
        // Node. Does not work with strict CommonJS, but
        // only CommonJS-like environments that support module.exports,
        // like Node.
        module.exports = factory(require('b'));
    } else {
        // Browser globals (root is window)
        root[outputName] = factory(root.b)[outputName];
    }
}(this, function (b) {
    //use b in some fashion.

    // Just return a value to define the module export.
    // This example returns an object, but the module
    // can return a function as the exported value.
    return {};
}));

4. getter和setter

设置属性的getter和setter有两种方式:

  1. 通过Object的API:Object.defineProperties()Object.defineProperty()
  2. 通过在对象里使用语法糖getset拦截属性的取值和赋值,例如:
class Test {
  get num(){return this.num}
  set num(val){this.num = val}
}

5. 把数值转化为千分位

可以使用正则表达式:

var num = 142343223
num.toString().replace(/(\d)(?=(?:\d{3})+$)/g,'$1,')

也可以使用一个小技巧:

var num = 142343223.89
num.toLocaleString('en-US')

6. 尾递归

递归非常消耗内存,容易发生“调用栈溢出”的错误。

ES6提出了尾递归概念,也就是函数最后一步是调用自身。

不过并不是所有浏览器都支持尾递归优化。

7. 事件模型里的once

参考VueJS的once:

Vue.prototype.$once = function (event: string, fn: Function): Component {
  const vm: Component = this
  function on () {
    vm.$off(event, on)
    fn.apply(vm, arguments)
  }
  on.fn = fn
  vm.$on(event, on)
  return vm
}

8. 原始类型和typeof

原始类型有:

  1. Boolean
  2. String
  3. Number
  4. Null
  5. Undefined
  6. Symbol(ES6 新定义)

typeof返回值有:

  1. undefined
  2. boolean
  3. number
  4. string
  5. object
  6. function
  7. symbol

判断类型还有一种方法:

借助:Object.prototype.toString.call

这个方法还可以作用于JS原生自带的对象,比如Date、RegExp、Error等。

Object.prototype.toString.call("hello")  # [object String]
Object.prototype.toString.call(123)  # [object Number]
Object.prototype.toString.call(true)  # [object Boolean]
Object.prototype.toString.call(undefined)  # [object Undefined]
Object.prototype.toString.call(null)  # [object Null]
Object.prototype.toString.call({})  # [object Object]
Object.prototype.toString.call(function(){})  # [object Function]
Object.prototype.toString.call([])  # [object Array]
Object.prototype.toString.call(new Date())  # [object Date]
Object.prototype.toString.call(new RegExp())  # [object RegExp]
Object.prototype.toString.call(new Error())  # [object Error]

参考

js模块化编程之彻底弄懂CommonJS和AMD/CMD!

Promise

由于内容涉及到Promise原理还有自己写的一个模仿Promise的异步框架库,所以独立成了一个项目。
项目链接戳此

Bind函数的模拟实现

Function.prototype.myBind = function () {
    var thisPoiter, argArray, self = this
    if (!thisPoiter && arguments[0] && typeof arguments[0] === 'object'){
        // 需要判断 arguments[0] 是不是对象
        thisPoiter = arguments[0]
        arguments.shift()
    }
    argArray = argArray.concat(arguments)
    return function () {
        return self.apply(thisPoiter, argArray)
    }
}

以上代码有缺陷,推荐自己写的另一篇博客——可以看看bind的polyfill是怎么实现的。

JS运行原理

简述JS运行原理的概念

这是一个涉及到编译原理的问题。简单来说,一般一个JS文件被执行的时候,是会经过扫描代码,并进行语法分析、语义分析等等步骤的,然后生成一些诸如语法树、符号表之类的数据结构,最后才是从上至下执行代码。

了解运行原理有什么用?

了解运行原理,才能更好预测到代码的效果,才能从容面对各种奇怪bug,以及一些提问运行结果的面试题。
如下:

console.log(a) // [Function: a]
var a = 1
function a () {}
console.log(a) // 1

这里涉及到了JS引擎的提升(hoisting)

JS运行原理的具体过程

1. 先简单介绍 JS引擎

JS引擎是根据ECMAScript规定的文档去实现的,这些文档定义了一些规则,然后呢,各个浏览器厂商就根据这些规则去实现自己的JS引擎,有点像Java的接口编程,所以目前的主流JS引擎有:Firefox浏览器为Gecko引擎,Safari为WebKit引擎,Chrome为Blink引擎等等。

它们都有以下两个主要特性:

  1. 单线程
  2. 同步执行

这两个特性省去了多线程的锁机制,降低了编程的门槛。

2. 解释执行JS文件

伪逻辑如下:

2.1 创建全局执行上下文

主要任务:
创建执行上下文栈
创建变量对象,包括初始化的全局对象,函数声明,变量声明
并把this指向全局对象(浏览器环境下是window)

2.2 从上至下解释执行语句

2.2.1 如果遇到的是函数执行语句

创建新的执行上下文,并放入执行上下文栈的栈顶
新的执行上下文主要有如下三个对象

  1. 作用域链,根据执行上下文栈求出作用域链
  2. 创建变量对象,包括arguments,函数声明,变量声明
  3. 定义this的指向

2.2.2 如果遇到的是异步语句

比如setTimeout,setInterval,Promise之类,将会交给异步处理模块,待到时机成熟的时候,它们会把回调放到任务队列中。

2.2.3 如果遇到的是创建新对象的语句

则把该对象放到堆里

2.3 主线程执行完之后进入事件循环阶段

事件循环阶段的伪逻辑如下:

  1. 运行micro-task队列的任务直到为空
  2. 然后再运行macro-task队列里截止目前存在的任务
  3. 回到1

可以参考我的另一篇博客《异步编程与事件循环》下对任务队列的讲解。

2.4 循环直到程序结束

最后总结一下

js

从上图可以看出,核心在执行上下文栈(JS线程),它关联着存储对象的堆,调用异步模块来处理异步任务,并不断从任务队列中索取回调来执行。

图中的异步处理模块几乎都是独立于JS线程的浏览器其他线程,比如定时器,就是浏览器的另一条线程,注册定时任务的时候,会在固定的时间之后把回调放入任务队列中。

接下来,拓展你的思维

浏览器进程和网页渲染过程
执行上下文
类相关
异步编程与事件循环

参考链接

https://www.kancloud.cn/digest/liao-js/149467
冴羽的博客
异步编程

DOM的事件模型

1. 事件等级

0级DOM:

从技术上来说,W3C的DOM标准并不支持上述最原始的添加事件监听函数的方式,这些都是在DOM标准形成前的事件模型。尽管没有正式的W3C标准,但这种事件模型仍然得到广泛应用,这就是我们通常所说的0级DOM。 有下面的缺点:

  1. 没有event对象
  2. 事件都是通过标签的attribute来注册的
  3. 所以也可以通过JS操作attribute来注册事件

2级DOM:

DOM级别1于1998年10月1日成为W3C推荐标准。1级DOM标准中并没有定义事件相关的内容,所以没有所谓的1级DOM事件模型。在2级DOM中除了定义了一些DOM相关的操作之外还定义了一个事件模型 ,这个标准下的事件模型就是我们所说的2级DOM事件模型。有如下特性:

  1. 规定事件流分为:1. 事件捕获;2. 目标阶段;3. 事件冒泡
  2. 有event对象,且非IE的回调函数第一个参数都是event对象
  3. 不同的JS引擎会实现自己的事件监听器系统(观察者模式)

2. 相关的类有:

  1. EventTarget,充当事件处理器的容器
  2. Event及其子类,充当事件容器,会在事件流中被传递

3. 事件流:

分为三个阶段:

  1. 事件捕获阶段
  2. 处于目标阶段
  3. 事件冒泡阶段
    我们可以把事件处理器注册在捕获阶段或者冒泡阶段(默认)。
    如下图:
    image

4. 浏览器处理事件流程:

每当一个事件发生时(比如点击事件),浏览器会生成一个事件对象(比如MouseEvent,它集成自UIEvent、Event),根据DOM树结构,找到流传到目标节点的所有父节点。然后进入事件流:
捕获阶段:把事件对象依次传递给各个父节点和自身节点(从上至下),并把注册在捕获阶段的所有事件处理器放到异步处理模块中处理。
冒泡阶段:把事件对象传递给自身节点和各个父节点(从下至上),并把注册在冒泡阶段的所有事件处理器放到异步处理模块中处理。

5. 事件的生命周期:

  1. 事件处理器注册阶段
每个DOM节点都实现了EventTarget的接口,都可以注册事件处理器。
  1. 事件触发阶段(不停地循环)
第一个阶段可以注册的事件的名称是不受限制的,比如你可以注册很多奇形怪状的事件的处理器。
但是,在触发阶段,浏览器只会生成规范的事件对象(比如`click`),也就是说,你注册的除了`click`事件处理器外,都不会被触发。
但是,这并没有阻止我们自己可以触发自己的事件,受限制的只是我们的想象力。

6. EventTarget的接口,详情请参考

addEventListener/attachEvent,注册事件处理器

语法:

target.addEventListener(type, listener[, options]);
target.addEventListener(type, listener ,{capture: Boolean, passive: Boolean, once: Boolean});
target.addEventListener(type, listener[, useCapture]);

参数:

options(默认值都为 false):
一个指定有关 listener 属性的可选参数对象。可用的选项如下:
capture:  Boolean,表示 listener 会在该类型的事件捕获阶段传播到该 EventTarget 时触发。
once:  Boolean,表示 listener 在添加之后最多只调用一次。如果是 true, listener 会在其被调用之后自动移除。
passive: Boolean,设置为true表示 listener 永远不会调用 preventDefault(),如果 listener 仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告。

useCapture:
Boolean,是指在DOM树中,注册了该listener的元素,是否会先于它下方的任何事件目标,接收到该事件。如果没有指定,useCapture 默认为 false 。 

返回值:
undefined

removeEventListener/detachEvent,注销事件处理器

语法:

target.removeEventListener(type, listener[, options]);
target.removeEventListener(type, listener[, useCapture]);

参数:

type
一个字符串,表示需要移除的事件类型,如 "click"。
listener
需要移除的 EventListener 函数(先前使用 addEventListener 方法定义的)。
options 可选
一个可选择的对象:指定有关事件侦听器的特征。可选项有:

- capture : 一个 Boolean 表示这个类型的事件将会被派遣到已经注册的侦听器,然后再派遣到DOM树中它下面的任何 EventTarget。
-  mozSystemGroup: 仅可运行于 XBL 或者 Firefox Chrome,它是一个 Boolean,用于定义是否将侦听器添加到系统组。

useCapture 可选
指定需要移除的 EventListener 函数是否为事件捕获。如果无此参数,默认值为 false。

返回值:
undefined

dispatchEvent/fireEvent,触发事件处理器

语法:

cancelled = !target.dispatchEvent(event)

参数:

event 是要被派发的事件对象。
target 被用来初始化 事件 和 决定将会触发 目标。

返回值:
当该事件是可取消的(cancleable为true)并且至少一个该事件的 事件处理方法 调用了Event.preventDefault(),则返回值为false;否则返回true。

7. Event对象,详情请参考

创建事件:

var event = document.createEvent(type);

方法:

1. preventDefault,阻止执行默认行为,但是事件还是会继续传播
2. stopImmediatePropagation,阻止本节点中剩下的本类型事件处理器执行
3. stopPropagation,阻止事件流继续传播(不论是在捕获还是冒泡阶段)

属性:

bubbles,表示该事件是否在DOM中冒泡。
cancelable,表示这个事件是否可以取消。
cancelBubble,通过在一个事件处理程序返回前设置这个属性的值为真,来阻止事件冒泡。
composed,表示这个事件是否可以在阴影DOM和常规DOM之间的边界上浮动。
currentTarget,当前注册事件的对象的引用。这个值会在传递的途中进行改变。
defaultPrevented,表示了是否已经执行过了event.preventDefault()。
eventPhase,指示事件流正在处理哪个阶段。0: Event.NONE;1: Event.CAPTURING_PHASE,2: Event.AT_TARGET,3: Event.BUBBLING_PHASE。
isTrusted,指明事件是否是由浏览器(当用户点击实例后)——true或者由脚本(使用事件的创建方法,例如event.initEvent)启动——false。
srcElement,target的别名。
target,对事件起源目标的引用。
timeStamp,事件创建时的时间戳,毫秒级别。
type,事件的类型(不区分大小写)。

8. 事件类型

事件类型是事件模型中的重点,但是内容很多,请参考
记录一些常见的事件:

  1. 资源事件,load、DOMContentLoaded(HTML文档完全加载和解析完成之后触发)、abort、error
  2. 网络事件,online、offline
  3. 焦点事件,focus、blur
  4. Websocket事件,open、message、error、close
  5. 会话历史事件
  6. CSS动画事件,animationstart、animationend、animationiteration
  7. 表单事件,reset、submit
  8. 打印事件
  9. 文本组合事件
  10. 视图事件,fullscreenchange、fullscreenerror、resize、scroll
  11. 剪贴板事件,cut、copy、paste
  12. 键盘事件,keydown、keypress、keyup
  13. 鼠标事件,mouseenter、mousemove、mouseout、mousedown、mouseup、click、dbclick、wheel
  14. 拖放事件,dragstart、drag、dragend、dragenter、dragover、dragleave、drop
  15. 媒体事件
  16. 进度事件

NodeJS 运行原理

概述

NodeJS本身不是开发语言,它是一个工具或者平台,在服务器端解释、运行Javascript。NodeJS利用Google V8来高效率地解释运行Javascript,而Javascript做的只是调用这些API而已。NodeJS里的libuv为开发者提供了异步编程的能力。

libuv 采用了 异步 (asynchronous), 事件驱动 (event-driven)的编程风格, 其主要任务是为开人员提供了一套事件循环和基于I/O(或其他活动)通知的回调函数, libuv 提供了一套核心的工具集, 例如定时器, 非阻塞网络编程的支持, 异步访问文件系统, 子进程以及其他功能.

node

1. 模块化的本质

NodeJS 把每个JavaScript文件封装成一个模块,一个模块其实就是函数,因为函数本来就是一个执行上下文,可以通过node demo.js得到,这个函数的参数

# demo.js
console.log(arguments);

# output:
{ '0': {},
  '1':
   { [Function: require]
     resolve: { [Function: resolve] paths: [Function: paths] },
     main:
      Module {
        id: '.',
        exports: {},
        parent: null,
        filename: 'E:\\myWorks\\Workbench\\web\\modu.js',
        loaded: false,
        children: [],
        paths: [Array] },
     extensions: { '.js': [Function], '.json': [Function], '.node': [Function] },
     cache: { 'E:\myWorks\Workbench\web\modu.js': [Object] } },
  '2':
   Module {
     id: '.',
     exports: {},
     parent: null,
     filename: 'E:\\myWorks\\Workbench\\web\\modu.js',
     loaded: false,
     children: [],
     paths:
      [ 'E:\\myWorks\\Workbench\\web\\node_modules',
        'E:\\myWorks\\Workbench\\node_modules',
        'E:\\myWorks\\node_modules',
        'E:\\node_modules' ] },
  '3': 'E:\\myWorks\\Workbench\\web\\modu.js',
  '4': 'E:\\myWorks\\Workbench\\web' }

从上图可以看到,每个JS文件之所以可以访问moduleexportsrequire()__filename__dirname,就是因为NodeJS把我们写的JS文件封装成一个模块,这个模块就是一个函数执行上下文,而函数的入参就有它们。

我们还可以通过global对象访问全局对象。

A. 模块的分类:

NodeJS 里的模块分为两种:

  1. 核心模块,系统自带的模块,安装NodeJS就已经带上了
  2. 文件模块,包括第三方模块(通过指令npmyarn引入的其他人写好的模块)和自己编写的模块

B. 访问主模块:

当 Node.js 直接运行一个文件时,require.main 会被设为它的 module。 这意味着可以通过 require.main === module 来判断一个文件是否被直接运行:
对于 foo.js 文件,如果通过 node foo.js 运行则为 true,但如果通过 require('./foo') 运行则为 false。

C. 模块解析:

1. 区别模块类型

当没有以 '/'、'./' 或 '../' 开头来表示文件时,这个模块必须是一个核心模块或加载自 node_modules 目录。

2. 填充后缀

如果按确切的文件名没有找到模块,则 Node.js 会尝试带上 .js、.json 或 .node 拓展名再加载。

但是文件名被解析成一个目录,如果目录下有package.json,入口文件将会是main字段指定的文件;如果目录下没有package.json,Node.js 就会试图加载目录下的 index.js 或 index.node 文件。

3. 填充路径

如果传递给 require() 的模块标识符不是一个核心模块,也没有以 '/' 、 '../' 或 './' 开头,则 Node.js 会从当前模块的父目录开始,尝试从它的 /node_modules 目录里加载模块。 Node.js 不会附加 node_modules 到一个已经以 node_modules 结尾的路径上。

4. 全局目录

如果 NODE_PATH 环境变量被设为一个以冒号分割的绝对路径列表,则当在其他地方找不到模块时 Node.js 会搜索这些路径。

5. 查找失败

如果给定的路径不存在,则 require() 会抛出一个 code 属性为 'MODULE_NOT_FOUND' 的 Error。

D. 模块缓存:

模块在第一次加载后会被缓存。 这也意味着(类似其他缓存机制)如果每次调用 require('foo') 都解析到同一文件,则返回相同的对象。

多次调用 require(foo) 不会导致模块的代码被执行多次。 这是一个重要的特性。 借助它, 可以返回“部分完成”的对象,从而允许加载依赖的依赖, 即使它们会导致循环依赖。

模块是基于其解析的文件名进行缓存的。 在不区分大小写的文件系统或操作系统中,被解析成不同的文件名可以指向同一文件,但缓存仍然会将它们视为不同的模块,并多次重新加载。

E. 核心模块:

Node.js 有些模块会被编译成二进制。require()总是会优先加载核心模块。 例如,require('http') 始终返回内置的 HTTP 模块,即使有同名文件。

F. 循环依赖:

当循环调用 require() 时,一个模块可能在未完成执行时被返回。

2. 包管理

NodeJS项目里可以通过NPMpackage.json管理第三方包。

NPM

NPM是随同NodeJS一起安装的包管理工具,能解决NodeJS代码部署上的很多问题。允许用户从NPM服务器下载别人编写的第三方包或命令行程序到本地使用,也允许用户将自己编写的包或命令行程序上传到NPM服务器供别人使用。

Package.json

name - 包名。
version - 包的版本号。
description - 包的描述。
homepage - 包的官网 url 。
author - 包的作者姓名。
contributors - 包的其他贡献者姓名。
dependencies - 依赖包列表。如果依赖包没有安装,npm 会自动将依赖包安装在 node_module 目录下。
repository - 包代码存放的地方的类型,可以是 git 或 svn,git 可在 Github 上。
main - main 字段指定了程序的主入口文件,require('moduleName') 就会加载这个文件。这个字段的默认值是模块根目录下面的 index.js。
keywords - 关键字.

发布自己的模块

首先使用npm adduser指令创建NPM账户,或者使用npm login指令登录NPM账号,然后创建自己的库,package.json是必须的,用于描述模块,使用npm publish指令发布出去。

3. 事件循环

下面是官方给出的NodeJS的事件循环示意图:

   ┌───────────────────────────┐
┌─>│           timers          │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │
│  └─────────────┬─────────────┘      ┌───────────────┐
│  ┌─────────────┴─────────────┐      │   incoming:   │
│  │           poll            │<─────┤  connections, │
│  └─────────────┬─────────────┘      │   data, etc.  │
│  ┌─────────────┴─────────────┐      └───────────────┘
│  │           check           │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │
   └───────────────────────────┘

事件循环可以说是NodeJS的核心,作为web服务器,可以把接收到的请求放到事件队列中去,并把阻塞的操作放到异步模块中,这样一来可以高效率使用cpu,提高用户响应。

1. 关键的API

setTimeout()setInterval()属于timers阶段的。
setImmediate()属于check阶段。
process.nextTick()将 callback 添加到"next tick 队列",在micro-task-queue被调用之前执行。递归调用nextTick callbacks 会阻塞任何I/O操作,就像一个while(true); 循环一样。
Promise.then()属于micro-task-queue。

2. 与浏览器事件循环机制的不同

NodeJS和浏览器的事件循环机制不一样。

浏览器环境下,microtask的任务队列是每个macrotask执行完之后执行。
而在Node.js中,microtask会在事件循环的各个阶段之间执行,也就是一个阶段执行完毕,就会去执行microtask队列的任务。

3. 关键阶段

  1. timers,执行由timer库产生的回调。
  2. poll,NodeJS内置的很多异步API的回调都是在poll阶段执行的。比如fs.read之类的。
  3. check,执行setImmediate()产生的回调。

参考

[译] 你不知道的 Node
深入理解js事件循环机制(Node.js篇)
The Node.js Event Loop, Timers, and process.nextTick()
【Node.js】理解事件循环机制
Node.js机制及原理理解初步
module - 模块

异步编程与事件循环

概述

我们都知道JS引擎是单线程、同步执行的了,但是对于I/O业务密集型的前端,没有异步编程是不行的,会非常影响用户的体验。

异步模块并不属于V8 引擎范围,它是浏览器内核提供的Web API,供开发者使用的,用于提供用户体验的模块,比如经常使用到的setTimeout、XMLHttpRequest、Fetch、Promise等等API。

js

定义

1. 同步和异步、阻塞与非阻塞的关联和区别

举个例子

同步就是烧开水,需要自己去轮询(每隔一段时间去看看水开了没),异步就是水开了,然后水壶会通知你水已经开了,你可以回来处理这些开水了。
同步和异步是相对于操作结果来说,是自动去询问操作有结果了么,还是等待操作结束了带着结果告知予你。

阻塞就是说在煮水的过程中,你不可以去干其他的事情,非阻塞就是在同样的情况下,可以同时去干其他的事情。
阻塞和非阻塞是相对于线程是否被阻塞,是等待操作结果返回,还是接着往下执行代码。

所以,JS的异步非阻塞场景就是,主线程遇到一个需要长时间等待结果操作,可以先不等它返回结果,继续执行其他操作;等待该操作产生结果了,会带着结果来通知主线程。那么JS是怎么实现这样的效果呢?答案就是异步编程和事件循环。

2. 浓缩成一段话

对于主线程需要异步的操作(长时间操作),都交给异步处理模块(通常是另一条线程),等到处理结果出来了,由异步处理模块把处理结果和回调传回给主线程的任务队列(task-queue),主线程会不停地去任务队列取回调来执行,这就是事件循环。

这篇博客涉及到的概念

  1. setTimeout和setInterval
  2. 任务队列
  3. 事件循环机制
  4. 研究VueJS的nextTick源码
  5. Promise异步处理
  6. 寻找跟任务队列交互的API

1. setTimeout和setInterval

这是定时器线程提供的两个api,两者的常用api如下:

setTimeout(func,[delay,param1,param2,····]);
setInterval(func, delay[,param1, param2, ...]);

A. setTimeout方法

setTimeout方法会让定时器线程在过了delay时间后,把回调(func)放入JS引擎线程的任务队列中,很多博客都会提到setTimeout的一些缺点,就是这个api不能保证在过了delay时间后执行,其实原理很简单,我们知道JS引擎会在主线程执行完毕之后才会到任务队列中提取回调到主线程中执行,所以定时器线程能保证delay时间之后把回调放入任务队列,但是不能保证这个时候主线程是空闲的。

B. setInterval方法

setInterval方法,在nodejs环境和浏览器环境下是表现不一样的。

1. NodeJs环境下

setInterval的回调在delay时间后,被放入JS引擎线程的任务队列中,等待被JS引擎线程放入执行上下文栈中执行,被执行完毕之后,会调用自身一次,也就是请求定时器线程在delay时间后再次把回调放入任务队列中。用setTimeout来表示这个逻辑的话:

setTimeout(function(){
    // some code
    setTimeout(arguments.callee, interval); 
}, interval);

2. 浏览器环境下

每隔delay时间后,定时器线程会把回调放入任务队列中,如果任务队列中已经存在回调还没被执行的话会被覆盖掉。换句话说,如果你想要6秒内执行3次(delay = 2000),但是回调执行时间太长,比如执行了4秒,那么这段时间内会被插入两个回调,那么只有第二个回调会被执行。

3. 测试上面的两个结论代码:

(function inter(num) {
    setInterval(() => {
        var t = +new Date();
        ++num;
        console.log(num, new Date());
        while (+new Date() - t < 1000); // 阻塞一秒
    }, 2000) // 间隔两秒
})(0)

NodeJs output(回调间隔3秒):
1 2018-05-09T01:55:54.841Z
2 2018-05-09T01:55:57.842Z
3 2018-05-09T01:56:00.842Z
4 2018-05-09T01:56:03.842Z
5 2018-05-09T01:56:06.842Z

浏览器 output(回调间隔2秒):
1 Wed May 09 2018 09:56:28 GMT+0800 (**标准时间)
2 Wed May 09 2018 09:56:30 GMT+0800 (**标准时间)
3 Wed May 09 2018 09:56:32 GMT+0800 (**标准时间)
4 Wed May 09 2018 09:56:34 GMT+0800 (**标准时间)
5 Wed May 09 2018 09:56:36 GMT+0800 (**标准时间)

2. 任务队列

任务队列分宏任务队列(macro-task-queue)和微任务队列(micro-task-queue),如下:

  1. 宏任务队列:script(JS文件)、setTimeout、setInterval、setImmediate、I/O、ajax、eventListener、UI rendering
  2. 微任务队列:process.nextTick、Promise、Object.observe()、MutationObserver

Object.observe(obj, callback[, acceptList])已废弃,可以使用Proxy对象代替。

MutationObserver接口提供了监视对DOM树所做更改的能力。它被设计为旧的Mutation Events功能的替代品,该功能是DOM3 Events规范的一部分。

一个网页有若干个宏任务队列,根据任务源来分类,大概有如下任务源:

  1. DOM操作任务源
  2. 用户交互任务源,比如鼠标事件之类
  3. 网络任务源,例如ajax
  4. history traversal任务源,例如history.back()

每一个宏任务都有一个微任务队列,在宏任务结束之前会把微任务队列里的任务执行完毕。

3. 事件循环机制

1. 事件循环的步骤

  1. 取出macro-task-queue中最老的一个task(最开始是script)到主线程中依次执行
  2. 执行micro-task-queue中的所有task,直到为空
  3. 查看将来最近一帧(浏览器每秒60帧),如果有需要的DOM更新,则进行UI Render更新
  4. 回到1

2. 网页渲染时机

  1. 浏览器一般是一秒60帧刷新页面的,也就是16.7ms一帧,在这16.7ms期间收集到的所有DOM变更(repaint或reflow)将会通过GPU进程在下一帧中更新到页面上。
  2. DOM的变更操作在一次执行上下文中仅保留最终态(意思是就算每次修改一个dom,也不会立马渲染到页面上),等到所在的执行上下文执行完了之后DOM的最终态将被GUI线程收集。如果一个执行上下文执行时间过长,会延迟DOM的更新。下面的代码说明这一点:
setTimeout(()=>{
    var demoDiv = document.getElementById('test')
    demoDiv.innerText = 'hello world'; // 变更 DOM
    var t = +new Date();
    while(+new Date() - t < 3000) ; // 阻塞3秒,才会把变更的dom穿给GUI线程
})

过程大概如下图:
default

3. requestAnimationFrame

这是浏览器专门为动画提供的API,用系统时间间隔,保持最佳绘制效率,不会因为间隔时间过短,造成过度绘制,增加开销;也不会因为间隔时间太长,使用动画卡顿不流畅,让各种网页动画效果能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果。

举个例子
如果有一个需求,一秒钟往ul标签里插入1000个li标签,可以借助这个api,每帧(1秒有60帧)插入17个li标签,那么用户会觉得画面很顺畅

4. 研究VueJS的nextTick源码

简单介绍:Vue对象的数据变更并不是执行了就会立马更新到DOM上,而是把所有变更存储起来等待事件循环来处理,为了得到立马更新的DOM,需要调用nextTick这个API。

1. 需要着重讲一下VueJs中数据的三种状态

  1. 更新到dom属性前,在一个执行上下文还未执行结束前,数据都是这个状态
<div id="example">{{message}}</div>
new Vue({
  el: '#example',
  data: {
    message: '123'
  }
  methods: {
    updateMessage: () => {
        vm.message = 'new message' // 更改数据
        console.log(vm.$el.textContent) // '123',因为还未更新到dom属性
        console.log(vm.message) // 'new message'
    }
  }
})
  1. 更新到dom属性
  2. 渲染到页面上

2. nextTicke源码

nextTicke源码(2.5.17版本)在这里

大致过程是这样的:优先使用(Promise > setImmediate > MessageChannel > setTimeout)来作为它的实现方式,用一个数组callbacks来存储一个执行上下文中多次调用的nextTick的回调,经过特殊处理后,并不是每个nextTick都产生一个micro-task(或者macro-task),而是在一个micro-task(或者macro-task)中把callbacks全部循环执行了。

3. 如何保证nextTick里能确切拿到更新后的dom属性呢?

VueJs官网关于异步更新队列,有下面一句话

Vue 在内部尝试对异步队列使用原生的 Promise.then 和 MessageChannel,如果执行环境不支持,会采用 setTimeout(fn, 0) 代替。

可以看得出来,如果异步更新队列使用的是micro-task方式更新的,那么nextTick也会是同样的micro-task方式实现的,所以只需要保证异步更新队列先放入micro-task-queue,nextTick后放入的话,nextTick里的回调是可以正确取到dom属性的值。(如果是使用macro-task的方式,那么nextTick就会被安排到下一次tick,也可以正确取到dom属性的值)

5. Promise异步处理

1. 介绍promise

前面说过Promise是一个异步处理框架。
在JS发展史里,最早广泛使用Promise的是jQuery.Deffered()。Promise/A+标准规定了一系列API,先是NodeJs有了开源的Promise库,后来ES6直接整合这个标准,NodeJs支持ES6的时候也不需要开源的Promise库了。

2. Promise的API

Promise.all()
Promise.prototype.catch()
Promise.prototype.finally()
Promise.prototype.then()
Promise.race()
Promise.reject()
Promise.resolve()

3. ES6的Promise

作为一个异步处理框架,提供API给前端工程师使用,但是内部实现不详。

4. 模仿实现ES6的Promise

以ES6的Promise为参考,使用timeout和ES6的语法(可选)实现。
由于篇幅过多,我把它独立成一个项目 —— 用代码讲述Promise原理——每个人都应该有自己的Promise

6. 寻找跟任务队列交互的API

我曾经想过找到这个api,然后每个前端工程师都可以写自己的异步框架了。
想象中的这个api应该是这样的:

push-task-into-queue(callback, type)
// 其中的callback是放入队列的回调,type是队列的类型:macro-task还是micro-task

但是一些网友告诉我,这个api是浏览器内部实现的,不会公开的。略感失望。

写在最后

JS相关的异步编程和事件队列涉及到的东西真不少,这篇博客有误的地方请指正。

参考链接

VueJs的异步更新队列
Promise API
Promises/A+ 规范
The Node.js Event Loop, Timers, and process.nextTick()
深入理解js事件循环机制(Node.js篇)
深入理解js事件循环机制(浏览器篇)
https://www.cnblogs.com/George1994/p/6702084.html
https://juejin.im/post/599ff3d5f265da24843e6276
https://segmentfault.com/a/1190000011198232
https://www.cnblogs.com/xiaohuochai/p/5777186.html
https://github.com/aooy/blog/issues/5
https://html.spec.whatwg.org/multipage/webappapis.html#event-loops

JavaScript 引擎

概述

JavaScript引擎是一个专门处理JavaScript脚本的虚拟机,它是根据ECMAScript规范来实现的,所以工程师可以编写符合ECMAScript规范的JS代码并运行在浏览器上。

根据MDN说明,JavaScript ( JS ) 是一种具有函数优先的轻量级解释型或即时编译型的编程语言。

JS引擎会以执行上下文为一个执行单元去解析执行代码。一个执行上下文可以是全局代码,也可以是函数代码,相对应的有全局作用域和函数作用域。解析代码的时候会触发变量提升。

但是,为了优化执行效率,JavaScriptCore引擎和V8引擎会带有JIT编译器,JIT会把执行超过一次的代码块编译并保存好,下次再次执行的时候会跳过翻译直接执行。这个代码块还会被继续观察并尝试更多的优化并保存好。在编译器进行优化的过程中会做一些关于变量类型和环境中值的假设;但是如果假设不成立就将这个优化的版本回退,如果假设成立的话,这将让代码性能更高。

所以得出结论,JS引擎是解释型语言,但是有些版本的JS引擎为了优化执行效率会带有JIT即时编译器。

解释型语言和编译型语言

编译型语言是代码在运行前编译器将人类可以理解的语言(编程语言)转换成机器可以理解的语言。

解释型语言也是人类可以理解的语言(编程语言),也需要转换成机器可以理解的语言才能执行,但是是在运行时转换的。

解释型语言运行慢的原因是,每次执行都需要把源码转换一次才可以执行。

编译优化代码阶段

  1. 词法分析,任务是识别源程序中的单词是否有误
  2. 语法分析,目的是识别出源程序的语法结构(即短语、句子、过程、程序),所以有时又称为句子分析
  3. 语义分析,检查程序的语义正确性,包括检查类型
  4. 中间代码产生,产生介于源语言与目标代码之间的一种中间代码,抽象语法树
  5. 优化,对代码进行加工转化,包括删除无用语句、循环优化
  6. 目标代码生成,转化成特定机器上的低级语言代码

涉及的数据结构有:语法树、符号表、常数表

JavaScriptCore

JavaScriptCore 执行 一系列步骤 来解释和优化脚本:

  1. 它进行词法分析,就是将源代码分解成一系列具有明确含义的符号或字符串。
  2. 然后用语法分析器分析这些符号,将其构建成语法树。
  3. 接着四个 JIT(Just-In-Time)进程开始参与进来,分析和执行解析器所生成的字节码。

V8

Google 的 V8 引擎 是用 C++ 编写的,它也能够编译并执行 JavaScript 源代码、处理内存分配和垃圾回收。它被设计成由两个编译器组成,可以把源码直接编译成机器码:

  1. Full-codegen:输出未优化代码的快速编译器
  2. Crankshaft: 输出执行效率高、优化过的代码的慢速编译器

如果 Crankshaft 确定需要优化的代码是由 Full-codegen 生成的未优化代码,它就会取代 Full-codegen,这个过程叫做“crankshafting”。

关键概念

1. 堆栈

堆是存储对象的地方;栈是存放一个又一个执行上下文的地方,函数是运行的基本单位,换句话说,一个函数对应一个上下文,一个作用域。
参考下图:
js

2. 执行上下文

JS引擎刚开始读取JS文件的时候,会创建一个全局上下文,并把它放到执行上下文栈里,之后如果遇到需要新创建执行上下文,会把它压到栈顶;直到相关的代码执行完毕并返回结果,该执行上下文会被弹出栈。执行上下文栈是创建作用域链的重要参考。

3. 变量对象

变量对象指的是每个执行上下文可以访问的变量合集。

全局执行上下文的变量对象有:

  1. 全局对象(浏览器环境中是window,nodejs中是global),允许直接访问属性(比如经常使用的new Date,本身就是Window.Date)
  2. 使用或不使用var定义的变量,差别是不使用var定义的变量可以用delete操作符删除
  3. 函数声明

函数执行上下文的变量对象有:

  1. arguments
  2. 实参
  3. 使用var定义的变量
  4. 函数声明

4. 作用域与作用域链

ES6之前没有块作用域,仅有全局作用域和函数作用域。

当前作用域没有定义的变量,这成为自由变量 。自由变量如何得到 —— 向父级作用域寻找。

比如:在函数执行上下文中未使用var定义的变量被调用的时候,因为在变量对象中找不到,所以会顺着作用域链查找下一个执行上下文中的变量对象。如果在全局作用域里也找不到,将会报错not defined

自由变量将从作用域链中去寻找,但是依据的是函数定义时的作用域链,而不是函数执行时

作用域链是根据执行上下文栈来确定的。

特殊作用域:

  1. try-catch语句块,临时在作用域链中添加error对象
  2. with语句块,也会临时在作用域链中添加with的对象
  3. eval语句,如果你间接的使用 eval(),比如通过一个引用来调用它,而不是直接的调用 eval ,从 ECMAScript 5 起,它工作在全局作用域下,而不是局部作用域中

5. 闭包

刚刚提到:自由变量将从作用域链中去寻找,但是依据的是函数定义时的作用域链,而不是函数执行时

闭包就是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。

遮蔽,自由变量会被函数的局部变量遮蔽,遮蔽的自由变量会访问不到。

闭包就是将函数内部和函数外部连接起来的一座桥梁。

为什么会用到高阶函数?粗糙的说,就是为了闭包。

6 提升

提升是相对于var声明的变量和函数声明的,函数表达式并不会被提升。

提升的伪逻辑如下:

  1. 提升var变量,如果存在同名变量(包括同名形参),不进行操作;若不存在,则初始化为undefined
  2. 提升函数声明,提升并覆盖已有的同名变量

7. this的指向

this刚好和闭包相反,确定this是在函数执行时,而不是函数定义时

一旦确定了this的指向,当访问this的某个成员变量或函数,如果不存在不会根据执行上下文栈找到上一个this并查询。

1. 全局执行上下文

this指向全局对象,在浏览器环境下是window,在nodejs下是global

2. 函数执行上下文

  1. 作为对象的方法,即使方法是来自该对象的原型链上的方法,this也指向该对象
  2. 作为构造函数,指向正在构建的新对象
  3. 如果是简单调用,就是形如fun()而不是obj.fun(),this指向全局对象,但是在严格模式下,会指向undefined
  4. 箭头函数,与封闭词法上下文的this保持一致

这里的封闭词法上下文没有那么高大上,说白了,箭头函数的执行上下文没有定义this属性,根据执行上下文栈找到上一个执行上下文的this,就是箭头函数中的this。

场景

  1. 作为构造函数执行,构造函数中,this指向新对象
  2. 作为对象属性执行,指向这个对象
  3. 作为普通函数执行,指向全局对象
  4. 用于call apply bind,绑定了的对象

8. 暂时性死区

只有在es6中的let和const才会发生这种情况。

进入一个执行上下文的时候,
let、const声明的变量和形参定义的变量,都会屏蔽掉前一个执行上下文中的同名变量,
且在声明语句之前是不可以使用该变量,
声明语句之后,若该变量没有被赋值,则默认为undefined。

参考

认识 V8 引擎
一篇给小白看的 JavaScript 引擎指南
JS工作原理:引擎,运行时和调用堆栈
V8基础学习一:从编译流程学习V8优化机制
JavaScript到底是解释型语言还是编译型语言?
什么是 JavaScript?
了解编译原理-笔记小结
编译原理学习笔记一

CSS 盒模型

概述

文档中每一个元素在页面布局结构中均呈现为一个矩形的盒子,包括块元素和行内元素。

1. 盒模型

有时候也被成为框模型。

基础特性:

  1. 溢流:overflow
  2. 背景裁剪:background-clip
  3. 轮廓:outline

高级特性:

  1. 设置宽和高的约束,[min | max]-[width | height]
  2. 改变盒模型,box-sizing
  3. 显示类型,display
  4. 边框样式

通过display设置显示类型:

css 1:
none,关闭一个元素的显示(对布局没有影响);其所有后代元素都也被会被关闭显示。
inline,该元素生成一个或多个行内元素盒。
block,该元素生成一个块元素盒。
list-item,该元素生成一个容纳内容和单独的列表行内元素盒的块状盒。需要父元素搭配padding,子元素搭配list-style-position: outside;list-style-type: disc;

css 2.1:
inline-block,该元素生成一个块状盒,该块状盒随着周围内容流动,如同它是一个单独的行内盒子(表现更像是一个被替换的元素)。外部看是inline元素,内部看是block元素
表格模型值:

css 3:
flex
inline-flex
grid
inline-grid

2. 包含块

一个元素的尺寸和位置经常受其包含块的影响。

1. 如果 position 属性为 static 或 relative ,包含块就是由它的最近的祖先块元素(比如说inline-block, block 或 list-item元素)或格式化上下文(比如说 a table container, flex container, grid container, or the block container itself)的内容区的边缘组成的。
2. 如果 position 属性为 absolute ,包含块就是由它的最近的 position 的值不是 static (也就是值为fixed, absolute, relative 或 sticky)的祖先元素的内边距区的边缘组成。
3. 如果 position 属性是 fixed,包含块就是由 viewport (in the case of continuous media) 。

3. 垂直外边距合并

发生的条件:

  1. 相邻元素之间
  2. 父元素与其第一个或最后一个子元素之间
  3. 空的块级元素

4. 块格式化上下文(BFC)

创建块格式化上下文的方式:

  1. 根元素或包含根元素的元素
  2. 浮动元素(元素的 float 不是 none)
  3. 绝对定位元素(元素的 position 为 absolute 或 fixed)
  4. 行内块元素(元素的 display 为 inline-block)
  5. 表格单元格(元素的 display为 table-cell,HTML表格单元格默认为该值)
  6. 表格标题(元素的 display 为 table-caption,HTML表格标题默认为该值)
  7. 匿名表格单元格元素(元素的 display为 table、table-row、 table-row-group、table-header-group、table-footer-group(分别是HTML table、row、tbody、thead、tfoot的默认属性)或 inline-table)
  8. overflow 值不为 visible 的块元素
  9. display 值为 flow-root 的元素
  10. contain 值为 layout、content或 strict 的元素
  11. 弹性元素(display为 flex 或 inline-flex元素的直接子元素)
  12. 网格元素(display为 grid 或 inline-grid 元素的直接子元素)
  13. 多列容器(元素的 column-count 或 column-width 不为 auto,包括 column-count 为 1)
  14. column-span 为 all 的元素始终会创建一个新的BFC,即使该元素没有包裹在一个多列容器中(标准变更,Chrome bug)

块格式化上下文包含创建它的元素内部的所有内容。

用处:

  1. 浮动定位和清除浮动时只会应用于同一个BFC内的元素。
  2. 外边距折叠(Margin collapsing)也只会发生在属于同一BFC的块级元素之间。

5. 盒子阴影

box-shadow属性有四个值:

  1. 第一个长度值是水平偏移量(horizontal offset )——即向右的距离,阴影被从原始的框中偏移(如果值为负的话则为左)。
  2. 第二个长度值是垂直偏移量(vertical offset)——即阴影从原始盒子中向下偏移的距离(或向上,如果值为负)。
  3. 第三个长度的值是模糊半径(blur radius)——在阴影中应用的模糊度。
  4. 第四个颜色值是阴影的基本颜色(base color)。

可以声明多个盒子阴影,用逗号隔开。

box-shadow还可以设置五个值,并在第一个值设置为inset,使它成为内部阴影。

6. 过滤器

CSS过滤器提供了一种过滤元素的方法,就好比你在诸如Photoshop这样的平面设计程序中过滤元素一样。

参考:

盒模型
包含块
垂直外边距合并
块格式化上下文
Filter

CSS 概述

CSS 模型

数值与单位
收集与解析
布局
  盒模型
    块元素
    行内元素

为了完全掌握CSS,除了看了很多书籍和参考MDN之外,还花了很多时间整理出来一张思维导图:

css鸟瞰图

HTML 概述

概述

HTML5 主要强调标签语义性,所以很多标签的样式属性都不推荐使用,而改用CSS代替。
网页主要的展示内容包括:

  1. 文字
  2. 图片
  3. 音频
  4. 视频

所以标签大致可以分类如下:

  1. 文本
  2. 多媒体
  3. 表格
  4. 表单
  5. 布局相关

思维导图

附一张自己整理的思维导图:
html

参考

MDN的HTML

浏览器进程和网页渲染过程

前言

在开始讲之前,相信大家应该听过类似下面的语句:

  1. JS代码在执行的时候会阻塞DOM的渲染
  2. 网页推荐CSS放在后部,而JS放在尾部

这篇博客的目的就是为了讲述浏览器的进程并解释清楚浏览器渲染的过程。

进程与线程的介绍

  1. 进程是系统进行资源分配和调度的一个独立单位;
  2. 线程是进程的一个实体,是CPU调度和分派的基本单位。

浏览器的进程

default

1. 浏览器主线程

  1. 主要负责其它进程的创建和销毁
  2. 浏览器的一些通用操作,如后退、前进、主页、刷新等
  3. 下载资源管理,包括用户请求的网页

2. GPU线程

  1. 把每个网页渲染进程生成的render-tree绘制成网页;
  2. 3D绘制。

3. 第三方插件进程

为每个第三方插件创建一个独立的进程

4. 网页渲染进程

为每个网页创建一个独立的渲染进程;
该进程是多线程,主要有如下的几个常驻线程:
default

  1. 所有线程都可以和JS引擎线程交互,因为JS引擎线程是渲染进程的核心,也是网页交互能力的体现。
  2. JS引擎线程和GUI线程是互斥的,GUI线程会收集JS引擎线程里对DOM的修改,并在恰当的时机传给GPU进程渲染呈现。

4.1 网页渲染过程的伪逻辑

  1. 构建

根据 HTML 结构生成 DOM 树;根据 CSS 生成 CSSOM;将 DOM 和 CSSOM 整合形成 渲染树(RenderTree)

  1. 布局

结合浏览器的一些属性,计算RenderTree的几何信息,包括位置和大小等

  1. 绘制

把RenderTree传给GPU进程,由它渲染绘制到网页上展示

4.2 渲染过程的细节

  1. 执行JS

当遇到<script>标签时,会触发网页的渲染,因为JS有可能需要操作DOM和CSSOM,在执行JS的过程中会阻塞DOM的解析,也就会阻塞网页的渲染。但是如果script标签有async和defer属性的话,加载JS文件的过程是不会阻塞DOM的解析的(执行的时候还是会阻塞)。

  1. 解析CSS

每当解析CSS的时候,会阻塞渲染RenderTree,但是不会阻塞DOM的解析

这就是为什么网页推荐CSS放在头部,JS放在尾部的原因。

浏览器进程的总结

1. 加载和初步渲染

当用户请求访问一个网址的时候,首先由浏览器的主进程DNS解析、建立TCP连接、http/https请求网页资源(包括HTML、CSS、JS等);然后新建一个网页渲染进程,并把网页资源传递给GUI线程和JS引擎线程,由它们创建渲染树;最后交给GPU进程绘成页面。

2. 交互和重新渲染

用户和页面交互过程产生的重绘(repaint)和回流(reflow),就会重新生成渲染树,并由GPU进程更新页面。

浏览器主进程->网页渲染进程->GPU进程

参考链接:

https://juejin.im/post/5a72779c6fb9a01cb64f1d86
https://segmentfault.com/a/1190000012925872
http://imweb.io/topic/58e3bfa845e5c13468f567d5
https://juejin.im/post/5ad43c106fb9a028e25e062b
xiaoyu2er/blog#8

执行上下文

前言

前面的一篇博客《JS运行原理》,已经大致讲解过“执行上下文”。这篇博客计划详细说说这个概念。

定义

我们一般把JS引擎运行JS代码分为两步,

  1. 创建执行上下文
  2. 逐行执行代码

函数声明和函数表达式都属于函数定义阶段,只有在函数被执行阶段,新的执行上下文才会被创建。

执行上下文确定了第二步需要用的变量(或者说是寻找变量的途径),如果在执行代码的时候无法确定变量将会报错ReferenceError,不包括变量为undefined和null的情况。
执行上下文主要包括三个重要属性:

  1. 变量对象
  2. 作用域链
  3. this

提醒一下,for或if语句不会创建新的执行上下文,函数才会创建新的执行上下文

这篇博客涉及到的概念

  1. 执行上下文栈
  2. 提升
  3. 变量对象
  4. 作用域链
  5. 计算this的指向
  6. 暂时性死区

执行上下文栈

JS引擎刚开始读取JS文件的时候,会创建一个全局上下文,并把它放到执行上下文栈里,之后如果遇到需要新创建执行上下文,会把它压到栈顶;直到相关的代码执行完毕并返回结果,该执行上下文会被出栈。执行上下文栈是创建作用域链的重要参考。

提升(Hoisting)

提升是相对于var声明的变量和函数声明的,函数表达式并不会被提升。

// 函数声明形如:
// name是函数名,类似变量名可以被调用
function name([param,[, param,[..., param]]]) {
   [statements]
}

// 函数表达式形如:
// 其中的[name]是函数代名词,只是作为函数体的一个本地变量(意味着外部是不可以调用的),主要用于调用自身
var function_expression = function [name]([param1[, param2[, ..., paramN]]]) {
   statements
};

提升的伪逻辑如下:

  1. 提升var变量,如果存在同名变量(包括同名形参),不进行操作;若不存在,则初始化为undefined
  2. 提升函数声明,提升并覆盖已有的同名变量
(function (a, b) {
    console.log(a, b)
    var a;
    function b() {};
    a = 1;
    console.log(a, b)
})(3, 4)

output:
3 [Function: b]
1 [Function: b]

伪代码转换:
(function (a, b) {
    var a=3,b=4;
    b=function () {}

    console.log(a, b)
    a = 1;
    console.log(a, b)
})(3, 4)

变量对象

全局执行上下文的变量对象有:

  1. 全局对象(浏览器环境中是window,nodejs中是global),允许直接访问属性(比如经常使用的new Date,本身就是Window.Date)
  2. 使用或不使用var定义的变量,差别是不使用var定义的变量可以用delete操作符删除
  3. 函数声明

函数执行上下文的变量对象有:

  1. arguments
  2. 实参
  3. 使用var定义的变量
  4. 函数声明

作用域链

当在本执行上下文中的变量对象中没有找到目标变量的话,就会借助作用域来查找变量。
比如:在函数执行上下文中未使用var定义的变量被调用的时候,因为在变量对象中找不到,所以会顺着作用域链查找下一个执行上下文中的变量对象。

作用域链是根据执行上下文栈来确定的。

特殊作用域:

  1. try-catch语句块,临时在作用域链中添加error对象
  2. with语句块,也会临时在作用域链中添加with的对象
  3. eval语句,如果你间接的使用 eval(),比如通过一个引用来调用它,而不是直接的调用 eval ,从 ECMAScript 5 起,它工作在全局作用域下,而不是局部作用域中

计算this的指向

一旦确定了this的指向,当访问this的某个成员变量或函数,如果不存在不会根据执行上下文栈找到上一个this并查询。

1. 全局执行上下文

this指向全局对象,在浏览器环境下是window,在nodejs下是global

2. 函数执行上下文

  1. 作为对象的方法,即使方法是来自该对象的原型链上的方法,this也指向该对象
  2. 作为构造函数,指向正在构建的新对象
  3. 如果是简单调用,就是没有形如fun()而不是obj.fun(),this指向全局对象,但是在严格模式下,会指向undefined
  4. 箭头函数,与封闭词法上下文的this保持一致

这里的封闭词法上下文没有那么高大上,说白了,箭头函数的执行上下文没有定义this属性,根据执行上下文栈找到上一个执行上下文的this,就是箭头函数中的this。下面看一段代码。

var x = 'windows';
function fun1() {
    var fun2 = () => {
        console.log(this.x)
    }
    fun2()
}
fun1();

var obj = {
    x: 'obj',
    showX: fun1
}
obj.showX()

浏览器非严格模式的output:
windows
obj

nodejs,默认就是严格模式:
undefined
obj

fun2的上一个执行上下文就是fun1,所以fun2的this就是fun1的this。第一次调用是简单调用,所以fun1的this指向全局变量(window);第二次调用是作为对象的方法被调用,所以this指向新对象,而新对象里刚好有x变量。

3. 特殊情况

  1. 使用apply和call传递this指向
  2. 使用bind方法绑定this的指向,只对第一次的绑定生效
  3. DOM事件构造函数里的this指向触发事件的DOM元素
  4. 内联事件处理函数里的this指向监听器所绑定的DOM元素

暂时性死区

只有在es6中的let和const才会发生这种情况。

进入一个执行上下文的时候,
let、const声明的变量和形参定义的变量,都会屏蔽掉前一个执行上下文中的同名变量,
且在声明语句之前是不可以使用该变量,
声明语句之后,若该变量没有被赋值,则默认为undefined。

var x = 0;
function f(x = x) { // A行
    console.log(x)
    console.log(y) // B行
    let y = 1;
}
f()

output:
ReferenceError: x is not defined
ReferenceError: y is not defined

上面的代码里A行中的第一个x是形参定义的变量,它会屏蔽掉全局中的x,所以会报错;B行中的y由于是let定义的变量,所以在声明语句前调用将会报ReferenceError错误。

参考链接

http://yanhaijing.com/javascript/2014/04/29/what-is-the-execution-context-in-javascript/
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/eval
https://www.cnblogs.com/snandy/archive/2011/03/19/1988284.html
http://blog.csdn.net/yangbingbinga/article/details/61424363

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.