├── .gitignore ├── assets ├── how_http_cache_works.excalidraw ├── CFG_1.png ├── CFG_2.png ├── dns.png ├── tokens_1.png ├── ast_example_1.png ├── cache_hit_ratio.png ├── graph │ ├── concept.png │ ├── graph_1.png │ ├── adjacency_list.png │ └── adjacency_matrix.png ├── recursion_tree.png ├── dsa_linked_list_0.png ├── ts │ ├── ts_as_const_0.png │ ├── ts_as_const_1.png │ └── ts_as_const_2.png ├── how_http_cache_works.png ├── network │ ├── internetwork.png │ ├── using_router.png │ ├── point_to_point.png │ ├── using_router_1.png │ ├── ip_fragmentation.png │ ├── multiple_access.png │ ├── point_to_point_such_a_mess.png │ └── point_to_point_several_computers.png ├── proxy_design_pattern.png ├── recursion_tree_stack.png ├── recursive_call_stack.png ├── same_site_cross_site.png ├── proxy_design_pattern_1.png ├── diff_compiler_interpreter.png ├── recursive_call_stack_tco.png └── same_origin_cross_origin.png ├── articles ├── others │ ├── 2020-09-18-17-53-37.png │ ├── 2020-09-18-18-00-16.png │ ├── print-colored-text-in-terminal.md │ └── context_free_grammar.md ├── sorting │ ├── selection_sort.md │ ├── insertion_sort.md │ ├── counting_sort.md │ ├── bubble_sort.md │ └── merge_sort.md ├── typescript │ ├── typescript_as_const.md │ ├── typescript_infer.md │ ├── typescript_indexable_types.md │ └── typescript_leetcode_hire.md ├── network │ ├── same_site_&_same_origin.md │ ├── how_dns_works.md │ ├── basis_of_computer_network.md │ └── internet_protocol.md ├── design-pattern │ ├── design_pattern_proxy.md │ ├── design_pattern_singleton.md │ └── design_pattern_strategy.md ├── security │ ├── what_is_CSP.md │ └── clickjacking.md ├── workflow │ ├── commitlint.md │ ├── git-hooks.md │ ├── npm-publish.md │ ├── prettier.md │ ├── build-a-node-cli.md │ └── eslint.md ├── browser │ ├── inside_look_browser_4.md │ ├── inside_look_browser_1.md │ ├── inside_look_browser_2.md │ └── inside_look_browser_3.md ├── javascript │ ├── tail_call_optimization.md │ └── 4_common_memory_leak.md ├── dsa │ ├── dsa_avl_tree.md │ ├── dsa_trie.md │ ├── dsa_stack_and_queue.md │ ├── dsa_linked_list.md │ ├── big_O_complexity.md │ ├── dsa_binary_tree.md │ ├── dsa_union_find.md │ ├── quick_select.md │ ├── dsa_hashtable.md │ ├── dsa_graph.md │ └── dsa_recursion.md ├── compile │ ├── just_in_time_compiler.md │ ├── compiler_and_interpreter.md │ └── compilation_in_general.md ├── unit-test │ ├── notes_for_testing_vuejs_applications_1.md │ ├── notes_for_testing_vuejs_applications_2.md │ └── intro_to_vue_unit_test.md ├── vue │ ├── mini_vue.md │ ├── state_reactivity.md │ ├── virtual_dom.md │ └── intro_to_nuxtjs.md ├── dev-ops │ └── github-actions │ │ ├── 1.intro-to-actions.md │ │ └── 2.actions-for-ci.md └── performance │ ├── caching.md │ └── optimize_images.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.todo 2 | **.local.** 3 | -------------------------------------------------------------------------------- /assets/how_http_cache_works.excalidraw: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/CFG_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suukii/Articles/HEAD/assets/CFG_1.png -------------------------------------------------------------------------------- /assets/CFG_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suukii/Articles/HEAD/assets/CFG_2.png -------------------------------------------------------------------------------- /assets/dns.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suukii/Articles/HEAD/assets/dns.png -------------------------------------------------------------------------------- /assets/tokens_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suukii/Articles/HEAD/assets/tokens_1.png -------------------------------------------------------------------------------- /assets/ast_example_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suukii/Articles/HEAD/assets/ast_example_1.png -------------------------------------------------------------------------------- /assets/cache_hit_ratio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suukii/Articles/HEAD/assets/cache_hit_ratio.png -------------------------------------------------------------------------------- /assets/graph/concept.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suukii/Articles/HEAD/assets/graph/concept.png -------------------------------------------------------------------------------- /assets/graph/graph_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suukii/Articles/HEAD/assets/graph/graph_1.png -------------------------------------------------------------------------------- /assets/recursion_tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suukii/Articles/HEAD/assets/recursion_tree.png -------------------------------------------------------------------------------- /assets/dsa_linked_list_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suukii/Articles/HEAD/assets/dsa_linked_list_0.png -------------------------------------------------------------------------------- /assets/ts/ts_as_const_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suukii/Articles/HEAD/assets/ts/ts_as_const_0.png -------------------------------------------------------------------------------- /assets/ts/ts_as_const_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suukii/Articles/HEAD/assets/ts/ts_as_const_1.png -------------------------------------------------------------------------------- /assets/ts/ts_as_const_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suukii/Articles/HEAD/assets/ts/ts_as_const_2.png -------------------------------------------------------------------------------- /assets/graph/adjacency_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suukii/Articles/HEAD/assets/graph/adjacency_list.png -------------------------------------------------------------------------------- /assets/how_http_cache_works.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suukii/Articles/HEAD/assets/how_http_cache_works.png -------------------------------------------------------------------------------- /assets/network/internetwork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suukii/Articles/HEAD/assets/network/internetwork.png -------------------------------------------------------------------------------- /assets/network/using_router.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suukii/Articles/HEAD/assets/network/using_router.png -------------------------------------------------------------------------------- /assets/proxy_design_pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suukii/Articles/HEAD/assets/proxy_design_pattern.png -------------------------------------------------------------------------------- /assets/recursion_tree_stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suukii/Articles/HEAD/assets/recursion_tree_stack.png -------------------------------------------------------------------------------- /assets/recursive_call_stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suukii/Articles/HEAD/assets/recursive_call_stack.png -------------------------------------------------------------------------------- /assets/same_site_cross_site.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suukii/Articles/HEAD/assets/same_site_cross_site.png -------------------------------------------------------------------------------- /assets/graph/adjacency_matrix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suukii/Articles/HEAD/assets/graph/adjacency_matrix.png -------------------------------------------------------------------------------- /assets/network/point_to_point.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suukii/Articles/HEAD/assets/network/point_to_point.png -------------------------------------------------------------------------------- /assets/network/using_router_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suukii/Articles/HEAD/assets/network/using_router_1.png -------------------------------------------------------------------------------- /assets/proxy_design_pattern_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suukii/Articles/HEAD/assets/proxy_design_pattern_1.png -------------------------------------------------------------------------------- /assets/diff_compiler_interpreter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suukii/Articles/HEAD/assets/diff_compiler_interpreter.png -------------------------------------------------------------------------------- /assets/network/ip_fragmentation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suukii/Articles/HEAD/assets/network/ip_fragmentation.png -------------------------------------------------------------------------------- /assets/network/multiple_access.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suukii/Articles/HEAD/assets/network/multiple_access.png -------------------------------------------------------------------------------- /assets/recursive_call_stack_tco.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suukii/Articles/HEAD/assets/recursive_call_stack_tco.png -------------------------------------------------------------------------------- /assets/same_origin_cross_origin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suukii/Articles/HEAD/assets/same_origin_cross_origin.png -------------------------------------------------------------------------------- /articles/others/2020-09-18-17-53-37.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suukii/Articles/HEAD/articles/others/2020-09-18-17-53-37.png -------------------------------------------------------------------------------- /articles/others/2020-09-18-18-00-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suukii/Articles/HEAD/articles/others/2020-09-18-18-00-16.png -------------------------------------------------------------------------------- /assets/network/point_to_point_such_a_mess.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suukii/Articles/HEAD/assets/network/point_to_point_such_a_mess.png -------------------------------------------------------------------------------- /assets/network/point_to_point_several_computers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suukii/Articles/HEAD/assets/network/point_to_point_several_computers.png -------------------------------------------------------------------------------- /articles/sorting/selection_sort.md: -------------------------------------------------------------------------------- 1 | # 排序 - 选择排序 2 | 3 | ## 概念 4 | 5 | 选择排序算法就是不断地遍历数组中未排序的部分,从中选出最小的数字,换到未排序部分的开头。 6 | 7 | ## 算法 8 | 9 | 1. 从第一个数字开始遍历数组,同时记录当前遍历中遇到的最小的数字。 10 | 2. 一轮遍历结束后,把最小的数字和数组的第一个数字交换位置。 11 | 3. 接着从第二个数字开始遍历,找出剩余数组中的最小数字,与第二个数字交换位置,重复以上步骤,直到数组中所有数字都已排序。 12 | 13 | ## 复杂度 14 | 15 | - 时间复杂度:$O(n^2)$,无论数组是否已经排好序,时间复杂度都是这个,因为每个数字都差不多要跟数组中的其他数字比较一次。 16 | - 空间复杂度:$O(1)$。 17 | 18 | ## 应用 19 | 20 | - 待排序数组比较小,不计较时间复杂度的时候可以用。 21 | - 如果排序方案要求每个数字都要跟数组中的其他数字做一次比较,可以考虑选择排序。 22 | - 写操作(交换数字)次数有限制时可以考虑,相较冒泡排序中的 $O(n^2)$ 次交换元素的操作,选择排序中只需要 $O(n)$ 次。 23 | 24 | ## 代码 25 | 26 | JavaScript Code 27 | 28 | ```js 29 | function selectionSort(arr) { 30 | for (let i = 0; i < arr.length; i++) { 31 | let minIndex = i; 32 | for (let j = i; j < arr.length; j++) { 33 | if (arr[j] < arr[minIndex]) minIndex = j; 34 | } 35 | [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]]; 36 | } 37 | } 38 | ``` 39 | -------------------------------------------------------------------------------- /articles/sorting/insertion_sort.md: -------------------------------------------------------------------------------- 1 | # 排序 - 插入排序 2 | 3 | ## 概念 4 | 5 | 假设我们手上有一副扑克牌,现在要把所有牌按顺序摆放在桌面上,我们可以抽出第一张牌,先放到桌上,然后抽出第二张牌,如果第二张牌比桌面上的牌大,就放到它右边,反之就放到左边。 6 | 7 | 插入排序也是相似的思路,把数组分成“已排序”和“未排序”两个部分,不断地从“未排序”中取出数字,插入“已排序”部分。 8 | 9 | ## 算法 10 | 11 | - 假设数组的第一个数字是“已排序”部分。 12 | - 从第二个数字开始,首先用一个变量记录这个数字 key: 13 | - 将 key 与其左边的数字进行比较, 14 | - 如果它比左边的数字小,那就把左边的数字右移一位, 15 | - 继续向左比较,直到找到比 key 更小的数字,把 key 插到它的后面。 16 | 17 | ## 复杂度 18 | 19 | - 时间复杂度:$O(n^2)$,n 为数组长度。 20 | - 空间复杂度:$O(1)$。 21 | 22 | ## 应用 23 | 24 | - 数组规模小的情况下。 25 | - 数组已经是部分排好序了,只剩一小部分需要排序。 26 | 27 | ## 代码 28 | 29 | JavaScript Code 30 | 31 | ```js 32 | function insertionSort(arr) { 33 | for (let i = 1; i < arr.length; i++) { 34 | const key = arr[i]; 35 | let j = i - 1; 36 | while (j >= 0 && arr[j] > key) { 37 | arr[j + 1] = arr[j]; 38 | j--; 39 | } 40 | arr[j + 1] = key; 41 | } 42 | } 43 | ``` 44 | -------------------------------------------------------------------------------- /articles/typescript/typescript_as_const.md: -------------------------------------------------------------------------------- 1 | # TypeScript 学习笔记 - `as const` 2 | 3 | 假设我们定义了一个 `colors` 变量 4 | 5 | ```ts 6 | const colors: { [prop: string]: string } = { 7 | red: '#f00', 8 | white: '#fff', 9 | black: '#000', 10 | }; 11 | ``` 12 | 13 | 之后我们想要在程序的其他地方使用这个变量时,可能会得到这样的提示 14 | 15 |  16 | 17 | 提示告诉我们,`black` 的类型是 `string`,但如果我们想要知道 `black` 的值是什么,就得回到 `colors` 的定义处查询。 18 | 19 | 如果我们加上 `as const`,再来看看提示 20 | 21 | ```ts 22 | const colors: { [prop: string]: string } = { 23 | red: '#f00', 24 | white: '#fff', 25 | black: '#000', 26 | } as const; 27 | ``` 28 | 29 |  30 | 31 | 这次提示不仅告诉了我们 `black` 属性是 `string` 类型的,还把它的值 `#000` 也提示出来了。 32 | 33 | `as const` 还有一个用途,就是把对象的属性都变成 `readonly`,而且这个作用对于**嵌套属性**也是生效的。 34 | 35 |  36 | -------------------------------------------------------------------------------- /articles/network/same_site_&_same_origin.md: -------------------------------------------------------------------------------- 1 | # 如何分辨同源和同站 2 | 3 | ## Origin 4 | 5 | `scheme` + `hostname` + `port` 都一样的两个 URL 才会被认为是同源(Same-Origin),否则就是 Cross-Origin。 6 | 7 |  8 | 9 | ## Site 10 | 11 | `Top-level domain (TLD)` + `TLD 前面的那部分 domain` 相同的两个 URL 就是同站(Same-Site),否则就是 Cross-Site。 12 | 13 |  14 | 15 | 不过,对于 `.co.jp` 和 `.github.io` 这种 domain,如果只是把 `.jp` 和 `.io` 当作 TLD,这样是不够判断两个 URL 是否 Same-Site 的。所以就有了 effective TLD 这个概念,简写成 `eTLD`,比如以 `.co.jp` 结尾的 URL,它们的 `eTLD` 就是 `.co.jp`。 16 | 17 | `eTLD` 和它前面的那部分 domain 加起来被称为 `eTLD+1`,只要两个 URL 的这个部分一致,它们就是 Same-Site。 18 | 19 | 可以看到 Same-Site 的判断是不会考虑 scheme 的,不过有时候我们可能想要加上这个判断条件,把 scheme 考虑在内的 Same-Site 判断就叫 schemeful same-site,除了增加判断两个 URL 的 scheme 是否一致这个条件,其他判断两者是一样的。 20 | 21 | ## Sec-Fetch-Site 22 | 23 | 用来判断请求是否 Same-Site 或者 Same-Origin,目前只有 Chrome 支持这个头部字段,其值为以下之一: 24 | 25 | - cross-site 26 | - same-site 27 | - same-origin 28 | - none 29 | -------------------------------------------------------------------------------- /articles/sorting/counting_sort.md: -------------------------------------------------------------------------------- 1 | # 排序 - 计数排序 2 | 3 | ## 概念 4 | 5 | 计数排序是先统计原数组中每个数字出现的次数,然后再根据这些统计构建结果数组。 6 | 7 | 统计时是使用一个辅助数组,将原数组中的数字作为辅助数组的下标,将数字出现的次数作为值。 8 | 9 | ## 应用 10 | 11 | 计数排序的优点是: 12 | 13 | - 线性复杂度 14 | 15 | 缺点是: 16 | 17 | - 数字的大小范围受限 18 | - 额外的空间,如果数字范围比较大,空间消耗也会变大 19 | 20 | 所以适合使用计数排序的场景是: 21 | 22 | - 数字都是整数,而且数字范围比较小 23 | - 需要实现线性复杂度时 24 | 25 | ## 复杂度 26 | 27 | - 时间复杂度:$O(n+k)$,n 是排序数组的长度,k 是排序数组中数字的范围,也就是最大的数字。一共有 4 层循环,找最大数字以及计算每个数字出现次数的时间复杂度是 $O(n)$;生成结果数组时,遍历辅助数组 `countArr` 的时间是 $O(k)$,在遍历 `countArr` 内部还有一个 `while` 循环,那个加起来也是 $O(n)$,所以总的时间复杂度是 $O(3n+k)$,也就是 $O(n+k)$。 28 | - 空间复杂度:$O(n+k)$,n 是结果数组的长度,k 是辅助计数数组的长度。 29 | 30 | ## 代码 31 | 32 | JavaScript Code 33 | 34 | ```js 35 | function countingSort(array) { 36 | const maxNum = Math.max(...array); 37 | const countArr = Array(maxNum + 1).fill(0); 38 | 39 | array.forEach(n => countArr[n]++); 40 | 41 | const resArr = Array(array.length); 42 | let index = 0; 43 | countArr.forEach((count, num) => { 44 | while (count > 0) { 45 | resArr[index++] = num; 46 | count--; 47 | } 48 | }); 49 | return resArr; 50 | } 51 | ``` 52 | -------------------------------------------------------------------------------- /articles/sorting/bubble_sort.md: -------------------------------------------------------------------------------- 1 | # 排序 - 冒泡排序 2 | 3 | ## 概念 4 | 5 | 冒泡排序会比较数组中相邻的两个数字,如果这两个数字不是排好序的话(按要求,升序或者降序),就将它们位置交换。 6 | 7 | ## 算法 8 | 9 | 1. 从 `arr[0]` 开始,比较 `arr[0]` 和 `arr[1]`,如果 `arr[0] > arr[1]`,将两者交换位置(升序),否则什么都不做。 10 | 2. 继续比较 `arr[1]` 和 `arr[2]`,如果 `arr[1] > arr[2]`,将两者交换位置。 11 | 3. 继续比较,直到数组的最后一个数字,这样一轮下来之后,最大的数字就被移动到了数组的最后一个位置。 12 | 4. 回到 `arr[0]`,重复步骤 1-3。 13 | 5. 重复步骤 4 直到数组中的所有数字都排好序。 14 | 15 | ## 复杂度 16 | 17 | - 时间复杂度:$O(n^2)$,n 是数组长度。简单地想,有两层循环,最坏的情况是所有数字都需要改变位置,也就是原本的数组是反向排序的,而最好的情况就是 $O(n)$。 18 | - 空间复杂度:$O(1)$。 19 | 20 | ## 应用 21 | 22 | - 不用考虑时间复杂度的时候(时间复杂度是真的很高)。 23 | - 想要简单实现排序的时候(代码是真的很简单)。 24 | 25 | ## 代码 26 | 27 | JavaScript 28 | 29 | ```js 30 | const bubbleSort = arr => { 31 | while (true) { 32 | let swapped = false; 33 | for (let i = 0; i < arr.length - 1; i++) { 34 | if (arr[i] > arr[i + 1]) { 35 | // swap 36 | [arr[i], arr[i + 1]] = [arr[i + 1], arr[i]]; 37 | // mark 38 | swapped = true; 39 | } 40 | } 41 | // 没有数字发生交换,说明已经排好序了,结束循环 42 | if (!swapped) break; 43 | } 44 | return arr; 45 | }; 46 | ``` 47 | -------------------------------------------------------------------------------- /articles/network/how_dns_works.md: -------------------------------------------------------------------------------- 1 | # DNS 如何查询 IP 地址? 2 | 3 | ## 授权型 DNS 服务器 4 | 5 | 授权型 DNS 服务器(authoritative DNS server, aka, nameserver 名称服务器),会把它们所管理的域名下的所有 IP 地址存放在数据库中,我们向授权型 DNS 服务器查询某个域名的 IP 地址时,可以直接获取返回结果。 6 | 7 | 比如 `github.com` 的一个授权型 DNS 服务器是 `ns-421.awsdns-52.com.`,我们可以通过 8 | 9 | ``` 10 | dig @ns-421.awsdns-52.com github.com +short 11 | ``` 12 | 13 | 直接查到 `github.com` 的 IP 地址,`+short` 是一个参数,表示只显示结果,隐藏其他查询细节。 14 | 15 | 不过通常客户端不会对授权型 DNS 服务器直接进行查询,而是通过递归型 DNS 服务器。 16 | 17 | ## 递归型 DNS 服务器 18 | 19 | 递归型 DNS 服务器(recursive DNS server)并不知道哪个域名对应哪个 IP 地址,它们是通过向授权型 DNS 服务器询问,最终找到正确的 IP 地址,然后把 IP 地址进行缓存以便再次查询。 20 | 21 | ``` 22 | dig github.com +trace 23 | ``` 24 | 25 | 使用 `+trace` 参数会显示递归查询的过程。 26 | 27 | ## 如何通过递归型 DNS 服务器查询 IP 地址? 28 | 29 |  30 | 31 | 1. 用户在浏览器中输入 `github.com`; 32 | 2. `github.com` 请求被路由到 DNS 解析程序(一般由 ISP 进行管理); 33 | 3. 如果 ISP 的 DNS 解析程序上有 `github.com` IP 地址的缓存,直接返回缓存,如果没有缓存或者缓存已过期,那就继续查询; 34 | 4. DNS 解析程序将请求转发到 DNS 根名称服务器(root nameserver); 35 | 5. 根名称服务器根据请求的信息,决定把请求转发到 `.com` 域的一个 TLD(top-level domain)名称服务器; 36 | 6. 对于这个请求,`.com` 域的名称服务器响应了 8 个与 `github.com` 相关的名称服务器; 37 | 7. ISP 的 DNS 解析程序选择其中一个名称服务器 `ns-421.awsdns-52.com.` 并向它转发 `github.com` 请求; 38 | 8. 这个名称服务器在托管 `github.com` 的区域找到相应的 IP 地址 `13.229.188.59` 返回; 39 | 9. ISP 的 DNS 解析程序最终获取用户需要的 IP 地址,将它缓存起来,并把地址返回给浏览器; 40 | 10. 浏览器将请求发送到 `13.229.188.59`; 41 | 11. `13.229.188.59` 上的 Web 服务器将页面返回显示在浏览器中。 42 | -------------------------------------------------------------------------------- /articles/design-pattern/design_pattern_proxy.md: -------------------------------------------------------------------------------- 1 | # 代理模式 2 | 3 | ## 目的 4 | 5 | 当调用方不方便直接操作某个对象,又或者希望在访问对象之前或之后做某些操作时,可以使用代理模式。 6 | 7 | - 使用一个代理对象作为本体对象的替身; 8 | - 调用方访问代理对象,代理对象做完某些操作后,按照调用方的需求去访问本体对象,把结果返回给调用方; 9 | 10 |  11 | 12 | ## 问题 & 解决办法 13 | 14 | 举个例子,我们有一个存放着超多数据的对象,如果把这个对象直接在程序中初始化的话,肯定会占据很多内存;而且,程序不一定一开始就需要用到这个对象,也不一定需要这个对象的所有数据;所以,比较经济的方法是,把这个超大对象放在服务端,当程序需要的时候再发起网络请求去访问。 15 | 16 | 问题是,在程序中可能不止一处要用到这个对象,我们总不想把发起网络请求的逻辑到处复制粘贴吧。而代理模式的思想就是,提供一个代理对象(替身),把这个代理对象伪装成本体对象,然后把本体对象藏起来。调用方需要数据的时候,就去访问代理对象,其实调用方是分不清自己在访问代理对象还是本体对象的,它也根本不需要区分。 17 | 18 | 用图解释大概是这个样子: 19 | 20 |  21 | 22 | ## 应用 23 | 24 | 代理模式大概有以下几种用途: 25 | 26 | - 惰性初始化:在真正需要的时候才通过代理对象去初始化本体对象,一般是本体对象消耗很大的时候才需要这样做; 27 | 28 | - 控制访问权限:代理对象负责甄别哪些调用方有权访问本体对象,对有权访问的调用方,代理对象把它们的请求转发给本体对象,对于无权访问的,就无视它们的请求; 29 | 30 | - 打印日志:想要记录某个对象的访问日志时,可以用一个代理对象来做这件事,这样就不需要对本体对象做修改; 31 | 32 | - 缓存请求结果:就像上例一样,代理对象负责发送网络请求并缓存请求结果,下次有同样的请求时可以直接返回缓存的数据; 33 | 34 | - 智能引用:可以用一个代理对象来记录某个对象当前有多少个引用,如果没有调用方引用这个本体对象,这个对象的内存就可以被及时释放了; 35 | 36 | ## 优点 37 | 38 | - 可以通过代理对象来控制本体对象而不必修改调用方代码和本体对象; 39 | - 可以很方便地引入多个代理对象,代理对象们可以链式访问(对代理对象来说,访问另一个代理对象和访问本体对象没什么不同); 40 | 41 | > 唯一的修改是,把调用方的访问对象从本体对象改成代理对象,其他不变,因为代理对象是一个“伪本体对象”,在调用方看来,它和本体对象是一样的。 42 | -------------------------------------------------------------------------------- /articles/design-pattern/design_pattern_singleton.md: -------------------------------------------------------------------------------- 1 | # 单例模式 2 | 3 | 单例模式就是一个 class 永远都返回同一个实例,而且这个实例还可以在全局中访问到。 4 | 5 | 听起来是不是很像一个全局变量? 6 | 7 | ## 单例模式 vs 全局变量 8 | 9 | - 同样都是全局可访问,全局变量有被其他代码修改的风险,而单例模式提供的实例不能通过外部代码来修改替换,除非这个 class 暴露了修改实例的方法。 10 | 11 | - 单例模式还能封装一些代码逻辑。 12 | 13 | ## 实现思路 14 | 15 | 单例模式永远返回同一个实例对象,所以我们不能使用普通的构造函数来实现,因为每次 new 的时候都会创建一个新的实例。 16 | 17 | > 其实如果是 JS 的普通构造函数语法的话,new 的时候如果函数中返回一个对象,那 new 出来的对象就会被丢弃,然后我们也可以把单例对象作为构造函数的静态属性缓存起来,不过这样我们也把单例对象暴露出去了。 18 | 19 | 我们得把构造函数和单例对象隐藏起来,然后另外暴露一个获取单例对象的方法。在这个方法中,我们需要实现的是: 20 | 21 | 1. 首次调用时,在方法中调用隐藏的构造函数,创建实例对象并缓存起来; 22 | 2. 之后的调用就直接返回缓存的实例对象; 23 | 24 | ## 实现代码 25 | 26 | 因为 JS 的 class 还不支持私有属性,所以我们先用一个 IIFE 来实现单例模式。 27 | 28 | ```js 29 | let count = 0 30 | 31 | const Singleton = (function () { 32 | // 缓存单例实例对象 33 | let instance = null 34 | 35 | // 只有首次调用 getInstance 方法时才会用 new 调用 Constructor 方法 36 | // 外部代码无法通过 new 调用 Constructor 创建实例对象 37 | // Contructor 只会执行一次 38 | function Constructor() { 39 | count++ 40 | } 41 | 42 | function getInstance() { 43 | // 首次调用,创建单例实例并缓存 44 | if (!instance) { 45 | instance = new Constructor() 46 | } 47 | // 之后调用,直接返回缓存的实例对象 48 | return instance 49 | } 50 | // 暴露获取单例对象的方法 51 | return { 52 | getInstance 53 | } 54 | })() 55 | 56 | const a = Singleton.getInstance() 57 | const b = Singleton.getInstance() 58 | console.log(a === b) // true 59 | console.log(count) // 1 60 | ``` 61 | 62 | ## 优点 63 | 64 | - 保证了一个 class 只有一个实例; 65 | - 这个实例是全局可访问的; 66 | - 惰性初始化,单例对象只有在首次获取的时候才会被创建,这也是和全局变量不同的一个点。 67 | -------------------------------------------------------------------------------- /articles/security/what_is_CSP.md: -------------------------------------------------------------------------------- 1 | # CSP 内容安全策略 2 | 3 | ### 什么是 CSP 4 | 5 | 一个网页可以跟各种网络资源打交道,比如请求 JS 或者 CSS 文件,发送 AJAX 请求,加载图片/字体文件等,在这些过程中很有可能会发生 XSS 或者代码注入攻击等。 6 | 7 | 为了防御攻击,就有了 CSP(Content Security Policy),它是一层安全保障,用来约束浏览器只能从白名单站点下载 scripts(包括 inline scripts)、styles、媒体文件等资源。 8 | 9 | P.S. 虽然 CSP 可以减少 XSS 攻击的风险,但它不能完全替代校验用户输入以及编码输出这些安全性检查操作 10 | 11 | ### 如何开启 CSP 12 | 13 | 开启 CSP 有两个方法: 14 | 15 | - 通过设置 HTTP 响应头部(Content-Security-Policy) 16 | - 通过在 HTML 文档中使用 `` 标签并设置属性 `http-equiv="Content-Security-Policy"` 17 | 18 | CSP 设置内容: 19 | 20 | 不管使用以上哪种方法,CSP 的内容设置都是一样的,一个 CSP 设置包含了一系列允许和禁止的操作,写法上,它是由一系列“指令+源”(directive + resource)组成的。指令和源中间以空格分隔,一个指令可以指定多个源,每个源也以空格分隔。每个“指令+源”组合中间使用 `;` 隔开。 21 | 22 | 通过指令我们可以控制网站的一些行为,以下是一些例子 23 | 24 | - `script-src` 指定 js 文件的可信任源 25 | 26 | `script-src 'self' *.mozilla.com`: `'self'` 是一个关键字,表示允许从当前域(不包括子域)下载 js 文件,`*.mozilla.com` 表示来自 mozilla.com 的 js 文件也可以下载 27 | 28 | - `img-src` 指定 img 和 favicon 的可信任源 29 | 30 | - `default-src` fallback, 如果要请求的文件类型没有相应的指令,那就使用这个指令的设置 31 | 32 | `default-src 'none'`: `'none'` 表示不信任来自任何站点的资源 33 | 34 | [w3.org 完整指令列表](https://www.w3.org/TR/CSP/#csp-directives) 35 | 36 | ### CSP 的两种模式 37 | 38 | 以上所说的是 enforce 模式的 CSP,也就是 `Content-Security-Policy` 头部里面指定的的指令都会被执行。CSP 还有另外一个模式 report 模式,在这种模式下,指令不会被执行,也就是,网站该下载什么文件照样下载,即使下载源不在白名单里,不过如果网站发生了违反 CSP 设置的行为的话,比如向非白名单源请求 js 文件,浏览器就会向指定的服务端发送一份报告。 39 | 40 | 开启 report 模式只需要使用 `Content-Security-Policy-Report-Only` 头部即可,内容设置的规则和 `Content-Security-Policy` 是一样的,而且,这两个头部是可以同时存在,互不干扰的。 41 | 42 | 另外,在 enforce 模式下,如果 CSP 设置有被违反的话,默认是不会向服务端发送报告的,我们可以通过 `report-uri example.com` 指令来开启这个功能。 43 | -------------------------------------------------------------------------------- /articles/workflow/commitlint.md: -------------------------------------------------------------------------------- 1 | # 如何规范 git commit 信息 2 | 3 | ## commit message 4 | 5 | 每次 git commit 我们都要写 commit message(提交说明)。 6 | 7 | 如果一行信息足够了,可以执行 `git commit -m 'hello world`。 8 | 9 | 如果想要输入更多信息,可以执行 `git commit` 调出编辑器填入多行信息。 10 | 11 | 理论上 commit message 写什么都行,不过为了项目的可维护性,规范还是要有的。 12 | 13 | ## commit message 规范 14 | 15 | 社区有很多规范,这里只列举 commit 的类型: 16 | 17 | 1. feat: 新特性 18 | 2. fix: 修复 bug 19 | 3. style: 关于代码风格的修改(注意不是 CSS style) 20 | 4. refactor: 重构 21 | 5. test: 测试相关的代码 22 | 6. docs: 文档相关 23 | 7. chore: 构建过程或者辅助工具的变动 24 | 25 | ## commitlint 26 | 27 | 当然,靠自觉来遵守规范是不太现实的,所以,还是用工具吧。commitlint 就是一个用来检查 commit message 是否符合规范的工具。 28 | 29 | **安装** 30 | 31 | 第一步:安装 commitlint cli 32 | 33 | ```shell 34 | npm i -D @commitlint/cli 35 | ``` 36 | 37 | 第二步:安装规范,这里安装的是 `config-conventional`,也可以选择[其他规范](https://github.com/conventional-changelog/commitlint#shared-configuration) 38 | 39 | ```shell 40 | npm i -D @commitlint/config-conventional 41 | ``` 42 | 43 | **配置** 44 | 45 | ```js 46 | // commitlint.config.js 47 | module.exports = { 48 | // 指定要用的规范 49 | extends: ['@commitlint/config-conventional'], 50 | }; 51 | ``` 52 | 53 | **使用** 54 | 55 | ```shell 56 | npx commitlint --from HEAD~1 --to HEAD --verbose 57 | ``` 58 | 59 | - 检查上一次 commit 的信息是否符合规范。 60 | 61 | 当然,这没什么 🥚 用。我们要的是在 commit 前就检查信息是否符合规范,如果不符合就阻止他提交。 62 | 63 | ## husky + commitlint 64 | 65 | [这里](./git-hooks.md) 已经介绍过 husky 是干什么的了。 66 | 67 | **配置** 68 | 69 | ```json 70 | { 71 | "hooks": { 72 | // 在提交 commit 信息的时候运行 commitlint 命令 73 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" 74 | } 75 | } 76 | ``` 77 | 78 | - https://dev.to/talohana/husky-and-commitlint-for-clean-git-log-44be 79 | - https://www.freecodecamp.org/news/writing-good-commit-messages-a-practical-guide/ 80 | -------------------------------------------------------------------------------- /articles/browser/inside_look_browser_4.md: -------------------------------------------------------------------------------- 1 | # 深入理解现代浏览器 - 交互 2 | 3 | ## 从浏览器的角度看用户输入 4 | 5 | 在浏览器看来,用户输入包括: 6 | 7 | - 在输入框中输入文字 8 | - 点击鼠标 9 | - 滚动鼠标滚轮 10 | - 移动鼠标,或者手指在屏幕上滑动 11 | - ... 12 | 13 | 当这些事件发生时,浏览器进程是最先收到消息的,但是,浏览器进程只知道发生的事件类型以及事件发生的坐标,因为网页内容是渲染器进程在管,所以浏览器进程会把事件类型(event type)和事件发生的坐标传送给渲染器进程,渲染器进程再找到事件目标(event target)并触发事件处理函数(event handler)。 14 | 15 | ## 合成器线程接收事件 16 | 17 | 如果用户触发了页面滑动事件,浏览器进程会把事件信息发送到渲染器进程中的合成器线程,合成器线程的处理分两种情况: 18 | 19 | - 如果页面上没有绑定事件处理函数,那么合成器线程只需要把已经栅格化的图层合成一个新的合成器帧就可以了,这样整个滑动过程会非常丝滑。 20 | 21 | - 如果页面绑定了事件处理函数,合成器线程会通知主线程来执行相应的事件处理函数。 22 | 23 | ## 非快速滑动区域(non-fast scrollable region) 24 | 25 | 当合成器线程在合成页面时,它会把页面中有绑定事件处理函数的区域标记为“非快速滑动区域”,如果用户输入事件发生在这些区域,合成器线程会通知主线程去处理这些事件,否则合成器线程就可以不用和主线程通信,直接合成新的合成器帧即可。 26 | 27 | 那么问题来了,我们在绑定事件处理函数的时候,常常会使用事件代理模式,试想一下我们把整个页面的事件处理都交给 `
` 元素来代理监听,那整个页面都会被合成器线程标记为“非快速滑动区域”,那合成器线程的丝滑滑动优点就发挥不了作用了,现在每次页面滑动时合成器线程都得先和主线程通信并等待主线程的回应。 28 | 29 | 为了合成器丝滑滑动和事件代理这两个优点能共存,我们可以在监听事件的时候传递第三个参数 `{ passive: true }`,意思是,主线程还是照样监听事件,但合成器线程就不用等待主线程的回复了,可以在通知主线程之后直接开始合成新的合成器帧。 30 | 31 | ## 定位事件对象 32 | 33 | 当主线程从合成器线程那里接收到事件信息之后,它要做的第一件事情就是命中测试(hit test),找到事件对象,命中测试会使用在渲染过程中生成的绘制记录(paint record)来找到在事件发生坐标的具体是哪个元素。 34 | 35 | ## 降低向主线程发送事件的频率 36 | 37 | 一般触屏设备每秒会触发 60-120 次 `touch` 事件,一般鼠标每秒会触发 100 鼠标事件,而显示器每秒只会刷新 60 次。假如每秒连续向主线程发送 120 次 `touchmove`,就会触发 120 次命中测试,由于屏幕刷新次数远小于 120 次,有些命中测试就有点多余了。 38 | 39 | 所以 Chrome 会把连续触发的事件(如 `wheel`, `mousewheel`, `mousemove`, `pointermove`, `touchmove`)结合起来,等到下一个 `requestAnimationFrame()` 调用前再把事件信息发送给主线程。 40 | 41 | 不过非连续的事件,如 `keydown`, `keyup`, `mouseup`, `mousedown`, `touchstart`, `touchend` 都是立刻就通知主线程的。 42 | 43 | ## getCoalescedEvents() 44 | 45 | 对于一般应用程序,Chrome 合并连续触发事件的这个操作并不会影响用户体验,不过如果有需要,可以通过 `event.getCoalescedEvents()` 来获取被合并的事件的信息。 46 | 47 | ## 小结 48 | 49 | 这一节介绍了浏览器是怎么处理用户输入的。 50 | -------------------------------------------------------------------------------- /articles/workflow/git-hooks.md: -------------------------------------------------------------------------------- 1 | # 如何监听 git hooks 2 | 3 | ## git hooks 4 | 5 | 首先来简单了解下 git hooks 是什么。 6 | 7 | 在 git 的生命周期中,会有很多事件发生,比如 `commit`, `push` 等等。在这些事件发生时,我们可以指定执行一些脚本,这些脚本就是 git hooks。 8 | 9 | 要用比喻来说的话,有点像 Vue 组件的生命周期函数,但也不完全一样,Vue 的生命周期函数不能阻止组件进入下一个生命周期,而 git hooks 却可以阻止 git 事件的发生。 10 | 11 | 我们可以自己写 git hook 脚本,不过有更简单的办法,就是用 husky(git hooks for Node.js)! 12 | 13 | // 谁要自己写啊 (⊙ˍ⊙)? 14 | 15 | ## husky 16 | 17 | [https://typicode.github.io/husky/#/](https://typicode.github.io/husky/#/) 18 | 19 | **安装** 20 | 21 | ```shell 22 | npm i -D husky 23 | ``` 24 | 25 | **安装注意** 26 | 27 | - 在初始化 git 仓库之后再安装 husky。 28 | - 或者在安装 husky 之后运行 `npx husky install` 命令。 29 | 30 | 这样 husky 才能起作用。 31 | 32 | **package.json 配置** 33 | 34 | ```json 35 | // package.json 36 | { 37 | "husky": { 38 | "hooks": { 39 | // 要监听的 git hook: 要执行的命令 40 | "pre-commit": "echo hello suukii" 41 | } 42 | } 43 | } 44 | ``` 45 | 46 | **.huskyrc 配置** 47 | 48 | 配置也可以单独放在一个文件中: 49 | 50 | ```json 51 | // .huskyrc 52 | { 53 | "hooks": { 54 | "pre-commit": "echo hello suukii" 55 | } 56 | } 57 | ``` 58 | 59 | **绕过 git hooks** 60 | 61 | 如果想要绕过 git hooks,可以使用 `--no-verify` 或者 `-n` 参数: 62 | 63 | ```shell 64 | git commit -m 'yoho' --no-verify 65 | ``` 66 | 67 | 如果是没有 `--no-verify` 参数的 git 命令,可以使用 HUSKY 环境变量: 68 | 69 | ```shell 70 | HUSKY=0 git push 71 | ``` 72 | 73 | **常用的 hooks** 74 | 75 | - `commit-msg`: `git commit` 带参数的时候触发 76 | - `pre-commit`: `git commit` 不带参数参数时也会触发 77 | - `push` 78 | - husky 几乎支持 [git 提供的所有 hooks](https://git-scm.com/docs/githooks) 79 | 80 | ## 一般用 git hooks 来做什么 81 | 82 | - 规范 commit 信息 83 | - 规范代码质量/风格 84 | - 在提交代码前运行测试 85 | - ...... 86 | 87 | * https://www.freecodecamp.org/news/how-to-add-commit-hooks-to-git-with-husky-to-automate-code-tasks/ 88 | -------------------------------------------------------------------------------- /articles/typescript/typescript_infer.md: -------------------------------------------------------------------------------- 1 | # TypeScript 学习笔记 - `infer` 2 | 3 | 先来看一个例子: 4 | 5 | ```ts 6 | type FlattenIfArray = (
35 | props: P,
36 | ) => R;
37 |
38 | type DestructuredArgsOfFunction<
39 | F extends FunctionWithOneObjectArgument ` 的子集,如果是子集则将其中的 `P` 类型抽出来返回,如果不是则返回 `never`
57 |
58 | - `typeof myFunction` 会返回 `myFunction` 的函数签名,也就是 `(props: { x: number; y: number }): string`
59 |
60 | - `DestructuredArgsOfFunction