├── 2021 ├── video.html └── video.md ├── 2022 └── promise.js ├── .DS_Store ├── .DS_Store~9b2ec44531bba1e5376ccbed365e6826e7b64789 ├── .vscode └── settings.json ├── 2019年文档总结 ├── 2019年总结.md ├── Axios 源码解读.md ├── Fetch 的.md ├── Hooks 深入剖析.md ├── Js 基础-new操作符.md ├── React_hooks.md ├── Redux.md ├── hooks实例模拟和源码解读.md ├── http协议之浏览器缓存.md ├── ts泛型.md ├── 抽象语法树.md └── 自定义状态管理useContext.md ├── 2020年文档 └── 多文件下载.md ├── 2021年文档总结 ├── 前端开发规范.md ├── 正则分享.md ├── 英文短语学习.md └── 项目开发规范.md ├── 2023年文档 ├── gitlab-runner 自动化部署搭建.md ├── linux.md └── nginx │ └── 快捷路径.md ├── Ajax_fetch.md ├── Antd ├── Typography.md ├── dom.html └── node.md ├── Dictionary.html ├── HashTable.html ├── Promise.md ├── Promise ├── README.md ├── async.js ├── index.js ├── polyfill.js ├── polyfill_class.js ├── simple.js └── test.js ├── README.md ├── TypeScript └── Omit..md ├── XMLHttpRequest.md ├── create-app-react.md ├── extends.html ├── flex-note.md ├── git.md ├── git2.md ├── git_use.md ├── html ├── canvas │ └── index.html ├── tabs滚动布局 │ ├── 1.vue │ └── 2增加自动居中.vue ├── water.html ├── 一些Js实现 │ └── 无线轮播.html └── 类美团点餐布局 │ ├── 1.html │ ├── 1.png │ └── index.vue ├── immutable.md ├── inline-block.html ├── js_api └── deepCopy.js ├── npm ├── img │ ├── four.jpg │ ├── one.jpg │ ├── three.jpg │ └── two.jpg ├── npm.md ├── npm_link.md ├── pull_requests.md └── 好用的一些包.md ├── react └── README.md ├── review ├── 640.webp ├── amd_cmd.md ├── api.md ├── dom事件流.md ├── eventLoop.md ├── note.md ├── prototype.md └── study.md ├── src ├── App.js ├── Router.jsx ├── index.js ├── layout │ ├── Head │ │ └── index.jsx │ └── Side │ │ └── index.jsx └── pages │ ├── HandOver │ ├── AddForm.jsx │ ├── index.css │ ├── index.jsx │ └── mock.js │ ├── Home │ └── index.jsx │ └── Page404 │ └── index.jsx ├── utils └── isColorDarkOrLight.js ├── webpack.md ├── webpack ├── Tapble │ └── README.md ├── bin │ └── my_webpack.js └── lib │ └── Compiler.js ├── webpakc.md ├── 单元测试 ├── jest.md └── myJest.md ├── 小程序 ├── avatar.vue ├── index.js ├── login.js └── uniapp.json ├── 算法 ├── DFC-BFC.html ├── heapSort.md ├── js实现超长文字省略.html ├── 函数柯里化.html ├── 堆排序.html ├── 快排.html ├── 语法高亮.html └── 黄金问题.html ├── 输入URL后发生了什么.md └── 链表.html /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zouxiaomingya/JavaScript/5f354b8a06677af80210d9620fd27d7391a17373/.DS_Store -------------------------------------------------------------------------------- /.DS_Store~9b2ec44531bba1e5376ccbed365e6826e7b64789: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zouxiaomingya/JavaScript/5f354b8a06677af80210d9620fd27d7391a17373/.DS_Store~9b2ec44531bba1e5376ccbed365e6826e7b64789 -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "liveServer.settings.port": 5501 3 | } -------------------------------------------------------------------------------- /2019年文档总结/2019年总结.md: -------------------------------------------------------------------------------- 1 | 白驹过隙,转眼就到了 2020 年,掏出手机,找出当年立下的 flag(无知装的 B)。 2 | 都说力 flag 一时爽,一直立 flag 一直爽,果不其然。 3 | 4 | > 文末有福利~~~ 5 | 6 | ### 2019 年的 flag 7 | 8 | 9 | ![](https://user-gold-cdn.xitu.io/2020/1/16/16faed30425ef1a3?w=1060&h=1354&f=png&s=2075490) 10 | 11 | 看记录,感觉当时写的也是蛮随意。又是看书又是做题,背单词的同时还要减肥,现在这样看感觉当时的自己对未来的自己要求还蛮多,果然当时自己是个狠人。不过好在经过了 1 年的努力,成功让我可以在 2020 年不用立 flag 了。没错,就是一个都没完成。 12 | 13 | 这不经让我陷入了沉思,反省了自我,为什么当时深夜不睡觉,要立个 flag 呢。不过看到最后一条要励志减肥,我想可能深夜吃完了炸鸡外卖,又或者是刚刚打完游戏躺床上安慰自己立下的?没错,我觉得很有能!! 14 | 15 | ### 2019 flag 完成情况。 16 | 17 | 1. 读书 12 本(仅限技术类书籍) 18 | > 读了:7 本。完成度:58% (仅读完,掌握程度未知) 19 | 2. 掘金落实文章 12 篇 20 | > 写了:9 篇。 完成度:75% 21 | 3. 每天背 20 个单词 22 | > 背了:139 天。 完成度:38% 23 | 4. leetcode 刷题 120 道 24 | > 刷了:72 题。 完成度:60% 25 | 5. 减肥 10 斤以上 26 | > 减了 -8 斤。完成度:呵呵% 27 | 28 | #### 掘金 29 | ![](https://user-gold-cdn.xitu.io/2020/1/16/16fae403e5e642e1?w=780&h=764&f=png&s=454323) 30 | 31 | #### 默默背单词 32 | 33 | ![](https://user-gold-cdn.xitu.io/2020/1/16/16faed23452cdea4?w=1050&h=1302&f=png&s=483074) 34 | 35 | #### leetcode app 36 | ![](https://user-gold-cdn.xitu.io/2020/1/16/16fae41f7cf59848?w=770&h=1326&f=png&s=238299) 37 | 38 | #### 书籍 39 | ![](https://user-gold-cdn.xitu.io/2020/1/16/16fae426d57d2691?w=1716&h=1242&f=png&s=3454564) 40 | 41 | ### 总结 2019 的 flag 42 | 总体平均完成 50% 不到。很爽。反思 2 秒确实是时间还是花的不够。都不是特别大的 flag。而且当时为了鼓励自己完成,答应基本完成后给自己买个最新版的 MacBook Pro。不过也是不刁难自己,年底前还是给自己买了,哈哈哈。 43 | 44 | 45 | ### 立下 2020 的 flag 46 | > 这次就不默默的立下 flag 写出来希望自己能够时刻看到。同时大家也可以监督一下(虽然也不管用) 47 | 48 | 并且希望 2020 年的 flag 能够基本完成,决定还是在清晨(有逼数)时刻来立下 2020 年的 flag。 49 | 50 | ![](https://user-gold-cdn.xitu.io/2020/1/16/16fae54bb0c6f7d2?w=722&h=450&f=png&s=333618) 51 | 52 | ### 最后 53 | 54 | 快放假了,提前祝福大家新年快乐。下面是我有的电子书,如果有需要可以加我给你们分享。 55 | 百度云 id:阿明明曰(注:最后一个字是孔子曰的曰 yue ) 56 | 57 | ![](https://user-gold-cdn.xitu.io/2020/1/17/16fb2256e55d2d12?w=1304&h=644&f=png&s=366370) 58 | -------------------------------------------------------------------------------- /2019年文档总结/Fetch 的.md: -------------------------------------------------------------------------------- 1 | ### Fetch 的实例讲解 2 | #### 简介 3 | 4 | [Fetch API](https://link.jianshu.com/?t=https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) 提供了一个 JavaScript接口,用于访问和操纵 HTTP 管道的部分,例如请求和响应。它还提供了一个全局 [fetch()](https://link.jianshu.com/?t=https://developer.mozilla.org/zh-CN/docs/Web/API/GlobalFetch/fetch)方法,该方法提供了一种简单,合乎逻辑的方式来跨网络异步获取资源。 5 | 6 | `fetch()` 必须接受一个参数---路径。无论请求成功与否,它都返回一个 Promise 对象,resolve 对应请求的 [`Response`](https://developer.mozilla.org/zh-CN/docs/Web/API/Response)。另外你还可以设置第二个参数(可选的参数)[options](#配置)(常用配置如下,具体详情参见 [`Request`](https://developer.mozilla.org/zh-CN/docs/Web/API/Request))。 7 | 8 | #### 配置 9 | 10 | ```js 11 | options={ 12 | // 请求方式 GET,POST,等 13 | method: "GET", 14 | 15 | // 请求头headers 16 | headers: { 17 | Accept: 'application/json, text/plain, */*', 18 | 'Content-Type': 'application/json;charset=UTF-8', 19 | 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', 20 | }, 21 | 22 | // omit: 从不发送cookies. 23 | // same-origin 只有当URL与响应脚本同源才发送 cookies 24 | // include 总是发送请求资源域在本地的 cookies 验证信息 25 | credentials: 'include', 26 | 27 | //包含请求的模式 (例如: cors, no-cors, same-origin, navigate) 28 | //no-cors: 常用于跨域请求不带CORS响应头场景 29 | //cors表示同域和带有CORS响应头的跨域下可请求成功. 其他请求将被拒绝 30 | mode: 'cors', 31 | 32 | //包含请求的缓存模式 (例如: default, reload, no-cache).具体参数见下面(cache参数) 33 | cache: 'default' 34 | } 35 | ``` 36 | 37 | ##### cache参数 38 | 39 | > cache 表示如何处理缓存, 遵守 http 规范, 拥有如下几种值: 40 | 41 | 1. default: 浏览器从 HTTP 缓存中寻找匹配的请求. 42 | 2. no-store: 浏览器在不先查看缓存的情况下从远程服务器获取资源,并且不会使用下载的资源更新缓存 43 | 3. reload: 请求之前将忽略 http 缓存的存在, 但请求拿到响应后, 它将主动更新 http 缓存. 44 | 4. no-cache: 如果存在缓存, 那么 fetch 将发送一个条件查询 request 和一个正常的 request , 拿到响应后, 它会更新http缓存. 45 | 5. force-cache: 浏览器在其HTTP缓存中查找匹配的请求。 46 | - 如果存在匹配,*新鲜或过时*,则将从缓存中返回。 47 | - 如果没有匹配,浏览器将发出正常请求,并使用下载的资源更新缓存 48 | 6. only-if-cached: fetch 强行取缓存,( 即使缓存过期了也从缓存取). 如果没有缓存, 浏览器将返回错误 49 | 50 | #### 案例 51 | 52 | 这是一个比较基本的案例,这里为了看的清楚,没有处理 catch 问题 。 53 | 54 | ```javascript 55 | var url = 'https://zhidao.baidu.com/question/api/hotword?pn=1561&rn=5&t=1551165472017'; 56 | fetch(url).then(function(response) { 57 | return response; 58 | }).then(function(data) { 59 | console.log(data); 60 | }) 61 | ``` 62 | 63 | 可以自己动手把这个代码直接贴到控制台中 , 这里为了防止同源策略,并且看到更详细的数据我们最好在 https://zhidao.baidu.com 内的控制台中输入。如果在其他的页面我们应该在 fetch 方法的第二个参数中加上{ mode: "no-cors" }。为了看的更加清楚,我贴出这两种情况的打印结果,如下所示: 64 | 65 | > 在 https://zhidao.baidu.com 网页控制台输入的地址 66 | 67 | 68 | ![](https://user-gold-cdn.xitu.io/2019/2/26/1692a36bfc844d2c?w=783&h=241&f=png&s=28256) 69 | 70 | > 在其他网页控制台输入的地址 71 | 72 | 73 | ![](https://user-gold-cdn.xitu.io/2019/2/26/1692a3731963421a?w=704&h=226&f=png&s=21091) 74 | 75 | 我们会发现两个打印结果基本不同,首先跨域打印的结果中很多数据都是空的,另外我们也发现其中 type 也不相同,这里介绍下 response.type。 76 | 77 | > fetch请求的响应类型( response.type )为如下三种之一: 78 | 79 | - basic,同域下, 响应类型为 “basic”。 80 | - cors,跨域下,返回 cors 响应头, 响应类型为 “cors”。 81 | - opaque,跨域下, 服务器没有返回 cors 响应头, 响应类型为 “opaque”。 82 | 83 | 84 | > `fetch`设了`{mode='no-cors'}`表示不垮域,可以请求成功,但拿不到服务器返回数据 85 | 86 | 看到这里,心急的同学会发现,其实现在数据还是没有拿到,我们仅仅只是拿到了个 Response 对象。那么如何拿到数据呢? 87 | 88 | 瞧好: 89 | 90 | ```javascript 91 | var url = 'https://zhidao.baidu.com/question/api/hotword?pn=1561&rn=5&t=1551165472017'; 92 | fetch(url).then(function(response) { 93 | //通过 response 原型上的 json 方法拿到数据,在返回出去 94 | return response.json(); 95 | }).then(function(data) { 96 | // 接收到 data 打印出来 97 | console.log(data); 98 | }) 99 | ``` 100 | 101 | 通过 response 原型上的 json 方法拿到数据,在返回出去。response 原型打印如下: 102 | 103 | 104 | 105 | ![](https://user-gold-cdn.xitu.io/2019/2/27/1692ed9655bd956d?w=814&h=545&f=png&s=54811) 106 | 107 | 这里要注意的是 response .json / response.text 方法只能使用一个并且只能使用一次,同时使用两个,或则使用两次都会报如下错误: 108 | 109 | `Uncaught (in promise) TypeError: Failed to execute 'json' on 'Response': body stream is locked` 110 | 111 | > 为什么不能使用两次? 112 | 113 | 数据流只能读取一次,一旦读取,数据流变空,再次读取会报错。可以使用 response.clone() 复制一个副本。 114 | 115 | > 为什么只能读取一次? 116 | 117 | 答案还在查寻中,同时也希望知道的读者能够指点一下。 118 | 119 | 上面的写法,看起来有回调,又有链式调用,我们试着换个新写法,这里使用 async/await 来实现: 120 | 121 | ```javascript 122 | var url = 'https://zhidao.baidu.com/question/api/hotword?pn=1&rn=5&t=1551191647420'; 123 | let getData = async () => { 124 | let response = await fetch(url); 125 | let data = response.json(); 126 | console.log(data) 127 | } 128 | ``` 129 | 130 | 打印 data 如下: 131 | 132 | ![](https://user-gold-cdn.xitu.io/2019/2/26/1692a3aec5d3fddd?w=710&h=326&f=png&s=36589) 133 | > 这里 data 是一个 Promise 对象,由于他的内部变量[[PromiseValue]]在外部无法得到,只能在 then 中获取,所以在后面加上 then 获取 data 134 | 135 | ```javascript 136 | let response = await fetch(url); 137 | let data = response.json(); 138 | data.then((res)=>{ 139 | console.log(res) 140 | }) 141 | // 搞定 res 就是我们要的数据拉 142 | ``` 143 | 144 | #### 封装 145 | 146 | > 我们通过 fetch 来做个简易的 request 封装,顺便把 options 再回顾一遍,很多配置在实际中可能是不需要的,都有默认配置(options 的默认配置在下面括号中)。 147 | 148 | ```javascript 149 | function request(url, data = {}) { 150 | // options配置的默认选项在括号中 151 | return fetch(url, { 152 | method: "POST", //(GET), POST, PUT, DELETE, etc 153 | mode: "cors", // (same-origin), no-cors, cors 154 | cache: "no-cache", // (default), no-cache, reload, force-cache, only-if-cached 155 | credentials: "same-origin", //(same-origin), include, omit 156 | headers: { 157 | "Content-Type": "application/json", 158 | // "Content-Type": "application/x-www-form-urlencoded", 159 | }, 160 | redirect: "follow", //(follow), manual, error 161 | referrer: "no-referrer", //(client), no-referrer 162 | body: JSON.stringify(data), // 这里格式要与 "Content-Type" 同步 163 | }) 164 | .then(response => response.json()); 165 | } 166 | ``` 167 | 168 | #### 最后 169 | 170 | > ##### 初次写帖,如有错误或不严谨的地方,请务必给予指正,谢谢 171 | 172 | 参考: 173 | 174 | - [Fetch API](https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API) 175 | - [传统 Ajax 已死,Fetch 永生](https://segmentfault.com/a/1190000003810652) -------------------------------------------------------------------------------- /2019年文档总结/http协议之浏览器缓存.md: -------------------------------------------------------------------------------- 1 | ## http 请求 -- 浏览器缓存 2 | 3 | #### 基本介绍 4 | 5 | - 在Chrome浏览器中的控制台Network中size栏通常会有**三种状态**: 6 | 7 | - 1. **资源本身的大小** (如下图:197B) 8 | 2. **from memory cache** (如下图:idnex.js) 9 | 3. **from disk cache** (如下图:css.js) 10 | 11 | ![](https://user-gold-cdn.xitu.io/2020/1/6/16f7ac75367c3e59?w=1914&h=488&f=png&s=69480) 12 | 13 | 14 | from memory cache 15 | 16 | > 代表使用内存中的缓存 17 | 18 | - from memory cache 代表使用内存中的缓存,即请求的资源是直接从内存中拿到的,不会请求服务器。 19 | - 一般已经加载过该资源且资源已经缓存在了内存当中,当关闭该页面时,此资源就被内存释放掉了**,**再次重新打开相同页面时不会出现 from memory cache 的情况。 20 | - 内存缓存具有两个特点,分别是快速读取和时效性: 21 | 22 | - - **快速读取**:内存缓存会将编译解析后的文件,直接存入该进程的内存中,占据该进程一定的内存资源,以方便下次运行使用时的快速读取。 23 | - **时效性**:一旦该进程关闭,则该进程的内存则会清空。 24 | 25 | from disk cache 26 | 27 | > 代表使用的是硬盘中的缓存 28 | 29 | - from disk cache 表示此资源是从磁盘当中取出的,也是在已经在之前的某个时间加载过该资源,不会请求服务器。但此资源不会随着该页面的关闭而释放掉,因为是存在硬盘当中的,下次打开仍会出现 from disk cache的情况。 30 | - 硬盘缓存(from disk cache):硬盘缓存则是直接将缓存写入硬盘文件中,读取缓存需要对该缓存存放的硬盘文件进行 I/O 操作,然后重新解析该缓存内容,读取复杂,速度比内存缓存慢。 31 | 32 | 浏览器读取命中强缓存资源顺序为 memory ---> disk 33 | 34 | - - 先去 `内存` 看,如果有,直接加载; 35 | - 如果内存没有,则取 `硬盘` 获取; 36 | - 如果硬盘也没有,那么就进行 `网络请求`; 37 | - 加载到的 `资源缓存到硬盘和内存`。 38 | 39 | 资源本身大小 40 | 41 | - 当HTTP状态码为 200 时,资源是实实在在从服务器请求后获取得到的(不是缓存数据),该数字是资源本身的大小; 42 | - 当HTTP状态码为 304 时,该数字是 `与服务端通信报文的大小`,并 `不是该资源本身的大小`,该资源是从本地获取的(协商缓存)。 43 | 44 | #### Chrome 采取措施的准则 45 | 46 | | 状态 | 类型 | 说明 | 47 | | ---- | ----------------- | ------------------------------------------------------------ | 48 | | 200 | from memory cache | 不请求网络资源,资源在内存当中,一般`脚本`、`字体`、`图片`会存在内存当中 | 49 | | 200 | from disk cache | 不请求网络资源,在磁盘当中,一般非脚本会存在内存当中,如`CSS`等 | 50 | | 200 | 资源大小数值 | 从服务器下载最新资源 | 51 | | 304 | 报文大小 | 请求服务端发现资源没有更新,使用本地资源(协商缓存) | 52 | 53 | 所以 `样式表` 一般在磁盘中,不会缓存到内存中去,因为CSS样式加载一次即可渲染出网页。但是,` 脚本` 却可能随时会执行,如果 `脚本 ` 在磁盘当中,在执行该 `脚本` 需要从磁盘中取到内存当中来。这样的IO开销是比较大的,有可能会导致浏览器失去响应。因此,`脚本` 一般在内存中。 -------------------------------------------------------------------------------- /2019年文档总结/ts泛型.md: -------------------------------------------------------------------------------- 1 | TypeScript 泛型之 Omit = Pick> 2 | 首先来看这样一个 ts 例子 3 | 4 | import { Button, ButtonProps } from './components/button' 5 | 6 | type Omit = Pick> 7 | type BigButtonProps = Omit 8 | 9 | function BigButton(props: BigButtonProps) { 10 | return Button({ ...props, size: 'big' }) 11 | } 12 | 复制代码 13 | 如果对这个例子很清晰,大佬请点击右上角。 14 | 15 | 如果不清楚 我们可以来往下共同探索一番。。 16 | 17 | partial, Readonly, Record, Pick 18 | 19 | partial 20 | Partial 作用是将传入的属性变为可选项. 21 | 22 | keyof, in 23 | 24 | 首先我们需要理解两个关键字 keyof 和 in, keyof 可以用来取得一个对象接口的所有 key 值. 25 | 26 | 比如 27 | 28 | interface Foo { 29 | name: string; 30 | age: number 31 | } 32 | 33 | // T -> "name" | "age" 34 | type T = keyof Foo 35 | 36 | type Keys = "a" | "b" 37 | type Obj = { 38 | [p in Keys]: any 39 | } 40 | // Obj -> { a: any, b: any } 41 | 复制代码 42 | keyof 产生联合类型, in 则可以遍历枚举类型, 所以他们经常一起使用, 看下 Partial 源码 43 | 44 | type Partial = { [P in keyof T]?: T[P] }; 45 | 复制代码 46 | 使用 47 | 48 | interface Foo { 49 | name: string; 50 | age: number; 51 | } 52 | 53 | type B = Partial; 54 | 55 | // 最多只能够定义 name, age 中的属性(可以不定义) 56 | let b: B = { 57 | name: '1', 58 | age: 3, 59 | }; 60 | 复制代码 61 | Required 62 | Required 的作用是将传入的属性变为必选项, 源码如下 63 | 64 | type Required = { [P in keyof T]-?: T[P] }; 65 | 复制代码 66 | 我们发现一个有意思的用法 -?, 这里很好理解就是将可选项代表的 ? 去掉, 从而让这个类型变成必选项. 与之对应的还有个+? , 这个含义自然与-?之前相反, 它是用来把属性变成可选项的. 67 | 68 | Pick 69 | 从 T 中取出 一系列 K 的属性 70 | 71 | type Pick = { [P in K]: T[P] }; 72 | 复制代码 73 | 小结 74 | 总结一下 Partial, Required, Pick。 75 | 76 | type Partial = { 77 | [P in keyof T]?: T[P]; 78 | }; 79 | 80 | type Required = { 81 | [P in keyof T]-?: T[P]; 82 | }; 83 | 84 | type Pick = { 85 | [P in K]: T[P]; 86 | }; 87 | 88 | interface User { 89 | age: number; 90 | name: string; 91 | }; 92 | 93 | // 相当于: type PartialUser = { age?: number; name?: string; } 94 | type PartialUser = Partial 95 | 96 | // 相当于: type PickUser = { age: number; name: string; } 97 | type PickUser = Pick 98 | 复制代码 99 | Exclude (排除) 100 | 在 ts 2.8 中引入了一个条件类型, 示例如下 101 | 102 | T extends U ? X : Y 103 | 复制代码 104 | 以上语句的意思就是 如果 T 是 U 的子类型的话,那么就会返回 X,否则返回 Y 105 | 106 | type Exclude = T extends U ? never : T; 107 | 108 | const str: Exclude<'a' | '1' | '2', 'a' | 'y' | 'z'> = '1'; 109 | 复制代码 110 | 111 | 从代码 str 的提示,可以很好的理解,Exclude 就是将前面类型的与后面类型对比,过滤出前面独有的属性 112 | 113 | 通过使用 Pick 和 Exclude 来组合一个新的类型 Omit 114 | 115 | Omit (省略) 116 | // Pick 117 | type Pick = { [P in K]: T[P] }; 118 | 119 | // Exclude 120 | type Exclude = T extends U ? never : T; 121 | 122 | // Omit 123 | type Omit = Pick> 124 | 125 | 126 | type Omit = Pick>; 127 | 128 | interface User { 129 | id: number; 130 | age: number; 131 | name: string; 132 | } 133 | 134 | // 相当于: type OmitUser = { age: number; name: string; } 135 | type OmitUser = Omit; 136 | 复制代码 137 | 很好理解,去除掉 User 接口内的 id 属性。 138 | 139 | 最后 140 | 回过头看之前的代码 141 | 142 | import { Button, ButtonProps } from './components/button' 143 | 144 | type Omit = Pick> 145 | type BigButtonProps = Omit 146 | 147 | function BigButton(props: BigButtonProps) { 148 | return Button({ ...props, size: 'big' }) 149 | } 150 | 复制代码 151 | 首先定义了一个 BigButtonProps 类型, 这个类型通过 Omit 除去了 size 属性。解决!! 152 | 153 | 最后 154 | 全文章,如有错误或不严谨的地方,请务必给予指正,谢谢! 155 | 156 | 参考: ts高级技巧 -------------------------------------------------------------------------------- /2019年文档总结/抽象语法树.md: -------------------------------------------------------------------------------- 1 | ### 抽象语法树 2 | 3 | ### AST 是什么 4 | 5 | 抽象语法树 (Abstract Syntax Tree),简称 AST,它是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。 6 | 7 | 8 | 9 | ### AST 有什么用 10 | 11 | AST 运用广泛,比如: 12 | 13 | - 编辑器的错误提示、代码格式化、代码高亮、代码自动补全; 14 | - `elint`、`pretiier` 对代码错误或风格的检查; 15 | - `webpack` 通过 `babel` 转译 `javascript` 语法; 16 | 17 | 并且如果你想了解 js 编译执行的原理,那么你就得了解 AST。 18 | 19 | 20 | 21 | ### AST 如何生成 22 | 23 | js 执行的第一步是读取 js 文件中的字符流,然后通过词法分析生成 `token`,之后再通过语法分析( Parser )生成 AST,最后生成机器码执行。 24 | 25 | 26 | 整个解析过程主要分为以下两个步骤: 27 | 28 | - 分词:将整个代码字符串分割成最小语法单元数组 29 | - 语法分析:在分词基础上建立分析语法单元之间的关系 30 | 31 | 32 | 33 | JS Parser 是 js 语法解析器,它可以将 js 源码转成 AST,常见的 Parser 有 esprima、traceur、acorn、shift 等。 34 | 35 | #### 词法分析 36 | 37 | 词法分析,也称之为扫描(scanner),简单来说就是调用 next() 方法,一个一个字母的来读取字符,然后与定义好的 JavaScript 关键字符做比较,生成对应的Token。Token 是一个不可分割的最小单元: 38 | 39 | > 例如 var 这三个字符,它只能作为一个整体,语义上不能再被分解,因此它是一个 Token。 40 | 41 | 词法分析器里,每个关键字是一个 Token ,每个标识符是一个 Token,每个操作符是一个 Token,每个标点符号也都是一个 Token。除此之外,还会过滤掉源程序中的注释和空白字符(换行符、空格、制表符等。 42 | 43 | 最终,整个代码将被分割进一个tokens列表(或者说一维数组)。 44 | 45 | 46 | 47 | #### 语法分析 48 | 49 | 语法分析会将词法分析出来的 Token 转化成有语法含义的抽象语法树结构。同时,验证语法,语法如果有错的话,抛出语法错误。 50 | 51 | 说了这么多我们来看下 javaScript 代码片段转成 AST 之后是什么样的我们拿一行简单的代码来展示 52 | 53 | 54 | 55 | #### 🌰例子 1 56 | 57 | ``` 58 | const fn = a => a; 59 | ``` 60 | 61 | 62 | 63 | top.png 64 | ![](https://user-gold-cdn.xitu.io/2019/12/31/16f579f319f2ef0e?w=936&h=1396&f=png&s=214847) 65 | 66 | 67 | 68 | 如图从这个 AST 语法树我们就能够很清楚的看出一个代码他的具体含义,并且使用的是什么语法,方法等。 69 | 70 | 用人话翻译这个图就是:用类型 const 声明变量 fn 指向一个箭头函数表达式,它的参数是 a 函数体也是 a。 71 | 72 | #### 🌰例子 2 73 | 74 | ``` 75 | const fn = a => { 76 | let i = 1; 77 | return a + i; 78 | }; 79 | ``` 80 | 81 | 我们来看 body 这块: 82 | 83 | 84 | 85 | mid.png 86 | ![](https://user-gold-cdn.xitu.io/2019/12/31/16f579f88c4cfe59?w=882&h=1870&f=png&s=211271) 87 | 88 | 89 | 90 | 91 | 92 | #### 🌰例子 3 93 | 94 | > 函数调用 95 | 96 | ``` 97 | function test(){ 98 | let a = 1; 99 | console.log(a) 100 | } 101 | ``` 102 | 103 | 104 | 105 | 主要看 `MemberExpression` 106 | 107 | btm.png 108 | ![](https://user-gold-cdn.xitu.io/2019/12/31/16f579fb424f6c8c?w=1224&h=1746&f=png&s=255988) 109 | 110 | 111 | 112 | 以上截图均是使用 Acorn 解析。使用 Acorn 的原因是据我了解在 parser 解析中,Acorn 是公认的最快的。并且我们使用的 Webpack 打包工具中 babel 用的也是 Acorn。 113 | 114 | 115 | 116 | 上述截图的属性是 AST 的一部分,这个结构包含了很多属性。 117 | 118 | - VariableDeclaration 变量声明 119 | - VariableDeclarator 变量声明的描述 120 | - Expression 表达式节点 121 | - … 122 | 123 | 更多属性展示: 124 | 125 | 1. 可以去 [AST explorer](https://astexplorer.net/) 可以在线看到不同的 parser 解析 js 代码后得到的 AST。 126 | 2. github 上看所有的 ESTree [ESTree](https://github.com/estree/estree) 127 | 3. 关于属性介绍的文档 [抽象语法树AST介绍](http://www.goyth.com/2018/12/23/AST/#Expressions) 128 | 129 | 130 | 131 | ### 实战 AST 的运用 132 | 133 | #### 题目 134 | 135 | 通过上面介绍的 `console.log` AST,下面我们就来完成一个在调用 `console.log(xx)` 时候给前面加一个函数名,这样用户在打印时候能改方便看到是哪个函数调用的。 136 | 137 | `举例` 138 | 139 | ```javascript 140 | // 源代码 141 | function getData() { 142 | console.log("data") 143 | } 144 | 145 | // -------------------- 146 | 147 | // 转化后代码 148 | function getData() { 149 | console.log("getData", "data"); 150 | } 151 | ``` 152 | 153 | #### 介绍 154 | 155 | 首先介绍下我们需要使用的工具 `Babel` 156 | 157 | - `@babel/parser` : 将 js 代码 ------->>> `AST` 抽象语法树; 158 | - `@babel/traverse` 对 `AST` 节点进行递归遍历; 159 | - `@babel/types ` 对具体的 `AST` 节点进行进行修改; 160 | - `@babel/generator` : `AST` 抽象语法树 ------->>> 新的 js 代码; 161 | 162 | 为什么使用 babel ? 主要是比较好用(只对这个比较熟悉😭)。 163 | 164 | 进入 [@babel/parser](https://babeljs.io/docs/en/babel-parser) 官网开头就介绍了它是使用的 Acorn 来解析 js 代码成 AST 语法树(说明确实 Acorn 比较好)。 165 | 166 | ![](https://user-gold-cdn.xitu.io/2019/12/31/16f57a8ab009e6fc?w=1394&h=644&f=png&s=104363) 167 | 168 | #### 开始码起来 169 | 170 | 1. 新建文件打开控制台安装需要的包 171 | 172 | ``` 173 | cnpm i @babel/parser @babel/traverse @babel/types @babel/generator -D 174 | ``` 175 | 176 | 2. 创建 js 文件, 编写大致布局如下 使用 AST 177 | 178 | ```javascript 179 | const generator = require("@babel/generator"); 180 | const parser = require("@babel/parser"); 181 | const traverse = require("@babel/traverse"); 182 | const types = require("@babel/types"); 183 | 184 | function compile(code) { 185 | // 1.parse 将代码解析为抽象语法树(AST) 186 | const ast = parser.parse(code); 187 | 188 | // 2,traverse 转换代码 189 | traverse.default(ast, {}); 190 | 191 | // 3. generator 将 AST 转回成代码 192 | return generator.default(ast, {}, code); 193 | } 194 | 195 | const code = ` 196 | function getData() { 197 | console.log("data") 198 | } 199 | `; 200 | const newCode = compile(code) 201 | ``` 202 | 203 | 使用 node 跑出结果,因为什么都没处理,输出的是原代码, 204 | 205 | ![](https://user-gold-cdn.xitu.io/2019/12/31/16f57a25de8a834d?w=1768&h=1054&f=png&s=442076) 206 | 207 | 完善 compile 方法 208 | 209 | ```javascript 210 | function compile(code) { 211 | // 1.parse 212 | const ast = parser.parse(code); 213 | 214 | // 2,traverse 215 | const visitor = { 216 | CallExpression(path) { 217 | // 拿到 callee 数据 218 | const { callee } = path.node; 219 | // 判断是否是调用了 console.log 方法 220 | // 1. 判断是否是成员表达式节点,上面截图有详细介绍 221 | // 2. 判断是否是 console 对象 222 | // 3. 判断对象的属性是否是 log 223 | const isConsoleLog = 224 | types.isMemberExpression(callee) && 225 | callee.object.name === "console" && 226 | callee.property.name === "log"; 227 | if (isConsoleLog) { 228 | // 如果是 console.log 的调用 找到上一个父节点是函数 229 | const funcPath = path.findParent(p => { 230 | return p.isFunctionDeclaration(); 231 | }); 232 | // 取函数的名称 233 | const funcName = funcPath.node.id.name; 234 | // 将名称通过 types 来放到函数的参数前面去 235 | path.node.arguments.unshift(types.stringLiteral(funcName)); 236 | } 237 | } 238 | }; 239 | // traverse 转换代码 240 | traverse.default(ast, visitor); 241 | 242 | // 3. generator 将 AST 转回成代码 243 | return generator.default(ast, {}, code); 244 | } 245 | ``` 246 | 247 | 纯代码看起来比较难理解下面是我将上面的 path.node 写入到文件中给大家看下数据格式。 248 | 249 | ```json 250 | { 251 | "type": "CallExpression", 252 | "start": 24, 253 | "end": 43, 254 | "loc": { 255 | "start": { "line": 3, "column": 2 }, 256 | "end": { "line": 3, "column": 21 } 257 | }, 258 | "callee": { 259 | "type": "MemberExpression", 260 | "start": 24, 261 | "end": 35, 262 | "loc": { 263 | "start": { "line": 3, "column": 2 }, 264 | "end": { "line": 3, "column": 13 } 265 | }, 266 | "object": { 267 | "type": "Identifier", 268 | "start": 24, 269 | "end": 31, 270 | "loc": { 271 | "start": { "line": 3, "column": 2 }, 272 | "end": { "line": 3, "column": 9 }, 273 | "identifierName": "console" 274 | }, 275 | "name": "console" 276 | }, 277 | "property": { 278 | "type": "Identifier", 279 | "start": 32, 280 | "end": 35, 281 | "loc": { 282 | "start": { "line": 3, "column": 10 }, 283 | "end": { "line": 3, "column": 13 }, 284 | "identifierName": "log" 285 | }, 286 | "name": "log" 287 | }, 288 | "computed": false 289 | }, 290 | "arguments": [ 291 | { 292 | "type": "StringLiteral", 293 | "start": 36, 294 | "end": 42, 295 | "loc": { 296 | "start": { "line": 3, "column": 14 }, 297 | "end": { "line": 3, "column": 20 } 298 | }, 299 | "extra": { "rawValue": "data", "raw": "'data'" }, 300 | "value": "data" 301 | } 302 | ] 303 | } 304 | 305 | ``` 306 | 307 | 我们将不必要的位置信息(start, end, loc)属性删除,对照数据来看代码将会一目了然 308 | 309 | ![](https://user-gold-cdn.xitu.io/2019/12/31/16f57a2c3469ff0e?w=2880&h=1626&f=png&s=615297) 310 | 311 | 在跑该文件 312 | 313 | ![](https://user-gold-cdn.xitu.io/2019/12/31/16f57a4781b22ace?w=1188&h=772&f=png&s=213363) 314 | 315 | 很好,调用 console.log 方法参数前面增加了函数名,完成!! 316 | 317 | 看到这里,如果你觉得都没什么问题,相信你对 AST 已经有了很清楚的认识了,并且对 babel 编译代码也有了一定的理解,以后写 webpack 配置也就不会对 babel 那么陌生了。 318 | 319 | 为了大家能够方便运行,下面是完整代码 320 | 321 | ```javascript 322 | const generator = require("@babel/generator"); 323 | const parser = require("@babel/parser"); 324 | const traverse = require("@babel/traverse"); 325 | const types = require("@babel/types"); 326 | const fs = require("fs"); 327 | 328 | 329 | function compile(code) { 330 | // 1.parse 331 | const ast = parser.parse(code); 332 | 333 | // 2,traverse 334 | const visitor = { 335 | CallExpression(path) { 336 | const { callee } = path.node; 337 | const isConsoleLog = 338 | types.isMemberExpression(callee) && 339 | callee.object.name === "console" && 340 | callee.property.name === "log"; 341 | if (isConsoleLog) { 342 | const funcPath = path.findParent(p => { 343 | return p.isFunctionDeclaration(); 344 | }); 345 | const funcName = funcPath.node.id.name; 346 | fs.writeFileSync("./funcPath.json", JSON.stringify(funcPath.node), err => { 347 | if (err) throw err; 348 | console.log("写入成功"); 349 | }); 350 | path.node.arguments.unshift(types.stringLiteral(funcName)); 351 | } 352 | } 353 | }; 354 | traverse.default(ast, visitor); 355 | 356 | // 3. generator 357 | return generator.default(ast, {}, code); 358 | } 359 | 360 | const code = ` 361 | function getData() { 362 | console.log('data') 363 | } 364 | `; 365 | console.log(compile(code).code); 366 | 367 | ``` 368 | 369 | ### 总结 370 | 371 | 为了兼容低版本浏览器 我们也通常会使用 webpack 打包编译我们的代码将 ES6 语法降低版本,比如箭头函数变成普通函数。将 const、let 声明改成 var 等等,他都是通过 AST 来完成的,只不过实现的过程比较复杂,精致。不过也都是这三板斧: 372 | 373 | 1. js 语法解析成 AST; 374 | 2. 修改 AST; 375 | 3. AST 转成 js 语法; 376 | 377 | ### 最后 378 | 379 | 有时间,大家在尝试完成之后也同样可以试试箭头函数转普通函数等一些常用的代码转换,这样可以很好的加深印象。 380 | 381 | > 全文章,如有错误或不严谨的地方,请务必给予指正,谢谢! 382 | 383 | 参考 384 | 385 | - [github ES Tree](https://github.com/estree/estree) 386 | 387 | - [babel 官网](https://babeljs.io/docs/en/babel-parser) 388 | 389 | - [抽象语法树](http://www.goyth.com/2018/12/23/AST/#Functions) -------------------------------------------------------------------------------- /2019年文档总结/自定义状态管理useContext.md: -------------------------------------------------------------------------------- 1 | # 使用 useContext 封装自己的状态管理(十几行代码) 2 | 3 | ## 开头 4 | 5 | 一个项目,一个复杂的逻辑,我觉得状态管理显得尤为的重要,状态管理的好不好,直接体现了一个项目的逻辑性、可读性、维护性等是否清晰,易读,和高效。 6 | 7 | 从最早的类组件使用 this.state, this.setState 去管理状态,到 redux , subscribe, dispatch 的发布订阅,redux 的使用就面临重复和沉重的的 reducer,让我俨然变成了 Ctrl CV 工程师。于是后面接触 [dva](https://dvajs.com/guide/#特性),它是一个基于 [redux](https://github.com/reduxjs/redux) 和 [redux-saga](https://github.com/redux-saga/redux-saga) 的数据流方案。通过 model 来分片管理全局状态,使用 connect 方法去给需要的深层次的组件传递状态。 8 | 9 | 到后面 react hooks 出来之后,业界也出了很多自身管理状态的,基于 hooks 封装,各个模块都有一个基于自己 hooks 的状态 store。确实很好的解决了函数组件的状态管理,和模块自身内部的状态管理,但是还是解决不了在全局组件中,层层传递的状态依赖让结构变得复杂,繁琐的问题。不用任何的管理工具我们如何做到跨组件通信? 10 | 11 | ## 为什么不用? 12 | 13 | 不是说我们不去用 dva 这样的管理工具?我并不是说 dva 不好用,而是我觉得有时候没必要用。我觉得他太重了。 14 | 15 | ## 学到什么? 16 | 17 | 读完这边文章,即使你觉得我的管理方式不好,你也可以学习和了解到 useMemo, useContext,useImmer 等。 18 | 19 | ## react context 20 | 21 | [Context-React](https://zh-hans.reactjs.org/docs/context.html) 官网介绍 22 | 23 | ```javascript 24 | // Context 可以让我们无须明确地传遍每一个组件,就能将值深入传递进组件树。 25 | // 为当前的 theme 创建一个 context(“light”为默认值)。 26 | const ThemeContext = React.createContext('light'); 27 | 28 | class App extends React.Component { 29 | render() { 30 | // 使用一个 Provider 来将当前的 theme 传递给以下的组件树。 31 | // 无论多深,任何组件都能读取这个值。 32 | // 在这个例子中,我们将 “dark” 作为当前的值传递下去。 33 | return ( 34 | 35 | 36 | 37 | ); 38 | } 39 | } 40 | 41 | // 中间的组件再也不必指明往下传递 theme 了。 42 | function Toolbar(props) { 43 | return ( 44 |
45 | 46 |
47 | ); 48 | } 49 | 50 | class ThemedButton extends React.Component { 51 | // 指定 contextType 读取当前的 theme context。 52 | // React 会往上找到最近的 theme Provider,然后使用它的值。 53 | // 在这个例子中,当前的 theme 值为 “dark”。 54 | static contextType = ThemeContext; 55 | render() { 56 | return 25 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /html/tabs滚动布局/1.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 95 | 96 | 144 | -------------------------------------------------------------------------------- /html/tabs滚动布局/2增加自动居中.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 136 | 137 | 185 | -------------------------------------------------------------------------------- /html/water.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
123
9 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /html/一些Js实现/无线轮播.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 | 46 | 47 |
48 |
49 |
1
50 |
2
51 |
3
52 |
4
53 |
5
54 |
55 |
56 | 57 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /html/类美团点餐布局/1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 63 | 64 |
65 |
LEFT
66 |
67 |
68 |
手机品牌
69 |
70 |
71 | 72 |
联想
73 |
74 |
75 | 76 |
联想
77 |
78 |
79 | 80 |
联想
81 |
82 |
83 | 84 |
联想
85 |
86 |
87 | 88 |
联想
89 |
90 |
91 | 92 |
联想
93 |
94 |
95 |
96 |
97 |
98 | 99 | 100 | -------------------------------------------------------------------------------- /html/类美团点餐布局/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zouxiaomingya/JavaScript/5f354b8a06677af80210d9620fd27d7391a17373/html/类美团点餐布局/1.png -------------------------------------------------------------------------------- /html/类美团点餐布局/index.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 257 | 343 | -------------------------------------------------------------------------------- /immutable.md: -------------------------------------------------------------------------------- 1 | # 深入探究Immutable.js的实现机制 2 | 3 | Immutable.js 由 Facebook 花费 3 年时间打造,为前端开发提供了很多便利。我们知道 Immutable.js 采用了`持久化数据结构`,保证每一个对象都是不可变的,任何添加、修改、删除等操作都会生成一个新的对象,且通过`结构共享`等方式大幅提高性能。 4 | 5 | 网上已经有很多文章简单介绍了 Immutable.js 的原理,但基本都是浅尝辄止,针对 Clojure 或 Go 中持久化数据结构实现的文章倒是有一些。下面结合多方资料、Immutable.js 源码以及我自己的理解,深入一些探究 Immutable.js 实现机制。 6 | 7 | **本系列文章可能是目前全网关于 Immutable.js 原理最深入、全面的文章,欢迎点赞收藏σ`∀´)σ。** 8 | 9 | > Immutable.js 部分参考了 Clojure 中的`PersistentVector`的实现方式,并有所优化和取舍,该系列第一篇的部分内容也是基于它,想了解的可以阅读[这里](https://hypirion.com/musings/understanding-persistent-vector-pt-1)(共五篇,这是其一) 10 | 11 | # 简单的例子 12 | 13 | 在深入研究前,我们先看个简单的例子: 14 | 15 | 16 | 17 | ``` 18 | let map1 = Immutable.Map({}); 19 | 20 | for (let i = 0; i < 800; i++) { 21 | map1 = map1.set(Math.random(), Math.random()); 22 | } 23 | 24 | console.log(map1);复制代码 25 | ``` 26 | 27 | 这段代码先后往map里写入了800对随机生成的key和value。我们先看一下控制台的输出结果,对它的数据结构有个大致的认知(粗略扫一眼就行了): 28 | 29 | [![img](https://user-gold-cdn.xitu.io/2018/9/14/165d635e732bfa1d?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)](https://ygyooo.github.io/2018/09/12/深入探究immutable.js的实现机制(一)/console截图.png) 30 | 可以看到这是一个树的结构,子节点以数组的形式放在`nodes`属性里,`nodes`的最大长度似乎是 32 个。了解过 bitmap 的人可能已经猜到了这里`bitmap`属性是做什么的,它涉及到对树宽度的压缩,这些后面会说。 31 | 32 | 其中一个节点层层展开后长这样: 33 | 34 | [![img](https://user-gold-cdn.xitu.io/2018/9/14/165d635e67a54c23?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)](https://ygyooo.github.io/2018/09/12/深入探究immutable.js的实现机制(一)/console截图2.png) 35 | `ValueNode``entry[0]``entry[1]` 36 | 37 | 38 | 39 | 目前大致看个形状就行了,下面我们会由浅入深逐步揭开它的面纱。(第二篇文章里会对图中所有属性进行解释) 40 | 41 | # 基本原理 42 | 43 | 我们先看下维基对于`持久化数据结构`的定义: 44 | 45 | > In computing, a persistent data structure is a data structure that always preserves the previous version of itself when it is modified. 46 | 47 | 通俗点解释就是,对于一个`持久化数据结构`,每次修改后我们都会得到一个新的版本,且旧版本可以完好保留。 48 | 49 | Immutable.js 用树实现了`持久化数据结构`,先看下图这颗树: 50 | [![img](https://user-gold-cdn.xitu.io/2018/9/14/165d635e67c6de9f?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)](https://ygyooo.github.io/2018/09/12/深入探究immutable.js的实现机制(一)/tree1.png) 51 | 假如我们要在`g`下面插入一个节点`h`,如何在插入后让原有的树保持不变?最简单的方法当然是重新生成一颗树: 52 | [![img](https://user-gold-cdn.xitu.io/2018/9/14/165d635e5d7eedd0?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)](https://ygyooo.github.io/2018/09/12/深入探究immutable.js的实现机制(一)/tree2.png) 53 | 但这样做显然是很低效的,每次操作都需要生成一颗全新的树,既费时又费空间,因而有了如下的优化方案: 54 | [![img](https://user-gold-cdn.xitu.io/2018/9/14/165d635e63391b4c?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)](https://ygyooo.github.io/2018/09/12/深入探究immutable.js的实现机制(一)/tree3.png) 55 | 我们新生成一个根节点,对于有修改的部分,把相应路径上的所有节点重新生成,对于本次操作没有修改的部分,我们可以直接把相应的旧的节点拷贝过去,这其实就是`结构共享`。这样每次操作同样会获得一个全新的版本(根节点变了,新的`a`!==旧的`a`),历史版本可以完好保留,同时也节约了空间和时间。 56 | 57 | 至此我们发现,用树实现`持久化数据结构`还是比较简单的,Immutable.js提供了多种数据结构,比如回到开头的例子,一个map如何成为`持久化数据结构`呢? 58 | 59 | 60 | 61 | # Vector Trie 62 | 63 | 实际上对于一个map,我们完全可以把它视为一颗扁平的树,与上文实现`持久化数据结构`的方式一样,每次操作后生成一个新的对象,把旧的值全都依次拷贝过去,对需要修改或添加的属性,则重新生成。这其实就是`Object.assign`,然而这样显然效率很低,有没有更好的方法呢? 64 | 65 | 在实现`持久化数据结构`时,Immutable.js 参考了`Vector Trie`这种数据结构(其实更准确的叫法是`persistent bit-partitioned vector trie`或`bitmapped vector trie`,这是Clojure里使用的一种数据结构,Immutable.js 里的相关实现与其很相似),我们先了解下它的基本结构。 66 | 67 | 假如我们有一个 map ,key 全都是数字(当然你也可以把它理解为数组)`{0: ‘banana’, 1: ‘grape’, 2: ‘lemon’, 3: ‘orange’, 4: ‘apple’}`,为了构造一棵二叉`Vector Trie`,我们可以先把所有的key转换为二进制的形式:`{‘000’: ‘banana’, ‘001’: ‘grape’, ‘010’: ‘lemon’, ‘011’: ‘orange’, ‘100’: ‘apple’}`,然后如下图构建`Vector Trie`: 68 | [![img](https://user-gold-cdn.xitu.io/2018/9/14/165d635e6d01c49d?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)](https://ygyooo.github.io/2018/09/12/深入探究immutable.js的实现机制(一)/vectorTrie1.png) 69 | 可以看到,`Vector Trie`的每个节点是一个数组,数组里有`0`和`1`两个数,表示一个二进制数,所有值都存在叶子节点上,比如我们要找`001`的值时,只需顺着`0` `0` `1`找下来,即可得到`grape`。那么想实现`持久化数据结构`当然也不难了,比如我们想添加一个`5: ‘watermelon’`: 70 | [![img](https://user-gold-cdn.xitu.io/2018/9/14/165d635ebb85e04d?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)](https://ygyooo.github.io/2018/09/12/深入探究immutable.js的实现机制(一)/vectorTrie2.png) 71 | 可见对于一个 key 全是数字的map,我们完全可以通过一颗`Vector Trie`来实现它,同时实现`持久化数据结构`。如果key不是数字怎么办呢?用一套映射机制把它转成数字就行了。 Immutable.js 实现了一个[hash](https://github.com/facebook/immutable-js/blob/e65e5af806ea23a32ccf8f56c6fabf39605bac80/src/Hash.js#L10:17)函数,可以把一个值转换成相应数字。 72 | 73 | 这里为了简化,每个节点数组长度仅为2,这样在数据量大的时候,树会变得很深,查询会很耗时,所以可以扩大数组的长度,Immutable.js 选择了32。为什么不是31?40?其实数组长度必须是2的整数次幂,这里涉及到实现`Vector Trie`时的一个优化,接下来我们先研究下这点。 74 | 75 | > 下面的部分内容对于不熟悉进制转换和位运算的人来说可能会相对复杂一些,不过只要认真思考还是能搞通的。 76 | 77 | # 数字分区(Digit partitioning) 78 | 79 | 80 | 81 | `数字分区`指我们把一个 key 作为数字对应到一棵前缀树上,正如上节所讲的那样。 82 | 83 | 假如我们有一个 key ,以 7 为基数,即数组长度是 7,它在里是这么表示的:[![img](https://user-gold-cdn.xitu.io/2018/9/14/165d635eca568dee?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)](https://ygyooo.github.io/2018/09/12/深入探究immutable.js的实现机制(一)/base7.png)需要5层数组,我们先找到这个分支,再找到,之后依次到。为了依次得到这几个数字,我们可以预先把转为7进制的,但其实没有这个必要,因为转为 7 进制形式的过程就是不断进行除法并取余得到每一位上的数,我们无须预先转换好,类似的操作可以在每一层上依次执行。 84 | 85 | 运用进制转换相关的知识,我们可以采用这个方法`key / radixlevel - 1 % radix`得到每一位的数(**为了简便,本文除代码外所有`/`符号皆表示除法且向下取整**),其中`radix`是每层数组的长度,即转换成几进制,`level`是当前在第几层,即第几位数。比如这里`key`是`9128`,`radix`是`7`,一开始`level`是`5`,通过这个式子我们可以得到第一层的数`3`。 86 | 87 | 代码实现如下: 88 | 89 | ``` 90 | const RADIX = 7; 91 | 92 | function find(key) { 93 | let node = root; // root是根节点,在别的地方定义了 94 | 95 | // depth是当前树的深度。这种计算方式跟上面列出的式子是等价的,但可以避免多次指数计算。这个size就是上面的radix^level - 1 96 | for (let size = Math.pow(RADIX, (depth - 1)); size > 1; size /= RADIX) { 97 | node = node[Math.floor(key / size) % RADIX]; 98 | } 99 | 100 | return node[key % RADIX]; 101 | }复制代码 102 | ``` 103 | 104 | # 位分区(Bit Partitioning) 105 | 106 | 显然,以上`数字分区`的方法是有点耗时的,在每一层我们都要进行两次除法一次取模,显然这样并不高效,`位分区`就是对其的一种优化。 107 | 108 | `位分区`是建立在`数字分区`的基础上的,所有以2的整数次幂(2,4,8,16,32…)为基数的`数字分区`前缀树,都可以转为`位分区`。基于一些位运算相关的知识,我们就能避免一些耗时的计算。 109 | 110 | `数字分区`把 key 拆分成一个个数字,而`位分区`把 key 分成一组组 bit。以一个 32 路的前缀树为例,`数字分区`的方法是把 key 以 32 为基数拆分(实际上就是 32 进制),而`位分区`是把它以 5 个 bits 拆分,因为32 = 25,那我们就可以把 32 进制数的每一位看做 5 个二进制位 。实际上就是把 32 进制数当成 2 进制进行操作,这样原本的很多计算就可以用更高效的位运算的方式代替。因为现在基数是 32,即`radix`为 32,所以前面的式子现在是`key / 32level - 1 % 32`,而既然`32 =``25`,那么该式子可以写成这样`key / 25 × (level - 1) % 25`。根据位运算相关的知识我们知道`a / 2n === a >>> n `、`a % 2n === a & (2n - 1) `。这样我们就能通过位运算得出该式子的值。 111 | 112 | **如果你对位运算不太熟悉的话,大可不看上面的式子,举个例子就好理解了**:比如数字`666666`的二进制形式是`10100 **01011** 00001 01010`,这是一个20位的二进制数。如果我们要得到第二层那五位数`01011`,我们可以先把它右移`>>>`(左侧补0)10位,得到`00000 00000 10100 **01011**`,再`&`一下`00000 00000 00000 11111`,就得到了`**01011**`。 113 | 这样我们可以得到下面的代码: 114 | 115 | ``` 116 | const BITS = 5; 117 | const WIDTH = 1 << BITS, // 25 = 32 118 | const MASK = WIDTH - 1; // 31,即11111 119 | 120 | function find(key) { 121 | let node = root; 122 | 123 | for (let bits = (depth - 1) * BITS; bits > 0; bits -= BITS) { 124 | node = node[(key >>> bits) & MASK]; 125 | } 126 | 127 | return node[key & MASK]; 128 | }复制代码 129 | ``` 130 | 131 | 这样我们每次查找的速度就会得到提升。可以看一张图进行理解,为了简化展示,假设我们用了一个4路前缀树,4 = 22,所以用两位二进制数分区。对于`626`,查找过程如下: 132 | [![img](https://user-gold-cdn.xitu.io/2018/9/14/165d635eed8fa23f?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)](https://ygyooo.github.io/2018/09/12/深入探究immutable.js的实现机制(一)/base2.png) 133 | `626`的二进制形式是`10 01 11 00 10`,所以通过上面的位运算方法,我们便可以高效地依次得到`10`、`01`… 134 | 135 | 136 | 137 | # 源码 138 | 139 | 说了这么多,我们看一下 Immutable.js 的源码吧。我们主要看一下查找的部分就够了,这是`Vector Trie`的核心。 140 | 141 | ``` 142 | get(shift, keyHash, key, notSetValue) { 143 | if (keyHash === undefined) { 144 | keyHash = hash(key); 145 | } 146 | const idx = (shift === 0 ? keyHash : keyHash >>> shift) & MASK; 147 | const node = this.nodes[idx]; 148 | return node 149 | ? node.get(shift + SHIFT, keyHash, key, notSetValue) 150 | : notSetValue; 151 | }复制代码 152 | ``` 153 | 154 | 可以看到, Immutable.js 也正是采用了位分区的方式,通过位运算得到当前数组的 index 选择相应分支。(到这里我也不由赞叹,短短10行代码包含了多少思想呀) 155 | 156 | 不过它的实现方式与上文所讲的有一点不同,上文中对于一个 key ,我们是“正序”存储的,比如上图那个`626`的例子,我们是从根节点往下依次按照`10 01 11 00 10`去存储,而 Immutable.js 里则是“倒序”,按照`10 00 11 01 10`存储。所以通过源码这段你会发现 Immutable.js 查找时先得到的是 key 末尾的 SHIFT 个 bit ,然后再得到它们之前的 SHIFT 个 bit ,依次往前下去,而前面我们的代码是先得到 key 开头的 SHIFT 个 bit,依次往后。 157 | 158 | 用这种方式的原因之一是key的大小(二进制长度)不固定。 159 | 160 | # 时间复杂度 161 | 162 | 因为采用了`结构共享`,在添加、修改、删除操作后,我们避免了将 map 中所有值拷贝一遍,所以特别是在数据量较大时,这些操作相比`Object.assign`有明显提升。 163 | 164 | 然而,查询速度似乎减慢了?我们知道 map 里根据 key 查找的速度是`O(1)`,这里由于变成了一棵树,查询的时间复杂度变成了`O(log N)`,因为是 32 叉树,所以准确说是`O(log32 N)`。 165 | 166 | 等等 32 叉树?这棵树可不是一般地宽啊,Javascript里对象可以拥有的key的最大数量一般不会超过`232`个([ECMA-262第五版](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.2.2)里定义了JS里由于数组的长度本身是一个 32 位数,所以数组长度不应大于 232 - 1 ,JS里对象的实现相对复杂,但大部分功能是建立在数组上的,所以在大部分场景下对象里 key 的数量不会超过 232 - 1。相关讨论见[这里](https://stackoverflow.com/questions/30194088/do-javascript-variables-have-a-storage-limit)。而且假设我们有 232 个值、每个值是一个32bit的 Number ,只算这些值的话总大小也有17g了,前端一般是远不需要操作这个量级的数据的),这样就可以把查找的时间复杂度当做是“`O(log32 232)`”,差不多就是“`O(log 7)`”,所以我们可以认为在实际运用中,5bits (32路)的 Vector Trie 查询的时间复杂度是常数级的,32 叉树就是用了空间换时间。 -------------------------------------------------------------------------------- /inline-block.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 24 | 25 | 26 |
27 | 30 |
31 |
32 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /js_api/deepCopy.js: -------------------------------------------------------------------------------- 1 | function deepCopy(ori) { 2 | const type = getType(ori); 3 | let copy; 4 | switch (type) { 5 | case "array": 6 | return copyArray(ori, type, copy); 7 | case "object": 8 | return copyObject(ori, type, copy); 9 | case "function": 10 | return copyFunction(ori, type, copy); 11 | default: 12 | return ori; 13 | } 14 | } 15 | 16 | function copyArray(ori, type, copy = []) { 17 | for (const [index, value] of ori.entries()) { 18 | copy[index] = deepCopy(value); 19 | } 20 | return copy; 21 | } 22 | 23 | function copyObject(ori, type, copy = {}) { 24 | for (const [key, value] of Object.entries(ori)) { 25 | copy[key] = deepCopy(value); 26 | } 27 | return copy; 28 | } 29 | 30 | function copyFunction(ori, type, copy = () => {}) { 31 | const fun = eval(ori.toString()); 32 | fun.prototype = ori.prototype; 33 | return fun; 34 | } 35 | 36 | function deepCopy(target) { 37 | let copyed_objs = []; //此数组解决了循环引用和相同引用的问题,它存放已经递归到的目标对象 38 | function _deepCopy(target) { 39 | if (typeof target !== "object" || !target) { 40 | return target; 41 | } 42 | for (let i = 0; i < copyed_objs.length; i++) { 43 | if (copyed_objs[i].target === target) { 44 | return copyed_objs[i].copyTarget; 45 | } 46 | } 47 | let obj = {}; 48 | if (Array.isArray(target)) { 49 | obj = []; //处理target是数组的情况 50 | } 51 | copyed_objs.push({ target: target, copyTarget: obj }); 52 | Object.keys(target).forEach(key => { 53 | if (obj[key]) { 54 | return; 55 | } 56 | obj[key] = _deepCopy(target[key]); 57 | }); 58 | return obj; 59 | } 60 | return _deepCopy(target); 61 | } 62 | 63 | // function MinCoinChange(coins) { 64 | // var coins = coins; 65 | // var cache = {}; 66 | // this.makeChange = function(amount) { 67 | // var change = [], 68 | // total = 0; 69 | // for (var i = coins.length; i >= 0; i--) { 70 | // var coin = coins[i]; 71 | // while (total + coin <= amount) { 72 | // change.push(coin); 73 | // total += coin; 74 | // } 75 | // } 76 | // return change; 77 | // }; 78 | // } 79 | 80 | class MinCoinChange { 81 | constructor(coins) { 82 | this.coins = coins; 83 | this.cache = {}; 84 | } 85 | 86 | makeChange(amount) { 87 | if (!amount) return []; 88 | if (this.cache[amount]) return this.cache[amount]; 89 | let min = [], 90 | newMin, 91 | newAmount; 92 | this.coins.forEach(coin => { 93 | newAmount = amount - coin; 94 | if (newAmount >= 0) { 95 | newMin = this.makeChange(newAmount); 96 | } 97 | if ( 98 | newAmount >= 0 && 99 | (newMin.length < min.length - 1 || !min.length) && 100 | (newMin.length || !newAmount) 101 | ) { 102 | min = [coin].concat(newMin); 103 | } 104 | }); 105 | return (this.cache[amount] = min); 106 | } 107 | } 108 | 109 | const rninCoinChange = new MinCoinChange([1, 5, 10, 25]); 110 | console.log(rninCoinChange.makeChange(36)); 111 | const minCoinChange2 = new MinCoinChange([1, 3, 4]); 112 | console.log(minCoinChange2.makeChange(6)); 113 | -------------------------------------------------------------------------------- /npm/img/four.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zouxiaomingya/JavaScript/5f354b8a06677af80210d9620fd27d7391a17373/npm/img/four.jpg -------------------------------------------------------------------------------- /npm/img/one.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zouxiaomingya/JavaScript/5f354b8a06677af80210d9620fd27d7391a17373/npm/img/one.jpg -------------------------------------------------------------------------------- /npm/img/three.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zouxiaomingya/JavaScript/5f354b8a06677af80210d9620fd27d7391a17373/npm/img/three.jpg -------------------------------------------------------------------------------- /npm/img/two.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zouxiaomingya/JavaScript/5f354b8a06677af80210d9620fd27d7391a17373/npm/img/two.jpg -------------------------------------------------------------------------------- /npm/npm.md: -------------------------------------------------------------------------------- 1 | npm:node的包管理工具,是在命令行做管理。 2 | 3 | cnpm:淘宝的npm镜像文件 npm流程: 4 | 5 | \1. npm init (-y) 创建package.json文件 6 | 7 | \2. npm install 包名(eg:gulp) //下载包 8 | 9 | \3. npm install 包名 -g //将包下载到全局 10 | 11 | \4. npm uninstall 包名 (-g) //-g是卸载安装在全局的包文件 12 | 13 | \5. npm install gulp –save -dev(以gulp代替“包名”) //下载本地包并配置到package.json中 ps: –save 可以写成 -S, 14 | 15 | 保存在package中的devDependencies中 –save -dev 可以写成 -D 保存在dependencies中 install 写成 i ps: devDependencies 随程序一起上传到线上,项目依赖 dependencies 只用于本地开发,在上传时不需要上传,开发依赖 16 | 17 | \6. npm info gulp //查看包信息,其中可以查看历代版本号 18 | 19 | \7. npm i gulp@2 / @2.7 / @2.7.0 -D //更换本地的版本,省略的以满足条件的最高版本安装 ps: 版本号讲解 3. 9. 1 3.主版本号,每次变化都会发生很大的变化,比如H4到H5 9.子版本号,每次变化会增添一些新功能 1 是用来记录bug的修正 20 | 21 | 8.npm outdated (gulp) //显示所有包的已安装版本,程序所需版本,包的最新版。若无最新则不显示。可以全部查看,也可以单独查看某一个包 22 | 23 | 9.npm update //更新到最新的包 24 | 25 | \10. npm ls //查看包 26 | 27 | \11. nrm ls //查看所有支持的源 28 | 29 | \12. nrm test //测试哪个源的速度最快 30 | 31 | \13. nrm use cnpm //将源切换到cnpm上,即使用cnpm 32 | -------------------------------------------------------------------------------- /npm/npm_link.md: -------------------------------------------------------------------------------- 1 | ## 1. 背景 2 | 3 | node 应用开发中,我们不可避免的需要使用或拆分为 npm 模块,经常遇到的一个问题是: 4 | 5 | > 新开发或修改的 npm 模块,如何在项目中试验? 6 | 7 | 新同学一般会有以下几种方式: 8 | 9 | 为了方便示范,我们假设项目是 `my-project`, 需要用到一个独立的 `my-utils` 模块 10 | 11 | ### 1.1 发布一个 beta 版本 12 | 13 | - 优点:你高兴就好。 14 | - **缺点:** 无趣+无趣+无趣,麻烦+麻烦+麻烦。 15 | 16 | ### 1.2 直接用相对路径安装 17 | 18 | ``` 19 | $ cd path/to/my-project 20 | $ npm install path/to/my-utils 21 | ``` 22 | 23 | - 优点:简单明了 24 | - **缺点:** 调试过程中往往需要微调,此时需要切换到 my-utils 目录修改,然后反复重新 install,很麻烦 25 | 26 | ### 1.3 使用软链 27 | 28 | ``` 29 | $ cd path/to/my-project/node_modules 30 | $ ln -s path/to/my-utils my-utils 31 | ``` 32 | 33 | - 优点:软链后,两边修改直接同步 34 | - **缺点:** 指令操作麻烦,不同操作系统语法不一样 35 | 36 | ## 2. 正解 - npm link 37 | 38 | 但其实 npm 本身已经对此类情况提供了专门的 `npm link` 指令。 39 | 40 | 相关文档: 41 | 42 | 下面我们简单介绍下用法: 43 | 44 | ``` 45 | $ cd path/to/my-project 46 | $ npm link path/to/my-utils 47 | ``` 48 | 49 | 简单的替换一个单词,就搞定了,cool~ 50 | 51 | 如果这两种的目录不在一起,那还有一种方法: 52 | 53 | ``` 54 | $ # 先去到模块目录,把它 link 到全局 55 | $ cd path/to/my-utils 56 | $ npm link 57 | $ 58 | $ # 再去项目目录通过包名来 link 59 | $ cd path/to/my-project 60 | $ npm link my-utils 61 | ``` 62 | 63 | 该指令还可以用来调试 node cli 模块,譬如需要本地调试我们的 [egg-init](https://github.com/eggjs/egg-init),可以这样: 64 | 65 | ``` 66 | $ cd path/to/egg-init 67 | $ npm link 68 | $ # 此时全局的 egg-init 指令就已经指向你的本地开发目录了 69 | $ egg-init # 即可 70 | ``` 71 | 72 | 想去掉 link 也很简单: 73 | 74 | ``` 75 | $ npm unlink my-utils 76 | ``` 77 | 78 | ## 3. 写在最后 79 | 80 | - 该方法只是为了最后一步调试,模块本身的正确性,应该更多的通过单元测试来保证。 81 | - 单元测试相关内容,可以参见:[单元测试](https://eggjs.org/zh-cn/core/unittest.html) -------------------------------------------------------------------------------- /npm/pull_requests.md: -------------------------------------------------------------------------------- 1 | ## 背景 2 | 3 | 使用别人开源项目的时候,如果当你觉得有一些不足,或者发现一些 bug。的时候可以给别的开源项目 pull Request(以下简称 pr) 4 | 5 | ## 步骤 6 | 7 | 当你想更正别人仓库里的错误时,只需 3 步骤: 8 | 9 | 1. fork 别人的仓库 10 | 2. 开源i项目 clone 到本地分支,做一些修改 11 | 3. 发起 pull request 给原仓库 12 | 13 | 仅仅这几步整个 pull request 的过程就结束了。 14 | 15 | 16 | 17 | 已我 pr umi-request 实战为例 18 | 19 | ## 操作 20 | 21 | 1. 进入 github 开源项目的地址,进行 fork 22 | 23 | ## 图一 24 | 25 | > fork 之后该项目就会导入到自己的 github 仓库 26 | 27 | ![](https://raw.githubusercontent.com/zouxiaomingya/JavaScript/master/npm/img/one.jpg) 28 | 29 | 2. 进入自己的 github 主页 git clone umi-request 这个仓库。 30 | 31 | ![](https://raw.githubusercontent.com/zouxiaomingya/JavaScript/master/npm/img/two.jpg) 32 | 33 | 34 | 具体操作,打开控制台 35 | 36 | ``` 37 | $ git clone https://github.com/zouxiaomingya/umi-request.git 38 | $ cd umi-request 39 | // 拉出一个分支来修改 (也可以在主分支) 40 | $ git checkout -b Interceptors-pr 41 | // 做完 bug 的修改之后 42 | $ git add . 43 | $ git commit -m 'feat: add instance Interceptors' 44 | $ git push origin Interceptors-pr 45 | 46 | ``` 47 | 48 | 3. 进行 pr 操作 49 | 50 | > 进入自己仓库的 umi-request 的 pr 分支 51 | 52 | ![](https://raw.githubusercontent.com/zouxiaomingya/JavaScript/master/npm/img/three.jpg) 53 | 54 | 55 | 然后点击 Compare & pull request 按钮 进行提交 56 | 57 | ![](https://raw.githubusercontent.com/zouxiaomingya/JavaScript/master/npm/img/four.jpg) 58 | -------------------------------------------------------------------------------- /npm/好用的一些包.md: -------------------------------------------------------------------------------- 1 | ## schema-utils 2 | > 判断对象的类型是否符合 3 | -------------------------------------------------------------------------------- /review/640.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zouxiaomingya/JavaScript/5f354b8a06677af80210d9620fd27d7391a17373/review/640.webp -------------------------------------------------------------------------------- /review/amd_cmd.md: -------------------------------------------------------------------------------- 1 | ## AMD(Modules/Asynchronous-Definition)、CMD(Common Module Definition)规范区别? 2 | 3 | > 1. 对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。 2. CMD 推崇依赖就近,AMD 推崇依赖前置。 4 | 5 | ```javascript 6 | // CMD 7 | define(function(require, exports, module) { 8 | var a = require('./a') 9 | a.doSomething() 10 | // 此处略去 100 行 11 | var b = require('./b') // 依赖可以就近书写 12 | b.doSomething() 13 | // ... 14 | }) 15 | 16 | // AMD 默认推荐 17 | define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好 18 | a.doSomething() 19 | // 此处略去 100 行 20 | b.doSomething() 21 | // ... 22 | }) 23 | ``` 24 | AMD 规范在这里:https://github.com/amdjs/amdjs-api/wiki/AMD 25 | CMD 规范在这里:https://github.com/seajs/seajs/issues/242 26 | 27 | AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。 28 | CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。 29 | 类似的还有 CommonJS Modules/2.0 规范,是 BravoJS 在推广过程中对模块定义的规范化产出。 30 | 还有不少⋯⋯ 31 | 32 | 这些规范的目的都是为了 JavaScript 的模块化开发,特别是在浏览器端的。 33 | 目前这些规范的实现都能达成浏览器端模块化开发的目的。 34 | 35 | 区别: 36 | 37 | 1. 对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。CMD 推崇 as lazy as possible. 38 | 39 | 2. CMD 推崇依赖就近,AMD 推崇依赖前置。看代码: 40 | 41 | // CMD 42 | define(function(require, exports, module) { 43 | var a = require('./a') 44 | a.doSomething() 45 | // 此处略去 100 行 46 | var b = require('./b') // 依赖可以就近书写 47 | b.doSomething() 48 | // ... 49 | }) 50 | 51 | // AMD 默认推荐的是 52 | define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好 53 | a.doSomething() 54 | // 此处略去 100 行 55 | b.doSomething() 56 | ... 57 | }) 58 | 59 | 虽然 AMD 也支持 CMD 的写法,同时还支持将 require 作为依赖项传递,但 RequireJS 的作者默认是最喜欢上面的写法,也是官方文档里默认的模块定义写法。 60 | 61 | 62 | 3. AMD 的 API 默认是一个当多个用,CMD 的 API 严格区分,推崇职责单一。比如 AMD 里,require 分全局 require 和局部 require,都叫 require。CMD 里,没有全局 require,而是根据模块系统的完备性,提供 seajs.use 来实现模块系统的加载启动。CMD 里,每个 API 都简单纯粹。 63 | 64 | 65 | 4. 还有一些细节差异,具体看这个规范的定义就好,就不多说了。 66 | 67 | 另外,SeaJS 和 RequireJS 的差异,可以参考:https://github.com/seajs/seajs/issues/277 68 | 69 | 相同之处 70 | RequireJS 和 Sea.js 都是模块加载器,倡导模块化开发理念,核心价值是让 JavaScript 的模块化开发变得简单自然。 71 | 72 | 不同之处 73 | 两者的主要区别如下: 74 | 75 | 定位有差异。RequireJS 想成为浏览器端的模块加载器,同时也想成为 Rhino / Node 等环境的模块加载器。Sea.js 则专注于 Web 浏览器端,同时通过 Node 扩展的方式可以很方便跑在 Node 环境中。 76 | 遵循的规范不同。RequireJS 遵循 AMD(异步模块定义)规范,Sea.js 遵循 CMD (通用模块定义)规范。规范的不同,导致了两者 API 不同。Sea.js 更贴近 CommonJS Modules/1.1 和 Node Modules 规范。 77 | 推广理念有差异。RequireJS 在尝试让第三方类库修改自身来支持 RequireJS,目前只有少数社区采纳。Sea.js 不强推,采用自主封装的方式来“海纳百川”,目前已有较成熟的封装策略。 78 | 对开发调试的支持有差异。Sea.js 非常关注代码的开发调试,有 nocache、debug 等用于调试的插件。RequireJS 无这方面的明显支持。 79 | 插件机制不同。RequireJS 采取的是在源码中预留接口的形式,插件类型比较单一。Sea.js 采取的是通用事件机制,插件类型更丰富。 80 | 还有不少差异,涉及具体使用方式和源码实现,欢迎有兴趣者研究并发表看法。 81 | 82 | 总之,如果说 RequireJS 是 Prototype 类库的话,则 Sea.js 致力于成为 jQuery 类库。 83 | 84 | 最重要的 85 | 最后,向 RequireJS 致敬!RequireJS 和 Sea.js 是好兄弟,一起努力推广模块化开发思想,这才是最重要的。 86 | 87 | -------------------------------------------------------------------------------- /review/api.md: -------------------------------------------------------------------------------- 1 | ## 多维数组 => 一维数组 2 | 3 | ```javascript 4 | let ary = [1, [2, [3, [4, 5]]], 6]; 5 | let str = JSON.stringify(ary); 6 | 复制代码 7 | //第0中处理:直接的调用 8 | arr_flat = arr.flat(Infinity); 9 | 复制代码 10 | //第一种处理 11 | ary = str.replace(/(\[|\])/g, '').split(','); 12 | 复制代码 13 | //第二种处理 14 | str = str.replace(/(\[\]))/g, ''); 15 | str = '[' + str + ']'; 16 | ary = JSON.parse(str); 17 | 复制代码 18 | //第三种处理:递归处理 19 | let result = []; 20 | let fn = function(ary) { 21 | for(let i = 0; i < ary.length; i++) }{ 22 | let item = ary[i]; 23 | if (Array.isArray(ary[i])){ 24 | fn(item); 25 | } else { 26 | result.push(item); 27 | } 28 | } 29 | } 30 | 复制代码 31 | //第四种处理:用 reduce 实现数组的 flat 方法 32 | function flatten(ary) { 33 | return ary.reduce((pre, cur) => { 34 | return pre.concat(Array.isArray(cur) ? flatten(cur) : cur); 35 | }, []); 36 | } 37 | let ary = [1, 2, [3, 4], [5, [6, 7]]] 38 | console.log(flatten(ary)) 39 | 复制代码 40 | //第五种处理:扩展运算符 41 | while (ary.some(Array.isArray)) { 42 | ary = [].concat(...ary); 43 | } 44 | ``` -------------------------------------------------------------------------------- /review/dom事件流.md: -------------------------------------------------------------------------------- 1 | ## 关于一道题的思考 2 | 3 | ```html 4 |
div1 5 |
div2
6 |
7 | 17 | ``` 18 | 19 | #### 点击div1 20 | 21 | 点击 div1 还是比较简单, 22 | 23 | > 7 - 1 - 3 24 | 25 | #### 点击div2 26 | 27 | > 3 - 5 -2 - 4 - 7 - 1 28 | 29 | 没有 6 是因为 被 30 | `div2.onclick = function () { console.log(5) }` 这个事件所覆盖了 31 | 32 | 33 | 这里就压迫考虑捕获和冒泡的事件机制了 34 | 35 | 这里 addEventListener 事件 第三个参数 36 | 37 | > Boolean,在DOM树中,注册了listener的元素, 是否要先于它下面的EventTarget,调用该listener。 当useCapture(设为true) 时,沿着DOM树向上冒泡的事件,不会触发listener。当一个元素嵌套了另一个元素,并且两个元素都对同一事件注册了一个处理函数时,所发生的事件冒泡和事件捕获是两种不同的事件传播方式。事件传播模式决定了元素以哪个顺序接收事件。进一步的解释可以查看 事件流 及 JavaScript Event order 文档。 如果没有指定, useCapture 默认为 false 。 38 | 39 | 总结下正常的 dom 事件流机制 是 先捕获 后冒泡 40 | 41 | 注意: 对于事件目标上的事件监听器来说,事件会处于“目标阶段”,而不是冒泡阶段或者捕获阶段。在目标阶段的事件会触发该元素(即事件目标)上的所有监听器,而不在乎这个监听器到底在注册时useCapture 参数值是true还是false。 -------------------------------------------------------------------------------- /review/eventLoop.md: -------------------------------------------------------------------------------- 1 | ## 打印什么 2 | ```javaScript 3 | async function async1() { 4 | console.log("async1 start"); 5 | await async2(); 6 | console.log("async1 end"); 7 | } 8 | async function async2() { 9 | console.log( 'async2'); 10 | } 11 | console.log("script start"); 12 | setTimeout(function () { 13 | console.log("settimeout"); 14 | }); 15 | async1() 16 | new Promise(function (resolve) { 17 | console.log("promise1"); 18 | resolve(); 19 | }).then(function () { 20 | console.log("promise2"); 21 | }); 22 | setImmediate(()=>{ 23 | console.log("setImmediate") 24 | }) 25 | process.nextTick(()=>{ 26 | console.log("process") 27 | }) 28 | console.log('script end'); 29 | 30 | ``` 31 | #### 第一轮: 32 | 33 | current task:"script start","async1 start",'async2',"promise1",“script end” 34 | micro task queue:[async,promise.then,process] 35 | macro task queue:[setTimeout,setImmediate] 36 | 37 | #### 第二轮 38 | 39 | current task:process,async1 end ,promise.then 40 | micro task queue:[] 41 | macro task queue:[setTimeout,setImmediate] 42 | 43 | #### 第三轮 44 | 45 | current task:setTimeout,setImmediate 46 | micro task queue:[] 47 | macro task queue:[] 48 | 49 | 最终结果:[script start,async1 start,async2,promise1,script end,process,async1 end,promise2,setTimeout,setImmediate] 50 | 同样"async1 end","promise2"之间的优先级,因平台而异。 51 | -------------------------------------------------------------------------------- /review/prototype.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zouxiaomingya/JavaScript/5f354b8a06677af80210d9620fd27d7391a17373/review/prototype.md -------------------------------------------------------------------------------- /review/study.md: -------------------------------------------------------------------------------- 1 | 1. Frontend Masters 2 | 老师是 Douglas Crockford,JS 教父级人物,《JavaScript 语言精髓》作者。这门课讲了 JS 的历史和一些重要的语言细节,并把重点放在函数上。Crockford 认为函数这是 JS 这门语言最重要的部分。后半部分讲了浏览器和服务器的工作原理,以及网络安全。 3 | (1) Deep JavaScript Foundations (2) Rethinking Asynchronous JavaScript (3) Functional-Light JavaScript, v2 (4) ES6: The Right Parts (5) Organizing JavaScript Functionality (6) Coercion in JavaScript 4 | 共 6 门课,老师都是 Kyle Simpson.《你不知道的 JS 》系列书作者,等下还会提到他。第一门课深入了 JS 的大部分重要细节,这是学好 JS 的第一步。其它几门课分专题继续深入,分别是异步编程,ES6 的重要部分,组织 JS 功能模块(学了这个后,我从没写过面条代码),轻量级函数式编程(有配套书,个人觉得是 JS 开发必学),最后是 JS 中的强制类型转换。 5 | (1) Webpack 4 Fundamentals (2) Web Performance with Webpack (3) Webpack Plugins System 6 | 共 3 门关于 Webpack 的课程,老师都是 Sean Larkin。微软 Edge 团队的 Technical Program Manager,Webpack 和 Angular 核心团队成员,前不久刚来中国参加过前端圈的大会。这几门课从基础开始,一直到进阶,呈现了 Webpack 的基本原理,操作技巧,以及插件生态。 7 | Hardcore Functional Programming in JavaScript 8 | 老师是 Brian Lonsdorf,学函数式编程的话,不知道他会多很多挣扎。等下还会提到他。课程从基本的函数组合开始,逐渐讲到硬核函数式编程(Functors, Applicatives 和 Monads 的应用等) 9 | (1) Asynchronous Programming in JavaScript (with Rx.js Observables) (2) Advanced Asynchronous JavaScript 10 | 共两门课。老师是 Jafar Husain。Netflix UI 工程团队 leader,响应式编程专家,TC39 成员。第一门课从零开始写常用的 Rxjs 操作符,逐渐进阶到 DOM 事件流处理,网络请求的处理等。第二门课是进阶课,从零开始写个 Observable,然后用 Observable 来解决一些棘手的动画问题。最后会用课程知识写个应用。 11 | Advanced Vue.js Features from the Ground Up 12 | 老师是尤雨溪,这位不用我介绍了吧。这门课里面,尤雨溪会教你从零开始实现 Vue 的核心构成,如响应式系统,插件,渲染函数,路由,状态管理等。在我入职我目前所在公司的时候,我还没写过 Vue,入职后第一周学了这门课,然后带着团队重构 Vue 项目了。 13 | 还有很多优秀课程,全部列出来篇幅太大了。建议大家去探索寻宝。另外 Frontend Masters 是订阅制,费用比较贵,按月付的话,接近 40 美元一个月,年付会便宜很多。黑五和开学季会有折扣。我是在开学季用折扣价订了一年。 14 | 15 | 另外,Frontend Masters 每年都会出一个免费的前端学习手册。今年的点击这里。 16 | 17 | 2. Egghead 18 | 这个网站的风格是简短精炼。每个视频都很短,语速很快,适合有一定基础,想用碎片时间充电的前端从业人员。很多库的作者会在这里讲他们的作品,比如 Dan Abramov 会在这里讲 Redux,Michel Weststrate 会在这里讲 Mobx 和 Mobx State Tree 等,这些都是免费的。付费课程质量也大部分很高。你想学的某些主流技术,热门的库,大部分都在这里找得到教程。比如 Ramda,它有 200 多个 API,而且与其它库风格迥异,我是怎么短时间内对这么多 API 应用自如的?除了大量地训练和挤地铁时间查看文档,还离不开 Egghead 上的实战课程。这里再挑几门对我帮助最大的部分课程。剩下一些课程我会在本文后面再提。 19 | 20 | (1) RxJS Beyond the Basics: Creating Observables from scratch (2) RxJS Subjects and Multicasting Operators (3) Save time avoiding common mistakes using RxJS (4)Use Higher Order Observables in RxJS Effectively 21 | 全是 RxJS 的课程,老师是 André Staltz,我最崇拜的技术人之一,等下还会讲到他。这些课程从 RxJS 的入门一直讲到高阶操作。这些课程和前面提到的 Jafar Husain 的课程会有重合,但我觉得从不同的老师那里,从不同角度学习,可以掌握地更全。当然你不用每个主题都找两个老师学……我只是发现我订阅的两个网站都有 RxJS 课程,所以全学了。 22 | Automate Daily Development Tasks with Bash 23 | 作为开发人员,你应该掌握一些自动化工作流,提升开发效率。这门课讲了开发中常用的 Bash 操作技巧,不管是前端和后端,都适用。 24 | Quickly Transform Data with Transducers 25 | 我之前发表的消灭 for 循环的那篇文章,里面用的 Transduce 写法,就是在这门课里学到的。 26 | Egghead 还有很多高阶 CSS 课程以及其它大前端的课程,比如单元测试,Serverless,等等。还有三门高阶函数式编程的课程,我放到后面部分讲。 27 | 28 | 3. Udemy 29 | Udemy 是个在线教育界的淘宝,什么课程都有。你能在那里学音乐,学绘画,甚至还能学咏春拳…… 当然能学编程,而且有些热门编程老师确实很厉害。Udemy 有个套路,标价 200 美元的课程,经常悄悄打折 9.9 美元卖。我所有课程都是最低价买的,前后一年半共买了 60 多门课,通常是在黑五圣诞等折扣季买,当然,Udemy 几乎每个月都在促销。我买的课程覆盖前端后端,深度学习,区块链开发等。我只把前端课程的 2 / 3 学了,其它的真学不动了,大多数属于冲动消费…… 30 | 31 | 32 | 33 | 34 | 35 | 36 | 我学习的课程部分截图 37 | 38 | 这部分我就不详细介绍课程了,只推荐三个老师。 39 | 40 | 一是 Stephen Grider,我买了他大部分课程。Stephen 擅长用很直观的图表来拆解工程概念,再难的东西他也能拆到用日常语言解释。我一开始自学算法时,感到很吃力。Stephen 的算法课让我通过 JS 掌握了基础的计算机算法。他还有 Node,React,MongoDB 以及 GraphQL 的课程,大部分涵盖了入门和进阶。 41 | 42 | 第二个是 Andrew Mead。他讲课能力也很优秀,我第一次学 Node 是学的他的课程,收获很大。另外他对学员的问题回应非常及时和详细。他的课程和 Stephen 的重复率挺高,不用两个都买。 43 | 44 | 如果有兴趣学 iOS 开发,强烈推荐 Angela Yu 的课。(我做了半年 React Native 开发,所以去学了原生开发。)Angela 讲课幽默可爱,新人友好。她似乎是中国长大的,在英国学医。本来是医生,后来转行做 iOS 开发和设计了。厉害的人生各有各的不同啊…… 45 | 46 | 4. Wes Bos 47 | Wes Bos 可能相当于北美阮一峰…… 当然这种类比是不恰当的。我的意思是,他的课程覆盖了前端很广领域,也广受欢迎。如果你入门没多久,可以学他的免费课程 JavaScript 30. 用一个月时间,每天用原生 JS 写个网页应用。Mozilla 还赞助他开了 CSS Grid 的课程,吸引开发者用 FireFox。你也可以免费学这门课。他还有付费的 Node 和 React 课程。最近他准备出一个高阶 React 和 GraphQL 课程,我观望中,可能会买。另外他还主持了一个播客节目叫 Syntax,主题是前端开发,我每期都会听。挤地铁时用两倍语速听,能吸收到很多新鲜知识。 48 | 49 | 5. YouTube 50 | YouTube 是个很神奇的地方,每个知识和娱乐的领域都能在这里找到精华。我在这里列出几个前端和泛编程的频道。 51 | 52 | Fun Fun Function 53 | 主播名字太长了,粉丝都叫他 MPJ。MPJ 是瑞典人,之前一直在 Spotify 工作,最近辞职后全职做 Fun Fun Forum 论坛了。这个频道覆盖了很杂的 JS 和前端开发知识。我在这个频道学到的都是在其它地方学过的,比如函数式编程,设计模式等,但是在这里学更像一种放松和实时看高手是怎样写代码和解决问题的。 54 | 55 | Traversy Media 56 | 主播非常勤奋,更新很频繁。内容大多是初级和中级,非常适合新人学习。我现在偶尔也会看他新出的教程,用原生 JS 写个动效,用 CSS 写完成度 100% 的企业展示页面等。 57 | 58 | Coding Tech 59 | 这个频道会持续更新计算机行业最新的优质演讲。前端和其它领域都有。 60 | 61 | 6. GitHub 62 | GitHub 上参考别人的代码,能加快自己的理解。比如,Jason Miller(等下我还会介绍他) 的热门 repo 我每个都会看。EventEmitter,状态管理,异步函数自动放到 web worker 的工具库,Fetch API polyfill,等等听起来挺复杂的东西,他五六行,十几行代码就实现了,还发布到 NPM 成为完整的包。还有 You Don't Need jQuery, You Don't Need Lodash, 30 Seconds of Code 等 Repo,对提升原生 JS 解决问题的能力有非常大的帮助。碎片时间可以在手机上学习。 63 | 64 | 7. JavaScript Weekly 65 | 我觉得 JavaScript Weekly(免费 Newsletter) 是前端开发者必须订阅的,但我发现好像身边人都不知道。很少有人能不关心技术趋势还能走在前面的。你应该关注同行最近又开发出了什么酷的东西,你关注的技术又出了哪些新教程。不过,每天盯着技术热点看也容易分心。一周关注一次,频率刚刚好。 66 | 67 | 四,影响我的技术人 68 | 如果你了解过一万小时天才理论,你可能知道一个好的 Mentor 在个人成长中的无法替代的作用。大多数人都没有如此幸运,找到一个好的导师。我也是。对此我的一个并不完美的替代方案是关注行业里的思考者和先行者,听下他们的建议,了解他们是怎样思考和工作的。下面是对我影响最大的技术人: 69 | 70 | Kyle Simpson. 我的 JS 是他领入门的,也是他带着走向进阶的。我的整个学习路径,都有他的影响。作为一个教育者,他会给学习者很多各方面的建议。我会关注他所有的演讲和开源项目。Twitter: @getify 71 | Eric Elliott. 我学函数式编程是始于他。我比较幸运,一开始学编程的时候刚好碰到 Eric 开始在 Medium 上连载函数式编程教程。我在学了三个月 JS 之后,就遇到了一个全新的编程范式,并且被说服了。我推荐所有 JS 开发者都了解下这一系列文章 Composing Software 你可以听一个从 BASIC 时代就开始写程序的前辈,是怎样看待不同编程范式的;了解为什么组合比继承更优,为什么 JS 适合用来函数式编程。Twitter: _ericelliott 72 | André Staltz. 他是社区里面的响应式编程专家。如果你想学响应式编程的话,一定要看他写的这篇文章,The introduction to Reactive Programming you've been missing. 他还写了一个函数响应式框架叫 Cycle.js 除了技术,他最让我佩服的还有他对技术与社会之间关系的思考。他对于目前 Facebook 和 Google 等互联网巨头控制用户数据的现状不满,他的一个 mission 就是要创造去中心化的互联网。(万维网的创始人,Tim Berners-Lee 爵士,也在做这件事)他最近发布了一款安卓手机应用叫 Manyverse,一个真正去中心化社交平台。这款应用是用 React Native 写的,开源。感兴趣的话,你可以看下源码,看下他是怎么组织代码的。Twitter: @andrestaltz 73 | Sarah Drasner. Sarah 是个非常 nice 的姐姐,非常有亲和力。她是 SVG 动画专家,CSS 专家,微软资深开发。还是 Vue 核心团队成员。她在 Frontend Masters 上有 Vue.js,高阶 SVG 动画,UI 设计等课程。她在 Twitter 上也会发很多开发 Tips。Twitter: @sarah_edo 74 | Wes Bos. 刚刚提到过这位。他除了在播客上给开发者提供很多建议外,还在 Twitter 上分享很多开发技巧,JS, CSS, Bash, VSCode 等技巧都有。我在开发中也用了很多他分享的技巧,省了很多时间。Twitter: @wesbos 75 | Mathias Bynens. 谷歌 V8 引擎工程师,TC39 成员。他会从引擎实现的角度,告诉开发者怎么写代码。他也会分享一些 V8 的项目细节等。我之前写了消灭 for 循环的文章,好多人反对,还有些人直接嘲讽我。其中有一个说法是高阶函数没有 for 循环性能好。作为一个新手,我哪来勇气去对杠来自资深开发者的质疑的?因为引擎开发者都说了,那点微观语言层面的性能差异,不会成为你整个应用的性能瓶颈。你应该把注意力放在编写易理解和易维护的代码上。 Twitter: @mathias 76 | Brian Lonsdorf. 网名 Dr. Boolean. 这家伙就是个天才。他有些害羞,但是在讲他最擅长的函数式编程的时候,总能把满脑子天马行空的想法讲地很清楚。如果想学硬核函数式编程的话,推荐从关注他开始。他 Twitter 更新不频繁,但是更新的时候一般都是值得你记笔记的时候。Twitter: @drboolean 77 | Jason Miller. 刚有提到他。他是 Google Chrome 团队工程师,还是 Preact 的作者。他写了一堆平均只有十几行代码的库。研究他的代码不用一个文件一个文件地跳,因为就在一个文件里…… 他在 Twitter 上也很活跃。Twitter: @_developit 78 | Bartosz Milewski. 这位真是位大神。我说我被他影响都有强行给自己贴金的嫌疑。他有量子物理博士学位,后来去做软件开发了,成了 C++ 专家,出过 C++ 的专著。后来因受不了 C++ 糟糕的设计,去写 Haskell 了,也成了专家。最近又把兴趣转向范畴论了,开始给程序员教范畴论。业余还研究音乐理论(我看他 YouTube 点赞列表知道的……)。我最近几个月每周都安排几个早晨,去他的 YouTube 频道听他讲范畴论(这个不需要高数基础)。等下我会继续谈他的范畴论教程。Twitter: @BartoszMilewski -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Layout } from "antd"; 3 | import { BrowserRouter } from "react-router-dom"; 4 | import Head from "./layout/Head"; 5 | import Side from "./layout/Side"; 6 | import Router, { routerConfig } from "./Router"; 7 | 8 | function App() { 9 | return ( 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ); 22 | } 23 | export default App; 24 | -------------------------------------------------------------------------------- /src/Router.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Layout } from "antd"; 3 | import { Route, Switch } from "react-router-dom"; 4 | import Home from './pages/Home'; 5 | import handover from './pages/handover'; 6 | import Page404 from './pages/Page404' 7 | const { Content } = Layout; 8 | 9 | export const routerConfig = [ 10 | { 11 | path: '/home', 12 | icon: 'home', 13 | text: '主页', 14 | component: Home, 15 | }, { 16 | path: '/handover', 17 | icon: 'home', 18 | text: '交接工作', 19 | component: handover, 20 | } 21 | ]; 22 | function Router() { 23 | return ( 24 | 32 | {/* exact 严格模式匹配完整的路由*/ } 33 | 34 | {routerConfig.map(({ path, component }) => ( 35 | 36 | ))} 37 | 38 | 39 | 40 | ) 41 | } 42 | 43 | export default Router; -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import App from "./App"; 4 | import { BrowserRouter } from "react-router-dom"; 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById("root") 11 | ); 12 | -------------------------------------------------------------------------------- /src/layout/Head/index.jsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { Layout } from 'antd'; 4 | const { Header } = Layout; 5 | function Head() { 6 | return ( 7 |
8 |
9 | 我是头部 10 |
11 |
12 | ) 13 | } 14 | 15 | export default Head; -------------------------------------------------------------------------------- /src/layout/Side/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Layout, Menu, Icon } from 'antd'; 3 | import { Link, withRouter } from "react-router-dom"; 4 | 5 | const { Sider } = Layout; 6 | function Side({ routerConfig,location }) { 7 | const { pathname } = location; 8 | // const filterPathName = `/${pathname.split('/')[1]}` 9 | return ( 10 | 11 | 16 | {routerConfig.map((item) => ( 17 | 18 | 19 | 20 | {item.text} 21 | 22 | 23 | ))} 24 | 25 | 26 | ) 27 | } 28 | 29 | export default withRouter(Side); -------------------------------------------------------------------------------- /src/pages/HandOver/AddForm.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Form, Input, message, Row, Col, Button } from 'antd'; 3 | 4 | function AddForm({ form, handleCloseDrawer }) { 5 | const { getFieldDecorator, validateFields } = form 6 | const formItemLayout = { 7 | labelCol: { 8 | sm: { span: 4 }, 9 | }, 10 | wrapperCol: { 11 | sm: { span: 18 }, 12 | }, 13 | }; 14 | 15 | const handleSubmit = () => { 16 | validateFields((errors, values) => { 17 | if (errors) return false; 18 | console.log(values); 19 | handleCloseDrawer(); 20 | success(); 21 | }); 22 | } 23 | const success = () => { 24 | message.success('提交成功!'); 25 | }; 26 | 27 | return ( 28 | <> 29 |
30 | 31 | {getFieldDecorator('username', { 32 | rules: [{ required: true, message: '名字一定要有噢!' }], 33 | })( 34 | , 35 | )} 36 | 37 | 38 | {getFieldDecorator('password', {})( 39 | , 40 | )} 41 | 42 | 43 | 44 | 45 | 46 | 47 |
48 | 49 | ); 50 | } 51 | export default Form.create()(AddForm); 52 | -------------------------------------------------------------------------------- /src/pages/HandOver/index.css: -------------------------------------------------------------------------------- 1 | .operateButton{ 2 | color: #1890ff; 3 | text-decoration: none; 4 | background-color: transparent; 5 | outline: none; 6 | cursor: pointer; 7 | -webkit-transition: color 0.3s; 8 | transition: color 0.3s; 9 | } -------------------------------------------------------------------------------- /src/pages/HandOver/index.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-script-url */ 2 | /* eslint-disable jsx-a11y/anchor-is-valid */ 3 | import React, { useState } from "react"; 4 | 5 | import { Card, Input, Button, Select, Table, Drawer } from 'antd'; 6 | import { mockList } from './mock'; 7 | import AddForm from './AddForm'; 8 | import './index.css'; 9 | 10 | const { Search } = Input; 11 | const { Option } = Select; 12 | 13 | function HandOver() { 14 | const [visible, setVisible] = useState(false); 15 | const [handoverType, sethandoverType] = useState('get'); 16 | const [inputValue, setinputValue] = useState(); 17 | 18 | const onChange = (value) => { 19 | sethandoverType(value); 20 | console.log(handoverType + inputValue); 21 | } 22 | 23 | const searchUerName = (value) => { 24 | setinputValue(value) 25 | console.log(value + handoverType); 26 | } 27 | 28 | const handleDel = (id) => { 29 | console.log(id); 30 | } 31 | 32 | const renderOperate = () => ( 33 | <> 34 | 38 | searchUerName(value)} enterButton /> 39 | 40 | ); 41 | 42 | const stateMap = { 43 | 'on': 'dakai', 44 | 'off': 'shibai', 45 | 'close': 'guanbi', 46 | } 47 | const columns = [ 48 | { 49 | title: '姓名', 50 | dataIndex: 'name', 51 | key: 'name', 52 | }, 53 | { 54 | title: '花名', 55 | dataIndex: 'nickName', 56 | key: 'nickName', 57 | }, 58 | { 59 | title: '项目', 60 | dataIndex: 'project', 61 | key: 'project', 62 | }, 63 | { 64 | title: '状态', 65 | dataIndex: 'state', 66 | key: 'state', 67 | render: (text) => stateMap[text] 68 | }, 69 | { 70 | title: '创建时间', 71 | dataIndex: 'time', 72 | key: 'time', 73 | }, 74 | { 75 | title: '操作', 76 | render: (_text, { id }) => ( 77 | <> 78 | 查看 79 | handleDel(id)}>删除 80 | 81 | ) 82 | }, 83 | ]; 84 | 85 | const handleCloseDrawer = () => setVisible(false); 86 | const handleOpenDrawer = () => setVisible(true); 87 | 88 | return ( 89 | <> 90 | { handleOpenDrawer() }} >新增交接} 96 | > 97 | 98 | 99 | 107 | 108 | 109 | 110 | 111 | ); 112 | } 113 | 114 | export default HandOver; 115 | -------------------------------------------------------------------------------- /src/pages/HandOver/mock.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | export const mockList = new Array(10).fill(0).map((item, index) => ({ 3 | id: index, 4 | name: `xingmin${index}`, 5 | nickName: `nickName${index}`, 6 | project: `project${index}`, 7 | time: moment().format("L"), 8 | state: index % 2 === 0 ? 'on' : 'close', 9 | })); 10 | -------------------------------------------------------------------------------- /src/pages/Home/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | function Home() { 4 | return ( 5 |
home
6 | ); 7 | } 8 | export default Home; 9 | -------------------------------------------------------------------------------- /src/pages/Page404/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | function App() { 4 | return ( 5 |
6 | 404界面 7 |
8 | ); 9 | } 10 | 11 | export default App; 12 | -------------------------------------------------------------------------------- /utils/isColorDarkOrLight.js: -------------------------------------------------------------------------------- 1 | const REG_HEX = /(^#?[0-9A-F]{6}$)|(^#?[0-9A-F]{3}$)/i; 2 | 3 | /* 4 | * rgb字符串解析 5 | * accepts: #333 #accded (without # is also fine) 6 | * not accept yet: rgb(), rgba() 7 | */ 8 | function parseRGB(str) { 9 | if (typeof str === "string" && REG_HEX.test(str)) { 10 | str = str.replace("#", ""); 11 | let arr; 12 | if (str.length === 3) { 13 | arr = str.split("").map((c) => c + c); 14 | } else if (str.length === 6) { 15 | arr = str.match(/[a-zA-Z0-9]{2}/g); 16 | } else { 17 | throw new Error("wrong color format"); 18 | } 19 | return arr.map((c) => parseInt(c, 16)); 20 | } 21 | throw new Error("color should be string"); 22 | } 23 | 24 | /* 25 | * rgb value to hsl 色相(H)、饱和度(S)、明度(L) 26 | */ 27 | function rgbToHsl(rgbStr) { 28 | let [r, g, b] = parseRGB(rgbStr); 29 | (r /= 255), (g /= 255), (b /= 255); 30 | let max = Math.max(r, g, b), 31 | min = Math.min(r, g, b); 32 | let h, 33 | s, 34 | l = (max + min) / 2; 35 | 36 | if (max == min) { 37 | h = s = 0; // achromatic 38 | } else { 39 | let d = max - min; 40 | s = l > 0.5 ? d / (2 - max - min) : d / (max + min); 41 | switch (max) { 42 | case r: 43 | h = (g - b) / d + (g < b ? 6 : 0); 44 | break; 45 | case g: 46 | h = (b - r) / d + 2; 47 | break; 48 | case b: 49 | h = (r - g) / d + 4; 50 | break; 51 | } 52 | h /= 6; 53 | } 54 | return [h, s, l]; 55 | } 56 | 57 | /* 58 | * 判断颜色属于深色还是浅色 59 | */ 60 | function isColorDarkOrLight(rgbStr) { 61 | let [h, s, l] = rgbToHsl(rgbStr); 62 | return l > 0.5 ? "light" : "dark"; 63 | } 64 | -------------------------------------------------------------------------------- /webpack/Tapble/README.md: -------------------------------------------------------------------------------- 1 | ## Tapable 2 | 3 | ### Sync** 4 | 5 | 1. SyncHook 6 | 2. SyncBailHook 7 | 3. SyncWaterfallHook 8 | 4. SyncLoopHook 9 | 10 | ### Async** 11 | 12 | #### AsyncParallel** 13 | 14 | 1. AsyncParallelHook 15 | 2. AsyncParallelBallHook 16 | 17 | #### AsyncSeries** 18 | 19 | 3. AsyncSeriesHook 20 | 4. AsyncSeriesBailHook 21 | 5. AsyncSeriesWaterfallHook 22 | 23 | 24 | -------------------------------------------------------------------------------- /webpack/bin/my_webpack.js: -------------------------------------------------------------------------------- 1 | let path = require('path') 2 | 3 | let config = require(path.resolve(__dirname)); 4 | 5 | let Compiler = require('../lib/Compiler.js') 6 | 7 | let compiler = new Compiler(config) 8 | 9 | compiler.run() -------------------------------------------------------------------------------- /webpack/lib/Compiler.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | const babylon = require('babylon'); 4 | const t = require('@babel/types'); 5 | const traverse = require('@babel/traverse').default; 6 | const generator = require('@babel/generator').default; 7 | // babylon 包,主要就是把源码,转换成 ast 语法 8 | // @babel/traverse 9 | // @babel/types 10 | // @babel/generator 11 | class Compiler { 12 | constructor(config) { 13 | this.config = config; 14 | this.entryId; 15 | this.module = {} 16 | this.entry = config.entry 17 | this.root = process.cwd(); 18 | } 19 | getSource(modulePath) { 20 | const content = fs.readFileSync(modulePath, 'utf8') 21 | return content; 22 | } 23 | parse(source, parentPath) { 24 | const ast = babylon.parse(source); 25 | traverse(ast, { 26 | CallExpression(p) { 27 | const node = p.node; 28 | if (node.callee.name === 'require') { 29 | node.callee.name === '___webpack_require__'; 30 | const moduleName = node.arguments[0].value; 31 | moduleName = moduleName + (path.extname(moduleName) ? '' : '.js'); 32 | moduleName = './' + path.join(parentPath, moduleName); 33 | dependencies.push(moduleName); 34 | node.arguments = [t.stringLiteral(moduleName)]; 35 | } 36 | 37 | } 38 | }); 39 | const sourceCode = generator(ast).code; 40 | return { sourceCode, dependencies } 41 | } 42 | buildModule(modulePath, isEntry) { 43 | const source = this.getSource(modulePath); 44 | const moduleName = './' + path.relative(this.root, modulePath) 45 | const { sourecCode, dependencies } = this.parse(source, path.dirname(moduleName)) 46 | this.module[modulePath] = sourecCode; 47 | } 48 | emtiFile() { 49 | 50 | } 51 | run() { 52 | this.buildModule(path.resolve(this.root, this.entry), true) 53 | this.emitFile() 54 | } 55 | } 56 | 57 | module.exports = Compiler; 58 | -------------------------------------------------------------------------------- /webpakc.md: -------------------------------------------------------------------------------- 1 | ### webpack 的配置 2 | 3 | ```javascript 4 | const path = require('path'); 5 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 6 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 7 | 8 | 9 | const resolve = (...arg) => path.join(__dirname, ...arg); 10 | module.exports = { 11 | entry: './src/index.js', 12 | mode: 'development', 13 | output: { 14 | path: resolve('dist'),//定位,输出文件的目标路径 15 | filename: '[name].js' //文件名[name].js默认,也可自行配置 16 | }, 17 | plugins:[ 18 | new HtmlWebpackPlugin({ 19 | template: './src/index.html', 20 | }), 21 | // // css 单独打包的插件 22 | // new MiniCssExtractPlugin({ 23 | // filename: "[name].css", 24 | // chunkFilename: "[id].css" 25 | // }) 26 | ], 27 | module: { 28 | rules:[ 29 | { 30 | test: /\.(js|jsx)$/, 31 | use:[ 32 | { 33 | loader:"babel-loader", 34 | options: { 35 | presets:[ 36 | '@babel/preset-react', 37 | '@babel/preset-env', 38 | ] 39 | } 40 | } 41 | ] 42 | }, 43 | { 44 | test: /\.css$/, 45 | use: ['style-loader','css-loader'], 46 | }, 47 | { 48 | test: /\.less$/, 49 | use: [ 50 | 'style-loader', { 51 | loader:'css-loader', 52 | // 开启 less 模块化 53 | options: { 54 | modules: true 55 | } 56 | }, 57 | 'less-loader' 58 | ] 59 | } 60 | ] 61 | }, 62 | resolve: { 63 | // 设置别名 64 | alias: { 65 | '@': resolve('src'), 66 | }, 67 | // 文件后缀补全 68 | extensions: ['.js', '.jsx'], 69 | }, 70 | 71 | } 72 | ``` 73 | 74 | ### antd 为了方便使用直接引入 75 | 76 | > import antd from "antd/dist/antd.css"; 77 | 78 | ### webpack 中使用 EChart 图 79 | 80 | 首先引入echarts 81 | 82 | > import echarts from 'echarts' 83 | 84 | 使用如下 85 | 86 | ```react 87 | import React, { useEffect, useRef } from 'react' 88 | import echarts from 'echarts' 89 | import option from './option' 90 | import Styles from './index.less'; 91 | function Chart() { 92 | const chartDom = useRef(null) 93 | let myChart 94 | useEffect(() => { 95 | // 通过 echarts.init 注册一个实例 96 | myChart = echarts.init(chartDom.current) 97 | // 传入 option 配置实现图表 98 | myChart.setOption(option) 99 | // resize() 方法 改变 chart 的大小 100 | window.addEventListener('resize',myChart.resize) 101 | // 闭包的使用 来移除监听 组件卸载时候执行 102 | return () => { 103 | window.removeEventListener('resize',myChart.resize) 104 | } 105 | }) 106 | return ( 107 |
108 | ) 109 | } 110 | 111 | 112 | export default Chart; 113 | ``` 114 | 115 | -------------------------------------------------------------------------------- /单元测试/myJest.md: -------------------------------------------------------------------------------- 1 | # Jest 2 | 常用的匹配器如下: 3 | 4 | toBe 使用 Object.is 判断是否严格相等。 5 | toEqual 递归检查对象或数组的每个字段。 6 | toBeNull 只匹配 null。 7 | toBeUndefined 只匹配 undefined。 8 | toBeDefined 只匹配非 undefined。 9 | toBeTruthy 只匹配真。 10 | toBeFalsy 只匹配假。 11 | toBeGreaterThan 实际值大于期望。- # Jest 实例教程 12 | 13 | [Jest](https://link.juejin.im/?target=https%3A%2F%2Fjestjs.io%2F) 是由 Facebook 开源出来的一个测试框架,它集成了断言库、mock、快照测试、覆盖率报告等功能。它非常适合用来测试 React 代码,但不仅仅如此,所有的 js 代码都可以使用 Jest 进行测试。 14 | 15 | ## 初始化 16 | 17 | ### 安 装 jest 18 | 19 | ``` 20 | $ npm i -D jest 21 | ``` 22 | 23 | ### 生成 package.json 24 | 25 | ``` 26 | npm init -y 27 | ``` 28 | 29 | ### 修改 package.json 30 | 31 | 在 `package.json` 文件中添加下面的内容: 32 | 33 | ``` 34 | "scripts": { 35 | "test": "jest" 36 | } 37 | ``` 38 | 39 | 这样你就可以直接在命令行使用 `jest` 命令。如果你是本地安装,但是也想在命令行使用 `jest`,可以通过 `node_modules/.bin/webpack` 访问它的 bin 版本,如果你的 npm 版本在 **5.2.0** 以上,你也可以通过 `npx jest` 访问。 40 | 41 | ### 使用 Babel 42 | 43 | 如果你在代码中使用了新的语法特性,而当前 Node 版本不支持,则需要使用 Babel 进行转义。 44 | 45 | 安装如下依赖 46 | 47 | ```json 48 | "devDependencies": { 49 | "@babel/core": "^7.6.4", 50 | "@babel/preset-env": "^7.6.3", 51 | "babel-core": "^6.26.3", 52 | "babel-jest": "^24.9.0", 53 | "babel-preset-env": "^1.7.0" 54 | } 55 | ``` 56 | 57 | 我们还需在根目录下创建 `.babelrc` 文件: 58 | 59 | ```json 60 | { "presets": ["@babel/preset-env"] } 61 | ``` 62 | 63 | ## 基本用法 64 | 65 | 我们从一个基本的 Math 模块开始。首先创建一个 `math.js` 文件: 66 | 67 | ```javascript 68 | // index.js 69 | 70 | const sum = (a, b) => a + b 71 | const mul = (a, b) => a * b 72 | const sub = (a, b) => a - b 73 | const div = (a, b) => a / b 74 | 75 | export { sum, mul, sub, div } 76 | ``` 77 | 78 | 要测试这个 Math 模块是否正确,我们需要编写测试代码。通常,测试文件与所要测试的源码文件同名,但是后缀名为 `.test.js` 或者 `.spec.js`。我们这里则创建一个 `math.test.js` 文件: 79 | 80 | ```javascript 81 | // index.test.js 82 | 83 | import { sum, mul, sub, div } from './index' 84 | 85 | test('Adding 1 + 1 equals 2', () => { 86 | expect(sum(1, 1)).toBe(2) 87 | }) 88 | 89 | test('Multiplying 1 * 1 equals 1', () => { 90 | expect(mul(1, 1)).toBe(1) 91 | }) 92 | 93 | test('Subtracting 1 - 1 equals 0', () => { 94 | expect(sub(1, 1)).toBe(0) 95 | }) 96 | 97 | test('Dividing 1 / 1 equals 1', () => { 98 | expect(div(1, 1)).toBe(1) 99 | }) 100 | 复制代码 101 | ``` 102 | 103 | 执行 `npm test` Jest 将会执行所有匹配的测试文件,并最终返回测试结果:通过测试会有绿色勾 104 | 105 | 大致信息如下 106 | 107 | ``` 108 | √ Adding 1 + 1 equals 2 (3ms) 109 | √ Multiplying 1 * 1 equals 1 110 | √ Subtracting 1 - 1 equals 0 (1ms) 111 | √ Dividing 1 / 1 equals 1 112 | 113 | Test Suites: 1 passed, 1 total 114 | Tests: 4 passed, 4 total 115 | Snapshots: 0 total 116 | Time: 3.6s 117 | ``` 118 | 测试单个文件可以使用 119 | > jest 120 | 121 | ## 匹配器 122 | 123 | 匹配器用来实现断言功能。在前面的例子中,我们只使用了 `toBe()` 匹配器: 124 | 125 | ``` 126 | test('Adding 1 + 1 equals 2', () => { 127 | expect(sum(1, 1)).toBe(2) 128 | }) 129 | 复制代码 130 | ``` 131 | 132 | 在此代码中,`expect(sum(1, 1))` 返回一个“期望”对象,`.toBe(2)` 是匹配器。匹配器将 `expect()` 的结果(**实际值**)与自己的参数(**期望值**)进行比较。当 Jest 运行时,它会跟踪所有失败的匹配器,并打印出错误信息。 133 | 134 | 常用的匹配器如下: 135 | 136 | - `toBe` 使用 [Object.is](https://link.juejin.im/?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fzh-CN%2Fdocs%2FWeb%2FJavaScript%2FReference%2FGlobal_Objects%2FObject%2Fis) 判断是否严格相等。 137 | - `toEqual` 递归检查对象或数组的每个字段。 138 | - `toBeNull` 只匹配 `null`。 139 | - `toBeUndefined` 只匹配 `undefined`。 140 | - `toBeDefined` 只匹配非 `undefined`。 141 | - `toBeTruthy` 只匹配真。 142 | - `toBeFalsy` 只匹配假。 143 | - `toBeGreaterThan` 实际值大于期望。 144 | - `toBeGreaterThanOrEqual` 实际值大于或等于期望值 145 | - `toBeLessThan` 实际值小于期望值。 146 | - `toBeLessThanOrEqual` 实际值小于或等于期望值。 147 | - `toBeCloseTo` 比较浮点数的值,避免误差。 148 | - `toMatch` 正则匹配。 149 | - `toContain` 判断数组中是否包含指定项。 150 | - `.toHaveProperty(keyPath, value)` 判断对象中是否包含指定属性。 151 | - `toThrow` 判断是否抛出指定的异常。 152 | - `toBeInstanceOf` 判断对象是否是某个类的实例,底层使用 `instanceof`。 153 | 154 | ## 异步测试 155 | 当执行到测试代码的尾部时,Jest 即认为测试完成。因此,如果存在异步代码,Jest 不会等待回调函数执行。要解决这个问题,在测试函数中我们接受一个参数叫做 done,Jest 将会一直等待,直到我们调用 done()。如果一直不调用 done(),则此测试不通过。 156 | ```javaScript 157 | // async/fetch.js 158 | export const fetchApple = (callback) => { 159 | setTimeout(() => callback('apple'), 300) 160 | } 161 | 162 | // async/fetch.test.js 163 | import { fetchApple } from './fetch' 164 | 165 | test('the data is apple', (done) => { 166 | expect.assertions(1) 167 | const callback = data => { 168 | expect(data).toBe('apple') 169 | done() 170 | } 171 | 172 | fetchApple(callback) 173 | }) 174 | ``` 175 | 复制代码expect.assertions(1) 验证当前测试中有 1 处断言会被执行,在测试异步代码时,能确保回调中的断言被执行。 176 | -------------------------------------------------------------------------------- /小程序/index.js: -------------------------------------------------------------------------------- 1 | import { Toast } from 'live/common/toast'; 2 | 3 | Component({ 4 | data: { 5 | 6 | }, 7 | 8 | options: { 9 | multipleSlots: true, // 在组件定义时的选项中启用多slot支持 10 | }, 11 | 12 | properties: { 13 | show: Boolean, 14 | shareIcon: { 15 | type: Boolean, 16 | value: true, 17 | }, 18 | posterIcon: { 19 | type: Boolean, 20 | value: true, 21 | }, 22 | }, 23 | 24 | methods: { 25 | onHide() { 26 | if (this.data.posterLoading) Toast.clear(); 27 | this.triggerEvent('close'); 28 | }, 29 | resetPoster() { 30 | this.setData({ posterUrl: '' }); 31 | }, 32 | noop() {}, 33 | }, 34 | 35 | observers: { 36 | show(showable) { 37 | 38 | }, 39 | }, 40 | }); 41 | -------------------------------------------------------------------------------- /小程序/login.js: -------------------------------------------------------------------------------- 1 | let isSilentLoginIng = false; 2 | let subscribers = []; 3 | let code = ""; // 模拟 code, 真实场景这个存放在本地内存中 4 | let MAX_TIME = 3; 5 | let currentLoginTime = 1; 6 | 7 | function onAccessTokenFetched() { 8 | subscribers.forEach((request) => { 9 | request(); 10 | }); 11 | subscribers = []; 12 | } 13 | 14 | function addSubscriber(callback) { 15 | subscribers.push(callback); 16 | } 17 | 18 | // 静默登录 用户无感知,获取 token 19 | const silentLogin = (option = {}) => { 20 | const { firstResolve } = option; 21 | return new Promise((resolve, reject) => { 22 | setTimeout(() => { 23 | const isBig = Math.random() > 0.5; 24 | console.log(isBig, "isBig>>>>>>"); 25 | if (isBig) { 26 | code = "xxx1"; 27 | console.log("code 获取成功>>>>>>"); 28 | if (firstResolve) { 29 | return firstResolve(); 30 | } 31 | resolve(); 32 | } else { 33 | if (currentLoginTime++ < MAX_TIME) { 34 | console.log(`code 获取失败, 准备第${currentLoginTime}次调用`); 35 | silentLogin({ firstResolve: firstResolve || resolve }); 36 | } else { 37 | console.log("code 最终还是失败"); 38 | reject(); 39 | } 40 | } 41 | }, 1000); 42 | }); 43 | }; 44 | 45 | const request = (params = {}) => { 46 | const { callback } = params; 47 | return new Promise((resolve, reject) => { 48 | if (isSilentLoginIng) { 49 | return addSubscriber(() => { 50 | request({ 51 | ...params, 52 | temp: "这是队列里面的接口,我只调用过一次", 53 | callback: resolve, 54 | }); 55 | }); 56 | } 57 | 58 | // 模拟 request 59 | setTimeout(() => { 60 | console.log(params.name, "执行第一次"); 61 | if (!code) { 62 | addSubscriber(() => { 63 | request({ 64 | ...params, 65 | temp: `${params.name}第二次执行`, 66 | callback: resolve, 67 | }); 68 | }); 69 | 70 | if (!isSilentLoginIng) { 71 | isSilentLoginIng = true; 72 | silentLogin() 73 | .then(() => { 74 | isSilentLoginIng = false; 75 | console.log("code 成功获取了,开始执行队列请求>>>>>>"); 76 | // 依次去执行缓存的接口 77 | onAccessTokenFetched(); 78 | }) 79 | .catch(() => { 80 | console.log(11212, "11212>>>>>>"); 81 | }); 82 | } 83 | return; 84 | } else { 85 | // 有 callback 说明是队列里面的请求, 直接 callback 返回 86 | if (callback) { 87 | return callback({ ...params }); 88 | } 89 | resolve(params); 90 | } 91 | }, 100); 92 | }); 93 | }; 94 | 95 | // 1 2 模拟小程序上来就调用的接口 96 | request({ name: "接口1" }).then((res) => { 97 | console.log(res, ">>>>>>"); 98 | }); 99 | 100 | request({ name: "接口2" }).then((res) => { 101 | console.log(res, ">>>>>>"); 102 | }); 103 | 104 | // 模拟用户 操作调用的接口,但是这个时候 code 身份验证 接口还没有响应完成 105 | setTimeout(() => { 106 | request({ name: "接口3" }).then((res) => { 107 | console.log(res, ">>>>>>"); 108 | }); 109 | }, 400); 110 | 111 | // 模拟 code 身份校验完成后调用的接口 112 | setTimeout(() => { 113 | request({ name: "接口4" }).then((res) => { 114 | console.log(res, ">>>>>>"); 115 | }); 116 | }, 2000); 117 | -------------------------------------------------------------------------------- /小程序/uniapp.json: -------------------------------------------------------------------------------- 1 | uniapp XBuilder 的数据配置 2 | { 3 | "editor.codeassist.px2upx.proportion": "0.5", 4 | "editor.colorScheme": "Monokai", 5 | "editor.tabSize": 2, 6 | "editor.tokenColorCustomizations": { 7 | "[Monokai]": { 8 | "rules": [ 9 | { 10 | "scope": ["variable"], 11 | "settings": { 12 | "foreground": "#9cdcfe" 13 | } 14 | }, 15 | { 16 | "scope": ["storage.type"], 17 | "settings": { 18 | "fontStyle": "italic ", 19 | "foreground": "#569cd6" 20 | } 21 | }, 22 | { 23 | "scope": ["keyword"], 24 | "settings": { 25 | "foreground": "#c586c0" 26 | } 27 | }, 28 | { 29 | "scope": ["entity.name.function"], 30 | "settings": { 31 | "foreground": "#dcdcaa" 32 | } 33 | }, 34 | { 35 | "scope": ["variable.parameter"], 36 | "settings": { 37 | "fontStyle": "italic ", 38 | "foreground": "#9cdcfe" 39 | } 40 | }, 41 | { 42 | "scope": ["string"], 43 | "settings": { 44 | "foreground": "#ce9178" 45 | } 46 | }, 47 | { 48 | "scope": ["constant.numeric"], 49 | "settings": { 50 | "foreground": "#b5cea8" 51 | } 52 | }, 53 | { 54 | "scope": ["comment"], 55 | "settings": { 56 | "foreground": "#6a9955" 57 | } 58 | }, 59 | { 60 | "scope": [ 61 | "punctuation.definition.template-expression", 62 | "punctuation.section.embedded" 63 | ], 64 | "settings": { 65 | "foreground": "#569cd6" 66 | } 67 | }, 68 | { 69 | "scope": ["entity.other.attribute-name"], 70 | "settings": { 71 | "foreground": "#d7ba7d" 72 | } 73 | }, 74 | { 75 | "scope": ["entity.name.tag"], 76 | "settings": { 77 | "foreground": "#569cde" 78 | } 79 | } 80 | ] 81 | } 82 | }, 83 | "explorer.iconTheme": "vs-seti", 84 | "weApp.devTools.path": "D:/微信web开发者工具", 85 | "workbench.colorCustomizations": { 86 | "[Default]": {} 87 | }, 88 | "editor.contentAssistSelectionMode": "Alt+数字模式" 89 | } 90 | -------------------------------------------------------------------------------- /算法/DFC-BFC.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 98 | 99 | -------------------------------------------------------------------------------- /算法/heapSort.md: -------------------------------------------------------------------------------- 1 | **堆排序** 2 | 3 |   堆排序是利用**堆**这种数据结构而设计的一种排序算法,堆排序是一种**选择排序,**它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。首先简单了解下堆结构。 4 | 5 | **堆** 6 | 7 |   **堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。如下图:** 8 | 9 | ![img](https://images2015.cnblogs.com/blog/1024555/201612/1024555-20161217182750011-675658660.png) 10 | 11 | 同时,我们对堆中的结点按层进行编号,将这种逻辑结构映射到数组中就是下面这个样子 12 | 13 | ![img](https://images2015.cnblogs.com/blog/1024555/201612/1024555-20161217182857323-2092264199.png) 14 | 15 | 该数组从逻辑上讲就是一个堆结构,我们用简单的公式来描述一下堆的定义就是: 16 | 17 | **大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]** 18 | 19 | **小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]** 20 | 21 | ok,了解了这些定义。接下来,我们来看看堆排序的基本思想及基本步骤: 22 | 23 | # 堆排序基本思想及步骤 24 | 25 | >   **堆排序的基本思想是:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了** 26 | 27 | **步骤一 构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。** 28 | 29 |   a.假设给定无序序列结构如下 30 | 31 | ![img](https://images2015.cnblogs.com/blog/1024555/201612/1024555-20161217192038651-934327647.png) 32 | 33 | 2.此时我们从最后一个非叶子结点开始(叶结点自然不用调整,第一个非叶子结点 arr.length/2-1=5/2-1=1,也就是下面的6结点),从左至右,从下至上进行调整。 34 | 35 | ![img](https://images2015.cnblogs.com/blog/1024555/201612/1024555-20161217192209433-270379236.png) 36 | 37 | 4.找到第二个非叶节点4,由于[4,9,8]中9元素最大,4和9交换。 38 | 39 | ![img](https://images2015.cnblogs.com/blog/1024555/201612/1024555-20161217192854636-1823585260.png) 40 | 41 | 这时,交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中6最大,交换4和6。 42 | 43 | ![img](https://images2015.cnblogs.com/blog/1024555/201612/1024555-20161217193347886-1142194411.png) 44 | 45 | 此时,我们就将一个无需序列构造成了一个大顶堆。 46 | 47 | **步骤二 将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。** 48 | 49 | a.将堆顶元素9和末尾元素4进行交换 50 | 51 | ![img](https://images2015.cnblogs.com/blog/1024555/201612/1024555-20161217194207620-1455153342.png) 52 | 53 | b.重新调整结构,使其继续满足堆定义 54 | 55 | ![img](https://images2015.cnblogs.com/blog/1024555/201612/1024555-20161218153110495-1280388728.png) 56 | 57 | c.再将堆顶元素8与末尾元素5进行交换,得到第二大元素8. 58 | 59 | ![img](https://images2015.cnblogs.com/blog/1024555/201612/1024555-20161218152929339-1114983222.png) 60 | 61 | 后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序 62 | 63 | ![img](https://images2015.cnblogs.com/blog/1024555/201612/1024555-20161218152348229-935654830.png) 64 | 65 | 再简单总结下堆排序的基本思路: 66 | 67 |   **a.将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;** 68 | 69 |   **b.将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;** 70 | 71 |   **c.重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。** 72 | 73 | # 代码实现 74 | 75 | ```javascript 76 | var len; // 因为声明的多个函数都需要数据长度,所以把len设置成为全局变量 77 | 78 | function buildMaxHeap(arr) { // 建立大顶堆 79 | len = arr.length; 80 | for (var i = Math.floor(len/2); i >= 0; i--) { 81 | heapify(arr, i); 82 | } 83 | } 84 | 85 | function heapify(arr, i) { // 堆调整 86 | var left = 2 * i + 1, 87 | right = 2 * i + 2, 88 | largest = i; 89 | 90 | if (left < len && arr[left] > arr[largest]) { 91 | largest = left; 92 | } 93 | 94 | if (right < len && arr[right] > arr[largest]) { 95 | largest = right; 96 | } 97 | 98 | if (largest != i) { 99 | swap(arr, i, largest); 100 | heapify(arr, largest); 101 | } 102 | } 103 | 104 | function swap(arr, i, j) { 105 | var temp = arr[i]; 106 | arr[i] = arr[j]; 107 | arr[j] = temp; 108 | } 109 | 110 | function heapSort(arr) { 111 | buildMaxHeap(arr); 112 | 113 | for (var i = arr.length-1; i > 0; i--) { 114 | swap(arr, 0, i); 115 | len--; 116 | heapify(arr, 0); 117 | } 118 | return arr; 119 | } 120 | ``` 121 | [图解排序算法](https://www.cnblogs.com/chengxiao/p/6129630.html) 122 | [github 各类语言算法](https://github.com/hustcc/JS-Sorting-Algorithm/blob/master/7.heapSort.md) -------------------------------------------------------------------------------- /算法/js实现超长文字省略.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 20 | 21 |
22 | 23 | 零一二三四五六七八九以而山斯吴留起吧就 24 | 零一二三四五六七八九以而山斯吴留起吧就 25 | 零一二三四五六七八九以而山斯吴留起吧就 26 | 零一二三四五六七八九以而山斯吴留起吧就 27 | 零一二三四五六七八九以而山斯吴留起吧就 28 | 29 | 30 |
31 | 32 | 96 | 97 | -------------------------------------------------------------------------------- /算法/函数柯里化.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 |

请实现一个 add 函数,满足以下功能

13 |

add(1); // 1

14 |

add(1)(2); // 3

15 |

add(1)(2)(3); // 6

16 |

add(1)(2, 3); // 6

17 |

add(1, 2)(3); // 6

18 |

add(1, 2, 3); // 6

19 | 20 | 37 | 38 | -------------------------------------------------------------------------------- /算法/堆排序.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 57 | 58 | -------------------------------------------------------------------------------- /算法/快排.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 32 | 33 | -------------------------------------------------------------------------------- /算法/语法高亮.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | auto complete 8 | 17 | 18 | 19 | 20 |
21 |
    22 |
    23 | 24 | 74 | -------------------------------------------------------------------------------- /算法/黄金问题.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 |

    题目

    13 |

    有一个国家发现了5座金矿,每座金矿的黄金储量不同,

    14 |

    需要参与挖掘的工人数也不同。参与挖矿工人的总数是10人。

    15 |

    每座金矿要么全挖,要么不挖(不能派出一半人挖取一半金矿)。 16 |

    要求用程序求解出,要想得到尽可能多的黄金,应该选择挖取哪几座金矿?

    17 |

    金矿1 400金 / 5人

    18 |

    金矿2 500金 / 5人

    19 |

    金矿3 200金 / 3人

    20 |

    金矿4 300金 / 4人

    21 |

    金矿5 350金 / 3人

    22 |
    23 |

    24 |

    我们设:金矿数 n,工人数 w, 黄金量为数组 g[], 金矿用工量为数组 P[],

    25 | 26 | 53 | 54 | -------------------------------------------------------------------------------- /输入URL后发生了什么.md: -------------------------------------------------------------------------------- 1 | 第一步:浏览器输入域名 2 | 3 | 例如输入:www.baidu.com 4 | 5 | 第二步:浏览器查找域名的IP地址 6 | 7 | 浏览器会把输入的域名解析成对应的IP,其过程如下: 8 | 9 | 1.查找浏览器缓存:因为浏览器一般会缓存DNS记录一段时间,不同浏览器的时间可能不一样,一般2-30分钟不等,浏览器去查找这些缓存,如果有缓存,直接返回IP,否则下一步。 10 | 11 | 2.查找系统缓存:浏览器缓存中找不到IP之后,浏览器会进行系统调用(windows中是gethostbyname),查找本机的hosts文件,如果找到,直接返回IP,否则下一步。 12 | 13 | 3.查找路由器缓存:如果1,2步都查询无果,则需要借助网络,路由器一般都有自己的DNS缓存,将前面的请求发给路由器,查找ISP 服务商缓存 DNS的服务器,如果查找到IP则直接返回,没有的话继续查找。 14 | 15 | 4.递归查询:如果以上步骤还找不到,则ISP的DNS服务器就会进行递归查询,所谓递归查询就是如果主机所询问的本地域名服务器不知道被查询域名的IP地址,那么本地域名服务器就以DNS客户的身份,向其他根域名服务器继续发出查询请求报文,而不是让该主机自己进行下一步查询。(本地域名服务器地址是通过DHPC协议获取地址,DHPC是负责分配IP地址的) 16 | 17 | 5.迭代查询:本地域名服务器采用迭代查询,它先向一个根域名服务器查询。本地域名服务器向根域名服务器的查询一般都是采用迭代查询。所谓迭代查询就是当根域名服务器收到本地域名服务器发出的查询请求报文后,要么告诉本地域名服务器下一步应该查询哪一个域名服务器,然后本地域名服务器自己进行后续的查询。(而不是替代本地域名服务器进行后续查询)。 18 | 19 | 本例子中:根域名服务器告诉本地域名服务器,下一次应查询的顶级域名服务器dns.net的IP地址。本地域名服务器向顶级域名服务器dns.net进行查询。顶级域名服务器dns.net告诉本地域名服务器,下一次应查询的权限域名服务器dns.csdn.net的IP地址。本地域名服务器向权限域名服务器dns.csdn.net进行查询。权限域名服务器dns.csdn.net告诉本地域名服务器,所查询的主机www.csdn.net的IP地址。本地域名服务器最后把结果告诉主机。 20 | 21 | 第三步 :浏览器与目标服务器建立TCP连接 22 | 23 | 1.      主机浏览器通过DNS解析得到了目标服务器的IP地址后,与服务器建立TCP连接。 24 | 25 | 2.      TCP3次握手连接:浏览器所在的客户机向服务器发出连接请求报文(SYN标志为1);服务器接收报文后,同意建立连接,向客户机发出确认报文(SYN,ACK标志位均为1);客户机接收到确认报文后,再次向服务器发出报文,确认已接收到确认报文;此处客户机与服务器之间的TCP连接建立完成,开始通信。 26 | 27 | 第四步:浏览器通过http协议发送请求 28 | 29 | 浏览器向主机发起一个HTTP-GET方法报文请求。请求中包含访问的URL,也就是http://www.csdn.com/ ,KeepAlive,长连接,还有User-Agent用户浏览器操作系统信息,编码等。值得一提的是Accep-Encoding和Cookies项。Accept-Encoding一般采用gzip,压缩之后传输html文件。Cookies如果是首次访问,会提示服务器建立用户缓存信息,如果不是,可以利用Cookies对应键值,找到相应缓存,缓存里面存放着用户名,密码和一些用户设置项。 30 | 31 | 第五步:某些服务会做永久重定向响应 32 | 33 | 对于大型网站存在多个主机站点,了负载均衡或者导入流量,提高SEO排名,往往不会直接返回请求页面,而是重定向。返回的状态码就不是200OK,而是301,302以3开头的重定向码,浏览器在获取了重定向响应后,在响应报文中Location项找到重定向地址,浏览器重新第一步访问即可。 34 | 35 | 重定向的作用:重定向是为了负载均衡或者导入流量,提高SEO排名。利用一个前端服务器接受请求,然后负载到不同的主机上,可以大大提高站点的业务并发处理能力;重定向也可将多个域名的访问,集中到一个站点;由于baidu.com,www.baidu.com会被搜索引擎认为是两个网站,照成每个的链接数都会减少从而降低排名,永久重定向会将两个地址关联起来,搜索引擎会认为是同一个网站,从而提高排名。 36 | 37 | 第六步:浏览器跟踪重定向地址 38 | 39 | 当浏览器知道了重定向后最终的访问地址之后,重新发送一个http请求,发送内容同上。 40 | 41 | 第七步:服务器处理请求 42 | 43 | 服务器接收到获取请求,然后处理并返回一个响应。 44 | 45 | 第八步:服务器发出一个HTML响应 46 | 47 | 返回状态码200 OK,表示服务器可以响应请求,返回报文,由于在报头中Content-type为“text/html”,浏览器以HTML形式呈现,而不是下载文件。 48 | 49 | 第九步:释放TCP连接 50 | 51 | 1.     浏览器所在主机向服务器发出连接释放报文,然后停止发送数据; 52 | 53 | 2.     服务器接收到释放报文后发出确认报文,然后将服务器上未传送完的数据发送完; 54 | 55 | 3.     服务器数据传输完毕后,向客户机发送连接释放报文; 56 | 57 | 4.     客户机接收到报文后,发出确认,然后等待一段时间后,释放TCP连接; 58 | 59 | 第十步:浏览器显示页面 60 | 61 | 在浏览器没有完整接受全部HTML文档时,它就已经开始显示这个页面了,浏览器接收到返回的数据包,根据浏览器的渲染机制对相应的数据进行渲染。渲染后的数据,进行相应的页面呈现和脚步的交互。 62 | 63 | 第十一步:浏览器发送获取嵌入在HTML中的其他内容 64 | 65 | 比如一些样式文件,图片url,js文件url等,浏览器会通过这些url重新发送请求,请求过程依然是HTML读取类似的过程,查询域名,发送请求,重定向等。不过这些静态文件是可以缓存到浏览器中的,有时访问这些文件不需要通过服务器,直接从缓存中取。某些网站也会使用第三方CDN进行托管这些静态文件。 66 | 原文链接:https://blog.csdn.net/jiao_0509/article/details/82491299 -------------------------------------------------------------------------------- /链表.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 | 77 | 78 | --------------------------------------------------------------------------------