├── .prettierrc
├── CSS(3)
├── BEM 命名规范笔记.md
├── CSS 实现等比缩放.md
├── CSS 重排与重绘笔记.md
├── CSS面试题总结.md
├── images
│ ├── bem-block.jpg
│ ├── browser-render.png
│ ├── deep-understand-font-size.png
│ ├── font-size-support.png
│ ├── inline-elem-margin-padding.png
│ ├── rander-reflow.png
│ ├── render-tree.png
│ ├── stacking-and-float-1.png
│ └── stacking-and-float-2.png
├── 七种水平垂直居中方法总结.md
├── 各种像素概念.md
└── 深入理解 font-size 笔记.md
├── HTML(5)
└── HTML5
│ ├── 1px 的解决方案.md
│ ├── HTML5 知识总结.md
│ ├── WebSocket 学习总结.md
│ ├── images
│ ├── border-image-1px-line1.png
│ ├── border-image-1px-line2.png
│ ├── border-image-1px-line4.png
│ ├── cut_img_ps_action.png
│ ├── fake_array_to_array.png
│ ├── h5_ability_model.png
│ ├── h5_datalist.gif
│ ├── h5_location.png
│ ├── h5_location_error.png
│ ├── h5_location_succ.png
│ ├── h5_media_attr.png
│ ├── h5_oninvalid.png
│ ├── h5_upload_drag.gif
│ ├── h5_upload_multiply.gif
│ ├── h5_yuyihua.png
│ └── html_yuyihua.png
│ ├── 像素、分辨率相关概念.md
│ ├── 大厂 H5 开发实战
│ └── 基础页面开发.md
│ └── 探究移动端适配.md
├── JavaScript
├── ES6 笔记
│ ├── ES6 基础.md
│ ├── ES6-异步操作.md
│ └── ES6-面向对象.md
├── JS 基础知识点1.md
├── JS 基础知识点2.md
├── __test__
│ └── bind.test.js
├── images
│ ├── circle_refer.png
│ ├── js-prototype-chain-all.png
│ ├── js-prototype-chain1.png
│ └── regexp_knowledge_map.png
├── 从 JS 高阶函数到柯里化.md
├── 手写系列
│ ├── 手写 Ajax.md
│ ├── 手写 JSONP.md
│ ├── 手写 XXX.md
│ ├── 手写柯里化.md
│ └── 手写防抖节流.md
├── 正则表达式总结.md
└── 零碎知识笔记.md
├── LICENSE
├── Node
├── .gitignore
├── Node.js 包教不包会
│ ├── README.md
│ ├── lesson1
│ │ ├── README.md
│ │ ├── app.js
│ │ └── package.json
│ ├── lesson2
│ │ ├── README.md
│ │ ├── app.js
│ │ ├── imgs
│ │ │ ├── have_query.png
│ │ │ └── no_query.png
│ │ └── package.json
│ ├── lesson3
│ │ ├── README.md
│ │ ├── app.js
│ │ ├── package.json
│ │ └── show.png
│ ├── lesson4
│ │ ├── README.md
│ │ ├── app.js
│ │ ├── cnode_crawler.png
│ │ └── package.json
│ ├── lesson5
│ │ ├── README.md
│ │ ├── app.js
│ │ ├── async_crewler.png
│ │ └── package.json
│ ├── lesson6
│ │ ├── READMD.md
│ │ ├── app.js
│ │ ├── coverage
│ │ │ ├── coverage.json
│ │ │ ├── lcov-report
│ │ │ │ ├── base.css
│ │ │ │ ├── index.html
│ │ │ │ ├── lesson6
│ │ │ │ │ ├── index.html
│ │ │ │ │ └── main.js.html
│ │ │ │ ├── prettify.css
│ │ │ │ ├── prettify.js
│ │ │ │ ├── sort-arrow-sprite.png
│ │ │ │ └── sorter.js
│ │ │ └── lcov.info
│ │ ├── main.js
│ │ ├── package.json
│ │ └── test
│ │ │ └── main.test.js
│ └── lesson7
│ │ ├── package.json
│ │ └── vendor
│ │ ├── index.html
│ │ ├── mocha.css
│ │ ├── mocha.js
│ │ └── tests.js
└── 七天学会 Node.js
│ ├── README.md
│ ├── cat.test
│ └── tests.js
│ ├── cat
│ ├── body.js
│ ├── head.js
│ └── index.js
│ ├── myCopy
│ ├── big-copy.js
│ └── small-copy.js
│ ├── node-echo.test
│ └── tests.js
│ └── node-echo
│ ├── README.md
│ ├── bin
│ └── node-echo.js
│ ├── lib
│ └── echo.js
│ └── package.json
├── README.md
├── React
└── egghead
│ └── start-learning-react
│ ├── JSX编译器.md
│ ├── README.md
│ ├── React.children和React.cloneElement.md
│ ├── imgs
│ ├── React.children_React.cloneElement.gif
│ ├── react-HOC.gif
│ ├── react-event-system.gif
│ ├── react_ref.gif
│ └── reusable_component.gif
│ ├── 事件系统.md
│ ├── 编写可重用的组件.md
│ ├── 获取组件的引用.md
│ └── 高价组件.md
├── UI 设计
├── MBE风格图标设计
│ ├── README.md
│ └── imgs
│ │ ├── 515_cloud_disk_logo.psd
│ │ ├── 515_cloud_disk_logo_background.jpg
│ │ ├── 515_cloud_disk_logo_transparent.png
│ │ ├── 515_cloud_disk_origin.png
│ │ ├── chandle.psd
│ │ ├── chandle_background.jpg
│ │ ├── chandle_origin.jpg
│ │ ├── chandle_transparent.png
│ │ ├── egg_love.psd
│ │ ├── egg_love_background.jpg
│ │ ├── egg_love_origin.png
│ │ ├── egg_love_transparent.png
│ │ ├── ice_cream.psd
│ │ ├── ice_cream2.psd
│ │ ├── ice_cream2_background.jpg
│ │ ├── ice_cream2_origin.jpg
│ │ ├── ice_cream2_transparent.png
│ │ ├── ice_cream_background.jpg
│ │ ├── ice_cream_origin.jpg
│ │ └── ice_cream_transparent.png
└── Photoshop (PS)
│ └── README.md
├── 代码规范
└── Stranard 规范总结.md
├── 性能优化
├── Web 性能优化总结.md
├── images
│ ├── css_backflow.png
│ ├── file_compress.png
│ ├── html_render_process.png
│ ├── png_type.png
│ ├── service_cache.png
│ ├── web-cache.png
│ ├── web_speed_cookie.png
│ └── what_happen_when_url_send.png
└── 浏览器缓存机制总结.md
├── 打包工具
├── gulp
│ ├── README.md
│ ├── gulpfile.js
│ └── package.json
├── rollup
│ └── README.md
└── webpack
│ └── README.md
├── 模仿面试
├── 八股速查.md
└── 封装 request 专题
│ ├── 取消前一个 pending 的,然后发送下一个.js
│ ├── 只发送第一个,后面的直接复用第一个的结果.js
│ └── 控制最大并发量、错误重试.js
├── 浏览器相关
├── images
│ ├── event-loop-callstack.png
│ ├── event-loop-excute-demo1.gif
│ ├── event-loop-task-queue.png
│ ├── localstorage-weixin.png
│ └── url-struct.png
├── 前端路由原理和实现.md
├── 深入了解浏览器存储.md
├── 深入理解 JS 事件循环机制(浏览器篇).md
└── 跨域及其实现原理.md
├── 版本控制
├── Git 相关总结.md
└── images
│ ├── a-picture-to-understand-git.png
│ ├── git-flow-1.png
│ ├── git-flow-2.png
│ ├── git-flow-3.png
│ ├── git-flow-4.png
│ ├── git-merge.png
│ └── modify-pr.png
├── 网络相关
├── HTTP(S) 相关
│ ├── images
│ │ └── tls-four-handshake.png
│ └── 深入探究 TLS 协议.md
├── TCP 相关
│ ├── images
│ │ ├── tcp-four-wave.png
│ │ ├── tcp-message-struct.png
│ │ ├── tcp-three-handshake-fail-in-three.png
│ │ └── tcp-three-handshake.png
│ └── 深入探究 TCP 协议.md
└── Web 安全
│ ├── CSRF 攻击
│ ├── README.md
│ └── imgs
│ │ ├── csrf-attack-yuanli.png
│ │ ├── csrf-double-cookie-test.png
│ │ ├── github-same-site.png
│ │ ├── referer-policy-value.png
│ │ ├── referer-policy.png
│ │ ├── same-site-browser-support-mobile.png
│ │ └── same-site-browser-support-pc.png
│ ├── Cookie 安全
│ ├── README.md
│ └── imgs
│ │ └── github-cookies-secure.png
│ ├── HTML5 安全
│ ├── README.md
│ └── imgs
│ │ ├── h5-postMessage-iframe-secure.gif
│ │ ├── h5-postMessage-iframe.gif
│ │ ├── h5-postMessage-win-open.gif
│ │ └── simple-req.png
│ ├── SQL 注入攻击
│ └── README.md
│ ├── Web 安全总结.md
│ ├── XSS 攻击
│ ├── README.md
│ └── imgs
│ │ ├── browser-csp-value.png
│ │ ├── browser-xss-protectino.png
│ │ └── github-csp-example.png
│ ├── 上传文件攻击
│ └── README.md
│ ├── 密码安全
│ └── README.md
│ └── 点击劫持攻击
│ ├── README.md
│ └── imgs
│ ├── click-hijack.png
│ ├── frame-ancestors.png
│ └── x-frame-options.png
├── 设计模式
└── 前端常用的设计模式.md
└── 读书笔记
├── JS 高级程序设计
├── DOM.md
├── README.md
└── images
│ └── section10
│ └── node_map.png
└── 你不知道的 JS
├── README.md
├── 上卷.md
├── 下卷.md
└── 中卷.md
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "none",
3 | "tabWidth": 2,
4 | "semi": true,
5 | "singleQuote": true
6 | }
7 |
--------------------------------------------------------------------------------
/CSS(3)/BEM 命名规范笔记.md:
--------------------------------------------------------------------------------
1 | - [BEM 规范](#bem-规范)
2 | - [B -- Block](#b----block)
3 | - [E -- Element](#e----element)
4 | - [M -- Modifier](#m----modifier)
5 | - [混合](#混合)
6 | - [命名规则](#命名规则)
7 | - [命名风格](#命名风格)
8 | - [文件结构](#文件结构)
9 | - [如何定位一个块](#如何定位一个块)
10 | - [选择器](#选择器)
11 | - [如何切换到 BEM 样式的 CSS](#如何切换到-bem-样式的-css)
12 | - [如何在已有的项目中使用 BEM](#如何在已有的项目中使用-bem)
13 |
14 | # BEM 规范
15 |
16 | ## B -- Block
17 |
18 | 块。表示有意义的实体,块名称用于描述它的目的。例如:`header`, `container`, `main`, `menu`, `input`。
19 |
20 | 特点:
21 |
22 | - 不能影响其他元素(即不能设置 `margin` 或 `position`)。
23 | - 可以相互嵌套(任意嵌套级别)。
24 |
25 | 示例:
26 |
27 | 
28 |
29 | ```html
30 |
39 | ```
40 |
41 | > 块的位置是任意的,可以在页面或项目之间随意移动,且无需修改 CSS 或 JavaScript 代码。因此,要将块实现为独立的实体。
42 |
43 | ## E -- Element
44 |
45 | 元素。块的一部分,语义上与块相关联,没有独立意义,**不能单独使用**。例如:`menu item`, `list item`, `header title`。
46 |
47 | 结构:`block__element`。
48 |
49 | 特点:
50 |
51 | - 可以相互嵌套(任意嵌套级别)。
52 | - 不能单独使用,没有块就不能使用元素。
53 | - 元素是可选的,并不是所有块都要有元素。
54 |
55 | 示例:
56 |
57 | ```html
58 |
59 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
78 | ```
79 |
80 | ## M -- Modifier
81 |
82 | 修饰符。用于定义块或元素的外观、状态、行为。例如:
83 |
84 | > `disabled, highlighted, checked, fixed, big, yellow`
85 |
86 | 结构:
87 |
88 | - `block_modifier` -- 块 + 修饰符。
89 | - `block__element_modifier` -- 块 + 元素 + 修饰符。
90 | - `block_modifier_value` -- 块 + 修饰符 + 修饰符的值。
91 | - `block__element_modifier_value` -- 块 + 元素 + 修饰符 + 修饰符的值。
92 |
93 | 特点:
94 |
95 | - 修饰符名称与 B 或 E 之间用单线划线 `_` 分隔。
96 | - 不能同时使用多个具有不同值的修饰符。
97 | - 不能单独使用,没有块或元素就不能使用修饰符。
98 |
99 | 示例:
100 |
101 | ```html
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
121 |
122 |
123 |
128 | ```
129 |
130 | 上面的示例中可以看到,元素和修饰符名称的前面带有块的名称,这样做的好处是:[为什么在修饰符和元素名中包含块名](https://en.bem.info/methodology/faq/#why-include-the-block-name-in-modifier-and-element-names)。
131 |
132 | ## 混合
133 |
134 | 特点:
135 |
136 | - 在不复制代码的情况下组合多个实体的行为和样式。
137 | - 基于现有的 UI 组件创建语义上新的 UI 组件。
138 |
139 | 示例:
140 |
141 | ```html
142 |
146 | ```
147 |
148 | ## 命名规则
149 |
150 | `block__elem_mod_value`
151 |
152 | - 名称全小写。
153 | - 如果名称中有多个单词,使用 `-` 分隔。
154 |
155 | 例如:`search-form__input-username`
156 |
157 | ## 命名风格
158 |
159 | 除了上述示例的命名风格外,还有其他几种可供选择:
160 |
161 | - `block__elem--mod--value` -- 双破折号风格
162 | - `blockName-elemName_modName_modVal` -- 小驼峰风格
163 | - `BlockNmae-ElemName_modName_modVal` -- 大驼峰风格
164 | - 自定义风格(要符合 BEM 的规范)
165 |
166 | ## 文件结构
167 |
168 | 文件结构中目录或文件的命名也适用于 BEM 命名。详见:[File structure](https://en.bem.info/methodology/quick-start/#file-structure)
169 |
170 | ## 如何定位一个块
171 |
172 | 块具有独立性,不能影响其他块的定位。下面是定位块的几种方法。
173 |
174 | - 通过混合来定位一个块
175 |
176 | ```html
177 |
178 |
179 |
180 |
181 | ```
182 |
183 | ```css
184 | /* 影响定位的属性写在混合元素 `page__header` 上 */
185 | .page__header {
186 | margin: 20px;
187 | }
188 | ```
189 |
190 | - 添加额外的元素来定位一个快
191 |
192 | ```html
193 |
194 |
195 |
196 |
197 | ```
198 |
199 | ```css
200 | .page__inner {
201 | margin: 0 auto;
202 | width: 960px;
203 | }
204 | ```
205 |
206 | ## 选择器
207 |
208 | BEM 命名中,不能使用**标签选择器**和 **ID 选择器**。所有的元素都可以使用类选择器定位到。
209 |
210 | 对于一些特殊情况,可以使用下面几种选择器(都不推荐使用):
211 |
212 | - 联合标签选择器和类选择器。例如:`button.button_active`。
213 | - 联合类选择器。例如:`.button.button_active`。
214 | - 嵌套选择器。例如:`.button_hovered .button__text`。
215 |
216 | 这几种选择器改变了选择器的权重,增加了耦合度,不利于维护,所以不推荐使用。
217 |
218 | ## 如何切换到 BEM 样式的 CSS
219 |
220 | 在项目中应用以下 BEM 准则:
221 |
222 | - 抛开 DOM 模型,尝试创建块。
223 | - 不要使用 ID 或 标签选择器。
224 | - 减少嵌套选择器的数目。
225 | - 将一个块的 CSS 属性添加到修饰符上,如果它们看起来可能会更改。
226 | - 使用混合。
227 | - 重用块。
228 |
229 | ## 如何在已有的项目中使用 BEM
230 |
231 | - 根据 BEM 的准则创建新的组件,并根据需要修改旧的组件。
232 | - 在 CSS 类名前加私有前缀(例如:`bem-`),以便于和旧代码区分。
233 |
234 | ---
235 |
236 | 本文来源于:[BEM Methodology](https://en.bem.info/methodology/quick-start/),进行总结和概括后,作为笔记记录下来。
237 |
--------------------------------------------------------------------------------
/CSS(3)/CSS 实现等比缩放.md:
--------------------------------------------------------------------------------
1 | - [CSS 实现等比缩放](#css-实现等比缩放)
2 | - [vw 方案](#vw-方案)
3 | - [padding 方案](#padding-方案)
4 | - [padding 方案改进](#padding-方案改进)
5 |
6 | # CSS 实现等比缩放
7 |
8 | 元素的等比缩放(`宽:高 = 固定比例`)是一个常见的需求,也是面试中可能问到的问题。
9 |
10 | 我们最终要实现的效果是:**「实现一个宽度自适应父元素,高度始终为宽度 N % 的元素」**。
11 |
12 | 这里,我们以高是宽的 30% 作为举例。
13 |
14 | ## vw 方案
15 |
16 | CSS 中 `vw` 单位相对于**浏览器视口**宽度,100vw 等于视口宽度的 100%。其中“浏览器视口”指的是浏览器可视区域的大小,也就是 `window.innerWidth / window.innerHeight` 获取到的大小。
17 |
18 | 因此,如果**有一个元素是横向占满空间**,那么想要保持该元素的宽高比,就可以使用 `vw` 单位。如下所示:
19 |
20 | ```html
21 |
27 |
28 | 我的宽占满屏幕,我的高是宽的 30%
29 | ```
30 |
31 | 如果宽度不是占满屏幕,想要两边“留点缝”,可以借助 `calc()` 函数。如下所示:
32 |
33 | ```html
34 |
43 |
44 | 我的左右两边距离屏幕两侧各 20px,我的高是宽的 30%
45 | ```
46 |
47 | 从上面的介绍可以看出,`vw` 方案存在如下局限性:**需要确定元素宽度和浏览器视口宽度的比例**。也就是说,元素宽度和浏览器视口宽度之比,需要是 100%、50%、30% 等等,必须是一个确定的比例才行。
48 |
49 | 由于 `vw` 方案的局限性,无法实现我们最终想要的效果“宽度自适应父元素,高度始终为宽度 N %”,因此这里仅做介绍,下面才是今天的主角 `padding-top / padding-bottom`。
50 |
51 | ## padding 方案
52 |
53 | 要知道,`padding-top / padding-bottom` 设置百分比的值时,是相对于父元素的宽度来计算的。利用这一特点,可以很容易实现我们最终的效果。如下所示:
54 |
55 | ```html
56 |
64 |
65 | 我的高是宽的 30%,使用 padding-top 实现
66 | ```
67 |
68 | 其中,将元素的 `height` 设为 0,然后使用 `padding-top` 来撑开元素的高度,由于 `padding-top` 是根据元素宽度来计算的,所以就实现了等比缩放。
69 |
70 | ## padding 方案改进
71 |
72 | 由于元素 `height` 为 0,高度由 `padding-top` 来撑起,所以该元素有子元素时,就会被“挤到外面”。因此需要用 `position: absolute;` 来解决, 如下所示:
73 |
74 | ```html
75 |
100 |
101 |
106 | ```
107 |
108 | 改进后的方案中,对于等比缩放元素来说,如果想要**改变其宽度**或**在其两边留点缝**,需要再包一层父元素来控制。实现也很简单,这里不再赘述。
109 |
--------------------------------------------------------------------------------
/CSS(3)/CSS 重排与重绘笔记.md:
--------------------------------------------------------------------------------
1 | - [重排与重绘](#重排与重绘)
2 | - [浏览器渲染过程](#浏览器渲染过程)
3 | - [生成渲染树](#生成渲染树)
4 | - [重排](#重排)
5 | - [重绘](#重绘)
6 | - [触发条件](#触发条件)
7 | - [浏览器的优化机制](#浏览器的优化机制)
8 | - [优化点](#优化点)
9 |
10 | # 重排与重绘
11 |
12 | 想要深入了解浏览器的重排或重绘,需要先知道浏览器的工作原理。
13 |
14 | ## 浏览器渲染过程
15 |
16 | 浏览器的渲染流程如下:
17 |
18 | 
19 |
20 | 总结来说,如下:
21 |
22 | - HTML 解析为 DOM 树,CSS 解析为 CSSOM 树,然后 DOM 和 CSSOM 合并为渲染树(Render Tree)
23 | - 布局(Layout):根据渲染树进行布局,得到节点的几何信息(位置、大小)
24 | - 绘制(Painting):根据渲染树和布局得到的几何信息,得到节点的绝对像素
25 | - 显示(Display):将像素发送给 GPU,显示在页面上
26 |
27 | ## 生成渲染树
28 |
29 | 
30 |
31 | 构建渲染树的流程如下:
32 |
33 | - 从 DOM 树的根节点开始遍历所有**可见**节点
34 | - 对于每个可见节点,找到其在 CSSOM 中的对应样式并应用
35 | - 最后,将所有节点和样式组合成渲染树
36 |
37 | 上面提到了可见节点,因此我们需要知道,什么样的算是可见节点。**不可见节点如下**:
38 |
39 | - 不会渲染到页面上的节点。例如 `script`, `meta`, `link` 节点
40 | - 通过 CSS 隐藏的节点。例如:`display: none`
41 |
42 | > 注意:通过 `visibility` 或 `opacity` 隐藏的节点,仍会被添加到渲染树上,所以会在页面上渲染,只不过是人眼不可见而已。
43 |
44 | ## 重排
45 |
46 | 生成了渲染树之后,还需要知道元素在页面上的具体位置,这个计算元素具体位置的阶段就是重排。
47 |
48 | 例如,有以下节点:
49 |
50 | ```html
51 |
54 | ```
55 |
56 | 在重排阶段,会根据视口(viewport)的具体宽度,计算这些节点的位置。如图所示:
57 |
58 | 
59 |
60 | ## 重绘
61 |
62 | 经过了构造渲染树和重排阶段,接下来需要将渲染树上的节点都转换为屏幕上的实际像素,这个阶段就叫重绘。
63 |
64 | ## 触发条件
65 |
66 | 触发重排的条件如下:
67 |
68 | - 页面一开始渲染的时候(无法避免)
69 | - 删除 / 添加可见 DOM 元素
70 | - 浏览器窗口大小变化
71 | - 元素位置变化
72 | - 元素尺寸变化(包括 `margin`, `padding`, `border` 等)
73 | - 元素内容变化。比如:文字变化、图片大小改变
74 | - 激活 CSS 伪类(例如 `:hover`)
75 |
76 | ## 浏览器的优化机制
77 |
78 | 现代的浏览器都很聪明,自身是有一些优化策略的。其中**浏览器会维护一个队列,把所有引起重排、重绘的操作放入这个队列中,等到队列中的操作达到一定的阈值或达到一定的时间间隔,浏览器就会刷新这个队列,批量执行其中的操作,这样就使得多次重排重绘变成了一次重排重绘**。
79 |
80 | 当你使用某些属性时,浏览器会强制刷新维护的队列,并立即进行重排、重绘,因此要尽量避免使用这些属性。这些属性部分如下:
81 |
82 | ```
83 | offsetTop、offsetLeft、offsetWidth、offsetHeight
84 | scrollTop、scrollLeft、scrollWidth、scrollHeight
85 | clientTop、clientLeft、clientWidth、clientHeight
86 | getComputedStyle()、currentStyle()
87 | getBoundingClientRect()、getClientRects()
88 | ```
89 |
90 | 查看所有会引起重排或重绘的属性,点击[这里](https://gist.github.com/paulirish/5d52fb081b3570c81e3a)。
91 |
92 | ## 优化点
93 |
94 | - 将多次修改 CSS 样式,合并为一次修改
95 |
96 | 有以下两种方法:
97 |
98 | - 将样式通过 CSS 的 `class` 一次性添加
99 | - 使用 `cssText`(兼容性较差)
100 |
101 | ```js
102 | var elem = document.getElementById('div');
103 | elem.style.cssText = 'margin: 10px; padding: 5px;';
104 | ```
105 |
106 | - 使用 `transform` 代替 `top`、`bottom`、`left`、`right`
107 | - 使用 `opacity` 代替 `visibility`
108 | - **离线修改** DOM 样式
109 |
110 | 具体步骤是:先将 DOM 隐藏(`display: none;`),然后修改样式,最后在将其显示(`display: block;`)。
111 |
112 | 这种方法只会在隐藏、显示 DOM 的时候触发重排,因此适用于频繁修改样式的操作。
113 |
114 | - 使用**文档片段**(`DocumentFragment`),将频繁的 DOM 操作合并
115 | - 减少使用那些会强制刷新 flush 队列的属性,如果使用不要放在循环中
116 | - 尽量避免 `table` 布局
117 |
118 | 1. HTML 中的 `table` 布局,只要其中的一个元素发生变化,就会引起整个 `table` 的重排、重绘。
119 | 2. 浏览器在渲染 `table` 布局的时候,会花费比同等其他 DOM 元素多好几倍的时间。
120 |
121 | - 动画的的频率不要设置太高
122 | - 将动画涉及的 DOM 元素放入单独的合成层
123 | - 使用 GPU 加速
124 |
--------------------------------------------------------------------------------
/CSS(3)/images/bem-block.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/CSS(3)/images/bem-block.jpg
--------------------------------------------------------------------------------
/CSS(3)/images/browser-render.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/CSS(3)/images/browser-render.png
--------------------------------------------------------------------------------
/CSS(3)/images/deep-understand-font-size.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/CSS(3)/images/deep-understand-font-size.png
--------------------------------------------------------------------------------
/CSS(3)/images/font-size-support.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/CSS(3)/images/font-size-support.png
--------------------------------------------------------------------------------
/CSS(3)/images/inline-elem-margin-padding.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/CSS(3)/images/inline-elem-margin-padding.png
--------------------------------------------------------------------------------
/CSS(3)/images/rander-reflow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/CSS(3)/images/rander-reflow.png
--------------------------------------------------------------------------------
/CSS(3)/images/render-tree.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/CSS(3)/images/render-tree.png
--------------------------------------------------------------------------------
/CSS(3)/images/stacking-and-float-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/CSS(3)/images/stacking-and-float-1.png
--------------------------------------------------------------------------------
/CSS(3)/images/stacking-and-float-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/CSS(3)/images/stacking-and-float-2.png
--------------------------------------------------------------------------------
/CSS(3)/各种像素概念.md:
--------------------------------------------------------------------------------
1 | # 物理像素、逻辑像素、CSS 像素、PPI、DPR 是什么?
2 |
3 | - 物理像素(Physical Pixel):是指显示设备(如屏幕)的实际像素,它是设备硬件上的一个点。
4 |
5 | - 逻辑像素(Logical Pixel):也称为**设备独立像素**(Device Independent Pixel,DIP),是指应用程序中使用的虚拟像素,是对物理像素的一个抽象。在不同的设备上,同一个逻辑像素的尺寸可能会有所不同。
6 |
7 | - CSS 像素(CSS Pixel):是指网页上的一个虚拟像素,是 Web 开发中使用的一个单位。**CSS 像素通常等于逻辑像素**。
8 |
9 | 当页面缩放比不为 1 时(`CSS 像素:逻辑像素 != 1:1`)。比如当页面放大 200%,则 1 个 CSS 像素等于 4 个逻辑(形如:`口 -> 田`)。
10 |
11 | - PPI(Pixels Per Inch):是指**每英寸的物理像素数量**,用来度量显示设备的像素密度。像素密度越高,屏幕显示的图像越清晰。
12 |
13 | 计算:`PPI = 对角线物理像素数 / 屏幕尺寸(对角线英寸数)`。
14 |
15 | 例如:iPhone12 mini,物理像素数:1080x2040,屏幕尺寸 5.4 英寸,则:
16 |
17 | `PPI = Math.sqrt(1080², 2040²) / 5.4`
18 |
19 | - DPR,设备像素比(Device Pixel Ratio):是指设备上**物理像素的数量与逻辑像素的数量之比**。
20 |
21 | 例如,如果一个设备的物理像素是 320×240,而逻辑像素是 160×120,则该设备的 DPR 为 2。
22 |
23 | 在 JS 中可以通过 `window.devicePixelRatio` 读取/设置(不推荐手动设置) DPR 值。
24 |
--------------------------------------------------------------------------------
/CSS(3)/深入理解 font-size 笔记.md:
--------------------------------------------------------------------------------
1 | - [深入理解 font-size](#深入理解-font-size)
2 | - [字体相关概念](#字体相关概念)
3 | - [基线](#基线)
4 | - [em-box](#em-box)
5 | - [content-area](#content-area)
6 | - [半行距](#半行距)
7 | - [x-height](#x-height)
8 |
9 | # 深入理解 font-size
10 |
11 | ## 字体相关概念
12 |
13 | 
14 |
15 | ### 基线
16 |
17 | 基线就是图中 x 字母下面的线,所有的字符都是基于这条线定位的。`vertical-align: baseline;` 就是指的这条线。
18 |
19 | ### em-box
20 |
21 | `1em` 和 `font-size` 的大小一致,而 `em-box` 就是 `1em` 的大小。例如,`font-size: 20px` 那么 `em-box` 就是 `20px`。
22 |
23 | ### content-area
24 |
25 | 这个区域没有在图中标出,`content-area` 翻译成中文就是内容区域,默认情况下,和 `line-height` 一样高。
26 |
27 | 给**内联元素**设置一个背景,这个背景就可以看作是内容区域。
28 |
29 | `content-area` 与 `font-size` 和 `font-family` 有关。因此,有时我们设置了相同的 `font-size`,但是实际显示大小却不同,这就是因为 `font-family` 不同导致的。
30 |
31 | ### 半行距
32 |
33 | 行距的计算是:`content-area` - `em-box`,也就是 `line-height` - `font-size`。半行距就是行距的一半。
34 |
35 | ### x-height
36 |
37 | `x-height` 顾名思义就是 `x` 字符高度的意思。这个概念通常没什么用,但是有助于我们理解一些概念,例如:`vertical-align: middle;` 这个属性指的就是,与 `x-height` 高度的一半对齐。
38 |
--------------------------------------------------------------------------------
/HTML(5)/HTML5/WebSocket 学习总结.md:
--------------------------------------------------------------------------------
1 | - [WebSocket](#websocket)
2 | - [WebSocket 基本用法](#websocket-基本用法)
3 |
4 | # WebSocket
5 |
6 | 在 H5 WebSocket 出现之前,为了实现消息推送,最常用的方式有三种:**轮询**、**长轮询**、**iframe 流**。它们的优缺点以及适用场景如下:
7 |
8 | - 轮询:客户端定时向服务器发送 Ajax 请求,服务器接到请求后马上返回响应信息并关闭连接。
9 |
10 | 优点:后端程序编写比较容易。
11 | 缺点:请求大半是无用的、浪费带宽和服务器资源。
12 | 实例:适于小型应用。
13 |
14 | - 长轮询:客户端向服务器发送 Ajax 请求,服务器接到请求后 hold 住连接,直到有新消息或超时才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求。
15 |
16 | 优点:在无消息的情况下不会频繁的请求。
17 | 缺点:服务器 hold 连接会消耗资源。
18 | 实例:WebQQ、Hi 网页版、Facebook IM。
19 |
20 | - iframe 流:在页面里嵌入一个隐蔵 iframe,将这个隐蔵 iframe 的 src 属性设为对一个长连接的请求,服务器端就能源源不断地往客户端输入数据。
21 |
22 | 优点:消息即时到达、不发无用请求。
23 | 缺点:服务器维护一个长连接会增加开销、无法准确知道连接状态。
24 | 实例:Gmail 聊天。
25 |
26 | - WebSocket:客户端通过 `new WebSocket()` 发起通信连接,之后不论客户端还是服务端,均可向对方发送报文。
27 |
28 | 优点:双向通信实时性强、可发送二进制文件、较少的控制开销。
29 | 缺点:浏览器兼容较差、不支持断开重连。
30 | 实例:网络游戏、支付。
31 |
32 | ## WebSocket 基本用法
33 |
34 | 首先在前台创建一个 WebSocket 实例。
35 |
36 | `index.html`
37 |
38 | ```html
39 |
55 | ```
56 |
57 | 然后,后台接收和向前台发送消息。后台创建 WebSocket 服务之前,需要安装 ws 包:
58 |
59 | ```bash
60 | npm install ws -S
61 | ```
62 |
63 | 找个目录,新建 server.js 编写后台服务代码:
64 |
65 | ```js
66 | var express = require('express');
67 | var app = express();
68 |
69 | app.use(express.static(__dirname));
70 | // 监听 3000 端口
71 | app.listen(3000);
72 |
73 | // 创建 WebScoket 服务
74 | var Server = require('ws').Server;
75 | // 设置服务器端口号
76 | var ws = new Server({ port: 9999 });
77 |
78 | // 监听客户端和服务端的连接
79 | ws.on('connection', function (socket) {
80 | // 客户端发来消息
81 | socket.on('message', function (msg) {
82 | console.log('客户端发来的消息:', msg);
83 | socket.send('服务端返回的消息:', '你好,客户端');
84 | });
85 | });
86 | ```
87 |
88 | 在 `server.js` 文件的目录下执行指令:`node ./server.js`,然后将客户端 `index.html` 在浏览器里运行,打开控制台即可看见效果。
89 |
90 | ---
91 |
92 | 参考文章:
93 |
94 | - [Web 实时推送技术的总结](https://juejin.im/post/5c20e5766fb9a049b13e387b)
95 | - [WebSocket 是时候展现你优秀的一面了](https://juejin.im/post/5bc7f6b96fb9a05d3447eef8)
96 |
--------------------------------------------------------------------------------
/HTML(5)/HTML5/images/border-image-1px-line1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/HTML(5)/HTML5/images/border-image-1px-line1.png
--------------------------------------------------------------------------------
/HTML(5)/HTML5/images/border-image-1px-line2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/HTML(5)/HTML5/images/border-image-1px-line2.png
--------------------------------------------------------------------------------
/HTML(5)/HTML5/images/border-image-1px-line4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/HTML(5)/HTML5/images/border-image-1px-line4.png
--------------------------------------------------------------------------------
/HTML(5)/HTML5/images/cut_img_ps_action.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/HTML(5)/HTML5/images/cut_img_ps_action.png
--------------------------------------------------------------------------------
/HTML(5)/HTML5/images/fake_array_to_array.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/HTML(5)/HTML5/images/fake_array_to_array.png
--------------------------------------------------------------------------------
/HTML(5)/HTML5/images/h5_ability_model.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/HTML(5)/HTML5/images/h5_ability_model.png
--------------------------------------------------------------------------------
/HTML(5)/HTML5/images/h5_datalist.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/HTML(5)/HTML5/images/h5_datalist.gif
--------------------------------------------------------------------------------
/HTML(5)/HTML5/images/h5_location.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/HTML(5)/HTML5/images/h5_location.png
--------------------------------------------------------------------------------
/HTML(5)/HTML5/images/h5_location_error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/HTML(5)/HTML5/images/h5_location_error.png
--------------------------------------------------------------------------------
/HTML(5)/HTML5/images/h5_location_succ.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/HTML(5)/HTML5/images/h5_location_succ.png
--------------------------------------------------------------------------------
/HTML(5)/HTML5/images/h5_media_attr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/HTML(5)/HTML5/images/h5_media_attr.png
--------------------------------------------------------------------------------
/HTML(5)/HTML5/images/h5_oninvalid.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/HTML(5)/HTML5/images/h5_oninvalid.png
--------------------------------------------------------------------------------
/HTML(5)/HTML5/images/h5_upload_drag.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/HTML(5)/HTML5/images/h5_upload_drag.gif
--------------------------------------------------------------------------------
/HTML(5)/HTML5/images/h5_upload_multiply.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/HTML(5)/HTML5/images/h5_upload_multiply.gif
--------------------------------------------------------------------------------
/HTML(5)/HTML5/images/h5_yuyihua.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/HTML(5)/HTML5/images/h5_yuyihua.png
--------------------------------------------------------------------------------
/HTML(5)/HTML5/images/html_yuyihua.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/HTML(5)/HTML5/images/html_yuyihua.png
--------------------------------------------------------------------------------
/HTML(5)/HTML5/像素、分辨率相关概念.md:
--------------------------------------------------------------------------------
1 | - [设备像素比 DPR](#设备像素比-dpr)
2 | - [PPI 和 DPI](#ppi-和-dpi)
3 |
4 | ## 设备像素比 DPR
5 |
6 | DRP 在 JS 中可以通过 `window.devicePixelRatio` 获取,指的是:物理像素和设备独立像素的比例。
7 |
8 | > 其中,物理像素(physical pixel)是:**设备能控制显示的最小单位**。
9 | >
10 | > 我们常说的**分辨率**指的就是**物理像素**,比如 iPhone 6S Plus 的分辨率是 `1920x1080`,表示横向有 1920 个物理像素,竖向有 1080 个物理像素。
11 | >
12 | > 设备独立像素(又称设备无关像素 Device Independent Pixels,DIP 或 DP)是:**与设备无关,用于逻辑上衡量长度的单位,由底层系统的程序使用,最后会转成物理像素**。
13 | >
14 | > 所以它只是一个虚拟像素单位,那么为什么会有这种虚拟像素的产生呢?
15 | >
16 | > 举个例子,iPhone 3GS 和 iPhone 4/4s 的尺寸都是 3.5 寸,但 iPhone 3GS 的分辨率是 320x480,iPhone 4/4s 的分辨率是 640x960,这也就是意味着同样长度的屏幕,iPhone 3GS 有 320 个物理像素,iPhone 4/4s 有 640 个物理像素。
17 | >
18 | > 如果我们按照真实的物理像素进行布局,比如说我们按照 320 物理像素进行布局,到了 640 物理像素的手机上就会有一半的空白,为了避免这种问题,就产生了虚拟像素单位。
19 | >
20 | > 我们统一 iPhone 3GS 和 iPhone 4/4s 都是 320 个虚拟像素,只是在 iPhone 3GS 上,最终 1 个虚拟像素换算成 1 个物理像素,在 iPhone 4s 中,1 个虚拟像素最终换算成 2 个物理像素。
21 | >
22 | > 至于 1 个虚拟像素被换算成几个物理像素,这个数值我们称之为设备像素比(DPR)。
23 | >
24 | > 作者:冴羽
25 | > 链接:https://www.zhihu.com/question/26653449/answer/541637971
26 |
27 | 我们平时写程序时,使用的 px 值就是设备独立像素,最终一个虚拟像素被转换成几个物理像素,由底层的图形系统决定。
28 |
29 | ## PPI 和 DPI
30 |
31 | PPI(Pixels Per Inch):每英寸的像素数。是像素密度(pixel density)度量单位。
32 | DPI(Dots Per Inch):每英寸的点数。是打印机、鼠标等设备分辨率的度量单位。
33 |
34 | 计算公式:
35 |
36 | ```
37 | √ 水平像素数² + 垂直像素数²
38 | PPI = ----------------------------
39 | 尺寸
40 | ```
41 |
42 | 例如,iPhone 6 的 PPI 计算如下:
43 |
44 | ```
45 | √ 1334² + 750²
46 | PPI = ---------------- = 325.6 ≈ 326
47 | 4.7
48 | ```
49 |
--------------------------------------------------------------------------------
/HTML(5)/HTML5/大厂 H5 开发实战/基础页面开发.md:
--------------------------------------------------------------------------------
1 | - [基础页面开发](#基础页面开发)
2 | - [H5 开发能力参考模型](#h5-开发能力参考模型)
3 | - [分 5 步完成设计稿的页面开发](#分-5-步完成设计稿的页面开发)
4 | - [设计稿审查](#设计稿审查)
5 | - [编写页面的骨骼框架](#编写页面的骨骼框架)
6 | - [填充网页的内容](#填充网页的内容)
7 | - [润色](#润色)
8 | - [兼容性测试](#兼容性测试)
9 |
10 | # 基础页面开发
11 |
12 | ## H5 开发能力参考模型
13 |
14 | 
15 |
16 | > 基础页面开发 -> 响应式页面开发 -> 滑屏应用开发 -> 动画效果开发 -> 游戏开发
17 |
18 | ## 分 5 步完成设计稿的页面开发
19 |
20 | ### 设计稿审查
21 |
22 | - 确定设计的开发友好性(是否有还原成本高 或 无法还原的地方)
23 | - 确定特殊元素是否有合理的边界处理(文案超出容器的边界怎么办)
24 | - 确定页面的整体结构框架(Layout)
25 | - 确定跨页面可复用的组件(Site Component)
26 | - 确定当前页面可复用的组件(Page Component)
27 | - ...
28 |
29 | ### 编写页面的骨骼框架
30 |
31 | - 重置盒模型
32 |
33 | ```css
34 | *,
35 | *::before,
36 | *::after {
37 | -webkit-box-sizing: border-box;
38 | -moz-box-sizing: border-box;
39 | box-sizing: border-box;
40 | }
41 | ```
42 |
43 | - 布局
44 |
45 | - 普通文档流布局
46 | - 浮动布局(Float)
47 | - 绝对布局(Absolute)
48 | - 弹性布局(Flex)
49 | - 网格布局(Grid)
50 |
51 | > 根据场景选择合适的布局方案
52 |
53 | - 语义化
54 |
55 | 进行语义化的目的:1、便于阅读和维护 2、SEO 友好
56 |
57 | 
58 |
59 | ### 填充网页的内容
60 |
61 | > 切图。下面是几种 PS 切图技巧
62 |
63 | 1、Extract Assets 资源生成器
64 |
65 | 通过 `Extract Assets`,你可以:
66 |
67 | - 将 PSD 中的图层或图层组导出为一个或多个的图像资源
68 | - 导出 JPG、PNG、GIF 或 SVG 类型的图像资源
69 | - 为所有图像资源设置 1x、2x 等多分辨率的版本
70 | - 预览每个图像资源
71 | - 轻松将图像资源导出到你首选的文件夹中
72 | - 确保每当 PSD 发生变化时,被导出的资源都能得到自动更新
73 |
74 | **使用方法:**
75 |
76 | - 首选项 -> 增效工具 -> 启用生成器
77 | - 打开 PSD 文件后,选择:文件 -> 生成 -> 图像资源
78 | - 更改图层或图层组的名字为适当的文件格式扩展名
79 |
80 | > 这样资源生成器默认会在 PSD 的同一层目录下创建 assets 文件夹,并根据你修改的扩展名生成图片。
81 |
82 | 2、Extract Assets 进阶
83 |
84 | - 从一个图层或图层组中生成多个资源,请用半角逗号分隔该图层或图层组的名称
85 |
86 | ```text
87 | music.png, music.jpg, music_on.png
88 | ```
89 |
90 | - 图像资源保存到子文件夹中
91 |
92 | ```text
93 | 子文件夹/music.png
94 | ```
95 |
96 | - 指定图像品质和大小参数
97 |
98 | > 默认情况下, JPEG 资源会以 90% 品质生成, PNG 资源会以 32 位图像生成, GIF 资源则会以基本 Alpha 透明度生成
99 |
100 | 我们可以通过以下办法设置 JPEG 资源的参数:
101 |
102 | - 添加所需的输出品质作为资源名称的后缀:`jpg(1-10)` 或者 `jpg(1-100%)`,例如:
103 |
104 | ```text
105 | music.jpg5
106 | music.jpg50%
107 | ```
108 |
109 | - 添加所需的输出图片大小(相对大小或者支持的单位:px, in, cm 和 mm)作为该资源名称前缀。Photoshop 会相应的缩放图像,例如:
110 |
111 | ```text
112 | 200% music.png
113 | 240x300 music.png
114 | ```
115 |
116 | > 注意:前缀和资源名称之间要添加一个空格字符
117 |
118 | - 为资源指定默认位置
119 |
120 | 可以为生成的资源指定文件的默认位置,例如想将图层导出到 `hi-res/` (存放二倍图,并加上 `@2x` 的后缀),`lo-res/` 存放缩小 50% 的图标,可进行如下配置:
121 |
122 | A. 创建空图层
123 | B. 更改空图层的名称为 `default hi-res/@2x + 50% lo-res/`
124 |
125 | 3、PS 动作切图
126 |
127 | 用 `Extract Assets` 切图存在一个问题,它只能切画布范围内的资源,超出画布的部分会直接被裁减掉。如果想切完整的图片该怎么办?建议用原始的「导出图层」的方式来切图,步骤如下:
128 |
129 | - 右键点击图层或图层组
130 | - 选择转换为智能对象
131 | - 编辑内容
132 | - 导出图片
133 |
134 | > 为了避免重复劳动,可以用 PS 录制一个切图动作。
135 |
136 | 
137 |
138 | ### 润色
139 |
140 | > CSS 编写起来比较容易,但是维护起来却不那么简单。这里重点说一下 CSS 的命名问题!
141 |
142 | **BEM 命名法:**
143 |
144 | BEM 的意思就模块(Block)、元素(Element)、修饰符(Modifier),使用这种命名方式可以让 CSS 的类名变得有实际意义且能自我解释,具有更高的开发友好性。
145 |
146 | ```text
147 | Block - 模块,名字的单词之间用 `-` 符号连接。
148 | Element - 元素,模块中的子元素,用 `__` 符号连接。
149 | Modifier - 修饰符,表示父元素或子元素的其他形态,用 `--` 符号连接。
150 | ```
151 |
152 | 例如:
153 |
154 | 普通命名:
155 |
156 | ```html
157 |
158 |
159 |
160 |
161 |
162 |
164 |
165 | ```
166 |
167 | 使用 BEM 命名:
168 |
169 | ```html
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 | ```
179 |
180 | 通常会配合 CSS 预处理语言,如 Scss、Less 等。来一定程度上解决手写冗长的命名。
181 |
182 | ```css
183 | .search-bar {
184 | &__input {
185 | }
186 | &__button {
187 | }
188 | }
189 | ```
190 |
191 | 如果 HTML 嵌套层级越来越深,类名也会越来越长。那么如何解决 BEM 命名冗长的问题?
192 |
193 | **姓氏命名法:**
194 |
195 | 姓氏命名法中 Block 就是「姓」,Element 就是「名字」,而 Modifier 就是这个人的状态。例如:「张三--很有钱」
196 |
197 | 例如:
198 |
199 | 使用 BEM 命名:
200 |
201 | ```html
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
去围观
210 |
211 |
212 |
213 | ```
214 |
215 | 使用姓氏命名进行优化:
216 |
217 | ```html
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
去围观
226 |
227 |
228 |
229 | ```
230 |
231 | 进一步优化:
232 |
233 | ```html
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
去围观
243 |
244 |
245 |
246 | ```
247 |
248 | **小结:**
249 |
250 | - className 的命名应该尽量精短、明确,以英文单词命名,且全部字母为小写,避免意义不明的缩写。
251 | - 单词之间用下划线 `_` 或 `-` 连接均可,但必须统一。
252 | - 学习 BEM 的思想,参考使用姓氏命名法规范。
253 | - 定义样式模块,提高代码的可复用性。
254 |
255 | ### 兼容性测试
256 |
257 | 对于兼容性测试,主要关注两点:
258 |
259 | - 在不同浏览器、不同分辨率下网页的显示情况(HTML / CSS 兼容)。
260 | - 在不同浏览器中网页能否正常使用(JS 兼容)。
261 |
262 | **兼容性的基本原则是:**
263 |
264 | > 渐进增强与平稳退化
265 |
266 | **如果遇到兼容性问题,可以按如下步骤处理:**
267 |
268 | - 确认触发的场景:什么浏览器,什么版本,什么情况下触发的问题,做到稳定复现。
269 | - 找出问题原因:是什么问题导致的,具体表现如何?
270 | - 确定解决办法:参考现成的解决方案,如哪些属性不能使用以及相应的 Hack 处理。
271 | - **收集兼容性处理方法,积累成文档**。
272 |
273 | ---
274 |
275 | 本文来源于:[掘金小册 -- 大厂 H5 开发实战手册](https://juejin.im/book/5a7bfe595188257a7349b52a),进行总结和概括后,作为笔记记录下来。
276 |
--------------------------------------------------------------------------------
/JavaScript/ES6 笔记/ES6-异步操作.md:
--------------------------------------------------------------------------------
1 | - [ES6 中的异步操作](#es6-中的异步操作)
2 | - [Promise](#promise)
3 | - [async / await](#async--await)
4 |
5 | # ES6 中的异步操作
6 |
7 | ## Promise
8 |
9 | `Promise` 是异步编程的一种解决方案,简单来说就是对异步操作的封装。其中保存着未来才会结束的事件(通常是一个异步操作)的状态。
10 |
11 | > ```javascript
12 | > new Promise((resolve, reject) => { // ... });
13 | > ```
14 | >
15 | > 异步操作执行成功会调用 resolve,并将 promise 的状态改为 fulfilled(完成)
16 | > 异步操作执行失败会调用 reject,并将 promise 的状态改为 rejected(失败)
17 |
18 | 例子:
19 |
20 | ```javascript
21 | // 延迟一秒后输出结果
22 | const promise = new Promise((resolve, reject) => {
23 | setTimeout(() => {
24 | resolve('foo');
25 | }, 1000);
26 | });
27 |
28 | // then 中接收的两个函数分别就是 resolve 和 reject
29 | promise.then(
30 | (res) => {
31 | console.log(res); // => foo
32 | },
33 | (err) => {
34 | console.log(err);
35 | }
36 | );
37 | ```
38 |
39 | 其实 `Jquery` 库中的 `Ajax` 本身就是一个 `Promise`:
40 |
41 | ```javascript
42 | $.ajax(// ...).then(res => {
43 | console.log(res);
44 | }, err => {
45 | console.log(err);
46 | });
47 | ```
48 |
49 | 和上面 `例子` 中的 `promise` 写法一模一样。
50 |
51 | > 注意:`Promise` 内部的报错不会被抛出,需要手动捕获(`try-catch` 包裹异步操作代码)
52 |
53 | - `promise.all`
54 |
55 | 将多个异步操作包成一个 Promise 实例。
56 |
57 | > ```javascript
58 | > const p = Promise.all([p1, p2, p3]);
59 | > ```
60 |
61 | 例子:
62 |
63 | ```javascript
64 | // p1,p2,p3 是异步操作
65 | Promise.all([p1, p2, p3]).then(
66 | (res) => {
67 | console.log(res);
68 | },
69 | (err) => {
70 | console.log(err);
71 | }
72 | );
73 |
74 | // 当所有的异步操作都成功时,才会执行 `resolve` 函数;只要有一个异步操作失败,就会执行 `reject` 函数。
75 | ```
76 |
77 | ## async / await
78 |
79 | 使得异步操作变得更接近同步操作。实际上它们分别就是代替 `Generator` 和 `yield` 的语法糖。`async` 的返回值是 `Promise` 对象,进一步的说,`async` 完全可以看作多个异步操作,包装成的一个 `Promise` 对象。
80 |
81 | > 使用 async 关键字,表名函数内部有异步操作。调用该函数会立即返回一个 Promise 对象。
82 | > 使用 await 关键字,代码会等待 “被 await 修饰的异步操作” 执行完毕后,再继续向下执行。
83 |
84 | 例子:
85 |
86 | ```javascript
87 | // 间隔指定时间,且异步操作成功后,输出 value
88 | let timeout = (value, time) => {
89 | return new Promise((resolve) => {
90 | setTimeout(() => {
91 | resolve(value);
92 | }, time);
93 | }).then((res) => {
94 | console.log(res);
95 | });
96 | };
97 |
98 | async function asyncPrint(value, time) {
99 | console.log('start');
100 | await timeout(value, time);
101 | console.log('end');
102 | }
103 |
104 | asyncPrint('hello world', 1000);
105 | // 加上 await 关键字:
106 | // 立即输出:start,间隔 1 秒后,输出 hello world 和 end
107 |
108 | // 不加 await 关键字:
109 | // 立即输出 start 和 end,间隔 1 秒后,输出 hello world
110 | ```
111 |
112 | > 注意:使用 `await` 时,必须有 `async` 关键字
113 |
--------------------------------------------------------------------------------
/JavaScript/ES6 笔记/ES6-面向对象.md:
--------------------------------------------------------------------------------
1 | - [ES6 中的面向对象和模块化](#es6-中的面向对象和模块化)
2 | - [1、class、extends、constructor、super](#1classextendsconstructorsuper)
3 | - [2、ES5 中的伪面向对象](#2es5-中的伪面向对象)
4 | - [3、模块化](#3模块化)
5 | - [3.1、导入](#31导入)
6 | - [3.2、导出](#32导出)
7 | - [3.3、简单的 webpack 配置](#33简单的-webpack-配置)
8 |
9 | # ES6 中的面向对象和模块化
10 |
11 | ## 1、class、extends、constructor、super
12 |
13 | ```javascript
14 | // 父类 Person
15 | class Person {
16 | constructor(name, age) {
17 | this.name = name;
18 | this.age = age;
19 | }
20 |
21 | showName() {
22 | console.log(this.name);
23 | }
24 |
25 | showAge() {
26 | console.log(this.age);
27 | }
28 | }
29 |
30 | // 子类 Worker
31 | class Worker extends Person {
32 | constructor(name, age, job) {
33 | super(name, age);
34 |
35 | this.job = job;
36 | }
37 |
38 | showJob() {
39 | console.log(this.job);
40 | }
41 | }
42 |
43 | let coder = new Worker('liuyibo', 20, 'coding');
44 |
45 | coder.showName(); // => liuyibo
46 | coder.showAge(); // => 20
47 | coder.showJob(); // => coding
48 | ```
49 |
50 | ## 2、ES5 中的伪面向对象
51 |
52 | ES5 中没有专门声明类的方法,所以使用构造函数来充当(伪)类
53 |
54 | ```javascript
55 | // 父类 Person
56 | function Person(name, age) {
57 | // 既是构造函数又充当类
58 | this.name = name;
59 | this.age = age;
60 | }
61 |
62 | Person.prototype.showName = function () {
63 | console.log(this.name);
64 | };
65 | Person.prototype.showAge = function () {
66 | console.log(this.age);
67 | };
68 |
69 | // 子类 Worker
70 | function Worker(name, age, job) {
71 | Person.call(this, name, age); // 通过修改 this 指向来继承属性
72 |
73 | this.job = job; // 添加子类自己的属性
74 | }
75 |
76 | Worker.prototype = new Person(); // 将子类的原型指向父类的实例,从而继承父类的方法(子类的 constructor 也指向了父类)
77 | Worker.constructor = Worker; // 修正子类的 constructor
78 |
79 | // 添加子类自己的方法
80 | Worker.prototype.showJob = function () {
81 | console.log(this.job);
82 | };
83 |
84 | let coder = new Worker('liuyibo', 20, 'coding');
85 |
86 | coder.showName(); // => liuyibo
87 | coder.showAge(); // => 20
88 | coder.showJob(); // => coding
89 | ```
90 |
91 | ## 3、模块化
92 |
93 | ### 3.1、导入
94 |
95 | ```javascript
96 | import 'xxx'; // 只导入不引用
97 | import mod from 'xxx'; // 只导入 default 成员,并起一个别名 mod
98 | import * as mod from 'xxx'; // 导入所有成员,并起一个总的别名 mod
99 | import { mod1, mod2 as name2 } from 'xxx'; // 导入名称为 mod1,mod2 的成员,并给 mod2 起一个别名 name2
100 | let promise = import('xxx'); // 异步导入,返回一个 Promise 对象(导入之后不会立即使用,而是按需使用)
101 | ```
102 |
103 | ### 3.2、导出
104 |
105 | ```javascript
106 | export default xxx;
107 |
108 | export let a = 10;
109 | export {name: 'zhangsan', age: 20}
110 | export function () { // ... }
111 | export class Person { // ... }
112 | ```
113 |
114 | ### 3.3、简单的 webpack 配置
115 |
116 | 目前所有的浏览器还不支持模块化(简单来说就是:`import` 这样的语法浏览器还不认识),所有需要进行打包编译。
117 |
118 | Demo:
119 |
120 | ```javascript
121 | // mod.js
122 | export let a = 1;
123 | export let b = 2;
124 | ```
125 |
126 | ```javascript
127 | // index.js
128 | import * as mod from './mod'; // 文件名后缀可以省略(默认是 JS 文件)
129 |
130 | console.log(mod.a, mod.b); // 1 2
131 | ```
132 |
133 | ```node
134 | // webpack.config.js
135 | const path = require('path');
136 |
137 | module.exports = {
138 | mode: "production", // 生产模式(development:开发模式)
139 | entry: "./src/index.js",
140 | output: [
141 | path: path.resolve(__dirname, "build"), // 使用 node 的一个包,将路径转换为绝对路径,避免一些问题
142 | filename: "bundle.js"
143 | ]
144 | };
145 | ```
146 |
147 | 在项目目录下的命令行中执行:`webpack`
148 |
149 | 程序会自动寻找 `webpack.config.js` 文件,然后按照文件里的配置,进行打包编译。
150 |
--------------------------------------------------------------------------------
/JavaScript/JS 基础知识点2.md:
--------------------------------------------------------------------------------
1 | - [JavaScript 基础 2](#javascript-基础-2)
2 | - [new](#new)
3 | - [call、apply 和 bind 的区别](#callapply-和-bind-的区别)
4 | - [上下文 和 执行上下文](#上下文-和-执行上下文)
5 | - [继承](#继承)
6 | - [防抖和节流](#防抖和节流)
7 | - [防抖的应用场景](#防抖的应用场景)
8 | - [节流的应用场景](#节流的应用场景)
9 | - [模块化](#模块化)
10 | - [Promise](#promise)
11 | - [Generator](#generator)
12 | - [async/await](#asyncawait)
13 | - [实现并发请求](#实现并发请求)
14 | - [并发数量控制](#并发数量控制)
15 | - [Map、FlapMap 和 Reduce](#mapflapmap-和-reduce)
16 | - [Proxy](#proxy)
17 | - [为什么 `0.1 + 0.2 != 0.3`](#为什么-01--02--03)
18 | - [正则表达式](#正则表达式)
19 |
20 | # JavaScript 基础 2
21 |
22 | > - 这部分的知识总结,大部分来源于对掘金小册 [《前端面试之道》](https://juejin.im/book/5bdc715fe51d454e755f75ef) 的学习。
23 | > - 文中提到的:实现一个 xxx,表示实现其原理,并不代表将其功能完全实现。
24 |
25 | 涉及的知识点参考了下图中的 JS 部分:
26 |
27 | 
28 |
29 | ## new
30 |
31 | // TODO
32 |
33 | ## call、apply 和 bind 的区别
34 |
35 | // TODO
36 |
37 | ## 上下文 和 执行上下文
38 |
39 | > 上下文始终是 this 的值
40 | > 执行上下文是不同于上下文的另一个概念
41 |
42 | [https://github.com/mqyqingfeng/Blog/issues/4](https://github.com/mqyqingfeng/Blog/issues/4)
43 |
44 | ## 继承
45 |
46 | // TODO
47 |
48 | ## 防抖和节流
49 |
50 | [手写防抖节流.md](./手写系列/手写防抖节流.md)
51 |
52 | ### 防抖的应用场景
53 |
54 | - 每次 `resize` 触发的事件
55 | - 文本输入验证(连续输入文字后发送 Ajax 验证请求,验证一次就好了)
56 | - 监听页面滚动事件,只让事件执行一次(`scroll`)
57 |
58 | ### 节流的应用场景
59 |
60 | - DOM 元素的拖拽功能实现(`mousemove`)
61 | - 射击游戏的 `mousedown / keydown` 事件(单位时间内只能发射一颗子弹)
62 | - 计算鼠标移动距离(`mousemove`)
63 | - Canvas 模拟画板功能(`mousemove`)
64 | - 搜索联想功能(`keyup`)
65 | - 监听页面滚动事件,让事件以固定时间间隔执行(`scroll`)
66 |
67 | ## 模块化
68 |
69 | // TODO
70 |
71 | ## Promise
72 |
73 | // TODO
74 |
75 | ## Generator
76 |
77 | // TODO
78 |
79 | ## async/await
80 |
81 | 在 ES8 引入,是 Generator 和 Promise 的语法糖。
82 |
83 | 底层实现如下,举例:
84 |
85 | ```js
86 | async function log() {}
87 |
88 | async function print() {
89 | return await log();
90 | }
91 | ```
92 |
93 | 会被 Babel 编译成([在线尝试](https://babeljs.io/repl#?browsers=chrome%3E%3D54&build=&builtIns=usage&corejs=3.21&spec=false&loose=false&code_lz=IYZwngdgxgBAZgV2gFwJYHsIwDboOYAUAlDAN4C-AUJaJLIihlgA4BOqEyxZlAkKwFNkCVlmAB3YKmQ58xANyVyQA&debug=false&forceAllTransforms=false&modules=false&shippedProposals=false&circleciRepo=&evaluate=true&fileSize=false&timeTravel=false&sourceType=module&lineWrap=false&presets=env&prettier=true&targets=&version=7.21.3&externalPlugins=&assumptions=%7B%7D)):
94 |
95 | ```js
96 | function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
97 | try {
98 | var info = gen[key](arg);
99 | var value = info.value;
100 | } catch (error) {
101 | reject(error);
102 | return;
103 | }
104 |
105 | if (info.done) {
106 | resolve(value);
107 | } else {
108 | Promise.resolve(value).then(_next, _throw);
109 | }
110 | }
111 |
112 | function _asyncToGenerator(fn) {
113 | return function () {
114 | var self = this,
115 | args = arguments;
116 |
117 | return new Promise(function (resolve, reject) {
118 | var gen = fn.apply(self, args);
119 |
120 | function _next(value) {
121 | asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'next', value);
122 | }
123 | function _throw(err) {
124 | asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'throw', err);
125 | }
126 |
127 | _next(undefined);
128 | });
129 | };
130 | }
131 |
132 | function log() {
133 | return _log.apply(this, arguments);
134 | }
135 |
136 | function _log() {
137 | _log = _asyncToGenerator(function* () {});
138 | return _log.apply(this, arguments);
139 | }
140 |
141 | function print() {
142 | return _print.apply(this, arguments);
143 | }
144 |
145 | function _print() {
146 | _print = _asyncToGenerator(function* () {
147 | return yield log();
148 | });
149 | return _print.apply(this, arguments);
150 | }
151 | ```
152 |
153 | 由此可见,编译后:一个 `await` 对应一个 `yield`,没什么特别。主要是 `async`,只要函数被 `async` 修饰,整个函数体就会被包裹到一个特殊的函数中,这个特殊的函数形如(不准确,只是方便理解)下面的格式:
154 |
155 | ```js
156 | new Promise(function (resolve, reject) {
157 | function* () {
158 | // 函数体的所有代码在这
159 | }
160 | })
161 | ```
162 |
163 | ### 实现并发请求
164 |
165 | 请求组装到数组中,然后根据业务具体情况使用 `Promise.all` / `Promise.allSettled` / `Promise.race` 来并发执行数组中的请求。
166 |
167 | ### 并发数量控制
168 |
169 | 实现“错误重试、并发控制”,代码如下:[控制最大并发量、错误重试.js](../模仿面试/封装%20request%20专题/控制最大并发量、错误重试.js)。
170 |
171 | ## Map、FlapMap 和 Reduce
172 |
173 | // TODO
174 |
175 | ## Proxy
176 |
177 | // TODO
178 |
179 | ## 为什么 `0.1 + 0.2 != 0.3`
180 |
181 | // TODO
182 |
183 | ## 正则表达式
184 |
185 | [正则表达式](https://github.com/liuyib/study-note/tree/master/JavaScript/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F)
186 |
--------------------------------------------------------------------------------
/JavaScript/__test__/bind.test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 这些测试用例总结自 MDN:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
3 | */
4 |
5 | // =========================
6 | // case: this 只会被改变一次
7 | // =========================
8 |
9 | function foo(...args) {
10 | return [this, ...args];
11 | }
12 |
13 | const boundLog = foo.bind('a', 1, 2);
14 | // this 一旦被 bind 绑定,就不会再被改变了,这里不会指向 b
15 | const boundLog2 = boundLog.bind('b', 3, 4);
16 |
17 | console.assert(boundLog2(5, 6).join('') === 'a123456');
18 |
19 | // ================================
20 | // case: 使用 new 后,正确的原型指向
21 | // ================================
22 |
23 | class Foo {
24 | constructor(...args) {
25 | console.assert(new.target === Foo);
26 | console.assert(args.join('') === '1234');
27 | }
28 | }
29 |
30 | const BoundFoo = Foo.bind(null, 1, 2);
31 | const boundFoo = new BoundFoo(3, 4);
32 |
33 | console.assert(boundFoo instanceof Foo);
34 | console.assert(boundFoo instanceof BoundFoo);
35 |
36 | // ===============================================
37 | // case: bind 后的函数,prototype 丢了,不能再被继承
38 | // ===============================================
39 |
40 | try {
41 | class Derived extends class {}.bind(null) {}
42 | // 不应该执行到这一步
43 | console.assert(1 === 2);
44 | } catch (error) {
45 | console.assert(
46 | String(error) ===
47 | 'TypeError: Class extends value does not have valid prototype property undefined'
48 | );
49 | }
50 |
51 | // ====================
52 | // case: 正确的原型指向
53 | // ====================
54 |
55 | class Foo2 {}
56 | const BoundFoo2 = Foo2.bind(null, 1, 2);
57 |
58 | console.assert(JSON.stringify(Foo2.prototype) === '{}');
59 | console.assert(JSON.stringify(BoundFoo2.prototype) === undefined);
60 | console.assert(new Foo2() instanceof Foo2);
61 | console.assert(new Foo2() instanceof BoundFoo2);
62 | console.assert(new BoundFoo2() instanceof Foo2);
63 | console.assert(new BoundFoo2() instanceof BoundFoo2);
64 |
65 | // ===================
66 | // case: 正确的 name
67 | // ===================
68 |
69 | function Foo3() {}
70 | const BoundFoo3 = Foo3.bind(null);
71 |
72 | console.assert(BoundFoo3.name === 'bound Foo3');
73 |
74 | // ====================
75 | // case: 正确的接收参数
76 | // ====================
77 |
78 | function list(...args) {
79 | return args;
80 | }
81 |
82 | function addArguments(arg1, arg2) {
83 | return arg1 + arg2;
84 | }
85 |
86 | console.assert(list(1, 2, 3).join('') === '123');
87 | console.assert(addArguments(1, 2) === 3);
88 |
89 | const leadingThirtySevenList = list.bind(null, 37);
90 | const addThirtySeven = addArguments.bind(null, 37);
91 |
92 | console.assert(leadingThirtySevenList().join('') === '37');
93 | console.assert(leadingThirtySevenList(1, 2, 3).join('') === '37123');
94 | console.assert(addThirtySeven(5) === 42);
95 | // 10 将会被忽略,因为 bind 前的函数 addArguments 只接收两个参数
96 | console.assert(addThirtySeven(5, 10) === 42);
97 |
98 | // =========================================================
99 | // case: 绑定类时,当前类上的所有静态方法将丢失,但继承父类的不会丢
100 | // =========================================================
101 |
102 | class Base {
103 | static baseProp = 'base';
104 | }
105 |
106 | class Derived extends Base {
107 | static derivedProp = 'derived';
108 | }
109 |
110 | const BoundDerived = Derived.bind(null);
111 |
112 | console.assert(BoundDerived.baseProp === 'base');
113 | console.assert(BoundDerived.derivedProp === undefined);
114 | console.assert(new BoundDerived() instanceof Derived);
115 |
--------------------------------------------------------------------------------
/JavaScript/images/circle_refer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/JavaScript/images/circle_refer.png
--------------------------------------------------------------------------------
/JavaScript/images/js-prototype-chain-all.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/JavaScript/images/js-prototype-chain-all.png
--------------------------------------------------------------------------------
/JavaScript/images/js-prototype-chain1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/JavaScript/images/js-prototype-chain1.png
--------------------------------------------------------------------------------
/JavaScript/images/regexp_knowledge_map.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/JavaScript/images/regexp_knowledge_map.png
--------------------------------------------------------------------------------
/JavaScript/从 JS 高阶函数到柯里化.md:
--------------------------------------------------------------------------------
1 | - [从 JS 高阶函数到函数柯里化](#从-js-高阶函数到函数柯里化)
2 | - [函数柯里化](#函数柯里化)
3 | - [高阶函数实战](#高阶函数实战)
4 |
5 | # 从 JS 高阶函数到函数柯里化
6 |
7 | 所谓高阶函数,只要满足一下条件之一即可:
8 |
9 | - 函数作为参数
10 | - 返回值为函数
11 |
12 | 我们平时一直在用高阶函数,只不过可能没有了解过这个概念。JS 中内置的高阶函数有很多,例如:`forEach`、`filter`、`reduce` 等等。
13 |
14 | 为了更好的理解,这里举两个例子:
15 |
16 | 1)函数作为参数
17 |
18 | ```js
19 | function calc(a, b, fn) {
20 | return fn(a) + fn(b);
21 | }
22 |
23 | function multi(a) {
24 | return a * a;
25 | }
26 |
27 | calc(3, 4, multi); // => 25
28 | ```
29 |
30 | 2)返回值为函数
31 |
32 | ```js
33 | function isType(type) {
34 | return function (obj) {
35 | return Object.prototype.toString.call(obj) === '[object ' + type + ']';
36 | };
37 | }
38 |
39 | isType('String')('liuyib'); // => true
40 | ```
41 |
42 | ## 函数柯里化
43 |
44 | 在这里介绍函数柯里化,是因为柯里化函数中运用了很多高阶函数。
45 |
46 | **柯里化(Currying)是一种技术,用于把多个参数的函数转换为一系列使用单个参数的函数**。
47 |
48 | 柯里化函数在执行时,并不会立即求值,而是通过闭包把参数保存起来,当参数的数量可以满足函数执行时(可能是**参数为空时**,也可能是**参数全部接收后**),就开始执行函数了。
49 |
50 | 举例如下:
51 |
52 | 1)参数为空时,执行柯里化函数
53 |
54 | 场景:统计每天的销售额,到月底计算一次销售总额。
55 |
56 | ```js
57 | // 柯里化函数
58 | function currying(fn) {
59 | // 这个变量会保持在内存中(通过闭包)
60 | var args = [];
61 |
62 | return function () {
63 | // 不传变量的时候,执行 fn
64 | if (arguments.length === 0) {
65 | return fn(args);
66 | } else {
67 | // 传入变量时,将变量收集起来
68 | Array.prototype.push.apply(args, arguments);
69 | }
70 | };
71 | }
72 |
73 | // 实际用于计算的函数
74 | function total(arr) {
75 | var sum = 0;
76 | arr.forEach((a) => {
77 | sum += a;
78 | });
79 | return sum;
80 | }
81 |
82 | var sellAmount = currying(total);
83 | sellAmount(100);
84 | sellAmount(200);
85 | sellAmount(300);
86 | sellAmount(); // => 600
87 | ```
88 |
89 | 2)参数全部接收后,执行柯里化函数
90 |
91 | ```js
92 | function currying(fn, args) {
93 | return function () {
94 | // 收集上次递归传来的参数,形如 currying(fn, [1, 2, ...])
95 | var _args = args || [];
96 | // 收集 fn 的参数,形如 fn(1, 2, ...)
97 | Array.prototype.push.apply(_args, arguments);
98 |
99 | // 递归出口(收集的参数达到了指定数量)
100 | // fn.length 就是函数的参数个数
101 | if (_args.length === fn.length) {
102 | return fn.apply(this, _args);
103 | } else {
104 | // 把收集的参数递归向下传递
105 | return currying(fn, _args);
106 | }
107 | };
108 | }
109 |
110 | // 实际用于计算的函数
111 | function add(a, b, c, d) {
112 | return a + b + c + d;
113 | }
114 |
115 | // 将 add 函数柯里化
116 | var sum = currying(add);
117 | sum(1)(2)(3)(4); // => 10
118 | sum(1, 2)(3)(4); // => 10
119 | sum(1, 2, 3)(4); // => 10
120 | sum(1, 2, 3, 4); // => 10
121 |
122 | // 或者
123 |
124 | var sum2 = currying(add, [1]);
125 | sum2(2)(3)(4); // => 10
126 |
127 | var sum3 = currying(add, [1, 2]);
128 | sum3(3)(4); // => 10
129 |
130 | var sum4 = currying(add, [1, 2, 3]);
131 | sum4(4); // => 10
132 |
133 | var sum5 = currying(add, [1, 2, 3, 4]);
134 | sum5(); // => 10
135 | ```
136 |
137 | 更近一步,就是处理含有“占位符”的情况,例如 [lodash 的柯里化函数](https://www.lodashjs.com/docs/lodash.curry):
138 |
139 | ```js
140 | var abc = function (a, b, c) {
141 | return [a, b, c];
142 | };
143 |
144 | var curried = _.curry(abc);
145 |
146 | // 其中 _ 表示占位符
147 | curried(1)(_, 3)(2); // => [1, 2, 3]
148 | ```
149 |
150 | ## 高阶函数实战
151 |
152 | - 函数节流
153 |
154 | ```js
155 | function throttle(fn, wait) {
156 | var isRunning = false;
157 |
158 | return function () {
159 | if (isRunning) return;
160 |
161 | isRunning = true;
162 | fn.apply(this, arguments);
163 |
164 | setTimeout(function () {
165 | isRunning = false;
166 | }, wait);
167 | };
168 | }
169 |
170 | window.onscroll = throttle(function () {
171 | console.log('execute');
172 | }, 300);
173 | ```
174 |
175 | - `...`
176 |
--------------------------------------------------------------------------------
/JavaScript/手写系列/手写 Ajax.md:
--------------------------------------------------------------------------------
1 | - [手写 Ajax](#手写-ajax)
2 | - [初识 XMLHttpRequest](#初识-xmlhttprequest)
3 | - [封装代码](#封装代码)
4 | - [改进参数](#改进参数)
5 | - [添加请求头](#添加请求头)
6 | - [使用 ES6 简化代码](#使用-es6-简化代码)
7 | - [使用 Promise](#使用-promise)
8 | - [本文总结](#本文总结)
9 |
10 | # 手写 Ajax
11 |
12 | ## 初识 XMLHttpRequest
13 |
14 | 要发送一个 Ajax 请求,需要经过四个步骤:**初始化、连接、发送、接收**。
15 |
16 | 使用举例如下,使用 `XMLHttpRequest` 对象:
17 |
18 | ```js
19 | // 1. 初始化
20 | var xhr = new XMLHttpRequest();
21 |
22 | // 2. 连接
23 | xhr.open('GET', url, true);
24 |
25 | // 3. 发送
26 | xhr.send('hello world');
27 |
28 | // 4. 接收
29 | xhr.onreadystatechange = function (e) {
30 | var status = xhr.status;
31 |
32 | if (xhr.readyState == 4) {
33 | if ((status >= 200 && status < 300) || status == 304) {
34 | fnSucc(xhr.responseText);
35 | } else {
36 | fnFail(xhr);
37 | }
38 | }
39 | };
40 | ```
41 |
42 | > 其中,`xhr.send()` 是异步的,所以放在第三步或第四步都行。
43 |
44 | 如果需要兼容 IE 和低版本的 Edge,需要改造如下代码:
45 |
46 | ```js
47 | // 初始化
48 | var xhr = null;
49 |
50 | if (window.XMLHttpRequest) {
51 | xhr = new XMLHttpRequest();
52 | } else {
53 | xhr = new ActiveXObject('Microsoft.XMLHTTP');
54 | }
55 | ```
56 |
57 | 下文的代码示例中,不考虑兼容 IE。
58 |
59 | ## 封装代码
60 |
61 | 上面的代码段是可以复用的,因此我们用函数将其封装起来,并提供一些可配置的参数:
62 |
63 | ```js
64 | var ajax = function (method, url, body, fnSucc, fnFail) {
65 | var xhr = new XMLHttpRequest();
66 |
67 | xhr.open(method, url, true);
68 | xhr.send(body);
69 | xhr.onreadystatechange = function (e) {
70 | var status = xhr.status;
71 |
72 | if (xhr.readyState == 4) {
73 | if ((status >= 200 && status < 300) || status == 304) {
74 | fnSucc(xhr.response);
75 | } else {
76 | fnFail(xhr);
77 | }
78 | }
79 | };
80 | };
81 | ```
82 |
83 | 使用如下:
84 |
85 | ```js
86 | ajax(
87 | 'GET',
88 | '/api.json',
89 | '',
90 | // 成功回调
91 | function (res) {
92 | console.log('res', res);
93 | },
94 | // 失败回调
95 | function (e) {
96 | console.log('e', e);
97 | }
98 | );
99 | ```
100 |
101 | ## 改进参数
102 |
103 | 上面封装的函数中,我们很难知道每个参数的含义(需要借助**编辑器+函数注释** 或 **查看源码**),因此下面是对参数的改进:
104 |
105 | ```js
106 | var ajax = function (options) {
107 | var method = options.method;
108 | var url = options.url;
109 | var body = options.body;
110 | var fnSucc = options.fnSucc;
111 | var fnFail = options.fnFail;
112 |
113 | var xhr = new XMLHttpRequest();
114 |
115 | xhr.open(method, url, true);
116 | xhr.send(body);
117 | xhr.onreadystatechange = function (e) {
118 | var status = xhr.status;
119 |
120 | if (xhr.readyState == 4) {
121 | if ((status >= 200 && status < 300) || status == 304) {
122 | fnSucc(xhr.response);
123 | } else {
124 | fnFail(xhr);
125 | }
126 | }
127 | };
128 | };
129 | ```
130 |
131 | 使用如下:
132 |
133 | ```js
134 | ajax({
135 | method: 'GET',
136 | url: '/api.json',
137 | body: '',
138 | fnSucc: function (res) {
139 | console.log('res', res);
140 | },
141 | fnFail: function (e) {
142 | console.log('e', e);
143 | }
144 | });
145 | ```
146 |
147 | ## 添加请求头
148 |
149 | ```diff
150 | var ajax = function (options) {
151 | var method = options.method;
152 | var url = options.url;
153 | var body = options.body;
154 | + var headers = options.headers;
155 | var fnSucc = options.fnSucc;
156 | var fnFail = options.fnFail;
157 |
158 | var xhr = new XMLHttpRequest();
159 |
160 | xhr.open(method, url, true);
161 |
162 | + for (var key in headers) {
163 | + var value = headers[key];
164 | + xhr.setRequestHeader(key, value);
165 | + }
166 |
167 | xhr.send(body);
168 | xhr.onreadystatechange = function (e) {
169 | var status = xhr.status;
170 |
171 | if (xhr.readyState == 4) {
172 | if ((status >= 200 && status < 300) || status == 304) {
173 | fnSucc(xhr.response);
174 | } else {
175 | fnFail(xhr);
176 | }
177 | }
178 | };
179 | };
180 | ```
181 |
182 | 使用如下:
183 |
184 | ```js
185 | ajax({
186 | method: 'GET',
187 | url: '/api.json',
188 | body: '',
189 | headers: {
190 | 'Content-Type': 'multipart/form-data;'
191 | },
192 | fnSucc: function (res) {
193 | console.log('res', res);
194 | },
195 | fnFail: function (e) {
196 | console.log('e', e);
197 | }
198 | });
199 | ```
200 |
201 | ## 使用 ES6 简化代码
202 |
203 | ```js
204 | var ajax = function ({ method, url, body, headers, fnSucc, fnFail }) {
205 | var xhr = new XMLHttpRequest();
206 |
207 | xhr.open(method, url, true);
208 |
209 | for (var key in headers) {
210 | var value = headers[key];
211 | xhr.setRequestHeader(key, value);
212 | }
213 |
214 | xhr.send(body);
215 | xhr.onreadystatechange = function (e) {
216 | var status = xhr.status;
217 |
218 | if (xhr.readyState == 4) {
219 | if ((status >= 200 && status < 300) || status == 304) {
220 | fnSucc(xhr.response);
221 | } else {
222 | fnFail(xhr);
223 | }
224 | }
225 | };
226 | };
227 | ```
228 |
229 | ## 使用 Promise
230 |
231 | ```js
232 | const ajax = function ({ url, method, data, headers }) {
233 | return new Promise((resolve, reject) => {
234 | const xhr = new XMLHttpRequest();
235 |
236 | xhr.open(method, url, true);
237 |
238 | for (const key in headers) {
239 | const value = headers[key];
240 | xhr.setRequestHeader(key, value);
241 | }
242 |
243 | xhr.send(data);
244 | xhr.onreadystatechange = function () {
245 | const state = xhr.readyState;
246 | const status = xhr.status;
247 |
248 | if (state == 4) {
249 | if ((status >= 200 && status < 300) || status == 304) {
250 | resolve(xhr.response);
251 | } else {
252 | reject(xhr);
253 | }
254 | }
255 | };
256 | });
257 | };
258 | ```
259 |
260 | 使用如下:
261 |
262 | ```js
263 | ajax({
264 | url: '/api.json',
265 | method: 'GET',
266 | data: { name: 'liuyib', age: 22 },
267 | headers: {
268 | 'Content-Type': 'text/plain'
269 | }
270 | })
271 | .then((res) => {
272 | console.log('res', res);
273 | })
274 | .catch((err) => {
275 | console.log('err', err);
276 | });
277 | ```
278 |
279 | 使用了 `Promise` 后,就不用将回调函数放入参数,直接使用 `Promise` 的链式语法 `then`、`catch` 即可。
280 |
281 | ## 本文总结
282 |
283 | 以上代码在健壮性、兼容性、易用性等方面,还存在着很大的问题,不能用于生产环境,仅供学习参考使用。在正式的生产环境中,还是得用现有的 Ajax 库,例如:axios。
284 |
--------------------------------------------------------------------------------
/JavaScript/手写系列/手写 JSONP.md:
--------------------------------------------------------------------------------
1 | - [手写 JSONP](#手写-jsonp)
2 | - [初识 JSONP](#初识-jsonp)
3 | - [封装 JSONP](#封装-jsonp)
4 |
5 | # 手写 JSONP
6 |
7 | JSONP 是解决浏览器跨域问题的一种方案,虽然现在已很少使用,但是由于其兼容性好,在一些比较老的项目中仍可能会使用。因此,有必要学习其原理。
8 |
9 | ## 初识 JSONP
10 |
11 | 为了演示效果,我们利用 Express 框架搭建一个简易的 Web 服务器:
12 |
13 | ```js
14 | const express = require('express');
15 | const app = express();
16 |
17 | app.get('/jsonp', function (req, res) {
18 | const { data, callback } = req.query;
19 |
20 | // data 是前端传给后端的数据
21 | console.log('data', data);
22 |
23 | // 后端通过字符串,将数据传给前端
24 | res.send(`${callback}('hello world')`);
25 | res.end();
26 | });
27 | ```
28 |
29 | 前端使用 JSONP 跨域:
30 |
31 | ```html
32 |
37 |
38 | ```
39 |
40 | > 为了便于讲述原理,这里只给出了最简单的 JSONP 用法。
41 |
42 | 其原理就是:将数据放入 URL 的查询参数中(`data=123`),然后将回调函数名称(`callback=show`)传给后端,后端将**回调函数名称**和**响应数据**拼接成字符串,返回给前端。最后,前端解析接收到的数据,回调函数得以执行。
43 |
44 | ## 封装 JSONP
45 |
46 | 为了便于使用,我们利用 `Promise` 封装一个 JSONP 函数,来实现下面的使用方式:
47 |
48 | ```js
49 | jsonp({
50 | url: 'http://localhost:8080/jsonp',
51 | params: { name: '文一', age: '22' }
52 | })
53 | .then((res) => {
54 | console.log(res);
55 | })
56 | .catch((err) => {
57 | console.log(err);
58 | });
59 | ```
60 |
61 | 封装代码如下:
62 |
63 | ```js
64 | function jsonp({ url, params }) {
65 | return new Promise((resolve, reject) => {
66 | const arr = [];
67 |
68 | // 将数据拼接成 URL 查询参数
69 | for (const key in params) {
70 | const value = params[key];
71 | arr.push(`${key}=${value}`);
72 | }
73 |
74 | const callback = `jsonp_${Date.now()}`;
75 | const script = document.createElement('script');
76 | script.src = `${url}?${arr.join('&')}&callback=${callback}`;
77 |
78 | // 回调函数的名称不重要,主要用于触发 resolve 或 reject 逻辑
79 | window[callback] = (res) => {
80 | delete window[callback];
81 | document.body.removeChild(script);
82 |
83 | if (res) {
84 | resolve(res);
85 | } else {
86 | reject('没有接收到参数');
87 | }
88 | };
89 |
90 | // 发送请求
91 | document.body.appendChild(script);
92 | });
93 | }
94 | ```
95 |
96 | 进一步的,需要处理跨域请求失败时的情况:
97 |
98 | ```diff
99 | function jsonp({ url, params }) {
100 | return new Promise((resolve, reject) => {
101 | const callback = `jsonp_${Date.now()}`;
102 | const arr = [];
103 |
104 | // 将数据拼接成 URL 查询参数
105 | for (const key in params) {
106 | const value = params[key];
107 | arr.push(`${key}=${value}`);
108 | }
109 |
110 | const _url = `${url}?${arr.join("&")}&callback=${callback}`;
111 | const script = document.createElement("script");
112 | script.src = _url;
113 |
114 | // 回调函数的名称不重要,主要用于触发 resolve 或 reject 逻辑
115 | window[callback] = (res) => {
116 | delete window[callback];
117 | document.body.removeChild(script);
118 |
119 | if (res) {
120 | resolve(res);
121 | } else {
122 | reject("没有接收到参数");
123 | }
124 | };
125 |
126 | + script.addEventListener('error', () => {
127 | + delete window[callback];
128 | + document.body.removeChild(script);
129 | +
130 | + reject('JSONP 跨域请求失败');
131 | + }, false);
132 |
133 | // 发送请求
134 | document.body.appendChild(script);
135 | });
136 | }
137 | ```
138 |
--------------------------------------------------------------------------------
/JavaScript/手写系列/手写 XXX.md:
--------------------------------------------------------------------------------
1 | - [手写 instanceof](#手写-instanceof)
2 | - [手写 new](#手写-new)
3 | - [手写 call/apply/bind](#手写-callapplybind)
4 |
5 | ## 手写 instanceof
6 |
7 | `instanceof` 的原理很简单:**沿左边变量的原型链向上查找,如果能找到右边的变量,就返回 `true`,否则返回 `false`**。
8 |
9 | 因此按照该原理,实现代码如下:
10 |
11 | ```js
12 | function instanceof(a, b) {
13 | var left = a.__proto__;
14 | var right = b.prototype;
15 |
16 | while (true) {
17 | if (left === null) return false;
18 | if (left === right) return true;
19 |
20 | left = left.__proto__;
21 | }
22 | }
23 | ```
24 |
25 | ## 手写 new
26 |
27 | ```js
28 | function _new(constructor, ...args) {
29 | const instance = Object.create(constructor.prototype);
30 | const result = constructor.apply(instance, args);
31 |
32 | return typeof result === 'object' || result === null ? result : instance;
33 | }
34 | ```
35 |
36 | ## 手写 call/apply/bind
37 |
38 | `call`:
39 |
40 | ```js
41 | Function.prototype.call = function (context) {
42 | context = context ? Object(context) : window;
43 | context.fn = this;
44 |
45 | var args = [];
46 | // 注意循环从 1 开始,否则会把 context 包含进去
47 | for (var i = 1; i < arguments.length; i++) {
48 | args.push('args[' + i + ']');
49 | }
50 |
51 | var fn = new Function('ctx,args', `return ctx.fn(${args.join(',')})`);
52 | var result = fn(context, arguments);
53 |
54 | delete context.fn;
55 |
56 | return result;
57 | };
58 | ```
59 |
60 | `apply`:
61 |
62 | ```js
63 | Function.prototype.apply = function (context, arr) {
64 | context = context ? Object(context) : window;
65 | context.fn = this;
66 |
67 | if (!arr) {
68 | return context.fn();
69 | }
70 |
71 | var args = [];
72 | for (let i = 0; i < arr.length; i++) {
73 | args.push('args[' + i + ']');
74 | }
75 |
76 | var fn = new Function('ctx,args', `return ctx.fn(${args.join(',')})`);
77 | var result = fn(context, arr);
78 |
79 | delete context.fn;
80 |
81 | return result;
82 | };
83 | ```
84 |
85 | `bind`:
86 |
87 | https://github.com/zloirock/core-js/blob/master/packages/core-js/internals/function-bind.js
88 |
89 | > 这里直接使用 ES6 语法,省去不重要的细节。
90 |
91 | ```js
92 | Function.prototype.bind = function (context, ...args1) {
93 | // 处理异常
94 | if (typeof this !== 'function') {
95 | throw new TypeError('error');
96 | }
97 |
98 | const F = this;
99 | const P = F.prototype;
100 |
101 | const bound = function (...args2) {
102 | const args = args1.concat(args2);
103 |
104 | // 处理 new 和无 new 两种调用
105 | return this instanceof bound ? new F(...args) : F.apply(context, args);
106 | };
107 |
108 | // FIXME: MDN 上提到“However, because a bound function does not have the prototype property”,
109 | // 但是 core-js 和网上所有文章中实现的 bind 都与 MDN 的这句话不符,并且在浏览器中测试可知 MDN 是对的,
110 | // 经过我的尝试,目前用 JS 模拟实现 bind 貌似没法完美兼容原生 bind 的所有表现,可见测试用例:../__test__/bind.test.js
111 | // 原生的 bind 可以通过上面所有测试用例,但是网上所有模拟实现的 bind(包括 MDN 推荐的 Polyfill: core-js)都没法完全通过
112 | // 处理原型链(有问题,和原生 bind 不一致)
113 | if (P) {
114 | bound.prototype = P;
115 | }
116 |
117 | // 处理 bind 后的函数名
118 | Object.defineProperty(bound, 'name', {
119 | value: 'bound ' + F.name
120 | });
121 |
122 | return bound;
123 | };
124 | ```
125 |
126 | 测试用例:[`../__test__/bind.test.js`](../__test__/bind.test.js)(完全参考 [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) 总结的)
127 |
128 | 参考资料:
129 |
130 | - call/apply: https://github.com/sisterAn/JavaScript-Algorithms/issues/78
131 | - bind: https://github.com/yygmind/blog/issues/23
132 |
--------------------------------------------------------------------------------
/JavaScript/手写系列/手写柯里化.md:
--------------------------------------------------------------------------------
1 | - [手写柯里化](#手写柯里化)
2 | - [简易版(不支持占位符,ES6 代码)](#简易版不支持占位符es6-代码)
3 | - [进阶版(支持占位符,ES6 代码)](#进阶版支持占位符es6-代码)
4 |
5 | # 手写柯里化
6 |
7 | ## 简易版(不支持占位符,ES6 代码)
8 |
9 | ```js
10 | function curry(fn) {
11 | return function curried(...args) {
12 | if (args.length >= fn.length) {
13 | return fn.apply(this, args);
14 | }
15 |
16 | return function (...args2) {
17 | return curried.apply(this, args.concat(args2));
18 | };
19 | };
20 | }
21 |
22 | // 测试:
23 |
24 | const fn = curry(function (a, b, c) {
25 | console.log([a, b, c]);
26 | });
27 |
28 | fn('a', 'b', 'c'); // ['a', 'b', 'c']
29 | fn('a', 'b')('c'); // ['a', 'b', 'c']
30 | fn('a')('b')('c'); // ['a', 'b', 'c']
31 | fn('a')('b', 'c'); // ['a', 'b', 'c']
32 | ```
33 |
34 | ## 进阶版(支持占位符,ES6 代码)
35 |
36 | ```js
37 | function curry(fn, placeholder = curry.placeholder) {
38 | return function curried(...args) {
39 | if (args.length >= fn.length && !args.includes(placeholder)) {
40 | return fn.apply(this, args);
41 | } else {
42 | return function (...args2) {
43 | const newArgs = args
44 | .map((arg) =>
45 | arg === placeholder && args2.length ? args2.shift() : arg
46 | )
47 | .concat(args2);
48 |
49 | return curried.apply(this, newArgs);
50 | };
51 | }
52 | };
53 | }
54 |
55 | curry.placeholder = Symbol();
56 |
57 | // 测试:
58 | const _ = {};
59 | const log = function (a, b, c, d, e) {
60 | console.log([a, b, c, d, e]);
61 | };
62 | const fn = curry(log, _);
63 |
64 | fn(1, 2, 3, 4, 5); // [ 1, 2, 3, 4, 5 ]
65 | fn(_, 2, 3, 4, 5)(1); // [ 1, 2, 3, 4, 5 ]
66 | fn(1, _, 3, 4, 5)(2); // [ 1, 2, 3, 4, 5 ]
67 | fn(1, _, 3)(_, 4)(2)(5); // [ 1, 2, 3, 4, 5 ]
68 | fn(1, _, _, 4)(_, 3)(2)(5); // [ 1, 2, 3, 4, 5 ]
69 | fn(_, 2)(_, _, 4)(1)(3)(5); // [ 1, 2, 3, 4, 5 ]
70 | ```
71 |
--------------------------------------------------------------------------------
/JavaScript/手写系列/手写防抖节流.md:
--------------------------------------------------------------------------------
1 | ## 防抖
2 |
3 | https://github.com/lodash/lodash/blob/4.17.15/lodash.js#L10304-L10427
4 |
5 | ## 节流
6 |
7 | ## 参考资料
8 |
9 | - [JavaScript 专题之跟着 underscore 学节流](https://github.com/mqyqingfeng/Blog/issues/26)
10 | - [JavaScript 专题之跟着 underscore 学防抖](https://github.com/mqyqingfeng/Blog/issues/22)
11 |
--------------------------------------------------------------------------------
/JavaScript/正则表达式总结.md:
--------------------------------------------------------------------------------
1 | - [正则表达式](#正则表达式)
2 | - [知识图谱](#知识图谱)
3 | - [书写风格](#书写风格)
4 | - [可以使用正则的方法](#可以使用正则的方法)
5 | - [参数](#参数)
6 | - [量词](#量词)
7 | - [定位符](#定位符)
8 | - [非打印字符](#非打印字符)
9 | - [非捕获元](#非捕获元)
10 | - [其他特殊字符](#其他特殊字符)
11 |
12 | # 正则表达式
13 |
14 | ## 知识图谱
15 |
16 | 
17 |
18 | ## 书写风格
19 |
20 | - JS 风格: `new RegExp(/a/, 'ig')`
21 | - Perl 风格: `/a/ig`
22 |
23 | ## 可以使用正则的方法
24 |
25 | - String 上的方法:
26 |
27 | - search `str.search(re)` -- 返回位置索引(失败返回 `-1`)
28 | - match `str.match(re)` -- 返回数组(失败返回 `null`)
29 | - replace `str.replace(re)`
30 | - split `str.split(re)`
31 |
32 | - RegExp 上的方法:
33 | - test `re.test(str)` -- 返回 `true / false`
34 | - exec `re.exec(str)` -- 返回数组(失败返回 `null`)
35 |
36 | > 注意几点:
37 | >
38 | > 1. `match` 不加参数 `g` 只会匹配一个值
39 | > 2. `exec` 一次匹配只会匹配到一个值,如果想要匹配多个值,需要循环多次匹配
40 | > 3. 如果只判断是否匹配,用 `test、search` 更方便
41 |
42 | ## 参数
43 |
44 | - `i` -- `ignoreCase` 忽略大小写
45 | - `g` -- `global` 全局匹配(贪婪匹配,默认是非贪婪匹配)
46 | - `m` -- `multiline` 多行匹配
47 | - `y` -- 执行“粘性”搜索(从 `lastIndex` 处开始匹配)
48 |
49 | 关于参数 `y` 的用法,举个例子:
50 |
51 | ```javascript
52 | var str = '##foo##';
53 | var regex = /foo/y;
54 |
55 | regex.lastIndex = 2;
56 | console.log(regex.test(str)); // true (此例仅当 lastIndex = 1 时匹配成功,这就是 sticky 的作用)
57 | regex.lastIndex = 5;
58 | console.log(regex.test(str)); // false (lastIndex 被 sticky 标志考虑到,从而导致匹配失败)
59 | console.log(regex.lastIndex); // 0 (匹配失败后重置)
60 | ```
61 |
62 | ## 量词
63 |
64 | - `+` -- 至少 1 个,等价于 `{1,}`
65 | - `*` -- 至少 0 个,等价于 `{0,}`
66 | - `?` -- 0 或 1 个,等价于 `{0,1}`
67 | - `{}`
68 | - `{n}` -- 匹配 `n` 个
69 | - `{n,m}` -- 匹配 `n` 到 `m` 个
70 | - `{n,}` -- 匹配大于等于 `n` 个
71 |
72 | > 注意:
73 | > `?` 跟在任何量词后面,都会使量词变得 **非贪婪**
74 |
75 | ## 定位符
76 |
77 | - `^` -- 字符串开头
78 | - `$` -- 字符串结尾
79 | - `\b` -- 匹配一个词的边界
80 | - `\B` -- 匹配一个非 **单词边界**
81 |
82 | > 注:
83 | >
84 | > - 不加参数 m(单行模式):`^$` 表示 **整体** 字符串的开头结尾
85 | > - 加上参数 m(多行模式):`^$` 表示 **每行** 字符串的开头结尾
86 |
87 | ## 非打印字符
88 |
89 | - `\f` -- 换页
90 | - `\n` -- 换行
91 | - `\r` -- 回车
92 | - `\t` -- 制表(水平)
93 | - `\v` -- 制表(垂直)
94 | - `\s` -- 空白字符,包括:`空格、制表符、换页符、换行符`
95 | - `\S` -- 非空白字符
96 | - `\cX`
97 |
98 | ## 非捕获元
99 |
100 | 使用 `()` 有一个副作用就是相关匹配会被缓存,可以在 `()` 的最前面加上非捕获元来清除缓存。
101 |
102 | - `?:` -- `(?:x)`
103 | - `?=` -- `x(?=y)`
104 | - `?!` -- `x(?!y)`
105 |
106 | ## 其他特殊字符
107 |
108 | - `.` -- 匹配除了 **换行符以外** 的所有单字符
109 | - `|` -- 或者
110 | - `\d` -- 数字,等价于 `[0-9]`
111 | - `\D` -- 非数字
112 | - `\w` -- 数字、字母、下划线,等价于 `[0-9a-zA-Z_]`
113 | - `\W` -- 非(数字、字母、下划线)
114 | - `[\b]` -- 匹配退格(U+0008),不要和 `\b` 混淆了(`\b` 是匹配词边界)
115 | - `\数字` -- 表示匹配阶段的捕获
116 | - `$数字` -- 表示替换阶段的捕获
117 | - `[]`
118 | - 或 -- `[abc]` 匹配 `a` 或 `b` 或 `c`
119 | - 范围 -- `[a-z]` 匹配 `a` 到 `z`
120 | - 排除 -- `[^0-9]` 匹配非数字
121 | - `()` -- 捕获括号,匹配结果会储存在 **捕获** 里。
122 | 匹配阶段,**捕获** 用 `\1、\2 ...` 表示;替换阶段,**捕获** 用 `$1、$2 ...` 表示。
123 | 也用于 **设置匹配优先级**
124 |
125 | 对于 **捕获**,举个例子:
126 |
127 | ```javascript
128 | console.log('abcd'.replace(/(b)(c)/, '$2$1')); // => acbd
129 | // 捕获2($2)会匹配 c,捕获1($1)会匹配 b
130 | ```
131 |
--------------------------------------------------------------------------------
/JavaScript/零碎知识笔记.md:
--------------------------------------------------------------------------------
1 | - [零碎知识(暂未分类)](#零碎知识暂未分类)
2 | - [isNaN 和 Number.isNaN](#isnan-和-numberisnan)
3 | - [import() 和 require()](#import-和-require)
4 | - [事件传播的三个阶段](#事件传播的三个阶段)
5 | - [格式化数字](#格式化数字)
6 |
7 | # 零碎知识(暂未分类)
8 |
9 | ## isNaN 和 Number.isNaN
10 |
11 | ```js
12 | // 判断是否等价于 NaN
13 | isNaN;
14 | // ES6 中的方法,先判断是否是 Number,再判断 isNaN
15 | Number.isNaN;
16 |
17 | // 判断是否等价于 isFinite
18 | isFinite;
19 | // ES6 中的方法,先判断是否是 Number,再判断 isFinite
20 | Number.isFinite;
21 | ```
22 |
23 | ## import() 和 require()
24 |
25 | - `import()` 是编译时执行
26 | - `require()` 是运行时执行
27 |
28 | 因此,使用不同的模块规范,就会导致不同的代码执行顺序。举例如下:
29 |
30 | - ES6 规范
31 |
32 | ```js
33 | // index.js
34 | console.log('log 111');
35 | import { log } from './log.js';
36 |
37 | // log.js
38 | console.log('log 222');
39 | ```
40 |
41 | 输出顺序如下:
42 |
43 | ```
44 | log 222
45 | log 111
46 | ```
47 |
48 | - CommonJS 规范
49 |
50 | ```js
51 | // index.js
52 | console.log('log 111');
53 | const log = require('./log.js');
54 |
55 | // log.js
56 | console.log('log 222');
57 | ```
58 |
59 | 输出顺序如下:
60 |
61 | ```
62 | log 111
63 | log 222
64 | ```
65 |
66 | ES6 模块是引用,只读。CommonJS 模块是拷贝,可写。
67 |
68 | ## 事件传播的三个阶段
69 |
70 | **捕获 > 目标 > 冒泡**
71 |
72 | 在捕获阶段,事件通过父元素向下传递,到目标元素后,开始冒泡。
73 |
74 | ## 格式化数字
75 |
76 | - 每三位加个逗号
77 |
78 | ```js
79 | var num = 123456.789;
80 | // 将语言指定为英语(en)
81 | var formatNum = new Intl.NumberFormat('en').format(num);
82 |
83 | console.log(formatNum); // => "123,456.789"
84 | ```
85 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019-present liuyib
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Node/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | package-lock.json
3 |
--------------------------------------------------------------------------------
/Node/Node.js 包教不包会/README.md:
--------------------------------------------------------------------------------
1 | # Node 包教不包会
2 |
3 | 教程地址:[https://github.com/alsotang/node-lessons](https://github.com/alsotang/node-lessons)
4 |
5 | ## 用到的一些库,记录下部分
6 |
7 | - superagent([http://visionmedia.github.io/superagent/](http://visionmedia.github.io/superagent/)) 是个 http 方面的库,可以发起 get 或 post 请求。
8 | - cheerio([https://github.com/cheeriojs/cheerio](https://github.com/cheeriojs/cheerio)) 可以理解成一个 Node.js 版的 jquery,用来从网页中以 css selector 取数据,使用方式跟 jquery 一样。
9 | - eventproxy([https://github.com/JacksonTian/eventproxy](https://github.com/JacksonTian/eventproxy)) 避免回调函数的深度嵌套。
10 |
--------------------------------------------------------------------------------
/Node/Node.js 包教不包会/lesson1/README.md:
--------------------------------------------------------------------------------
1 | ##### hello world
2 |
3 | 使用的库 / 框架:
4 |
5 | - express
6 |
7 | 在浏览器输出 hello world
8 |
9 | 代码:
10 |
11 | ```node
12 | var express = require('express');
13 | var app = express(); // 创建一个 express 实例
14 |
15 | app.get('/', function(req, res) {
16 | res.send('Hello World');
17 | });
18 |
19 | app.listen(3000, function() {
20 | console.log('app is listening at port 3000');
21 | });
22 | ```
--------------------------------------------------------------------------------
/Node/Node.js 包教不包会/lesson1/app.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var app = express(); // 创建一个 express 实例
3 |
4 | app.get('/', function (req, res) {
5 | res.send('Hello World');
6 | });
7 |
8 | app.listen(3000, function () {
9 | console.log('app is listening at port 3000');
10 | });
--------------------------------------------------------------------------------
/Node/Node.js 包教不包会/lesson1/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lesson1",
3 | "version": "0.0.1",
4 | "description": "一个简单的 express 应用",
5 | "main": "app.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test splicified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/liuyib/study-note/blob/master/Node/alsotang-node-lessons/lesson1"
12 | },
13 | "keywords": [
14 | "test",
15 | "study"
16 | ],
17 | "author": "liuyibo",
18 | "license": "MIT"
19 | }
20 |
--------------------------------------------------------------------------------
/Node/Node.js 包教不包会/lesson2/README.md:
--------------------------------------------------------------------------------
1 | ##### 获取 url 中的查询参数
2 |
3 | 使用的库 / 框架:
4 |
5 | - express
6 | - utility - 提供了很多有用的工具。这里用到了里面的 md5 加密 api
7 |
8 | 代码如下:
9 |
10 | ```node
11 | var express = require('express');
12 | var utility = require('utility');
13 |
14 | var app = express();
15 |
16 | app.get('/', function(req, res) {
17 | // 从 req.query 获取查询参数
18 | // 如果是 post 传来的数据,那么查询参数在 req.body 里面。但是 express 默认不处理 body 里的信息
19 | // 需要引入中间件 https://github.com/expressjs/body-parser
20 | var q = req.query.q;
21 |
22 | // 调用utility.md5 方法,得到 md5 值
23 | var md5Value = '';
24 |
25 | if (q) {
26 | md5Value = utility.md5(q);
27 | } else {
28 | md5Value = 'no query param';
29 | }
30 |
31 | res.send(md5Value);
32 | });
33 |
34 | app.listen(4000, function(req, res) {
35 | console.log('app is listen at port 4000');
36 | })
37 | ```
38 |
39 | 效果图如下:
40 |
41 | 没有查询参数:
42 |
43 | 
44 |
45 | 有查询参数:
46 |
47 | 
--------------------------------------------------------------------------------
/Node/Node.js 包教不包会/lesson2/app.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var utility = require('utility');
3 |
4 | var app = express();
5 |
6 | app.get('/', function (req, res) {
7 | // 从 req.query 获取查询参数
8 | // 如果是 post 传来的数据,那么查询参数在 req.body 里面。但是 express 默认不处理 body 里的信息
9 | // 需要引入中间件 https://github.com/expressjs/body-parser
10 | var q = req.query.q;
11 |
12 | // 调用utility.md5 方法,得到 md5 值
13 | var md5Value = '';
14 |
15 | if (q) {
16 | md5Value = utility.md5(q);
17 | } else {
18 | md5Value = 'no query param';
19 | }
20 |
21 | res.send(md5Value);
22 | });
23 |
24 | app.listen(4000, function (req, res) {
25 | console.log('app is listen at port 4000');
26 | })
--------------------------------------------------------------------------------
/Node/Node.js 包教不包会/lesson2/imgs/have_query.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/Node/Node.js 包教不包会/lesson2/imgs/have_query.png
--------------------------------------------------------------------------------
/Node/Node.js 包教不包会/lesson2/imgs/no_query.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/Node/Node.js 包教不包会/lesson2/imgs/no_query.png
--------------------------------------------------------------------------------
/Node/Node.js 包教不包会/lesson2/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lesson2",
3 | "version": "0.0.1",
4 | "description": "学习使用外部模块",
5 | "main": "app.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test splicified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/liuyib/study-note/blob/master/Node/alsotang-node-lessons/lesson2"
12 | },
13 | "keywords": [
14 | "test",
15 | "study"
16 | ],
17 | "author": "liuyibo",
18 | "license": "MIT"
19 | }
20 |
--------------------------------------------------------------------------------
/Node/Node.js 包教不包会/lesson3/README.md:
--------------------------------------------------------------------------------
1 | ##### 爬取 cnode 社区
2 |
3 | 使用的库 / 框架:
4 |
5 | - express
6 | - superagent
7 | - cheerio
8 |
9 | 代码:
10 |
11 | ```node
12 | var express = require('express');
13 | var superagent = require('superagent');
14 | var cheerio = require('cheerio');
15 |
16 | var app = express();
17 |
18 | app.get('/', function(req, res, next) {
19 | // 使用 superagent 抓取 cnode 社区的内容
20 | superagent.get('https://cnodejs.org/')
21 | .end(function(err, sres) {
22 | if (err) {
23 | return next(err);
24 | }
25 |
26 | // sres.text 中储存了网页的 html 内容,将他传给 cheerio.load 之后
27 | // 就得到了一个实现 jquery 接口的变量。它和 jquery 的用法一样,我们习惯把它命名为 $
28 | // 就下来就都是 jquery 的内容了
29 | var $ = cheerio.load(sres.text);
30 | var items = [];
31 |
32 | $('#topic_list .topic_title').each(function(index, elem) {
33 | var $elem = $(elem);
34 |
35 | items.push({
36 | title: $elem.attr('title'),
37 | href: $elem.attr('href')
38 | });
39 | });
40 |
41 | res.send(items);
42 | });
43 | });
44 |
45 | app.listen(4000, function () {
46 | console.log('app is listening at port 4000');
47 | });
48 | ```
49 |
50 | 浏览器中输入 localhost:4000 结果如下:
51 |
52 | 
--------------------------------------------------------------------------------
/Node/Node.js 包教不包会/lesson3/app.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var superagent = require('superagent');
3 | var cheerio = require('cheerio');
4 |
5 | var app = express();
6 |
7 | app.get('/', function (req, res, next) {
8 | // 使用 superagent 抓取 cnode 社区的内容
9 | superagent.get('https://cnodejs.org/')
10 | .end(function (err, sres) {
11 | if (err) {
12 | return next(err);
13 | }
14 |
15 | // sres.text 中储存了网页的 html 内容,将他传给 cheerio.load 之后
16 | // 就得到了一个实现 jquery 接口的变量。它和 jquery 的用法一样,我们习惯把它命名为 $
17 | // 就下来就都是 jquery 的内容了
18 | var $ = cheerio.load(sres.text);
19 | var items = [];
20 |
21 | $('#topic_list .topic_title').each(function (index, elem) {
22 | var $elem = $(elem);
23 |
24 | items.push({
25 | title: $elem.attr('title'),
26 | href: $elem.attr('href')
27 | });
28 | });
29 |
30 | res.send(items);
31 | });
32 | });
33 |
34 | app.listen(4000, function () {
35 | console.log('app is listening at port 4000');
36 | });
--------------------------------------------------------------------------------
/Node/Node.js 包教不包会/lesson3/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lesson3",
3 | "version": "0.0.0",
4 | "description": "使用 superagent 与 cheerio 完成简单的爬虫",
5 | "main": "app.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test splicified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/liuyib/study-note/blob/master/Node/alsotang-node-lessons/lesson3"
12 | },
13 | "keywords": [
14 | "test",
15 | "study"
16 | ],
17 | "author": "liuyibo",
18 | "license": "MIT"
19 | }
20 |
--------------------------------------------------------------------------------
/Node/Node.js 包教不包会/lesson3/show.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/Node/Node.js 包教不包会/lesson3/show.png
--------------------------------------------------------------------------------
/Node/Node.js 包教不包会/lesson4/README.md:
--------------------------------------------------------------------------------
1 | ##### 使用 eventproxy 控制并发
2 |
3 | 使用的库 / 框架:
4 |
5 | - express
6 | - superagent
7 | - cheerio
8 | - eventproxy
9 |
10 | 代码如下:
11 |
12 | ```node
13 | var eventproxy = require('eventproxy');
14 | var superagent = require('superagent');
15 | var cheerio = require('cheerio');
16 | var url = require('url'); // node 里的 url 模块
17 |
18 | var CNODE_URL = 'https://cnodejs.org';
19 |
20 | superagent.get(CNODE_URL)
21 | .end(function (err, res) {
22 | if (err) {
23 | return console.error(err);
24 | }
25 |
26 | var topicUrls = []; // 要抓取话题的 url
27 | var $ = cheerio.load(res.text);
28 |
29 | $('#topic_list .topic_title').each(function (index, elem) {
30 | var $elem = $(elem);
31 | var href = url.resolve(CNODE_URL, $elem.attr('href'));
32 |
33 | if (index < 5) { // 只抓取 5 个 url 的第一条评论(抓太多会被网站限制)
34 | topicUrls.push(href);
35 | }
36 | });
37 |
38 | var ep = new eventproxy();
39 |
40 | // 重复监听 5 次 topic_html 事件后,在执行行动
41 | // topics 是一个数组,它包含了 ep.emit('topic_html', pair) 中的那 5 个 pair
42 | ep.after('topic_html', 5, function (topics) {
43 | topics = topics.map(function (topicPair) {
44 | // ep 从 emit 中接受到的参数。分别为 url 和 html
45 | var topicUrl = topicPair[0];
46 | var topicHtml = topicPair[1];
47 | var $ = cheerio.load(topicHtml);
48 |
49 | return ({
50 | title: $('.topic_full_title').text().trim(),
51 | href: topicUrl,
52 | comment1: $('.reply_content').eq(0).text().trim(),
53 | });
54 | });
55 |
56 | console.log('final:');
57 | console.log(topics);
58 | });
59 |
60 | // 同时抓取所有的 url
61 | topicUrls.forEach(function (topicUrl) {
62 | superagent.get(topicUrl)
63 | .end(function (err, res) {
64 | if (err) {
65 | console.log('error');
66 | }
67 |
68 | console.log('fetch: ' + topicUrl + ', successful');
69 |
70 | var _html = res.text;
71 | // 给 ep 发送 topic_html 事件
72 | ep.emit('topic_html', [topicUrl, _html]);
73 | })
74 | })
75 | });
76 | ```
77 |
78 | 效果如下:
79 |
80 | 
--------------------------------------------------------------------------------
/Node/Node.js 包教不包会/lesson4/app.js:
--------------------------------------------------------------------------------
1 | var eventproxy = require('eventproxy');
2 | var superagent = require('superagent');
3 | var cheerio = require('cheerio');
4 | var url = require('url'); // node 里的 url 模块
5 |
6 | var CNODE_URL = 'https://cnodejs.org';
7 |
8 | superagent.get(CNODE_URL)
9 | .end(function (err, res) {
10 | if (err) {
11 | return console.error(err);
12 | }
13 |
14 | var topicUrls = []; // 要抓取话题的 url
15 | var $ = cheerio.load(res.text);
16 |
17 | $('#topic_list .topic_title').each(function (index, elem) {
18 | var $elem = $(elem);
19 | var href = url.resolve(CNODE_URL, $elem.attr('href'));
20 |
21 | if (index < 5) { // 只抓取 5 个 url 的第一条评论(抓太多会被网站限制)
22 | topicUrls.push(href);
23 | }
24 | });
25 |
26 | var ep = new eventproxy();
27 |
28 | // 重复监听 5 次 topic_html 事件后,在执行行动
29 | // topics 是一个数组,它包含了 ep.emit('topic_html', pair) 中的那 5 个 pair
30 | ep.after('topic_html', 5, function (topics) {
31 | topics = topics.map(function (topicPair) {
32 | // ep 从 emit 中接受到的参数。分别为 url 和 html
33 | var topicUrl = topicPair[0];
34 | var topicHtml = topicPair[1];
35 | var $ = cheerio.load(topicHtml);
36 |
37 | return ({
38 | title: $('.topic_full_title').text().trim(),
39 | href: topicUrl,
40 | comment1: $('.reply_content').eq(0).text().trim(),
41 | });
42 | });
43 |
44 | console.log('final:');
45 | console.log(topics);
46 | });
47 |
48 | // 同时抓取所有的 url
49 | topicUrls.forEach(function (topicUrl) {
50 | superagent.get(topicUrl)
51 | .end(function (err, res) {
52 | if (err) {
53 | console.log('error');
54 | }
55 |
56 | console.log('fetch: ' + topicUrl + ', successful');
57 |
58 | var _html = res.text;
59 | // 给 ep 发送 topic_html 事件
60 | ep.emit('topic_html', [topicUrl, _html]);
61 | });
62 | });
63 | });
--------------------------------------------------------------------------------
/Node/Node.js 包教不包会/lesson4/cnode_crawler.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/Node/Node.js 包教不包会/lesson4/cnode_crawler.png
--------------------------------------------------------------------------------
/Node/Node.js 包教不包会/lesson4/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lesson4",
3 | "version": "0.0.0",
4 | "description": "使用 eventproxy 控制并发",
5 | "main": "app.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/liuyib/study-note/blob/master/Node/alsotang-node-lessons/lesson4"
12 | },
13 | "keywords": [
14 | "test",
15 | "study"
16 | ],
17 | "author": "liuyibo",
18 | "license": "MIT"
19 | }
20 |
--------------------------------------------------------------------------------
/Node/Node.js 包教不包会/lesson5/README.md:
--------------------------------------------------------------------------------
1 | ##### 使用 async 控制并发
2 |
3 | > 对于什么时候用 eventproxy,什么时候用 async。一般是这样的:当你需要多个源(一般是小于 10 个)汇总数据的时候,用 eventproxy 方便;
4 | > 当你需要用到队列时, 需要控制并发数,或者你喜欢函数式的编程思维时,使用 async。大部分场景是使用 eventproxy。
5 |
6 | 使用的库 / 框架:
7 |
8 | - async
9 |
10 | 代码如下:
11 |
12 | ```node
13 | var async = require('async');
14 |
15 | var concurrenceCount = 0; // 并发数计时器
16 | var fetchUrl = function(url, callback) {
17 | var delay = parseInt((Math.random() * 10000000) % 2000, 10); // 随即一个延迟时间
18 |
19 | concurrenceCount++;
20 |
21 | console.log('并发数是:' + concurrenceCount + ',正在抓取网页是:' + url + ',耗时:' + delay + '毫秒');
22 |
23 | setTimeout(function () {
24 | concurrenceCount--;
25 | callback(null, url + ': html content');
26 | }, delay);
27 | };
28 |
29 | // 伪造一组链接
30 | var urls = [];
31 | for (var i = 0; i < 30; i++) {
32 | urls.push('http://datasource_' + i);
33 | }
34 |
35 | // 使用 async.mapLimit 来并发抓取,并获取结果。
36 | /**
37 | * mapLimit
38 | * 并发抓取网页
39 | * @param {Array} 一组网页 url
40 | * @param {Number} 并发连接的数量
41 | * @param {Function} 异步函数
42 | * @param {Function} 回调函数
43 | */
44 | async.mapLimit(urls, 5, function (url, callback) {
45 | fetchUrl(url, callback);
46 | }, function (err, result) {
47 | if (err) {
48 | console.log('error');
49 | }
50 |
51 | console.log('final: ');
52 | console.log(result);
53 | });
54 | ```
55 |
56 | 效果如下:
57 |
58 | 
--------------------------------------------------------------------------------
/Node/Node.js 包教不包会/lesson5/app.js:
--------------------------------------------------------------------------------
1 | var async = require('async');
2 |
3 | var concurrenceCount = 0; // 并发数计时器
4 | var fetchUrl = function(url, callback) {
5 | var delay = parseInt((Math.random() * 10000000) % 2000, 10); // 随即一个延迟时间
6 |
7 | concurrenceCount++;
8 |
9 | console.log('并发数是:' + concurrenceCount + ',正在抓取网页是:' + url + ',耗时:' + delay + '毫秒');
10 |
11 | setTimeout(function () {
12 | concurrenceCount--;
13 | callback(null, url + ': html content');
14 | }, delay);
15 | };
16 |
17 | // 伪造一组链接
18 | var urls = [];
19 | for (var i = 0; i < 30; i++) {
20 | urls.push('http://datasource_' + i);
21 | }
22 |
23 | // 使用 async.mapLimit 来并发抓取,并获取结果。
24 | /**
25 | * mapLimit
26 | * 并发抓取网页
27 | * @param {Array} 一组网页 url
28 | * @param {Number} 并发连接的数量
29 | * @param {Function} 异步函数
30 | * @param {Function} 回调函数
31 | */
32 | async.mapLimit(urls, 5, function (url, callback) {
33 | fetchUrl(url, callback);
34 | }, function (err, result) {
35 | if (err) {
36 | console.log('error');
37 | }
38 |
39 | console.log('final: ');
40 | console.log(result);
41 | });
--------------------------------------------------------------------------------
/Node/Node.js 包教不包会/lesson5/async_crewler.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/Node/Node.js 包教不包会/lesson5/async_crewler.png
--------------------------------------------------------------------------------
/Node/Node.js 包教不包会/lesson5/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lesson5",
3 | "version": "0.0.0",
4 | "description": "使用 async 控制并发",
5 | "main": "app.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/liuyib/study-note/blob/master/Node/alsotang-node-lessons/lesson5"
12 | },
13 | "keywords": [
14 | "test",
15 | "study"
16 | ],
17 | "author": "liuyibo",
18 | "license": "MIT"
19 | }
20 |
--------------------------------------------------------------------------------
/Node/Node.js 包教不包会/lesson6/READMD.md:
--------------------------------------------------------------------------------
1 | ##### 《测试用例:mocha,should,istanbul》
2 |
3 | 用到的库 / 框架:
4 |
5 | - should - 断言库
6 | - mocha - 测试框架
7 | - istanbul - 在测试中输出覆盖率
8 |
9 |
--------------------------------------------------------------------------------
/Node/Node.js 包教不包会/lesson6/app.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/Node/Node.js 包教不包会/lesson6/app.js
--------------------------------------------------------------------------------
/Node/Node.js 包教不包会/lesson6/coverage/coverage.json:
--------------------------------------------------------------------------------
1 | {"/media/liuyibo/Data/myFolder/study-note/Node/alsotang-node-lessons/lesson6/main.js":{"path":"/media/liuyibo/Data/myFolder/study-note/Node/alsotang-node-lessons/lesson6/main.js","s":{"1":1,"2":181,"3":0,"4":181,"5":1,"6":180,"7":1,"8":179,"9":35,"10":144,"11":56,"12":88,"13":1,"14":0,"15":0,"16":1},"b":{"1":[0,181],"2":[1,180],"3":[1,179],"4":[35,144],"5":[56,88],"6":[0,1]},"f":{"1":181},"fnMap":{"1":{"name":"(anonymous_1)","line":1,"loc":{"start":{"line":1,"column":16},"end":{"line":1,"column":29}}}},"statementMap":{"1":{"start":{"line":1,"column":0},"end":{"line":19,"column":1}},"2":{"start":{"line":2,"column":2},"end":{"line":4,"column":3}},"3":{"start":{"line":3,"column":4},"end":{"line":3,"column":44}},"4":{"start":{"line":5,"column":2},"end":{"line":7,"column":3}},"5":{"start":{"line":6,"column":4},"end":{"line":6,"column":37}},"6":{"start":{"line":8,"column":2},"end":{"line":10,"column":3}},"7":{"start":{"line":9,"column":4},"end":{"line":9,"column":38}},"8":{"start":{"line":11,"column":2},"end":{"line":13,"column":3}},"9":{"start":{"line":12,"column":4},"end":{"line":12,"column":13}},"10":{"start":{"line":14,"column":2},"end":{"line":16,"column":3}},"11":{"start":{"line":15,"column":4},"end":{"line":15,"column":13}},"12":{"start":{"line":18,"column":2},"end":{"line":18,"column":45}},"13":{"start":{"line":23,"column":0},"end":{"line":27,"column":1}},"14":{"start":{"line":24,"column":2},"end":{"line":24,"column":34}},"15":{"start":{"line":26,"column":2},"end":{"line":26,"column":57}},"16":{"start":{"line":29,"column":0},"end":{"line":29,"column":30}}},"branchMap":{"1":{"line":2,"type":"if","locations":[{"start":{"line":2,"column":2},"end":{"line":2,"column":2}},{"start":{"line":2,"column":2},"end":{"line":2,"column":2}}]},"2":{"line":5,"type":"if","locations":[{"start":{"line":5,"column":2},"end":{"line":5,"column":2}},{"start":{"line":5,"column":2},"end":{"line":5,"column":2}}]},"3":{"line":8,"type":"if","locations":[{"start":{"line":8,"column":2},"end":{"line":8,"column":2}},{"start":{"line":8,"column":2},"end":{"line":8,"column":2}}]},"4":{"line":11,"type":"if","locations":[{"start":{"line":11,"column":2},"end":{"line":11,"column":2}},{"start":{"line":11,"column":2},"end":{"line":11,"column":2}}]},"5":{"line":14,"type":"if","locations":[{"start":{"line":14,"column":2},"end":{"line":14,"column":2}},{"start":{"line":14,"column":2},"end":{"line":14,"column":2}}]},"6":{"line":23,"type":"if","locations":[{"start":{"line":23,"column":0},"end":{"line":23,"column":0}},{"start":{"line":23,"column":0},"end":{"line":23,"column":0}}]}}}}
--------------------------------------------------------------------------------
/Node/Node.js 包教不包会/lesson6/coverage/lcov-report/base.css:
--------------------------------------------------------------------------------
1 | body, html {
2 | margin:0; padding: 0;
3 | height: 100%;
4 | }
5 | body {
6 | font-family: Helvetica Neue, Helvetica, Arial;
7 | font-size: 14px;
8 | color:#333;
9 | }
10 | .small { font-size: 12px; }
11 | *, *:after, *:before {
12 | -webkit-box-sizing:border-box;
13 | -moz-box-sizing:border-box;
14 | box-sizing:border-box;
15 | }
16 | h1 { font-size: 20px; margin: 0;}
17 | h2 { font-size: 14px; }
18 | pre {
19 | font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace;
20 | margin: 0;
21 | padding: 0;
22 | -moz-tab-size: 2;
23 | -o-tab-size: 2;
24 | tab-size: 2;
25 | }
26 | a { color:#0074D9; text-decoration:none; }
27 | a:hover { text-decoration:underline; }
28 | .strong { font-weight: bold; }
29 | .space-top1 { padding: 10px 0 0 0; }
30 | .pad2y { padding: 20px 0; }
31 | .pad1y { padding: 10px 0; }
32 | .pad2x { padding: 0 20px; }
33 | .pad2 { padding: 20px; }
34 | .pad1 { padding: 10px; }
35 | .space-left2 { padding-left:55px; }
36 | .space-right2 { padding-right:20px; }
37 | .center { text-align:center; }
38 | .clearfix { display:block; }
39 | .clearfix:after {
40 | content:'';
41 | display:block;
42 | height:0;
43 | clear:both;
44 | visibility:hidden;
45 | }
46 | .fl { float: left; }
47 | @media only screen and (max-width:640px) {
48 | .col3 { width:100%; max-width:100%; }
49 | .hide-mobile { display:none!important; }
50 | }
51 |
52 | .quiet {
53 | color: #7f7f7f;
54 | color: rgba(0,0,0,0.5);
55 | }
56 | .quiet a { opacity: 0.7; }
57 |
58 | .fraction {
59 | font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
60 | font-size: 10px;
61 | color: #555;
62 | background: #E8E8E8;
63 | padding: 4px 5px;
64 | border-radius: 3px;
65 | vertical-align: middle;
66 | }
67 |
68 | div.path a:link, div.path a:visited { color: #333; }
69 | table.coverage {
70 | border-collapse: collapse;
71 | margin: 10px 0 0 0;
72 | padding: 0;
73 | }
74 |
75 | table.coverage td {
76 | margin: 0;
77 | padding: 0;
78 | vertical-align: top;
79 | }
80 | table.coverage td.line-count {
81 | text-align: right;
82 | padding: 0 5px 0 20px;
83 | }
84 | table.coverage td.line-coverage {
85 | text-align: right;
86 | padding-right: 10px;
87 | min-width:20px;
88 | }
89 |
90 | table.coverage td span.cline-any {
91 | display: inline-block;
92 | padding: 0 5px;
93 | width: 100%;
94 | }
95 | .missing-if-branch {
96 | display: inline-block;
97 | margin-right: 5px;
98 | border-radius: 3px;
99 | position: relative;
100 | padding: 0 4px;
101 | background: #333;
102 | color: yellow;
103 | }
104 |
105 | .skip-if-branch {
106 | display: none;
107 | margin-right: 10px;
108 | position: relative;
109 | padding: 0 4px;
110 | background: #ccc;
111 | color: white;
112 | }
113 | .missing-if-branch .typ, .skip-if-branch .typ {
114 | color: inherit !important;
115 | }
116 | .coverage-summary {
117 | border-collapse: collapse;
118 | width: 100%;
119 | }
120 | .coverage-summary tr { border-bottom: 1px solid #bbb; }
121 | .keyline-all { border: 1px solid #ddd; }
122 | .coverage-summary td, .coverage-summary th { padding: 10px; }
123 | .coverage-summary tbody { border: 1px solid #bbb; }
124 | .coverage-summary td { border-right: 1px solid #bbb; }
125 | .coverage-summary td:last-child { border-right: none; }
126 | .coverage-summary th {
127 | text-align: left;
128 | font-weight: normal;
129 | white-space: nowrap;
130 | }
131 | .coverage-summary th.file { border-right: none !important; }
132 | .coverage-summary th.pct { }
133 | .coverage-summary th.pic,
134 | .coverage-summary th.abs,
135 | .coverage-summary td.pct,
136 | .coverage-summary td.abs { text-align: right; }
137 | .coverage-summary td.file { white-space: nowrap; }
138 | .coverage-summary td.pic { min-width: 120px !important; }
139 | .coverage-summary tfoot td { }
140 |
141 | .coverage-summary .sorter {
142 | height: 10px;
143 | width: 7px;
144 | display: inline-block;
145 | margin-left: 0.5em;
146 | background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent;
147 | }
148 | .coverage-summary .sorted .sorter {
149 | background-position: 0 -20px;
150 | }
151 | .coverage-summary .sorted-desc .sorter {
152 | background-position: 0 -10px;
153 | }
154 | .status-line { height: 10px; }
155 | /* dark red */
156 | .red.solid, .status-line.low, .low .cover-fill { background:#C21F39 }
157 | .low .chart { border:1px solid #C21F39 }
158 | /* medium red */
159 | .cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE }
160 | /* light red */
161 | .low, .cline-no { background:#FCE1E5 }
162 | /* light green */
163 | .high, .cline-yes { background:rgb(230,245,208) }
164 | /* medium green */
165 | .cstat-yes { background:rgb(161,215,106) }
166 | /* dark green */
167 | .status-line.high, .high .cover-fill { background:rgb(77,146,33) }
168 | .high .chart { border:1px solid rgb(77,146,33) }
169 | /* dark yellow (gold) */
170 | .medium .chart { border:1px solid #f9cd0b; }
171 | .status-line.medium, .medium .cover-fill { background: #f9cd0b; }
172 | /* light yellow */
173 | .medium { background: #fff4c2; }
174 | /* light gray */
175 | span.cline-neutral { background: #eaeaea; }
176 |
177 | .cbranch-no { background: yellow !important; color: #111; }
178 |
179 | .cstat-skip { background: #ddd; color: #111; }
180 | .fstat-skip { background: #ddd; color: #111 !important; }
181 | .cbranch-skip { background: #ddd !important; color: #111; }
182 |
183 |
184 | .cover-fill, .cover-empty {
185 | display:inline-block;
186 | height: 12px;
187 | }
188 | .chart {
189 | line-height: 0;
190 | }
191 | .cover-empty {
192 | background: white;
193 | }
194 | .cover-full {
195 | border-right: none !important;
196 | }
197 | pre.prettyprint {
198 | border: none !important;
199 | padding: 0 !important;
200 | margin: 0 !important;
201 | }
202 | .com { color: #999 !important; }
203 | .ignore-none { color: #999; font-weight: normal; }
204 |
205 | .wrapper {
206 | min-height: 100%;
207 | height: auto !important;
208 | height: 100%;
209 | margin: 0 auto -48px;
210 | }
211 | .footer, .push {
212 | height: 48px;
213 | }
214 |
--------------------------------------------------------------------------------
/Node/Node.js 包教不包会/lesson6/coverage/lcov-report/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Code coverage report for All files
5 |
6 |
7 |
8 |
9 |
14 |
15 |
16 |
17 |
18 |
19 | /
20 |
21 |
22 |
23 | 81.25%
24 | Statements
25 | 13/16
26 |
27 |
28 | 83.33%
29 | Branches
30 | 10/12
31 |
32 |
33 | 100%
34 | Functions
35 | 1/1
36 |
37 |
38 | 81.25%
39 | Lines
40 | 13/16
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | File |
50 | |
51 | Statements |
52 | |
53 | Branches |
54 | |
55 | Functions |
56 | |
57 | Lines |
58 | |
59 |
60 |
61 |
62 | lesson6/ |
63 | |
64 | 81.25% |
65 | 13/16 |
66 | 83.33% |
67 | 10/12 |
68 | 100% |
69 | 1/1 |
70 | 81.25% |
71 | 13/16 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
82 |
83 |
84 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/Node/Node.js 包教不包会/lesson6/coverage/lcov-report/lesson6/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Code coverage report for lesson6/
5 |
6 |
7 |
8 |
9 |
14 |
15 |
16 |
17 |
18 |
19 | all files lesson6/
20 |
21 |
22 |
23 | 81.25%
24 | Statements
25 | 13/16
26 |
27 |
28 | 83.33%
29 | Branches
30 | 10/12
31 |
32 |
33 | 100%
34 | Functions
35 | 1/1
36 |
37 |
38 | 81.25%
39 | Lines
40 | 13/16
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | File |
50 | |
51 | Statements |
52 | |
53 | Branches |
54 | |
55 | Functions |
56 | |
57 | Lines |
58 | |
59 |
60 |
61 |
62 | main.js |
63 | |
64 | 81.25% |
65 | 13/16 |
66 | 83.33% |
67 | 10/12 |
68 | 100% |
69 | 1/1 |
70 | 81.25% |
71 | 13/16 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
82 |
83 |
84 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/Node/Node.js 包教不包会/lesson6/coverage/lcov-report/lesson6/main.js.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Code coverage report for lesson6/main.js
5 |
6 |
7 |
8 |
9 |
14 |
15 |
16 |
17 |
18 |
21 |
22 |
23 | 81.25%
24 | Statements
25 | 13/16
26 |
27 |
28 | 83.33%
29 | Branches
30 | 10/12
31 |
32 |
33 | 100%
34 | Functions
35 | 1/1
36 |
37 |
38 | 81.25%
39 | Lines
40 | 13/16
41 |
42 |
43 |
44 |
45 |
46 | 1
47 | 2
48 | 3
49 | 4
50 | 5
51 | 6
52 | 7
53 | 8
54 | 9
55 | 10
56 | 11
57 | 12
58 | 13
59 | 14
60 | 15
61 | 16
62 | 17
63 | 18
64 | 19
65 | 20
66 | 21
67 | 22
68 | 23
69 | 24
70 | 25
71 | 26
72 | 27
73 | 28
74 | 29 | 1×
75 | 181×
76 |
77 |
78 | 181×
79 | 1×
80 |
81 | 180×
82 | 1×
83 |
84 | 179×
85 | 35×
86 |
87 | 144×
88 | 56×
89 |
90 |
91 | 88×
92 |
93 |
94 |
95 |
96 | 1×
97 |
98 |
99 |
100 |
101 |
102 | 1× | var fibonacci = function (n) {
103 | Iif (typeof n !== 'number') {
104 | throw new Error('n should be a Number');
105 | }
106 | if (n < 0) {
107 | throw new Error('n should >= 0');
108 | }
109 | if (n > 10) {
110 | throw new Error('n should <= 10');
111 | }
112 | if (n === 0) {
113 | return 0;
114 | }
115 | if (n === 1) {
116 | return 1;
117 | }
118 |
119 | return fibonacci(n - 1) + fibonacci(n - 2);
120 | }
121 |
122 | // 如果是直接执行 main.js,则执行此处
123 | // 如果 main.js 被其他文件 require,则此处不会执行
124 | Iif (require.main === module) {
125 | var n = Number(process.argv[2]);
126 |
127 | console.log('fibonacci(' + n + ') is ' + fibonacci(n));
128 | }
129 |
130 | exports.fibonacci = fibonacci; |
131 |
132 |
133 |
134 |
138 |
139 |
140 |
147 |
148 |
149 |
150 |
--------------------------------------------------------------------------------
/Node/Node.js 包教不包会/lesson6/coverage/lcov-report/prettify.css:
--------------------------------------------------------------------------------
1 | .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee}
2 |
--------------------------------------------------------------------------------
/Node/Node.js 包教不包会/lesson6/coverage/lcov-report/sort-arrow-sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/Node/Node.js 包教不包会/lesson6/coverage/lcov-report/sort-arrow-sprite.png
--------------------------------------------------------------------------------
/Node/Node.js 包教不包会/lesson6/coverage/lcov-report/sorter.js:
--------------------------------------------------------------------------------
1 | var addSorting = (function () {
2 | "use strict";
3 | var cols,
4 | currentSort = {
5 | index: 0,
6 | desc: false
7 | };
8 |
9 | // returns the summary table element
10 | function getTable() { return document.querySelector('.coverage-summary'); }
11 | // returns the thead element of the summary table
12 | function getTableHeader() { return getTable().querySelector('thead tr'); }
13 | // returns the tbody element of the summary table
14 | function getTableBody() { return getTable().querySelector('tbody'); }
15 | // returns the th element for nth column
16 | function getNthColumn(n) { return getTableHeader().querySelectorAll('th')[n]; }
17 |
18 | // loads all columns
19 | function loadColumns() {
20 | var colNodes = getTableHeader().querySelectorAll('th'),
21 | colNode,
22 | cols = [],
23 | col,
24 | i;
25 |
26 | for (i = 0; i < colNodes.length; i += 1) {
27 | colNode = colNodes[i];
28 | col = {
29 | key: colNode.getAttribute('data-col'),
30 | sortable: !colNode.getAttribute('data-nosort'),
31 | type: colNode.getAttribute('data-type') || 'string'
32 | };
33 | cols.push(col);
34 | if (col.sortable) {
35 | col.defaultDescSort = col.type === 'number';
36 | colNode.innerHTML = colNode.innerHTML + '';
37 | }
38 | }
39 | return cols;
40 | }
41 | // attaches a data attribute to every tr element with an object
42 | // of data values keyed by column name
43 | function loadRowData(tableRow) {
44 | var tableCols = tableRow.querySelectorAll('td'),
45 | colNode,
46 | col,
47 | data = {},
48 | i,
49 | val;
50 | for (i = 0; i < tableCols.length; i += 1) {
51 | colNode = tableCols[i];
52 | col = cols[i];
53 | val = colNode.getAttribute('data-value');
54 | if (col.type === 'number') {
55 | val = Number(val);
56 | }
57 | data[col.key] = val;
58 | }
59 | return data;
60 | }
61 | // loads all row data
62 | function loadData() {
63 | var rows = getTableBody().querySelectorAll('tr'),
64 | i;
65 |
66 | for (i = 0; i < rows.length; i += 1) {
67 | rows[i].data = loadRowData(rows[i]);
68 | }
69 | }
70 | // sorts the table using the data for the ith column
71 | function sortByIndex(index, desc) {
72 | var key = cols[index].key,
73 | sorter = function (a, b) {
74 | a = a.data[key];
75 | b = b.data[key];
76 | return a < b ? -1 : a > b ? 1 : 0;
77 | },
78 | finalSorter = sorter,
79 | tableBody = document.querySelector('.coverage-summary tbody'),
80 | rowNodes = tableBody.querySelectorAll('tr'),
81 | rows = [],
82 | i;
83 |
84 | if (desc) {
85 | finalSorter = function (a, b) {
86 | return -1 * sorter(a, b);
87 | };
88 | }
89 |
90 | for (i = 0; i < rowNodes.length; i += 1) {
91 | rows.push(rowNodes[i]);
92 | tableBody.removeChild(rowNodes[i]);
93 | }
94 |
95 | rows.sort(finalSorter);
96 |
97 | for (i = 0; i < rows.length; i += 1) {
98 | tableBody.appendChild(rows[i]);
99 | }
100 | }
101 | // removes sort indicators for current column being sorted
102 | function removeSortIndicators() {
103 | var col = getNthColumn(currentSort.index),
104 | cls = col.className;
105 |
106 | cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, '');
107 | col.className = cls;
108 | }
109 | // adds sort indicators for current column being sorted
110 | function addSortIndicators() {
111 | getNthColumn(currentSort.index).className += currentSort.desc ? ' sorted-desc' : ' sorted';
112 | }
113 | // adds event listeners for all sorter widgets
114 | function enableUI() {
115 | var i,
116 | el,
117 | ithSorter = function ithSorter(i) {
118 | var col = cols[i];
119 |
120 | return function () {
121 | var desc = col.defaultDescSort;
122 |
123 | if (currentSort.index === i) {
124 | desc = !currentSort.desc;
125 | }
126 | sortByIndex(i, desc);
127 | removeSortIndicators();
128 | currentSort.index = i;
129 | currentSort.desc = desc;
130 | addSortIndicators();
131 | };
132 | };
133 | for (i =0 ; i < cols.length; i += 1) {
134 | if (cols[i].sortable) {
135 | // add the click event handler on the th so users
136 | // dont have to click on those tiny arrows
137 | el = getNthColumn(i).querySelector('.sorter').parentElement;
138 | if (el.addEventListener) {
139 | el.addEventListener('click', ithSorter(i));
140 | } else {
141 | el.attachEvent('onclick', ithSorter(i));
142 | }
143 | }
144 | }
145 | }
146 | // adds sorting functionality to the UI
147 | return function () {
148 | if (!getTable()) {
149 | return;
150 | }
151 | cols = loadColumns();
152 | loadData(cols);
153 | addSortIndicators();
154 | enableUI();
155 | };
156 | })();
157 |
158 | window.addEventListener('load', addSorting);
159 |
--------------------------------------------------------------------------------
/Node/Node.js 包教不包会/lesson6/coverage/lcov.info:
--------------------------------------------------------------------------------
1 | TN:
2 | SF:/media/liuyibo/Data/myFolder/study-note/Node/alsotang-node-lessons/lesson6/main.js
3 | FN:1,(anonymous_1)
4 | FNF:1
5 | FNH:1
6 | FNDA:181,(anonymous_1)
7 | DA:1,1
8 | DA:2,181
9 | DA:3,0
10 | DA:5,181
11 | DA:6,1
12 | DA:8,180
13 | DA:9,1
14 | DA:11,179
15 | DA:12,35
16 | DA:14,144
17 | DA:15,56
18 | DA:18,88
19 | DA:23,1
20 | DA:24,0
21 | DA:26,0
22 | DA:29,1
23 | LF:16
24 | LH:13
25 | BRDA:2,1,0,0
26 | BRDA:2,1,1,181
27 | BRDA:5,2,0,1
28 | BRDA:5,2,1,180
29 | BRDA:8,3,0,1
30 | BRDA:8,3,1,179
31 | BRDA:11,4,0,35
32 | BRDA:11,4,1,144
33 | BRDA:14,5,0,56
34 | BRDA:14,5,1,88
35 | BRDA:23,6,0,0
36 | BRDA:23,6,1,1
37 | BRF:12
38 | BRH:10
39 | end_of_record
40 |
--------------------------------------------------------------------------------
/Node/Node.js 包教不包会/lesson6/main.js:
--------------------------------------------------------------------------------
1 | var fibonacci = function (n) {
2 | if (typeof n !== 'number') {
3 | throw new Error('n should be a Number');
4 | }
5 | if (n < 0) {
6 | throw new Error('n should >= 0');
7 | }
8 | if (n > 10) {
9 | throw new Error('n should <= 10');
10 | }
11 | if (n === 0) {
12 | return 0;
13 | }
14 | if (n === 1) {
15 | return 1;
16 | }
17 |
18 | return fibonacci(n - 1) + fibonacci(n - 2);
19 | }
20 |
21 | // 如果是直接执行 main.js,则执行此处
22 | // 如果 main.js 被其他文件 require,则此处不会执行
23 | if (require.main === module) {
24 | var n = Number(process.argv[2]);
25 |
26 | console.log('fibonacci(' + n + ') is ' + fibonacci(n));
27 | }
28 |
29 | exports.fibonacci = fibonacci;
--------------------------------------------------------------------------------
/Node/Node.js 包教不包会/lesson6/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lesson6",
3 | "version": "0.0.0",
4 | "description": "测试永立:mocha, should, istanbul",
5 | "main": "app.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/liuyib/study-note/blob/master/Node/alsotang-node-lessons/lesson5"
12 | },
13 | "keywords": [
14 | "test",
15 | "study"
16 | ],
17 | "author": "liuyibo",
18 | "license": "MIT"
19 | }
20 |
--------------------------------------------------------------------------------
/Node/Node.js 包教不包会/lesson6/test/main.test.js:
--------------------------------------------------------------------------------
1 | var main = require('../main');
2 | var should = require('should');
3 |
4 | describe('test/main.test.js', function () {
5 | it('should equal 0 when n === 0', function () {
6 | main.fibonacci(0).should.equal(0);
7 | });
8 |
9 | it('should equal 1 when n === 1', function () {
10 | main.fibonacci(1).should.equal(1);
11 | });
12 |
13 | it('should equal 55 when n === 10', function () {
14 | main.fibonacci(10).should.equal(55);
15 | });
16 |
17 | it('should throw when n > 10', function () {
18 | (function () {
19 | main.fibonacci(11);
20 | }).should.throw('n should <= 10');
21 | });
22 |
23 | it('should throw when n < 0', function () {
24 | (function () {
25 | main.fibonacci(-1);
26 | }).should.throw('n should >= 0');
27 | });
28 |
29 | if ('should throw when n isnt Number', function () {
30 | (function () {
31 | main.fibonacci('呵呵呵');
32 | }).should.throw('n should be a Number');
33 | });
34 | });
35 |
36 | // should 断言库的一些其他用法
37 | /*
38 | 1、测试一个数是不是大于 3:
39 | (5).should.above(3)
40 | 2、测试一个字符串是否有着特定的前缀:
41 | 'foo'.should.startWith('foo');
42 | */
--------------------------------------------------------------------------------
/Node/Node.js 包教不包会/lesson7/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lesson7",
3 | "version": "0.0.0",
4 | "description": "",
5 | "main": "mocha.js",
6 | "scripts": {
7 | "test": "mocha-phantomjs index.html --ssl-protocol=any --ignore-ssl-errors=true"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/liuyib/study-note/blob/master/Node/alsotang-node-lessons/lesson6"
12 | },
13 | "keywords": [
14 | "test",
15 | "study"
16 | ],
17 | "author": "liuyibo",
18 | "license": "MIT"
19 | }
20 |
--------------------------------------------------------------------------------
/Node/Node.js 包教不包会/lesson7/vendor/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Mocha
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
23 |
24 |
25 |
28 |
29 |
30 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/Node/Node.js 包教不包会/lesson7/vendor/mocha.css:
--------------------------------------------------------------------------------
1 | @charset "utf-8";
2 |
3 | body {
4 | margin:0;
5 | }
6 |
7 | #mocha {
8 | font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif;
9 | margin: 60px 50px;
10 | }
11 |
12 | #mocha ul,
13 | #mocha li {
14 | margin: 0;
15 | padding: 0;
16 | }
17 |
18 | #mocha ul {
19 | list-style: none;
20 | }
21 |
22 | #mocha h1,
23 | #mocha h2 {
24 | margin: 0;
25 | }
26 |
27 | #mocha h1 {
28 | margin-top: 15px;
29 | font-size: 1em;
30 | font-weight: 200;
31 | }
32 |
33 | #mocha h1 a {
34 | text-decoration: none;
35 | color: inherit;
36 | }
37 |
38 | #mocha h1 a:hover {
39 | text-decoration: underline;
40 | }
41 |
42 | #mocha .suite .suite h1 {
43 | margin-top: 0;
44 | font-size: .8em;
45 | }
46 |
47 | #mocha .hidden {
48 | display: none;
49 | }
50 |
51 | #mocha h2 {
52 | font-size: 12px;
53 | font-weight: normal;
54 | cursor: pointer;
55 | }
56 |
57 | #mocha .suite {
58 | margin-left: 15px;
59 | }
60 |
61 | #mocha .test {
62 | margin-left: 15px;
63 | overflow: hidden;
64 | }
65 |
66 | #mocha .test.pending:hover h2::after {
67 | content: '(pending)';
68 | font-family: arial, sans-serif;
69 | }
70 |
71 | #mocha .test.pass.medium .duration {
72 | background: #c09853;
73 | }
74 |
75 | #mocha .test.pass.slow .duration {
76 | background: #b94a48;
77 | }
78 |
79 | #mocha .test.pass::before {
80 | content: '✓';
81 | font-size: 12px;
82 | display: block;
83 | float: left;
84 | margin-right: 5px;
85 | color: #00d6b2;
86 | }
87 |
88 | #mocha .test.pass .duration {
89 | font-size: 9px;
90 | margin-left: 5px;
91 | padding: 2px 5px;
92 | color: #fff;
93 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
94 | -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
95 | box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
96 | -webkit-border-radius: 5px;
97 | -moz-border-radius: 5px;
98 | -ms-border-radius: 5px;
99 | -o-border-radius: 5px;
100 | border-radius: 5px;
101 | }
102 |
103 | #mocha .test.pass.fast .duration {
104 | display: none;
105 | }
106 |
107 | #mocha .test.pending {
108 | color: #0b97c4;
109 | }
110 |
111 | #mocha .test.pending::before {
112 | content: '◦';
113 | color: #0b97c4;
114 | }
115 |
116 | #mocha .test.fail {
117 | color: #c00;
118 | }
119 |
120 | #mocha .test.fail pre {
121 | color: black;
122 | }
123 |
124 | #mocha .test.fail::before {
125 | content: '✖';
126 | font-size: 12px;
127 | display: block;
128 | float: left;
129 | margin-right: 5px;
130 | color: #c00;
131 | }
132 |
133 | #mocha .test pre.error {
134 | color: #c00;
135 | max-height: 300px;
136 | overflow: auto;
137 | }
138 |
139 | #mocha .test .html-error {
140 | overflow: auto;
141 | color: black;
142 | line-height: 1.5;
143 | display: block;
144 | float: left;
145 | clear: left;
146 | font: 12px/1.5 monaco, monospace;
147 | margin: 5px;
148 | padding: 15px;
149 | border: 1px solid #eee;
150 | max-width: 85%; /*(1)*/
151 | max-width: -webkit-calc(100% - 42px);
152 | max-width: -moz-calc(100% - 42px);
153 | max-width: calc(100% - 42px); /*(2)*/
154 | max-height: 300px;
155 | word-wrap: break-word;
156 | border-bottom-color: #ddd;
157 | -webkit-box-shadow: 0 1px 3px #eee;
158 | -moz-box-shadow: 0 1px 3px #eee;
159 | box-shadow: 0 1px 3px #eee;
160 | -webkit-border-radius: 3px;
161 | -moz-border-radius: 3px;
162 | border-radius: 3px;
163 | }
164 |
165 | #mocha .test .html-error pre.error {
166 | border: none;
167 | -webkit-border-radius: 0;
168 | -moz-border-radius: 0;
169 | border-radius: 0;
170 | -webkit-box-shadow: 0;
171 | -moz-box-shadow: 0;
172 | box-shadow: 0;
173 | padding: 0;
174 | margin: 0;
175 | margin-top: 18px;
176 | max-height: none;
177 | }
178 |
179 | /**
180 | * (1): approximate for browsers not supporting calc
181 | * (2): 42 = 2*15 + 2*10 + 2*1 (padding + margin + border)
182 | * ^^ seriously
183 | */
184 | #mocha .test pre {
185 | display: block;
186 | float: left;
187 | clear: left;
188 | font: 12px/1.5 monaco, monospace;
189 | margin: 5px;
190 | padding: 15px;
191 | border: 1px solid #eee;
192 | max-width: 85%; /*(1)*/
193 | max-width: -webkit-calc(100% - 42px);
194 | max-width: -moz-calc(100% - 42px);
195 | max-width: calc(100% - 42px); /*(2)*/
196 | word-wrap: break-word;
197 | border-bottom-color: #ddd;
198 | -webkit-box-shadow: 0 1px 3px #eee;
199 | -moz-box-shadow: 0 1px 3px #eee;
200 | box-shadow: 0 1px 3px #eee;
201 | -webkit-border-radius: 3px;
202 | -moz-border-radius: 3px;
203 | border-radius: 3px;
204 | }
205 |
206 | #mocha .test h2 {
207 | position: relative;
208 | }
209 |
210 | #mocha .test a.replay {
211 | position: absolute;
212 | top: 3px;
213 | right: 0;
214 | text-decoration: none;
215 | vertical-align: middle;
216 | display: block;
217 | width: 15px;
218 | height: 15px;
219 | line-height: 15px;
220 | text-align: center;
221 | background: #eee;
222 | font-size: 15px;
223 | -webkit-border-radius: 15px;
224 | -moz-border-radius: 15px;
225 | border-radius: 15px;
226 | -webkit-transition:opacity 200ms;
227 | -moz-transition:opacity 200ms;
228 | -o-transition:opacity 200ms;
229 | transition: opacity 200ms;
230 | opacity: 0.3;
231 | color: #888;
232 | }
233 |
234 | #mocha .test:hover a.replay {
235 | opacity: 1;
236 | }
237 |
238 | #mocha-report.pass .test.fail {
239 | display: none;
240 | }
241 |
242 | #mocha-report.fail .test.pass {
243 | display: none;
244 | }
245 |
246 | #mocha-report.pending .test.pass,
247 | #mocha-report.pending .test.fail {
248 | display: none;
249 | }
250 | #mocha-report.pending .test.pass.pending {
251 | display: block;
252 | }
253 |
254 | #mocha-error {
255 | color: #c00;
256 | font-size: 1.5em;
257 | font-weight: 100;
258 | letter-spacing: 1px;
259 | }
260 |
261 | #mocha-stats {
262 | position: fixed;
263 | top: 15px;
264 | right: 10px;
265 | font-size: 12px;
266 | margin: 0;
267 | color: #888;
268 | z-index: 1;
269 | }
270 |
271 | #mocha-stats .progress {
272 | float: right;
273 | padding-top: 0;
274 |
275 | /**
276 | * Set safe initial values, so mochas .progress does not inherit these
277 | * properties from Bootstrap .progress (which causes .progress height to
278 | * equal line height set in Bootstrap).
279 | */
280 | height: auto;
281 | -webkit-box-shadow: none;
282 | -moz-box-shadow: none;
283 | box-shadow: none;
284 | background-color: initial;
285 | }
286 |
287 | #mocha-stats em {
288 | color: black;
289 | }
290 |
291 | #mocha-stats a {
292 | text-decoration: none;
293 | color: inherit;
294 | }
295 |
296 | #mocha-stats a:hover {
297 | border-bottom: 1px solid #eee;
298 | }
299 |
300 | #mocha-stats li {
301 | display: inline-block;
302 | margin: 0 5px;
303 | list-style: none;
304 | padding-top: 11px;
305 | }
306 |
307 | #mocha-stats canvas {
308 | width: 40px;
309 | height: 40px;
310 | }
311 |
312 | #mocha code .comment { color: #ddd; }
313 | #mocha code .init { color: #2f6fad; }
314 | #mocha code .string { color: #5890ad; }
315 | #mocha code .keyword { color: #8a6343; }
316 | #mocha code .number { color: #2f6fad; }
317 |
318 | @media screen and (max-device-width: 480px) {
319 | #mocha {
320 | margin: 60px 0px;
321 | }
322 |
323 | #mocha #stats {
324 | position: absolute;
325 | }
326 | }
327 |
--------------------------------------------------------------------------------
/Node/Node.js 包教不包会/lesson7/vendor/tests.js:
--------------------------------------------------------------------------------
1 | var should = chai.should();
2 |
3 | describe('simple test', function () {
4 | it('should equal 0 when n === 0', function () {
5 | window.fibonacci(0).should.equal(0);
6 | });
7 | });
--------------------------------------------------------------------------------
/Node/七天学会 Node.js/README.md:
--------------------------------------------------------------------------------
1 | # 七天学会 NodeJS
2 |
3 | 教程地址:[https://nqdeng.github.io/7-days-nodejs/](https://nqdeng.github.io/7-days-nodejs/)
4 |
--------------------------------------------------------------------------------
/Node/七天学会 Node.js/cat.test/tests.js:
--------------------------------------------------------------------------------
1 | var cat = require('../cat');
2 |
3 | console.log(cat.create('Tom'));
--------------------------------------------------------------------------------
/Node/七天学会 Node.js/cat/body.js:
--------------------------------------------------------------------------------
1 | var create = function () {
2 | return 'This is body';
3 | };
4 |
5 | exports.create = create;
--------------------------------------------------------------------------------
/Node/七天学会 Node.js/cat/head.js:
--------------------------------------------------------------------------------
1 | var create = function () {
2 | return 'This is head';
3 | };
4 |
5 | exports.create = create;
--------------------------------------------------------------------------------
/Node/七天学会 Node.js/cat/index.js:
--------------------------------------------------------------------------------
1 | var head = require('./head');
2 | var body = require('./body');
3 |
4 | var create = function (name) {
5 | return {
6 | name: name,
7 | head: head.create(),
8 | body: body.create()
9 | };
10 | };
11 |
12 | exports.create = create;
--------------------------------------------------------------------------------
/Node/七天学会 Node.js/myCopy/big-copy.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 |
3 | function copy(source, target) {
4 | fs.createReadStream(source).pipe(fs.createWriteStream(target));
5 | }
6 |
7 | function main(src) {
8 | copy(src[0], src[1]);
9 | }
10 |
11 | main(process.argv.slice(2));
12 |
13 | // 在命令行中执行命令 node big-copy.js /opt/aaa.pdf /tmp/test/bbb.pdf
14 | // 即可将 /opt/aaa.pdf 复制到 /tmp/test/bbb.pdf 中。(复制的时候会自动创建 bbb.pdf 文件)
15 | // 这个复制操作适用于大文件的拷贝
--------------------------------------------------------------------------------
/Node/七天学会 Node.js/myCopy/small-copy.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 |
3 | /**
4 | * 复制文件
5 | * @param {String} source 复制源文件
6 | * @param {String} target 复制目标文件
7 | */
8 | function copy(source, target) {
9 | fs.writeFileSync(target, fs.readFileSync(source));
10 | }
11 |
12 | /**
13 | * 接收一个路径数组,进行复制文件
14 | * @param {Array} src 复制文件的路径
15 | */
16 | function main(src) {
17 | copy(src[0], src[1]);
18 | }
19 |
20 | // process 是一个全局的变量。process.argv 用于获取命令行中的参数
21 | main(process.argv.slice(2));
22 |
23 | // 命令行中执行 node small-copy.js /opt/aaa.txt /tmp/bbb.txt
24 | // 然后 /opt/aaa.txt 文件里的内容就会被复制到 /tmp/bbb.txt 里。(复制的时候会自动创建 bbb.txt 文件)
25 | // 这个复制操作只适用于小文件的拷贝
--------------------------------------------------------------------------------
/Node/七天学会 Node.js/node-echo.test/tests.js:
--------------------------------------------------------------------------------
1 | var echo = require('../node-echo');
2 |
3 | console.log(echo('This is test'));
--------------------------------------------------------------------------------
/Node/七天学会 Node.js/node-echo/README.md:
--------------------------------------------------------------------------------
1 | 工程目录:
2 |
3 | ```shell
4 | - node-echo/ # 工程目录
5 | - bin/ # 存放命令行相关代码
6 | node-echo
7 | + doc/ # 存放文档
8 | - lib/ # 存放API相关代码
9 | echo.js
10 | + node_modules/ # 存放三方包
11 | + tests/ # 存放测试用例
12 | package.json
13 | README.md
14 | ```
15 |
16 | 这个部分文件示例如下:
17 |
18 | bin/node-echo.js:
19 | 输出命令行中的参数。
20 | ```node
21 | var echo = require('../lib/echo');
22 | var argv = process.argv.slice(2); // 命令行中的参数
23 |
24 | console.log(echo(argv.join(' ')));
25 |
26 | // 例如,命令行中执行下面这条命令:
27 | $ node node-echo.js hello world
28 | // => hello world
29 | ```
30 |
31 | lib/echo.js:
32 | 返回命令行中的参数。
33 |
34 | ```node
35 | module.exports = function (mess) {
36 | return mess;
37 | };
38 | ```
--------------------------------------------------------------------------------
/Node/七天学会 Node.js/node-echo/bin/node-echo.js:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env node
2 |
3 | var echo = require('../lib/echo');
4 | var argv = process.argv.slice(2); // 命令行中的参数
5 |
6 | console.log(echo(argv.join(' ')));
--------------------------------------------------------------------------------
/Node/七天学会 Node.js/node-echo/lib/echo.js:
--------------------------------------------------------------------------------
1 | module.exports = function (mess) {
2 | return mess;
3 | };
--------------------------------------------------------------------------------
/Node/七天学会 Node.js/node-echo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "node-echo",
3 | "version": "0.0.1",
4 | "description": "一个标准的 node 工程目录示例",
5 | "main": "./lib/echo.js",
6 | "bin": {
7 | "node-echo": "node-echo.js"
8 | },
9 | "directories": {
10 | "doc": "doc",
11 | "test": "tests"
12 | },
13 | "scripts": {
14 | "test": "echo \"no test specified\""
15 | },
16 | "keywords": [
17 | "test",
18 | "study"
19 | ],
20 | "author": "liuyibo",
21 | "license": "MIT"
22 | }
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 本仓库分为两部分,一是学习笔记,二是知识总结。
2 |
3 | 学习是一个重复的过程,很多时候学了一大堆知识,很快就会忘记,即使做了笔记。因此,对知识进行**系统的总结**显得尤为重要。
4 |
5 | 知识不成体系,也是基础薄弱的一个明显标志。记得有大佬说过 **“真正专业的人,不是获得了多厉害的奖,也不是学了多难的东西,而是脑海中有着成体系的知识”**。
6 |
7 | 当然,为了形成知识体系,并不建议从头到尾把所有内容重新学习一遍。这就陷入了“完美主义”的怪圈,**学习中“完成”胜过“完美”**。
8 |
9 | 那些制定完美计划的、为了巩固基础想要重新学习所有知识的,最终几乎都会以失败告终。正确的做法应该是:**在原来基础上,对知识点逐个击破,然后对知识进行系统的总结**(如果害怕自己总结不好,可以跟着系统的教程、视频进行总结)。
10 |
11 | ## 学习笔记
12 |
13 | - HTML
14 |
15 | - 《大厂 H5 开发实战》- 掘金小册
16 |
17 | - [基础页面开发](<./HTML(5)/HTML5/大厂%20H5%20开发实战/基础页面开发.md>)
18 |
19 | - CSS(3)
20 |
21 | - [CSS 重排与重绘笔记](<./CSS(3)/CSS%20重排与重绘笔记.md>)
22 | - [BEM 命名规范笔记](<./CSS(3)/BEM%20命名规范笔记.md>)
23 | - [深入理解 font-size 笔记](<./CSS(3)/深入理解%20font-size%20笔记.md>)
24 | - [CSS 实现等比缩放](<./CSS(3)/CSS%20实现等比缩放.md>)
25 |
26 | - JavaScript
27 |
28 | - [ES6 笔记](./JavaScript/ES6%20笔记/README.md)
29 | - [零碎知识笔记](./JavaScript/零碎知识笔记.md)
30 |
31 | - 数据结构 ==> 移步:[https://github.com/liuyib/fucking-algorithm](https://github.com/liuyib/fucking-algorithm)
32 |
33 | - Node
34 |
35 | - [Node.js 包教不包会](./Node/Node.js%20包教不包会/README.md)
36 | - [七天学会 Node.js](./Node/七天学会%20Node.js/README.md)
37 |
38 | - 打包工具
39 |
40 | - [Gulp 学习笔记](./打包工具/gulp/README.md)
41 | - [Rollup 学习笔记](./打包工具/rollup/README.md)
42 | - [webpack 学习笔记](./打包工具/webpack/README.md)
43 |
44 | - 读书笔记
45 |
46 | - [你不知道的 JS](./读书笔记/你不知道的%20JS/README.md)
47 | - [JS 高级程序设计](./读书笔记/JS%20高级程序设计/README.md)
48 |
49 | ## 系统总结
50 |
51 | - HTML(5)
52 |
53 | - [HTML5 知识总结](<./HTML(5)/HTML5/HTML5%20知识总结.md>)
54 | - [WebSocket 学习总结](<./HTML(5)/HTML5/WebSocket%20学习总结.md>)
55 | - [像素、分辨率相关概念](<./HTML(5)/HTML5/像素、分辨率相关概念.md>)
56 | - [探究移动端适配](<./HTML(5)/HTML5/探究移动端适配.md>)
57 | - [1px 的解决方案](<./HTML(5)/HTML5/1px%20的解决方案.md>)
58 |
59 | - CSS(3)
60 |
61 | - [七种水平垂直居中方法总结](<./CSS(3)/七种水平垂直居中方法总结.md>)
62 | - [CSS 面试题总结](<./CSS(3)/CSS面试题总结.md>)
63 |
64 | - JavaScript
65 |
66 | - [JS 基础知识点 1](./JavaScript/JS%20基础知识点1.md)
67 | - [JS 基础知识点 2](./JavaScript/JS%20基础知识点2.md)
68 | - 手写系列总结
69 |
70 | - [手写 Ajax](./JavaScript/手写系列/手写%20Ajax.md)
71 | - [手写 JSONP](./JavaScript/手写系列/手写%20JSONP.md)
72 | - [手写 XXX](./JavaScript/手写系列/手写%20XXX.md)
73 |
74 | - [正则表达式总结](./JavaScript/正则表达式总结.md)
75 | - [从 JS 高阶函数到柯里化](./JavaScript/从%20JS%20高阶函数到柯里化.md)
76 | - [深入理解 JS 事件循环机制(浏览器篇)](./浏览器相关/深入理解%20JS%20事件循环机制(浏览器篇).md)
77 |
78 | - 网络相关
79 |
80 | - [Web 安全总结](./网络相关/Web%20安全/Web%20安全总结.md)
81 |
82 | - [密码安全](./网络相关/Web%20安全/密码安全/README.md)
83 | - [Cookie 安全](./网络相关/Web%20安全/Cookie%20安全/README.md)
84 | - [HTML5 安全](./网络相关/Web%20安全/HTML5%20安全/README.md)
85 | - [XSS 攻击](./网络相关/Web%20安全/XSS%20攻击/README.md)
86 | - [CSRF 攻击](./网络相关/Web%20安全/CSRF%20攻击/README.md)
87 | - [点击劫持攻击](./网络相关/Web%20安全/点击劫持攻击/README.md)
88 | - [SQL 注入攻击](./网络相关/Web%20安全/SQL%20注入攻击/README.md)
89 | - [上传文件攻击](./网络相关/Web%20安全/上传文件攻击/README.md)
90 |
91 | - HTTP(S) 相关
92 |
93 | - [深入探究 TLS 协议](<./网络相关/HTTP(S)%20相关/深入探究%20TLS%20协议.md>)
94 |
95 | - TCP 相关
96 |
97 | - [深入探究 TCP 协议](./网络相关/TCP%20相关/深入探究%20TCP%20协议.md)
98 |
99 | - 浏览器相关
100 |
101 | - [跨域及其实现原理总结](./浏览器相关/跨域及其实现原理.md)
102 |
103 | - 性能优化
104 |
105 | - [浏览器缓存机制总结](./性能优化/浏览器缓存机制总结.md)
106 | - [Web 性能优化总结](./性能优化/Web%20性能优化总结.md)
107 |
108 | - 设计模式
109 |
110 | - [前端常用的设计模式](./设计模式/前端常用的设计模式.md)
111 |
112 | - 版本控制
113 |
114 | - [Git 相关总结](./版本控制/Git%20相关总结.md)
115 |
116 | - 代码规范
117 |
118 | - [Stranard 规范总结](./代码规范/Stranard%20规范总结.md)
119 |
120 | - UI 设计
121 |
122 | - [MEB 风格图标设计](./UI%20设计/MBE风格图标设计/README.md)
123 | - [Photoshop (PS)](<./UI%20设计/Photoshop%20(PS)/README.md>)
124 |
125 | ## 模仿面试
126 |
127 | - [面试前八股速查](./模仿面试/八股速查.md)
128 | - [面试前必刷算法题](https://github.com/liuyib/fucking-algorithm#面试前必刷题)
129 |
--------------------------------------------------------------------------------
/React/egghead/start-learning-react/JSX编译器.md:
--------------------------------------------------------------------------------
1 | # JSX 编译器
2 |
3 | 使用 Babel 在线进行解析 React:
4 |
5 | ```js
6 | import React, { Component } from 'react';
7 | import './App.css';
8 |
9 | export class App extends Component {
10 | constructor(props) {
11 | super(props);
12 |
13 | this.state = {
14 | input:
15 | 'const App = () => {\n return (\n hello world
\n );\n}\nexport default App;\n',
16 | output: '',
17 | error: ''
18 | };
19 | }
20 |
21 | update = e => {
22 | let code = e.target.value;
23 |
24 | try {
25 | this.setState({
26 | output: window.Babel.transform(code, { presets: ['es2015', 'react'] })
27 | .code,
28 | error: ''
29 | });
30 | } catch (err) {
31 | this.setState({
32 | error: err.message
33 | });
34 | }
35 | };
36 |
37 | render() {
38 | return (
39 |
40 |
41 |
42 |
{this.state.output}
43 |
44 | );
45 | }
46 | }
47 |
48 | export default App;
49 | ```
50 |
51 | 运行效果:
52 |
53 | [Click to CodePen](https://codepen.io/liuyib/pen/bJbepZ/)
54 |
--------------------------------------------------------------------------------
/React/egghead/start-learning-react/README.md:
--------------------------------------------------------------------------------
1 | # egghead 课程 Start Learning React
2 |
3 | 课程视频地址:[https://egghead.io/courses/start-learning-react](https://egghead.io/courses/start-learning-react)
4 |
--------------------------------------------------------------------------------
/React/egghead/start-learning-react/React.children和React.cloneElement.md:
--------------------------------------------------------------------------------
1 | # React.children 和 React.cloneElement
2 |
3 | 官网文档:
4 | [React.children](https://reactjs.org/docs/react-api.html#reactchildren)
5 | [React.cloneElement](https://reactjs.org/docs/react-api.html#cloneelement)
6 |
7 | 理解 `React.children` 和 `React.cloneElement`:
8 |
9 | ```js
10 | import React, { Component } from 'react';
11 |
12 | class App extends Component {
13 | render() {
14 | return (
15 |
20 | );
21 | }
22 | }
23 |
24 | class Button extends Component {
25 | constructor(props) {
26 | super(props);
27 |
28 | this.state = { selected: 'None' };
29 | }
30 |
31 | clickButton = selected => {
32 | this.setState({ selected });
33 | };
34 |
35 | render() {
36 | const { children } = this.props;
37 | const items = React.Children.map(children, child => {
38 | return React.cloneElement(child, {
39 | onClick: this.clickButton.bind(this, child.props.children)
40 | });
41 | });
42 |
43 | return (
44 |
45 |
You have selected: {this.state.selected}
46 | {items}
47 |
48 | );
49 | }
50 | }
51 |
52 | export default App;
53 | ```
54 |
55 | 效果如下:
56 |
57 | 
58 |
--------------------------------------------------------------------------------
/React/egghead/start-learning-react/imgs/React.children_React.cloneElement.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/React/egghead/start-learning-react/imgs/React.children_React.cloneElement.gif
--------------------------------------------------------------------------------
/React/egghead/start-learning-react/imgs/react-HOC.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/React/egghead/start-learning-react/imgs/react-HOC.gif
--------------------------------------------------------------------------------
/React/egghead/start-learning-react/imgs/react-event-system.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/React/egghead/start-learning-react/imgs/react-event-system.gif
--------------------------------------------------------------------------------
/React/egghead/start-learning-react/imgs/react_ref.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/React/egghead/start-learning-react/imgs/react_ref.gif
--------------------------------------------------------------------------------
/React/egghead/start-learning-react/imgs/reusable_component.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/React/egghead/start-learning-react/imgs/reusable_component.gif
--------------------------------------------------------------------------------
/React/egghead/start-learning-react/事件系统.md:
--------------------------------------------------------------------------------
1 | # 事件系统
2 |
3 | 当事件触发的时候,打印出事件的类型:
4 |
5 | ```js
6 | import React, { Component } from 'react';
7 |
8 | class App extends Component {
9 | constructor() {
10 | super();
11 |
12 | this.state = {
13 | currentEvent: `Event type: ---`
14 | };
15 | this.update = this.update.bind(this);
16 | }
17 |
18 | update(e) {
19 | this.setState({
20 | currentEvent: `Event type: ${e.type}`
21 | });
22 | }
23 |
24 | render() {
25 | return (
26 |
27 |
42 |
{this.state.currentEvent}
43 |
44 | );
45 | }
46 | }
47 |
48 | export default App;
49 | ```
50 |
51 | 实现效果如下:
52 |
53 | 
54 |
--------------------------------------------------------------------------------
/React/egghead/start-learning-react/编写可重用的组件.md:
--------------------------------------------------------------------------------
1 | # 编写可重用的组件
2 |
3 | 通过给组件传入参数,编写可重用的组件:
4 |
5 | ```js
6 | import React, { Component } from 'react';
7 | import PropTypes from 'prop-types';
8 |
9 | class App extends Component {
10 | constructor(props) {
11 | super(props);
12 |
13 | this.state = {
14 | inputValue: 0,
15 | inputType: 'range'
16 | };
17 | }
18 |
19 | update = e => {
20 | this.setState({
21 | inputValue: e.target.value
22 | });
23 | };
24 |
25 | toggle = () => {
26 | this.setState({
27 | inputType: this.state.inputType === 'range' ? 'number' : 'range'
28 | });
29 | };
30 |
31 | render() {
32 | return (
33 |
34 | {/* 使用自定义的组件 */}
35 |
44 | 点击切换输入
45 |
46 | );
47 | }
48 | }
49 |
50 | // 可重用的组件
51 | class NumInput extends Component {
52 | render() {
53 | const { type, min, max, step, value, update, label } = this.props;
54 |
55 | return (
56 |
57 |
65 | {label !== '' ? (
66 |
69 | ) : (
70 | ''
71 | )}
72 |
73 | );
74 | }
75 | }
76 |
77 | // 对参数进行类型检查
78 | NumInput.propTypes = {
79 | type: PropTypes.oneOf(['number', 'range']),
80 | min: PropTypes.number,
81 | max: PropTypes.number,
82 | step: PropTypes.number,
83 | value: PropTypes.number,
84 | update: PropTypes.func.isRequired,
85 | label: PropTypes.string
86 | };
87 |
88 | // 默认参数值
89 | NumInput.defaultProps = {
90 | type: 'range',
91 | min: 0,
92 | max: 0,
93 | step: 1,
94 | value: 0,
95 | label: ''
96 | };
97 |
98 | export default App;
99 | ```
100 |
101 | > `React.RropTypes` 自 React v15.5 就已经弃用。使用 `prop-types` 库代替
102 |
103 | 效果如下:
104 |
105 | 
106 |
--------------------------------------------------------------------------------
/React/egghead/start-learning-react/获取组件的引用.md:
--------------------------------------------------------------------------------
1 | # 获取组件引用
2 |
3 | 几种获取元素 / 组件引用的方式:
4 |
5 | ```js
6 | import React, { Component } from 'react';
7 | import ReactDOM from 'react-dom';
8 |
9 | class App extends Component {
10 | constructor() {
11 | super();
12 |
13 | this.state = {
14 | a: '',
15 | b: '',
16 | c: '',
17 | d: ''
18 | };
19 | this.update = this.update.bind(this);
20 | }
21 |
22 | update(e) {
23 | this.setState({
24 | a: e.target.value,
25 | b: this.refs.bbb.value,
26 | c: this.ccc.value,
27 | d: ReactDOM.findDOMNode(this.ddd).value
28 | });
29 | }
30 |
31 | render() {
32 | return (
33 |
34 |
35 | {this.state.a}
36 |
37 | {this.state.b}
38 | (this.ccc = input)} onChange={this.update} />
39 | {this.state.c}
40 | (this.ddd = component)}
42 | update={this.update}
43 | />
44 | {this.state.d}
45 |
46 | );
47 | }
48 | }
49 |
50 | class MyInput extends Component {
51 | render() {
52 | return ;
53 | }
54 | }
55 |
56 | export default App;
57 | ```
58 |
59 | 总结一下,获取**元素 / 组件**引用的四种方法:
60 |
61 | ```js
62 | // 直接获取(元素 / 组件)
63 | e.target.value;
64 |
65 | // 给元素添加 ref 属性,然后使用 this.refs.xxx 获取
66 | this.refs.xxx.value;
67 |
68 | // 给元素的 ref 属性传递回调函数,用来给 this.refs.xxx 起一个别名
69 | this.xxx.value;
70 |
71 | // 使用 react-dom 提供的 API,获取组件的引用
72 | ReactDOM.findDOMNode(this.refs.xxx).value;
73 | ```
74 |
75 | 实现效果如下:
76 |
77 | 
78 |
79 | 当组件中,要获取引用的元素不在最外层时,使用 `ReactDOM.findDOMNode(this.refs.xxx)` 无法获取到该元素的引用。
80 |
81 | 例如:
82 |
83 | ```js
84 | class MyInput extends Component {
85 | render() {
86 | return (
87 |
88 |
89 |
90 | );
91 | }
92 | }
93 | ```
94 |
95 | 这种情况下,`input` 不在最外层,无法通过 `ReactDOM.findDOMNode(this.refs.xxx)` 获取这个 `input` 的引用。
96 |
97 | 可以给 `MyInput` 组件中的 `input` 元素添加 `ref`,然后进行再次引用:
98 |
99 | ```js
100 | class MyInput extends Component {
101 | render() {
102 | return (
103 |
104 |
105 |
106 | );
107 | }
108 | }
109 | ```
110 |
111 | 然后获取其引用:
112 |
113 | ```js
114 | this.ddd.refs.myInput.value;
115 | ```
116 |
--------------------------------------------------------------------------------
/React/egghead/start-learning-react/高价组件.md:
--------------------------------------------------------------------------------
1 | # 高价组件
2 |
3 | 高价组件(HOC)就是一个函数,而且是一个没有副作用的纯函数。它接收一个组件作为参数,并返回一个新的组件。
4 |
5 | 高价组件用来重写组件逻辑。它并不是 React API,它只是一种模式。
6 |
7 | 高价组件不会修改输入组件,也不会使用继承拷贝它的行为。而是组合原始组件,通过一个容器组件包裹原始组件。
8 |
9 | 示例:
10 |
11 | ```js
12 | import React, { Component } from 'react';
13 |
14 | // HOC: Heigher Order Component
15 | const HOC = InnerComponent =>
16 | class WrapperComponent extends Component {
17 | constructor(props) {
18 | super(props);
19 |
20 | this.state = { count: 0 };
21 | }
22 |
23 | componentWillMount = () => {
24 | console.log('HOC will mount');
25 | };
26 |
27 | update = () => {
28 | this.setState({ count: this.state.count + 1 });
29 | };
30 |
31 | render() {
32 | return (
33 |
34 | );
35 | }
36 | };
37 |
38 | const ButtonHOC = HOC(props => (
39 |
40 | {props.children} - {props.count}
41 |
42 | ));
43 |
44 | class Label extends Component {
45 | componentWillMount = () => {
46 | console.log('Label will mount');
47 | };
48 |
49 | render() {
50 | return (
51 |
54 | );
55 | }
56 | }
57 |
58 | const LabelHOC = HOC(Label);
59 |
60 | class App extends Component {
61 | render() {
62 | return (
63 |
64 | label
65 |
66 | button
67 |
68 | );
69 | }
70 | }
71 |
72 | export default App;
73 | ```
74 |
75 | 效果如下:
76 |
77 | 
78 |
--------------------------------------------------------------------------------
/UI 设计/MBE风格图标设计/README.md:
--------------------------------------------------------------------------------
1 | # MBE 风格图标练习
2 |
3 | > 原图来源:[https://dribbble.com/Madebyelvis](https://dribbble.com/Madebyelvis)
4 | >
5 | > 本库中所有由我制作的图片(不包括原图)均可非商业任意使用,遵循 `MIT` 开源协议
6 | >
7 | > PSD 源文件已上传到库中,可随意下载
8 |
9 | | 原图 | 我的(有背景图) | 我的(无背景图) |
10 | | :--------------------------------------------------------: | :--------------------------------------------------------------------------: | :----------------------------------------------------------------------------: |
11 | |  |  |  |
12 | |  |  |  |
13 | |  |  |  |
14 | |  |  |  |
15 | |  |  |  |
16 |
--------------------------------------------------------------------------------
/UI 设计/MBE风格图标设计/imgs/515_cloud_disk_logo.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/UI 设计/MBE风格图标设计/imgs/515_cloud_disk_logo.psd
--------------------------------------------------------------------------------
/UI 设计/MBE风格图标设计/imgs/515_cloud_disk_logo_background.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/UI 设计/MBE风格图标设计/imgs/515_cloud_disk_logo_background.jpg
--------------------------------------------------------------------------------
/UI 设计/MBE风格图标设计/imgs/515_cloud_disk_logo_transparent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/UI 设计/MBE风格图标设计/imgs/515_cloud_disk_logo_transparent.png
--------------------------------------------------------------------------------
/UI 设计/MBE风格图标设计/imgs/515_cloud_disk_origin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/UI 设计/MBE风格图标设计/imgs/515_cloud_disk_origin.png
--------------------------------------------------------------------------------
/UI 设计/MBE风格图标设计/imgs/chandle.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/UI 设计/MBE风格图标设计/imgs/chandle.psd
--------------------------------------------------------------------------------
/UI 设计/MBE风格图标设计/imgs/chandle_background.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/UI 设计/MBE风格图标设计/imgs/chandle_background.jpg
--------------------------------------------------------------------------------
/UI 设计/MBE风格图标设计/imgs/chandle_origin.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/UI 设计/MBE风格图标设计/imgs/chandle_origin.jpg
--------------------------------------------------------------------------------
/UI 设计/MBE风格图标设计/imgs/chandle_transparent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/UI 设计/MBE风格图标设计/imgs/chandle_transparent.png
--------------------------------------------------------------------------------
/UI 设计/MBE风格图标设计/imgs/egg_love.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/UI 设计/MBE风格图标设计/imgs/egg_love.psd
--------------------------------------------------------------------------------
/UI 设计/MBE风格图标设计/imgs/egg_love_background.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/UI 设计/MBE风格图标设计/imgs/egg_love_background.jpg
--------------------------------------------------------------------------------
/UI 设计/MBE风格图标设计/imgs/egg_love_origin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/UI 设计/MBE风格图标设计/imgs/egg_love_origin.png
--------------------------------------------------------------------------------
/UI 设计/MBE风格图标设计/imgs/egg_love_transparent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/UI 设计/MBE风格图标设计/imgs/egg_love_transparent.png
--------------------------------------------------------------------------------
/UI 设计/MBE风格图标设计/imgs/ice_cream.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/UI 设计/MBE风格图标设计/imgs/ice_cream.psd
--------------------------------------------------------------------------------
/UI 设计/MBE风格图标设计/imgs/ice_cream2.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/UI 设计/MBE风格图标设计/imgs/ice_cream2.psd
--------------------------------------------------------------------------------
/UI 设计/MBE风格图标设计/imgs/ice_cream2_background.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/UI 设计/MBE风格图标设计/imgs/ice_cream2_background.jpg
--------------------------------------------------------------------------------
/UI 设计/MBE风格图标设计/imgs/ice_cream2_origin.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/UI 设计/MBE风格图标设计/imgs/ice_cream2_origin.jpg
--------------------------------------------------------------------------------
/UI 设计/MBE风格图标设计/imgs/ice_cream2_transparent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/UI 设计/MBE风格图标设计/imgs/ice_cream2_transparent.png
--------------------------------------------------------------------------------
/UI 设计/MBE风格图标设计/imgs/ice_cream_background.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/UI 设计/MBE风格图标设计/imgs/ice_cream_background.jpg
--------------------------------------------------------------------------------
/UI 设计/MBE风格图标设计/imgs/ice_cream_origin.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/UI 设计/MBE风格图标设计/imgs/ice_cream_origin.jpg
--------------------------------------------------------------------------------
/UI 设计/MBE风格图标设计/imgs/ice_cream_transparent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/UI 设计/MBE风格图标设计/imgs/ice_cream_transparent.png
--------------------------------------------------------------------------------
/UI 设计/Photoshop (PS)/README.md:
--------------------------------------------------------------------------------
1 | # PS 基础技能学习
2 |
--------------------------------------------------------------------------------
/代码规范/Stranard 规范总结.md:
--------------------------------------------------------------------------------
1 | # JavaScript Standard 规范(部分)
2 |
3 | Standard 规范[完整版](https://github.com/standard/standard/blob/master/docs/RULES-zhcn.md)。
4 |
5 | 这里只是记录了该规范中,我不熟悉或需要再次提醒自己的地方。
6 |
7 | > 这个规范中有两个鲜明的特点是 **省略行尾的分号** 和 **函数名后加空格**。不过我还是更愿意始终保留行尾分号。
8 | >
9 | > **行首以 `(` `[` `` ` `` 这三种符号开头,是省略分号后,唯一会造成问题的地方。**
10 |
11 | - 函数声明时括号与函数名间加空格。
12 |
13 | ```js
14 | function name(arg) { ... } // ✗ avoid
15 | function name (arg) { ... } // ✓ ok
16 |
17 | run(function() { ... }) // ✗ avoid
18 | run(function () { ... }) // ✓ ok
19 | ```
20 |
21 | - 使用浏览器全局变量时加上 `window.` 前缀。`document`、`console` 和 `navigator` 除外。
22 |
23 | ```js
24 | window.alert('hi'); // ✓ ok
25 | ```
26 |
27 | - 对于三元运算符 `?` 和 `:` 与他们所负责的代码处于同一行。
28 |
29 | ```js
30 | // ✗ avoid
31 | let location = env.development ?
32 | 'localhost' :
33 | 'www.api.com';
34 |
35 | // ✓ ok
36 | let location = env.development ? 'localhost' : 'www.api.com';
37 |
38 | // ✓ ok
39 | let location = env.development
40 | ? 'localhost'
41 | : 'www.api.com';
42 | ```
43 |
44 | - 条件语句中赋值语句使用括号包起来。
45 |
46 | ```js
47 | while (m = text.match(expr)) { } // ✗ avoid
48 | while ((m = text.match(expr))) { } // ✓ ok
49 | ```
50 |
51 | - 单行代码块两边加空格。
52 |
53 | ```js
54 | function foo () {return true;} // ✗ avoid
55 | function foo () { return true; } // ✓ ok
56 | ```
57 |
58 | - 模板字符串中变量前后不加空格。
59 |
60 | ```js
61 | const message = `Hello, ${ name }`; // ✗ avoid
62 | const message = `Hello, ${name}`; // ✓ ok
63 | ```
64 |
65 | - 不允许有多余的行末逗号。
66 |
67 | ```js
68 | let obj = {
69 | message: 'hello', // ✗ avoid
70 | };
71 | ```
72 |
73 | - 不要使用 `undefined` 来初始化变量。
74 |
75 | ```js
76 | let name = undefined; // ✗ avoid
77 |
78 | let name;
79 | name = 'value'; // ✓ ok
80 | ```
81 |
82 | - `return` 语句中的赋值必需有括号包裹。
83 |
84 | ```js
85 | function sum (a, b) {
86 | return result = a + b; // ✗ avoid
87 | }
88 |
89 | function sum (a, b) {
90 | return (result = a + b); // ✓ ok
91 | }
92 | ```
93 |
94 | - 检查 `NaN` 的正确姿势是使用 `isNaN`。
95 |
96 | ```js
97 | if (price === NaN) { } // ✗ avoid
98 | if (isNaN(price)) { } // ✓ ok
99 | ```
100 |
101 | - 关系运算符的左值不要做取反操作。
102 |
103 | > 这里的关系运算符指:`in`、`instanceof`
104 |
105 | ```js
106 | if (!key in obj) { } // ✗ avoid
107 | if (!obj instanceof Ctor) { } // ✗ avoid
108 |
109 | if (!(key in object)) { } // ✓ ok
110 | if (!(obj instanceof Ctor)) { } // ✓ ok
111 | if(("" + !key) in object) { } // ✓ ok
112 | ```
113 |
114 | - 对象中定义了存值器,一定要对应的定义取值器。
115 |
116 | ```js
117 | let person = {
118 | set name (value) { // ✗ avoid
119 | this._name = value;
120 | }
121 | };
122 |
123 | let person = {
124 | set name (value) {
125 | this._name = value;
126 | },
127 | get name () { // ✓ ok
128 | return this._name;
129 | }
130 | };
131 | ```
132 |
133 | - 子类的构造器中一定要调用 `super`
134 |
135 | ```js
136 | class Dog {
137 | constructor () {
138 | super(); // ✗ avoid
139 | }
140 | }
141 |
142 | class Dog extends Mammal {
143 | constructor () {
144 | super(); // ✓ ok
145 | }
146 | }
147 | ```
148 |
149 | - 使用 `this` 前请确保 `super` 已调用。
150 |
151 | ```js
152 | class Dog extends Animal {
153 | constructor () {
154 | this.legs = 4; // ✗ avoid
155 | super();
156 | }
157 | }
158 | ```
159 |
160 | - 同一模块有多个导入时一次性写完。
161 |
162 | ```js
163 | import { myFunc1 } from 'module';
164 | import { myFunc2 } from 'module'; // ✗ avoid
165 |
166 | import { myFunc1, myFunc2 } from 'module'; // ✓ ok
167 | ```
168 |
169 | - `import`, `export` 和解构操作中,禁止赋值到同名变量。
170 |
171 | ```js
172 | import { config as config } from './config'; // ✗ avoid
173 | import { config } from './config'; // ✓ ok
174 | ```
175 |
176 | - `yield *` 中的 `*` 前后都要有空格。
177 |
178 | ```js
179 | yield* increment(); // ✗ avoid
180 | yield * increment(); // ✓ ok
181 | ```
182 |
183 | - 不要扩展原生对象。
184 |
185 | ```js
186 | Object.prototype.age = 21; // ✗ avoid
187 | ```
188 |
189 | - 注意隐式的 `eval`。
190 |
191 | ```js
192 | setTimeout("alert('Hello world')"); // ✗ avoid
193 | setTimeout(function () { alert('Hello world'); }); // ✓ ok
194 | ```
195 |
196 | - 嵌套的代码块中禁止再定义函数。
197 |
198 | ```js
199 | if (authenticated) {
200 | function setAuthUser () {} // ✗ avoid
201 | }
202 | ```
203 |
204 | - 外部变量不要与对象属性重名。
205 |
206 | ```js
207 | let score = 100;
208 |
209 | function game () {
210 | score: while (true) { // ✗ avoid
211 | score -= 10;
212 | if (score > 0) continue score;
213 | break;
214 | }
215 | }
216 | ```
217 |
218 | - 不要使用标签语句。
219 |
220 | ```js
221 | label:
222 | while (true) {
223 | break label; // ✗ avoid
224 | }
225 | ```
226 |
227 | - 使用 `__dirname` 和 `__filename` 时尽量避免使用字符串拼接。
228 |
229 | ```js
230 | const pathToFile = __dirname + '/app.js'; // ✗ avoid
231 | const pathToFile = path.join(__dirname, 'app.js'); // ✓ ok
232 | ```
233 |
234 | - 使用 `getPrototypeOf` 来替代 `__proto__`。
235 |
236 | ```js
237 | const foo = obj.__proto__; // ✗ avoid
238 | const foo = Object.getPrototypeOf(obj); // ✓ ok
239 | ```
240 |
241 | - 避免使用逗号操作符。
242 |
243 | ```js
244 | if (doSomething(), !!test) { } // ✗ avoid
245 | ```
246 |
247 | - 禁止使用稀疏数组(Sparse arrays)。
248 |
249 | ```js
250 | let fruits = ['apple', , 'orange']; // ✗ avoid
251 | ```
252 |
253 | - `finally` 代码块中不要再改变程序执行流程。
254 |
255 | ```js
256 | try {
257 | // ...
258 | } catch (e) {
259 | // ...
260 | } finally {
261 | return 42; // ✗ avoid
262 | }
263 | ```
264 |
265 | - 避免使用 `arguments.callee` 和 `arguments.caller`。
266 | - 不要使用 `debugger。`
267 | - 不要使用 `eval`。
268 | - 不要使用 `with。`
269 | - 不要使用 `__iterator__`。
270 |
--------------------------------------------------------------------------------
/性能优化/images/css_backflow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/性能优化/images/css_backflow.png
--------------------------------------------------------------------------------
/性能优化/images/file_compress.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/性能优化/images/file_compress.png
--------------------------------------------------------------------------------
/性能优化/images/html_render_process.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/性能优化/images/html_render_process.png
--------------------------------------------------------------------------------
/性能优化/images/png_type.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/性能优化/images/png_type.png
--------------------------------------------------------------------------------
/性能优化/images/service_cache.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/性能优化/images/service_cache.png
--------------------------------------------------------------------------------
/性能优化/images/web-cache.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/性能优化/images/web-cache.png
--------------------------------------------------------------------------------
/性能优化/images/web_speed_cookie.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/性能优化/images/web_speed_cookie.png
--------------------------------------------------------------------------------
/性能优化/images/what_happen_when_url_send.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/性能优化/images/what_happen_when_url_send.png
--------------------------------------------------------------------------------
/性能优化/浏览器缓存机制总结.md:
--------------------------------------------------------------------------------
1 | - [缓存模式](#缓存模式)
2 | - [相关 HTTP 标头](#相关-http-标头)
3 | - [ETag](#etag)
4 | - [强 ETag](#强-etag)
5 | - [弱 ETag](#弱-etag)
6 | - [ETag 的作用](#etag-的作用)
7 |
8 | ## 缓存模式
9 |
10 | 浏览器的缓存可分为两种模式:**强缓存**和**协商缓存**。
11 |
12 | - 强缓存
13 |
14 | 直接读取本地缓存,无 HTTP 请求,无协商,HTTP 响应状态码是 `200`
15 |
16 | - `from memory cache`:从浏览器缓存中读取
17 | - `from disk cache`:从电脑磁盘上读取
18 |
19 | 涉及的 HTTP 标头:
20 |
21 | - `Expires`
22 | - `Cache-Control`
23 |
24 | - 协商缓存
25 |
26 | 虽然本地有缓存,但是不确定是否是最新的,于是询问服务器。若服务端判定资源未更新,则返回 `304 Not Modified`,客户端可以直接使用缓存;否则,返回 `200 OK`,并将最新的数据发给客户端。
27 |
28 | 涉及的 HTTP 标头:
29 |
30 | - `Last-Modified / If-Modified-Since`
31 | - `ETag / If-None-Match`
32 |
33 | 一张图解缓存过程:
34 |
35 | 
36 |
37 | ## 相关 HTTP 标头
38 |
39 | - 理解 `Cache-Control` 所控制的缓存策略
40 | - 理解 `Last-Modified` 和 `ETag` 以及整个服务端浏览器端的缓存流程
41 |
42 | HTTP Header:
43 |
44 | - `Expires`
45 |
46 | > 设置资源的过期时间。告诉浏览器:在过期时间前,可以从缓存中直接读取资源。
47 |
48 | - [`Cache-Control`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#cache_directives)
49 |
50 | > 注意 Request 头和 Response 头都可以设置改字段,但可选值不同。
51 |
52 | - `max-age`
53 |
54 | > 在 max-age 设置的时间内,客户端请求的资源都会从浏览器缓存中读取。
55 | > 优先级大于 `expires`。
56 |
57 | - `s-maxage`
58 |
59 | > 是对公共缓存设备(如:CDN,代理服务器)进行缓存设置。
60 | > 如果设置了 `s-maxage`,并且没有过期,资源就会向公共缓存设备请求。
61 | > 优先级大于 `max-age`。
62 |
63 | - `no-cache`
64 |
65 | > 始终发送请求去判断浏览器里的缓存资源是否过期。如果过期就从服务器获取资源,否则返回 304(响应不带 body),并使用浏览器中的缓存。
66 | >
67 | > 另外,这个属性需要配合 `max-age=0` 来使用。
68 |
69 | - `no-store`
70 |
71 | > 对指定的文件完全不使用缓存策略。
72 |
73 | - `Last-Modified` / `If-Modified-Since`
74 |
75 | > - `Last-Modified` 是响应头
76 | > - `If-Modified-Since` 是请求头
77 | >
78 | > 需要与 `cache-control` 共同使用。
79 |
80 | 使用这个 HTTP Header 的缺点:
81 |
82 | - 某些服务端不能获取精确的修改时间
83 | - 文件修改时间变了,文件内容却没有变
84 |
85 | - `ETag` / `If-None-Match`
86 |
87 | > - `ETag` 是响应头
88 | > - `If-None-Match` 是请求头
89 |
90 | > 这个 HTTP Header 用来解决 `Last-Modified` / `If-Modified-Since` 的缺点。
91 | > 它们的值是一个标识文件不同的 MD5 戳。如果 `ETag` 和 `If-None-Match` 的值相同,证明文件没有改变。
92 | > 优先级大于 `Last-Modified` / `If-Modified-Since`
93 |
94 | **浏览器分级缓存策略:**
95 |
96 | 
97 |
98 | > 304 也可以同时更新缓存文件的过期时间。
99 |
100 | ## ETag
101 |
102 | ETag 是 HTTP 响应的首部字段,**用于唯一标识资源的特定版本**。当资源的更新时,ETag 也会随之更新。
103 |
104 | 生成 ETag 值的算法不是统一的,由服务端决定。例如可以是:内容的 Hash、最新修改时间的 Hash、版本号等。
105 |
106 | ### 强 ETag
107 |
108 | 强 ETag,无论资源发生多么细微的变化,都会改变 ETag 的值。
109 |
110 | ### 弱 ETag
111 |
112 | 弱 ETag 只用于提示资源是否相同。只有资源发生根本变化,产生差异时才会改变 ETag 值。此时,在值前面添加 `W/`(注意大写),例如:
113 |
114 | ```http
115 | ETag: W/"123456"
116 | ```
117 |
118 | 相同资源的两个弱 ETag 的值,可能语义相同,但不是每个字节都相同。
119 |
120 | ### ETag 的作用
121 |
122 | - 避免“空中碰撞”(mid-air collisions)
123 |
124 | 所谓“空中碰撞”,就是防止对同一文件的多次修改相互覆盖。例如:张三正在修改 `a.txt`,在张三还没修改完之前,李四就已经修改并保存了 `a.txt`。此时,如果张三保存修改,就会覆盖李四的修改。解决方法是:让张三的这次修改失败,使其在李四修改的基础上再进行修改。
125 |
126 | 借助 `ETag` 和 `If-Match` 字段,可以检测空中编辑冲突。例如:
127 |
128 | 在编辑 MDN 文档时,将 Wiki 的内容 Hash 处理,并将其放入 ETag 响应中:
129 |
130 | ```http
131 | ETag: "123456"
132 | ```
133 |
134 | 如果修改了文档,发布数据时,`POST` 请求将包含 `If-Match` 字段(其值是 ETag 值):
135 |
136 | ```http
137 | If-Match: "123456"
138 | ```
139 |
140 | 如果服务端验证 `If-Match` 和 `ETag` 的值不匹配,证明文档已经编辑过,因此这次修改无效,返回 `412 Precondition Failed` 状态码。
141 |
142 | - 缓存资源
143 |
144 | 借助 `ETag` 和 `If-None-Match` 字段。客户端发送请求时,带上 `If-None-Match` 字段(其值是 ETag 值):
145 |
146 | ```http
147 | If-None-Match: "123456"
148 | ```
149 |
150 | 然后服务端验证 `If-None-Match` 的值,是否与资源当前的 `ETag` 值匹配。如果相同,则资源未修改,返回 `304 Not Modified` 状态码;否则资源修改了,返回 `200 OK` 状态码。
151 |
152 | ---
153 |
154 | 参考资料:
155 |
156 | - [浏览器之 HTTP 缓存的那些事](https://segmentfault.com/a/1190000016546106)
157 |
--------------------------------------------------------------------------------
/打包工具/gulp/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp');
2 | var jshint = require('gulp-jshint');
3 | var cleanHTML = require('gulp-htmlclean')
4 | var cleanCSS = require('gulp-clean-css')
5 | var uglify = require('gulp-uglify');
6 | var imagemin = require('gulp-imagemin');
7 | var concat = require('gulp-concat');
8 | var rename = require('gulp-rename');
9 | var del = require('del');
10 |
11 | var paths = {
12 | html: {
13 | src: 'src/*.html',
14 | dest: 'build/'
15 | },
16 | styles: {
17 | src: 'src/css/*.css',
18 | dest: 'build/'
19 | },
20 | scripts: {
21 | src: 'src/js/*.js',
22 | dest: 'build/'
23 | },
24 | images: {
25 | src: 'src/imgs/*',
26 | dest: 'build/imgs/'
27 | }
28 | };
29 |
30 | function clean() {
31 | return del([ 'build' ]); // 删除 build 目录
32 | }
33 |
34 | // 压缩 HTML
35 | function html() {
36 | return gulp.src(paths.html.src)
37 | .pipe(cleanHTML())
38 | .pipe(gulp.dest(paths.html.dest));
39 | }
40 |
41 | // 压缩 CSS
42 | function styles() {
43 | return gulp.src(paths.styles.src)
44 | .pipe(cleanCSS())
45 | .pipe(rename({
46 | basename: 'build',
47 | suffix: ''
48 | }))
49 | .pipe(gulp.dest(paths.styles.dest));
50 | }
51 |
52 | // 压缩 JS
53 | function scripts() {
54 | return gulp.src(paths.scripts.src, { sourcemaps: true })
55 | .pipe(jshint())
56 | .pipe(uglify())
57 | .pipe(rename({
58 | basename: 'build',
59 | suffix: ''
60 | }))
61 | .pipe(gulp.dest(paths.scripts.dest));
62 | }
63 |
64 | // 压缩图片
65 | function images() {
66 | return gulp.src(paths.images.src, {
67 | since: gulp.lastRun(images), // 过滤没有更新的图片,使其不参与编译
68 | })
69 | .pipe(imagemin([
70 | imagemin.gifsicle({interlaced: true}),
71 | imagemin.jpegtran({progressive: true}),
72 | imagemin.optipng({optimizationLevel: 5}),
73 | imagemin.svgo({
74 | plugins: [
75 | {removeViewBox: true},
76 | {cleanupIDs: false}
77 | ]
78 | })
79 | ]))
80 | .pipe(gulp.dest(paths.images.dest));
81 | }
82 |
83 | // 监听文件是否更改
84 | function watch() {
85 | gulp.watch(paths.styles.src, styles);
86 | gulp.watch(paths.scripts.src, scripts);
87 | gulp.watch(paths.images.src, images);
88 | }
89 |
90 | // 进行串操作(首先移除指定文件夹)
91 | var build = gulp.series(clean, gulp.parallel([html, styles, scripts, images]));
92 |
93 | exports.clean = clean;
94 | exports.html = html;
95 | exports.styles = styles;
96 | exports.scripts = scripts;
97 | exports.images = images;
98 | exports.watch = watch;
99 | exports.build = build;
100 |
101 | exports.default = build;
--------------------------------------------------------------------------------
/打包工具/gulp/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "test",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "devDependencies": {
12 | "del": "^4.0.0",
13 | "gulp": "^4.0.0",
14 | "gulp-clean-css": "^4.0.0",
15 | "gulp-concat": "^2.6.1",
16 | "gulp-htmlclean": "^2.7.22",
17 | "gulp-imagemin": "^5.0.3",
18 | "gulp-jshint": "^2.1.0",
19 | "gulp-rename": "^1.4.0",
20 | "gulp-uglify": "^3.0.2",
21 | "jshint": "^2.10.2"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/打包工具/rollup/README.md:
--------------------------------------------------------------------------------
1 | # Rollup 学习笔记
2 |
3 | ## 原生支持多入口和多出口
4 |
5 | ```js
6 | // rollup.config.js
7 | export default [{
8 | input: './src/main-a.js',
9 | output: {
10 | format: 'cjs'
11 | file: 'release/bundle-a.js',
12 | }
13 | }, {
14 | input: './src/main-b.js',
15 | output: [
16 | {
17 | format: 'cjs'
18 | file: 'release/bundle-b1.js',
19 | },
20 | {
21 | format: 'esm'
22 | file: 'release/bundle-b2.js',
23 | }
24 | ]
25 | }];
26 | ```
27 |
28 | ## 在 Rollup 中使用 Babel
29 |
30 | 在 Rollup 中使用 Babel 的最简单的办法就是使用插件 [rollup-plugin-babel](https://github.com/rollup/rollup-plugin-babel)
31 |
32 | ```shell
33 | npm install rollup-plugin-babel -D
34 | ```
35 |
36 | 添加到 `rollup.config.js` 里面:
37 |
38 | ```js
39 | // rollup.config.js
40 | import resolve from 'rollup-plugin-node-resolve';
41 | import babel from 'rollup-plugin-babel';
42 |
43 | export default {
44 | entry: 'src/main.js',
45 | output: {
46 | format: 'iife',
47 | name: 'packname',
48 | file: 'release/bundle.js',
49 | sourceMap: true
50 | },
51 | plugins: [
52 | resolve(),
53 | babel({
54 | exclude: 'node_modules/**' // 忽略 node_modules
55 | })
56 | ]
57 | };
58 | ```
59 |
60 | 还需要为 Bable 添加一个配置文件 `.babelrc`:
61 |
62 | ```json
63 | {
64 | "presets": [
65 | [
66 | "latest",
67 | {
68 | "es2015": {
69 | "modules": false
70 | }
71 | }
72 | ]
73 | ],
74 | "plugins": ["external-helpers"]
75 | }
76 | ```
77 |
78 | 这里设置 `modules: false`,防止 Babel 在 Rollup 之前,将我们的依赖模块转成 CommonJS,导致 Rollup 的一些失败处理。
79 |
80 | 使用 `external-helpers` 插件,它允许 Rollup 在文件束前仅引用一次任何的 helpers 函数,而不是在每个使用这些 helpers 函数的模块里都引入一遍。
81 |
82 | 那么在编译之前,还需要安装一下插件 `babel-preset-latest` 和 `babel-plugin-external-helpers`:
83 |
84 | ```shell
85 | npm install babel-preset-latest babel-plugin-external-helpers -D
86 | ```
87 |
88 | ## babel-preset-env
89 |
90 | 在没有任何配置选项的情况下,`babel-preset-env` 与 `babel-preset-latest`(是 preset 的集合,包括 `ES2015+`)的行为完全相同。
91 |
92 | `babel-preset-env` 的出现,是为了优化 `babel-preset-latest` ,它能够根据你的配置,自动决定适合你的 Babel 插件的 `preset`。
93 |
94 | 配置示例:
95 |
96 | - 开发目标是浏览器
97 |
98 | ```json
99 | // babel-preset-env
100 | {
101 | "presets": [
102 | [
103 | "env",
104 | {
105 | "targets": {
106 | "browsers": ["last 1 version", "> 1%", "not dead", "ie >= 10"]
107 | }
108 | }
109 | ]
110 | ]
111 | }
112 | ```
113 |
114 | > Borwserslist 的所有配置项:[Borwserslist](https://github.com/browserslist/browserslist)
115 |
116 | - 开发目标是 Node
117 |
118 | ```json
119 | {
120 | "presets": [
121 | [
122 | "env",
123 | {
124 | "targets": {
125 | "node": "8.0"
126 | }
127 | }
128 | ]
129 | ]
130 | }
131 | ```
132 |
133 | 为了方便,可以使用 `"node": "current"` 来包含运行 Babel 所必须的 Node 版本的 `polyfills` 和 `transforms`:
134 |
135 | ```diff
136 | {
137 | "presets": [
138 | ["env", {
139 | "targets": {
140 | + "node": "current"
141 | }
142 | }]
143 | ]
144 | }
145 | ```
146 |
--------------------------------------------------------------------------------
/模仿面试/封装 request 专题/取消前一个 pending 的,然后发送下一个.js:
--------------------------------------------------------------------------------
1 | // TODO:
--------------------------------------------------------------------------------
/模仿面试/封装 request 专题/只发送第一个,后面的直接复用第一个的结果.js:
--------------------------------------------------------------------------------
1 | const memo = {};
2 |
3 | const request = async function (url, { method, params, data, ...options }) {
4 | const getUid = () => {
5 | const paramsStr = JSON.stringify(params);
6 | const dataStr = JSON.stringify(data);
7 |
8 | return `url=${url}&method=${method}¶ms=${paramsStr}&data=${dataStr}`;
9 | };
10 |
11 | const uid = getUid();
12 |
13 | if (memo[uid]) {
14 | return new Promise((resolve, reject) => {
15 | memo[uid].push({
16 | resolve,
17 | reject
18 | });
19 | });
20 | }
21 |
22 | memo[uid] = [];
23 |
24 | return fetch(url, {
25 | method,
26 | params,
27 | body: data,
28 | ...options
29 | })
30 | .then((res) => res.json())
31 | .then((res) => {
32 | for (const { resolve } of memo[uid]) {
33 | resolve(res);
34 | }
35 |
36 | return res;
37 | })
38 | .catch((err) => {
39 | for (const { reject } of memo[uid]) {
40 | reject(err);
41 | }
42 | });
43 | };
44 |
45 | for (let i = 0; i < 5; i++) {
46 | // 这里需要自行建个 JSON 文件,然后起个静态资源服务器,然后把整个文件的代码粘贴到浏览器控制台,即可看到 Network 中发送的请求数量
47 | request('http://127.0.0.1:8080/test.json', {
48 | method: 'GET',
49 | params: { foo: 1, bar: 2 }
50 | }).then((res) => {
51 | console.log('res -->', res);
52 | });
53 | }
54 |
--------------------------------------------------------------------------------
/模仿面试/封装 request 专题/控制最大并发量、错误重试.js:
--------------------------------------------------------------------------------
1 | /** 默认参数 */
2 | const DEFAULT_OPTIONS = {
3 | /** 错误最大重试次数 */
4 | retryCount: 3,
5 | /** 错误重试前等待的时间 */
6 | retryWaitTime: 1000 * 3,
7 | /** 最大并发数量 */
8 | concurrentCount: 5,
9 | /** 并发过程中的回调(用于获取请求进度) */
10 | onProgress: () => {}
11 | };
12 |
13 | async function sleep(time) {
14 | return await new Promise((resolve) => setTimeout(resolve, time));
15 | }
16 |
17 | /**
18 | * 重试函数包装器
19 | * @param {Function} fn 要重试的函数
20 | * @param {number} options.retryCount 错误最大重试次数
21 | * @param {number} options.retryWaitTime 错误重试前等待的时间
22 | * @returns result | errorMsg
23 | */
24 | async function retryWrapper(fn, options = {}) {
25 | const { retryCount, retryWaitTime } = { ...DEFAULT_OPTIONS, ...options };
26 | let count = retryCount;
27 |
28 | while (count > 0) {
29 | try {
30 | const result = await fn();
31 | return result;
32 | } catch (error) {
33 | console.log(
34 | `${fn.name} 函数请求出错,将在 ${retryWaitTime} 毫秒后重试,剩余重试次数 ${count} 次`
35 | );
36 | count--;
37 | await sleep(retryWaitTime);
38 | }
39 | }
40 |
41 | const msg = `${fn.name} 函数请求结束,已超出失败最大重试次数`;
42 |
43 | console.log(msg);
44 | throw msg;
45 | }
46 |
47 | /**
48 | * 并发控制
49 | * @param {Promise[]} tasks 需要并发控制的请求任务
50 | * @param {number} options.concurrentCount 最大并发数量
51 | * @param {number} options.onProgress 并发过程中的回调(用于获取请求进度)
52 | * @param {*} rest 同 retryWrapper 函数的参数
53 | * @returns [results, errors]
54 | */
55 | async function concurrent(tasks, options = {}) {
56 | const { concurrentCount, onProgress, retryCount, retryWaitTime } = {
57 | ...DEFAULT_OPTIONS,
58 | ...options
59 | };
60 |
61 | // 任务池(原始任务、重试任务)
62 | const tasksPool = new Set();
63 | const results = [];
64 | const errors = [];
65 |
66 | for (const [index, task] of tasks.entries()) {
67 | const promise = retryWrapper(task, { retryCount, retryWaitTime })
68 | .then((data) => (results[index] = data))
69 | .catch((error) => (errors[index] = { task, error }))
70 | .finally(() => tasksPool.delete(promise));
71 |
72 | tasksPool.add(promise);
73 | onProgress(index, task);
74 |
75 | // 达到上限后,等待任意一个任务执行完成
76 | if (tasksPool.size >= concurrentCount) {
77 | await Promise.race(tasksPool);
78 | }
79 | }
80 |
81 | // 等待其中所有任务做完(做完指:完成或失败)
82 | await Promise.allSettled(tasksPool);
83 |
84 | return [results, errors];
85 | }
86 |
87 | // -------- test case --------
88 |
89 | const loadData = () => {
90 | return new Promise((resolve, reject) => {
91 | setTimeout(() => {
92 | if (Math.random() < 0.25) {
93 | resolve(`success ${Date.now()}`);
94 | } else {
95 | reject(`fail ${Date.now()}`);
96 | }
97 | }, 2000);
98 | });
99 | };
100 |
101 | const tasks = Array(20).fill(loadData);
102 | async function run() {
103 | const [results, errors] = await concurrent(tasks, {
104 | onProgress: (index) => {
105 | console.log(`onProgress 处理中,当前索引:${index}`);
106 | }
107 | });
108 |
109 | console.log(`results -->`, results);
110 | console.log(`errors -->`, errors);
111 | }
112 |
113 | run();
114 |
--------------------------------------------------------------------------------
/浏览器相关/images/event-loop-callstack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/浏览器相关/images/event-loop-callstack.png
--------------------------------------------------------------------------------
/浏览器相关/images/event-loop-excute-demo1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/浏览器相关/images/event-loop-excute-demo1.gif
--------------------------------------------------------------------------------
/浏览器相关/images/event-loop-task-queue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/浏览器相关/images/event-loop-task-queue.png
--------------------------------------------------------------------------------
/浏览器相关/images/localstorage-weixin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/浏览器相关/images/localstorage-weixin.png
--------------------------------------------------------------------------------
/浏览器相关/images/url-struct.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/浏览器相关/images/url-struct.png
--------------------------------------------------------------------------------
/浏览器相关/前端路由原理和实现.md:
--------------------------------------------------------------------------------
1 | - [什么是路由](#什么是路由)
2 | - [前端路由的原理](#前端路由的原理)
3 | - [Hash 路由原理](#hash-路由原理)
4 | - [History 路由原理](#history-路由原理)
5 | - [原生 JS 实现前端路由](#原生-js-实现前端路由)
6 | - [Hash 路由实现](#hash-路由实现)
7 | - [History 路由实现](#history-路由实现)
8 |
9 | 前端主流框架的路由解决方案 `angular/router`、`react-router`、`vue-router` 都是基于前端基础的路由原理封装实现的,因此有必要掌握其原理。
10 |
11 | 设想一下,如果不使用任何框架或第三方库,只使用 HTML、CSS、JavaScript 去开发一个网站,我们会遇到哪些问题?(也就是说,前端框架替我们做了哪些事?解决了哪些痛点?)对于这个问题的答案,**前端路由的封装**就是其中之一。
12 |
13 | ## 什么是路由
14 |
15 | 路由这个概念来源于后端。在早些年代,前后端还未分离时,由后端控制路由。到后来前后端分离,前端也可以控制路由时,这个概念就被两端通用了。
16 |
17 | 路由在前后端的具体含义分别是:
18 |
19 | - 后端:描述 URL 与具体处理函数之间的映射关系。
20 | - 前端:描述 URL 与 UI 之间的映射关系。
21 |
22 | ## 前端路由的原理
23 |
24 | 对于前端路由来说,访问不同的 URL 会显示不同的组件,从而实现页面的更新。主要实现方式有两种:`Hash` 和 `History`。
25 |
26 | 前端路由需要解决的问题是:
27 |
28 | - 如何改变 URL 而不刷新页面?
29 | - 如何检测 URL 变化了?
30 |
31 | 下面将围绕这两个问题,来讲述 Hash 和 History 两种路由实现方式。
32 |
33 | ### Hash 路由原理
34 |
35 | - Hash 即 `location.hash`,也就是 URL 中 `#` 后面的内容,常用作锚点在页面内导航,改变 URL 的 Hash 不会引起页面刷新。
36 | - 通过 [`hashchange`](https://developer.mozilla.org/en-US/docs/Web/API/Window/hashchange_event) 事件,监听 Hash 的变化。改变 URL 的方式有:**通过浏览器的前进后退**、**通过 `` 标签**、**通过 `window.location`**,这几种方式均会触发 `hashchange` 事件。
37 |
38 | ### History 路由原理
39 |
40 | - 主要利用 HTML5 的 [History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API) 提供的 `pushState` 和 `replaceState` 方法。通过这两个方法改变 URL 不会引起页面刷新。
41 | - 浏览器前进后退时,会触发 History API 提供的 [`popstate`](https://developer.mozilla.org/en-US/docs/Web/API/Window/popstate_event) 事件(其他方式改变 URL 不会触发该事件)。通过拦截`` 标签的点击事件,然后手动触发 `pushState` 和 `replaceState` 方法,就可以检测到 URL 的变化。
42 |
43 | 用到的 History API 比较:`pushState` 会增加一条新的历史记录,而 `replaceState` 则会替换当前的历史记录。
44 |
45 | ## 原生 JS 实现前端路由
46 |
47 | ### Hash 路由实现
48 |
49 | ```html
50 |
51 |
52 | Home
53 | About
54 |
55 |
56 |
57 |
58 | ```
59 |
60 | ```js
61 | // 防止第一次加载页面时,没有触发 hashchange
62 | window.addEventListener('DOMContentLoaded', onLoad);
63 | window.addEventListener('hashchange', onHashChange);
64 |
65 | var root = null;
66 |
67 | function onLoad() {
68 | root = document.querySelector('#root');
69 | onHashChange();
70 | }
71 |
72 | // Hash 改变时,更新 UI
73 | function onHashChange() {
74 | var hash = location.hash;
75 |
76 | if (hash == '#home') {
77 | root.innerHTML = 'This is Home Page';
78 | }
79 | if (hash == '#about') {
80 | root.innerHTML = 'This is About Page';
81 | }
82 | }
83 | ```
84 |
85 | ### History 路由实现
86 |
87 | ```html
88 |
89 |
90 | Home
91 | About
92 |
93 |
94 |
95 |
96 | ```
97 |
98 | ```js
99 | // 防止第一次加载页面时,没有触发 hashchange
100 | window.addEventListener('DOMContentLoaded', onLoad);
101 | window.addEventListener('popstate', onHistoryChange);
102 |
103 | var root = null;
104 | var history = window.history;
105 |
106 | function onLoad() {
107 | root = document.querySelector('#root');
108 | onHistoryChange();
109 |
110 | // 拦截所有 标签的点击事件
111 | var links = document.querySelectorAll('a');
112 | links.forEach((elem) => {
113 | elem.addEventListener('click', function (e) {
114 | e.preventDefault();
115 | history.pushState(null, '', elem.getAttribute('href'));
116 | onHistoryChange();
117 | });
118 | });
119 | }
120 |
121 | // URL 改变时,更新 UI
122 | function onHistoryChange() {
123 | var path = location.pathname;
124 |
125 | if (path == '/home') {
126 | root.innerHTML = 'This is home page';
127 | }
128 | if (path == '/about') {
129 | root.innerHTML = 'This is about page';
130 | }
131 | }
132 | ```
133 |
--------------------------------------------------------------------------------
/版本控制/images/a-picture-to-understand-git.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/版本控制/images/a-picture-to-understand-git.png
--------------------------------------------------------------------------------
/版本控制/images/git-flow-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/版本控制/images/git-flow-1.png
--------------------------------------------------------------------------------
/版本控制/images/git-flow-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/版本控制/images/git-flow-2.png
--------------------------------------------------------------------------------
/版本控制/images/git-flow-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/版本控制/images/git-flow-3.png
--------------------------------------------------------------------------------
/版本控制/images/git-flow-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/版本控制/images/git-flow-4.png
--------------------------------------------------------------------------------
/版本控制/images/git-merge.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/版本控制/images/git-merge.png
--------------------------------------------------------------------------------
/版本控制/images/modify-pr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/版本控制/images/modify-pr.png
--------------------------------------------------------------------------------
/网络相关/HTTP(S) 相关/images/tls-four-handshake.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/网络相关/HTTP(S) 相关/images/tls-four-handshake.png
--------------------------------------------------------------------------------
/网络相关/TCP 相关/images/tcp-four-wave.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/网络相关/TCP 相关/images/tcp-four-wave.png
--------------------------------------------------------------------------------
/网络相关/TCP 相关/images/tcp-message-struct.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/网络相关/TCP 相关/images/tcp-message-struct.png
--------------------------------------------------------------------------------
/网络相关/TCP 相关/images/tcp-three-handshake-fail-in-three.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/网络相关/TCP 相关/images/tcp-three-handshake-fail-in-three.png
--------------------------------------------------------------------------------
/网络相关/TCP 相关/images/tcp-three-handshake.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/网络相关/TCP 相关/images/tcp-three-handshake.png
--------------------------------------------------------------------------------
/网络相关/TCP 相关/深入探究 TCP 协议.md:
--------------------------------------------------------------------------------
1 | - [TCP 协议](#tcp-协议)
2 | - [TCP 三次握手](#tcp-三次握手)
3 | - [一些问题(三次握手相关)](#一些问题三次握手相关)
4 | - [为什么三次握手,两次可以吗?](#为什么三次握手两次可以吗)
5 | - [如果采用两次握手,会导致什么问题?](#如果采用两次握手会导致什么问题)
6 | - [两端同时发起请求时会怎样?建立几个连接?](#两端同时发起请求时会怎样建立几个连接)
7 | - [当服务器状态变为 SYN\_RECV 时,又收到了一个旧的 SYN 报文,该怎么处理?](#当服务器状态变为-syn_recv-时又收到了一个旧的-syn-报文该怎么处理)
8 | - [报文丢失导致握手失败会怎样?](#报文丢失导致握手失败会怎样)
9 | - [知道 SYN 洪泛攻击吗?如何防范?](#知道-syn-洪泛攻击吗如何防范)
10 | - [三次握手可以携带数据吗?](#三次握手可以携带数据吗)
11 | - [TCP 四次挥手](#tcp-四次挥手)
12 | - [一些问题(四次挥手相关)](#一些问题四次挥手相关)
13 | - [为什么三次握手,而挥手是四次?](#为什么三次握手而挥手是四次)
14 | - [四次挥手时,等待 2MSL 的意义?](#四次挥手时等待-2msl-的意义)
15 |
16 | # TCP 协议
17 |
18 | ## TCP 三次握手
19 |
20 | 所谓握手,即一次发包与接收的过程,可能是从客户端到服务端,也可能是从服务端到客户端。
21 |
22 | 进行三次握手的目的是:1、确认双方的接收能力和发送能力。2、同步双方的序列号和确认号。3、交换 TCP 窗口大小信息。
23 |
24 | 首先了解一下 TCP 报文的结构,如图所示:
25 |
26 | 
27 |
28 | 有几个字段需要重点介绍下:
29 |
30 | - 序号(seq):用来表示发送的字节流(由发起方进行标记)
31 | - 确认号(ack):只有 ACK 标志位为 1 时,确认号才有效
32 | - 标志位
33 |
34 | - `ACK`:表示确认号有效
35 | - `SYN`:表示发起一个新连接
36 | - `FIN`:表示释放一个新连接
37 | - `RST`:表示重置连接
38 |
39 | 刚开始客户端处于 Closed 的状态,服务端处于 Listen 状态。进行三次 TCP 握手过程如下:
40 |
41 | - 第一次握手
42 |
43 | 客户端给服务端发送一个 SYN 报文,其中指定同步位 `SYN = 1`,初始序号 `seq = x`,此时客户端处于 `SYN_SENT` 状态。
44 |
45 | > `SYN = 1` 的报文段不能携带数据,但是要消耗掉一个序号。
46 |
47 | - 第二次握手
48 |
49 | 服务端应答一个 SYN 报文,其中指定同步位 `SYN = 1`,确认位 `ACK = 1`,确认号 `ack = x + 1`(客户端发来的序号 + 1),序号 `seq = y`,此时服务端处于 `SYN_RECV` 状态。
50 |
51 | - 第三次握手
52 |
53 | 客户端应答一个 ACK 报文,其中指定确认位 `ACK = 1`,确认号 `ack = y + 1`(服务端发来的序号 + 1),序号 `seq = x + 1`(客户端初始序号 = x),此时客户端处于 `ESTABLISHED` 状态。当服务器收到 ACK 报文后,也处于 `ESTABLISHED` 状态。这时,双方就建立起了连接。
54 |
55 | > ACK 报文段可以携带数据,不携带数据则不消耗序号。
56 |
57 | 以上就是 TCP 三次握手的过程,如图所示:
58 |
59 | 
60 |
61 | ## 一些问题(三次握手相关)
62 |
63 | ### 为什么三次握手,两次可以吗?
64 |
65 | 简单来说,三次握手的目的是**验证双方的发送能力和接收能力**,如下:
66 |
67 | - 第一次握手,客户端发送,服务端接收。证明服务端知道了客户端的发送能力没问题。
68 | - 第二次握手,服务端发送,客户端接收。证明客户端知道了服务端的发送、接收能力都没问题。
69 |
70 | > 到这为止,如果只是两次握手,服务端并不知道客户端的的接收能力是否正常(因为客户端没有接收数据再发送给服务端)。
71 |
72 | - 第三次握手,客户端发送,服务端接收。这一次,客户端是接收到数据再发送给服务端的,所以服务端知道了客户端的接收能力没问题。
73 |
74 | ### 如果采用两次握手,会导致什么问题?
75 |
76 | 如果 A 发送了一个 SYN 报文段,但是由于某种原因,在网络上滞留了很长时间,导致连接超时并释放后的某个时间点才到达 B。此时 B 以为是 A 新发起的连接,于是向 A 发送确认报文。
77 |
78 | 由于 A 并没有新发起连接(之前的连接已经超时释放了),所以不会理睬 B 的确认请求,但是 B 会一直等待 A 发来数据,因此使得 B 白白浪费了很多资源。
79 |
80 | 总的来说,三次握手的目的是 **“为了防止已经过期的连接请求,又到达了服务端而产生错误”**。
81 |
82 | ### 两端同时发起请求时会怎样?建立几个连接?
83 |
84 | 如题,两端都会扮演发起者和接收者的角色,它们的状态分别如下:
85 |
86 | 依次经历 `CLOSED` -> `SYN_SENT` -> `SYN_RECV` -> `ESTABLISHED`。最终只会建立起一个连接。
87 |
88 | ### 当服务器状态变为 SYN_RECV 时,又收到了一个旧的 SYN 报文,该怎么处理?
89 |
90 | 首先需要知道,什么是 SYN_RECV 状态:**服务器已经接收到了客户端发送的 SYN 报文,并且向客户端发送了 SYN-ACK 报文作为回应,等待客户端发送 ACK 报文来确认连接**。
91 |
92 | 处理办法:忽略旧的 SYN 报文,并等待客户端发送 ACK 报文以确认连接。
93 |
94 | > 对于该问题,有的文章中提到:`会发送 RST 报文来重置连接`,这是不对的。
95 | >
96 | > 发送 RST 报文的条件如下:收到无效的 TCP 报文段、拒绝连接请求、强制关闭连接、收到错误的 TCP 报文。
97 | >
98 | > 因此,当处于 SYN_RECV 状态时,表明服务器已经接收到客户端的连接请求并且愿意建立连接,不符合以上条件,因此不会发送 RST 报文来重置连接。
99 |
100 | ### 报文丢失导致握手失败会怎样?
101 |
102 | 该情况如下图所示:
103 |
104 | 
105 |
106 | 当客户端收到服务端的确认报文后,状态变为 ESTABLISHED,然后发送 ACK 报文给服务端。如果 ACK 报文没有到达服务端,则会进行超时重传。当超过了重传次数,服务端还是没有收到 ACK 报文,会直接断开连接。
107 |
108 | 问题就在于,客户端认为连接已经建立,而服务端可能处于 SYN_RECV 或 CLOSED 状态。接下来根据服务端的两种状态来考虑:
109 |
110 | - **服务端处于 CLOSED 状态**
111 |
112 | 如果服务端收到“确认连接关闭的报文”,那么会返回 RST 报文,客户端收到 RST 报文会重置连接。
113 |
114 | - **服务端处于 SYN_RECV 状态**
115 |
116 | 1、如果接收到正常的 ACK 报文,那么直接恢复连接。2、如果接收到携带数据的请求(携带数据的请求也是 ACK 报文),同样也会恢复连接,然后变为 ESTABLISHED 状态,继续传输数据。
117 |
118 | 总结来说:**如果 ACK 报文丢失了,下一个报文没有丢失,则连接正常,否则连接会被重置**。
119 |
120 | ### 知道 SYN 洪泛攻击吗?如何防范?
121 |
122 | 所谓 SYN 洪泛攻击,就是通过发起大量的 TCP 连接请求,耗尽服务器的资源。这也是一种典型的 DDoS 攻击。
123 |
124 | 原理如下:建立 TCP 连接时,服务器会为请求分配缓存。因此,攻击者会使用大量的虚假 IP 向服务器发起 TCP 连接请求。服务器响应这些请求,但是得不到回复(因为都是假 IP),因此只能重复请求,直到超时才关闭连接。最终,服务器不断为连接分配资源,导致资源被耗尽。
125 |
126 | 防范措施:**SYN Cookie 防御**。
127 |
128 | SYN Cookie 防御的原理:服务端收到 SYN 报文后,暂时不分配缓存。而是使用**源 IP 地址**、**目的 IP 地址**、**端口**和**服务端存储的一个秘密数**进行 Hash,得到 server_isn,然后作为序列号(seq)发给客户端。下次收到客户端的确认报文时,判断确认号(ack)是否等于 `server_isn + 1`。如果是,则证明是一个合法的 ACK 报文,连接建立。否则,关闭连接。
129 |
130 | SYN Cookie 防御的缺点:由于服务端不保存**连接的半开状态**,因此丧失了重发确认报文的能力。一方面使得连接的成功率降低,另一方面使得通信双方对连接是否公共产生误解(例如:客户端发送的第三次握手的 ACK 报文丢失了,而客户端认为连接成功了,但是服务端没收到 ACK 报文,认为没有连接成功。解决措施是上层应用采取一定的措施进行处理)。
131 |
132 | ### 三次握手可以携带数据吗?
133 |
134 | 前面也提到,SYN 报文不能携带数据,ACK 报文可以。因此三次握手中,只有**第三次**握手可以携带数据。
135 |
136 | ## TCP 四次挥手
137 |
138 | 断开 TCP 连接需要发送四次包,因此称为四次挥手。客户端或服务端均可主动发起挥手动作。
139 |
140 | 一次连接需要三次握手,但是挥手却需要四次,这是由于 TCP 的半关闭状态造成的。所谓半关闭状态,就是连接的一端在结束发送后,还能再接收数据的状态。
141 |
142 | 刚开始,双方都处于 ESTABLISHED 状态,**假如客户端先发起关闭请求**。则挥手过程如下:
143 |
144 | - 第一次挥手
145 |
146 | 客户端发送一个 FIN 报文(连接释放报文),其中指定终止位 `FIN = 1`,序号 `seq = u`。此时客户端处于 `FIN_WAIT1` 状态(终止等待 1 状态),并停止发送数据,等待服务器确认。
147 |
148 | - 第二次挥手
149 |
150 | 服务端回应一个 ACK 报文(确认报文),其中指定确认位 `ACK = 1`,确认号 `ack = u + 1`,序号 `seq = v`。此时服务端处于 `CLOSE_WAIT` 状态(关闭等待状态),此时 TCP 连接处于半关闭状态(客户端的连接释放,并得到了服务端的确认;服务端还没释放连接)。
151 |
152 | 客户端收到服务端的确认报文后,进入 `FIN_WAIT2` 状态(终止等待 2 状态),等待服务端发出连接释放报文。
153 |
154 | - 第三次挥手
155 |
156 | 服务端发送一个 FIN 报文(连接释放报文),其中终止位 `FIN = 1`,确认位 `ACK = 1`,确认号 `ack = u + 1`,序号 `seq = w`。服务端处于 `LAST_ACK` 状态(最后确认状态),等待客户端的确认。
157 |
158 | - 第四次挥手
159 |
160 | 客户端回应一个 ACK 报文(确认报文),其中指定确认位 `ACK = 1`,确认号 `ack = w + 1`,序号,序号 `seq = u + 1`。此时客户端处于 `TIME_WAIT` 状态(时间等待状态),客户端并不会立即处于 CLOSED 状态,而是经过计时器设置的 2MSL 时间后才变为 CLOSED 状态。然后,服务端收到 ACK 报文后,会立即关闭连接,变为 CLOSED 状态。
161 |
162 | 以上就是 TCP 四次挥手的过程,如图所示:
163 |
164 | 
165 |
166 | > 其中,MSL(Maximum Segment Lifetime,报文段最大生存时间)表示报文段被丢弃前,在网络上的最长时间。
167 |
168 | ## 一些问题(四次挥手相关)
169 |
170 | ### 为什么三次握手,而挥手是四次?
171 |
172 | 1. 建立连接时,当服务端收到客户端的 SYN 报文后,可以直接回应 **SYN-ACK 报文**。其中 ACK 是用来应答的,SYN 是用来同步的。
173 |
174 | 2. 关闭连接时,当服务端收到 FIN 报文时,很可能还在发送/接收报文,不能立即关闭连接,所以只能先回复一个 ACK 报文(用于告诉服务端“你发送的 FIN 报文我收到了”)。只有等到服务端将所有报文都发送/接收完了,才能发送 FIN 报文。
175 |
176 | ### 四次挥手时,等待 2MSL 的意义?
177 |
178 | 1. **为了确保客户端回应的最后一个 ACK 报文能够到达服务端**。
179 |
180 | 我们必须假设网络是不可靠的,客户端最后发送的 ACK 报文可能丢失。如果服务端发送 FIN 而没有收到 ACK 报文,就会重发 FIN 报文。此时客户端会重新确认,再次发送 ACK 报文。
181 |
182 | 当然,客户端需要假设服务端会再次发送 FIN 报文,但又不能一直等待下去。因此,需要设定一个等待时间,这个时间设置为 2MSL 最为合适。
183 |
184 | 2. **防止已失效的连接的 SYN 报文,出现在新的连接中**。
185 |
186 | 客户端发送完最后一个 ACK 报文后,再等待 2MSL 时间,就可以使滞留在网络上的报文失效。从而使得下一次新的连接中不会再出现旧连接的报文。
187 |
188 | ---
189 |
190 | 参考资料:
191 |
192 | - [阿里面试: HTTP、HTTPS、TCP/IP、三次握手四次挥手过程?](https://zhuanlan.zhihu.com/p/103000747)
193 | - [面试官,不要再问我三次握手和四次挥手](https://juejin.im/post/5d9c284b518825095879e7a5)
194 |
--------------------------------------------------------------------------------
/网络相关/Web 安全/CSRF 攻击/imgs/csrf-attack-yuanli.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/网络相关/Web 安全/CSRF 攻击/imgs/csrf-attack-yuanli.png
--------------------------------------------------------------------------------
/网络相关/Web 安全/CSRF 攻击/imgs/csrf-double-cookie-test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/网络相关/Web 安全/CSRF 攻击/imgs/csrf-double-cookie-test.png
--------------------------------------------------------------------------------
/网络相关/Web 安全/CSRF 攻击/imgs/github-same-site.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/网络相关/Web 安全/CSRF 攻击/imgs/github-same-site.png
--------------------------------------------------------------------------------
/网络相关/Web 安全/CSRF 攻击/imgs/referer-policy-value.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/网络相关/Web 安全/CSRF 攻击/imgs/referer-policy-value.png
--------------------------------------------------------------------------------
/网络相关/Web 安全/CSRF 攻击/imgs/referer-policy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/网络相关/Web 安全/CSRF 攻击/imgs/referer-policy.png
--------------------------------------------------------------------------------
/网络相关/Web 安全/CSRF 攻击/imgs/same-site-browser-support-mobile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/网络相关/Web 安全/CSRF 攻击/imgs/same-site-browser-support-mobile.png
--------------------------------------------------------------------------------
/网络相关/Web 安全/CSRF 攻击/imgs/same-site-browser-support-pc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/网络相关/Web 安全/CSRF 攻击/imgs/same-site-browser-support-pc.png
--------------------------------------------------------------------------------
/网络相关/Web 安全/Cookie 安全/README.md:
--------------------------------------------------------------------------------
1 | - [Cookies 安全](#cookies-安全)
2 | - [Cookies 的特性](#cookies-的特性)
3 | - [使用](#使用)
4 |
5 | # Cookies 安全
6 |
7 | ## Cookies 的特性
8 |
9 | - 前端数据储存
10 | - 前端可读写
11 | - 后端通过 HTTP 头设置
12 | - 前端请求数据时,通过 HTTP 头传给后端
13 | - 遵守同源策略
14 |
15 | ## 使用
16 |
17 | - `用户 ID + 签名` 作为用户登录的凭证
18 |
19 | > 签名:一个 MD5 戳,可以使用盐值 + 用户 ID 加密成的。
20 |
21 | 用户 ID 明文保存在 `Cookies` 中,以及签名也保存在 `Cookies` 中,当请求数据时,后端将收到的用户 ID 再使用同样的方法加密,比较两个 MD5 戳是否一样。
22 |
23 | - `Cookies` 中存 `SessionID` 作为用户登录的凭证
24 |
25 | `SessionID` 是一个随机的字符串,后端可以将 `SessionID` 存入文件,数据库,Redis 等里面。
26 |
27 | - `Cookies` 和 XSS 的关系
28 |
29 | - XSS 脚本可以偷走 `Cookies`
30 | - 使用 `HttpOnly` 的 `Cookies` 不会被偷
31 |
32 | - `Cookies` 和 CSRF 的关系
33 |
34 | - CSRF 利用了用户的 `Cookies`
35 | - 攻击者无法读写 `Cookies`
36 |
37 | - `Cookies` 的安全策略
38 |
39 | - 使用签名
40 | - 加密传输
41 | - 设置 `Set-Cookie` 的 `HttpOnly` 属性(JS 不能修改 `Cookies`)
42 | - 设置 `Set-Cookie` 的 `Secure` 属性(只有 HTTPS 请求才能使用 `Cookies`)
43 | - 设置 `Set-Cookie` 的 `SameSite` 属性(请求跨站时 `Cookies` 会不会被发送。目前只有 Chrome 支持)
44 | - 设置 HTTP 请求头 STS (`Strict-Transport-Security`)
45 |
46 | > 限制 Web 只能使用 HTTPS 请求。可以包含最大过期时间,子域和预加载。
47 |
48 | ```http
49 | Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
50 | ```
51 |
52 | 示例:
53 |
54 | 
55 |
56 | 许多 Web 攻击方式都与 `Cookies` 有关,例如:`XSS` `CSRF`等,因此,保证 `Cookies` 的安全,是保证网站安全最基本的要求之一。
57 |
58 | ---
59 |
60 | 参考文章:
61 |
62 | [keycdn: Hardening Your HTTP Security Headers](https://www.keycdn.com/blog/http-security-headers)
63 |
--------------------------------------------------------------------------------
/网络相关/Web 安全/Cookie 安全/imgs/github-cookies-secure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liuyib/note/5444ec18c3864ce593f1986107932aa5a8f7a1a8/网络相关/Web 安全/Cookie 安全/imgs/github-cookies-secure.png
--------------------------------------------------------------------------------
/网络相关/Web 安全/HTML5 安全/README.md:
--------------------------------------------------------------------------------
1 | - [HTML5 安全](#html5-安全)
2 | - [新标签、属性带来的危险](#新标签属性带来的危险)
3 | - [sandbox 属性](#sandbox-属性)
4 | - [noreferrer 属性](#noreferrer-属性)
5 | - [CORS](#cors)
6 | - [postMessage](#postmessage)
7 | - [安全问题](#安全问题)
8 | - [Web Storage](#web-storage)
9 |
10 | # HTML5 安全
11 |
12 | ## 新标签、属性带来的危险
13 |
14 | HTML5 引入的新标签包括 `