├── .gitattributes ├── .gitignore ├── README.md ├── cache-request ├── README.md ├── axios.min.js ├── cacheRequest.js ├── index.html ├── index.js ├── package.json └── yarn.lock ├── data-structure ├── .gitignore ├── lib │ ├── parse-tree.js │ └── tree-node.js ├── package.json ├── src │ ├── kmp.js │ └── travel-binary-tree.js ├── test │ ├── kmp.test.js │ └── travel-binary-tree.test.js └── yarn.lock ├── promise └── promise.js ├── uploader ├── .gitignore ├── README.md ├── index.html ├── index.js ├── package.json ├── static │ └── .gitkeep ├── uploader.js └── yarn.lock └── vue2-reactivity-data ├── index.html ├── observe.js ├── vue.js └── watcher.js /.gitattributes: -------------------------------------------------------------------------------- 1 | * linguist-language=none -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | */node_modules -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## blog 2 | 3 | > 记录各种原理体会,代码实践= 4 | 5 | #### 原生 Javascript 6 | - [浏览器的事件循环与微任务,摘自 MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide) 7 | - [模拟实现new、bind、call和apply](https://github.com/impeiran/Blog/issues/1) 8 | - [模拟实现instanceof并谈其原理](https://github.com/impeiran/Blog/issues/5) 9 | - [模拟实现Promise(A+规范)](https://github.com/impeiran/Blog/tree/master/promise/promise.js) 10 | 11 | #### 理论 / 原理 12 | 13 | - [浏览器输入URL后发生什么(详细整理)](https://github.com/impeiran/Blog/issues/3) 14 | - [Webpack打包流程构建原理](https://github.com/impeiran/Blog/issues/6) 15 | - [vue2.x 响应式原理及依赖收集的简单实现](https://github.com/impeiran/Blog/issues/8) 16 | 17 | #### 实践 / 业务 18 | 19 | - [从设计到实现一个Uploader基础类](https://github.com/impeiran/Blog/tree/master/uploader) 20 | - [实现cacheRequest(),相同资源ajax只发一次请求](https://github.com/impeiran/Blog/tree/master/cache-request) 21 | - [谈谈H5中的audio的播放问题](https://github.com/impeiran/Blog/issues/4) 22 | 23 | #### 数据结构与算法 24 | 25 | - [二叉树遍历:前/中/后/层次遍历](https://github.com/impeiran/Blog/blob/master/data-structure/src/travel-binary-tree.js) 26 | - [字符串匹配:KMP](https://github.com/impeiran/Blog/blob/master/data-structure/src/kmp.js) 27 | 28 | #### 设计模式 29 | 30 | - [单例模式](https://github.com/impeiran/Blog/issues/7) 31 | - [观察者模式VS发布订阅模式](https://github.com/impeiran/Blog/issues/2) 32 | 33 | #### 工具资源 34 | 35 | - [漫画风格的在线画图站点 - https://excalidraw.com/](https://excalidraw.com/) 36 | -------------------------------------------------------------------------------- /cache-request/README.md: -------------------------------------------------------------------------------- 1 | 最近碰到的一道笔试题,题目大概如下: 2 | 3 | >请实现一个cacheRequest方法,保证当前ajax请求相同资源时,真实网络层中,实际只发出一次请求(假设已经存在request方法用于封装ajax请求) 4 | 5 | 一开始看到第一感觉是,设个Http Header`cache-control`,把`expire`调大,不就自然会找浏览器缓存拿了。但是看到后面说提供自带的`request`方法和只发起一次ajax。那估计是想让笔者在业务层自己用代码解决这个cache问题。 6 | 7 | 接下来,我们抛开实际场景价值,思考下怎样实现。 8 | 9 | 一般我们很简单的就可以得出以下思路: 10 | 11 | - 利用闭包或者模块化的设计,引用一个`Map`,存储对应的缓存数据。 12 | - 每次请求检查缓存,有则返回缓存数据,无则发起请求。 13 | - 请求成功后,再保存缓存数据并返回,请求失败则不缓存。 14 | 15 | 然后我们一般会写出下面的代码: 16 | 17 | ```javascript 18 | // 构建Map,用作缓存数据 19 | const dict = new Map() 20 | // 这里简单的把url作为cacheKey 21 | const cacheRquest = (url) => { 22 | if (dict.has(url)) { 23 | return Promise.resolve(dict.get(url)) 24 | } else { 25 | // 无缓存,发起真实请求,成功后写入缓存 26 | return request(url).then(res => { 27 | dict.set(url, res) 28 | return res 29 | }).catch(err => Promise.reject(err)) 30 | } 31 | } 32 | ``` 33 | 34 | **写到这里,你以为这篇文章就这么容易的结束了吗~~** 35 | 36 | 当然不是,我觉得还有一个小坑: 37 | 38 | **有这么一个小概率边缘情况,并发两个或多个相同资源请求时,第一个请求处于`pending`的情况下,实际上后面的请求依然会发起,不完全符合题意。** 39 | 40 | 因此,我们再重新设计出以下逻辑: 41 | 42 | 1. 当第一个请求处于`pending`时,设一个状态值锁住,后面并发的`cacheRequest`识别到`pending`时,则不发起请求,封装成异步操作,并塞进队列。 43 | 2. 当请求响应后,取出队列的异步`resolve`,把响应数据广播到每一个异步操作里。 44 | 3. 当发生请求错误时,同理:广播错误信息到每一个异步操作。 45 | 4. 之后的异步`cacheRequest`则正常的取出success的响应数据。 46 | 47 | 至此,并发的请求只要第一个返回成功了,后边都会马上响应,且真实只发起一个ajax请求。 48 | 49 | 首先,定义好我们缓存数据的`schema`,称为`cacheInfo`以此存入我们的`Map`里 50 | 51 | ```javascript 52 | { 53 | status: 'PENDING', // ['PENDING', 'SUCCESS', 'FAIL'] 54 | response: {}, // 响应数据 55 | resolves: [], // 成功的异步队列 56 | rejects: [] // 失败的异步队列 57 | } 58 | ``` 59 | 60 | 函数的主体我们梳理下主干的逻辑: 61 | 62 | - 额外的加多一个`option`,参数可传入自定义的cacheKey 63 | - 真实请求的`handleRequest`逻辑单独进行封装,因为不止一处用到,我们下面再单独实现。 64 | 65 | ```javascript 66 | const dict = new Map() 67 | 68 | const cacheRequest = function (target, option = {}) { 69 | const cacheKey = option.cacheKey || target 70 | 71 | const cacheInfo = dict.get(cacheKey) 72 | // 无缓存时,发起真实请求并返回 73 | if (!cacheInfo) { 74 | return handleRequest(target, cacheKey) 75 | } 76 | 77 | const status = cacheInfo.status 78 | // 已缓存成功数据,则返回 79 | if (status === 'SUCCESS') { 80 | return Promise.resolve(cacheInfo.response) 81 | } 82 | // 缓存正在PENDING时,封装单独异步操作,加入队列 83 | if (status === 'PENDING') { 84 | return new Promise((resolve, reject) => { 85 | cacheInfo.resolves.push(resolve) 86 | cacheInfo.rejects.push(reject) 87 | }) 88 | } 89 | // 缓存的请求失败时,重新发起真实请求 90 | return handleRequest(target, cacheKey) 91 | } 92 | ``` 93 | 94 | 接下来,就是发起真实请求的`handleRequest`,我们把改写`status`的操作和写入`cacheInfo`的操作一并封装在里面。当中抽离出两个公共函数:写入缓存的`setCache`和用于广播异步操作的`notify`。 95 | 96 | **首先是setCache**,逻辑非常简单,浅合并原来的`cacheInfo`并写入 97 | 98 | ```javascript 99 | // ... dict = new Map() 100 | 101 | const setCache = (cacheKey, info) => { 102 | dict.set(cacheKey, { 103 | ...(dict.get(cacheKey) || {}), 104 | ...info 105 | }) 106 | } 107 | ``` 108 | 109 | **接下来是handleRequest**:改写状态锁,发起真实请求,进行响应成功和失败后的广播操作 110 | 111 | ```javascript 112 | const handleRequest = (url, cacheKey) => { 113 | setCache(cacheKey, { 114 | status: 'PENDING', 115 | resolves: [], 116 | rejects: [] 117 | }) 118 | 119 | const ret = request(url) 120 | 121 | return ret.then(res => { 122 | // 返回成功,刷新缓存,广播并发队列 123 | setCache(cacheKey, { 124 | status: 'SUCCESS', 125 | response: res 126 | }) 127 | notify(cacheKey, res) 128 | return Promise.resolve(res) 129 | }).catch(err => { 130 | // 返回失败,刷新缓存,广播错误信息 131 | setCache(cacheKey, { status: 'FAIL' }) 132 | notify(cacheKey, err) 133 | return Promise.reject(err) 134 | }) 135 | } 136 | ``` 137 | 138 | **最后是广播函数notify**的实现,取出队列,逐个广播然后清空 139 | 140 | ```javascript 141 | // ... dict = new Map() 142 | 143 | const notify = (cacheKey, value) => { 144 | const info = dict.get(cacheKey) 145 | 146 | let queue = [] 147 | 148 | if (info.status === 'SUCCESS') { 149 | queue = info.resolves 150 | } else if (info.status === 'FAIL') { 151 | queue = info.rejects 152 | } 153 | 154 | while(queue.length) { 155 | const cb = queue.shift() 156 | cb(value) 157 | } 158 | 159 | setCache(cacheKey, { resolves: [], rejects: [] }) 160 | } 161 | ``` 162 | 163 | 接下来,就是紧张又刺激的测试环节,测试代码以截图形式呈现 164 | 165 | - 服务端用express简单搭建一下,构造一个延时2秒的接口测试并发时的情况 166 | - 客户端request采用axios代替,构造并发的请求与单独的请求。 167 | 168 | 服务端代码: 169 | ![](https://user-gold-cdn.xitu.io/2020/3/20/170f6e8704b84848?w=1100&h=752&f=png&s=134698) 170 | 171 | 客户端代码: 172 | 173 | ![](https://user-gold-cdn.xitu.io/2020/3/20/170f6ea0a23b4d58?w=1236&h=1114&f=png&s=265729) 174 | 175 | 效果预览: 176 | 177 | ![](https://user-gold-cdn.xitu.io/2020/3/20/170f6f3afcc45492?w=480&h=395&f=gif&s=4465610) 178 | 179 | **拓展与总结** 180 | 1. 测试与函数源代码已放到个人[Github仓库](https://github.com/impeiran/Blog/tree/master/cacheRequest) 181 | 2. 虽然可能会有人觉得笔者钻了牛角尖,但如果从实现一个库来说是必须得考虑到多种情况的。 182 | 3. 函数还有更多可拓展完善的地方,例如:设置expire的cache过期时间,自定义设置request等等; -------------------------------------------------------------------------------- /cache-request/axios.min.js: -------------------------------------------------------------------------------- 1 | /* axios v0.19.2 | (c) 2020 by Matt Zabriskie */ 2 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.axios=t():e.axios=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return e[r].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){e.exports=n(1)},function(e,t,n){"use strict";function r(e){var t=new s(e),n=i(s.prototype.request,t);return o.extend(n,s.prototype,t),o.extend(n,t),n}var o=n(2),i=n(3),s=n(4),a=n(22),u=n(10),c=r(u);c.Axios=s,c.create=function(e){return r(a(c.defaults,e))},c.Cancel=n(23),c.CancelToken=n(24),c.isCancel=n(9),c.all=function(e){return Promise.all(e)},c.spread=n(25),e.exports=c,e.exports.default=c},function(e,t,n){"use strict";function r(e){return"[object Array]"===j.call(e)}function o(e){return"undefined"==typeof e}function i(e){return null!==e&&!o(e)&&null!==e.constructor&&!o(e.constructor)&&"function"==typeof e.constructor.isBuffer&&e.constructor.isBuffer(e)}function s(e){return"[object ArrayBuffer]"===j.call(e)}function a(e){return"undefined"!=typeof FormData&&e instanceof FormData}function u(e){var t;return t="undefined"!=typeof ArrayBuffer&&ArrayBuffer.isView?ArrayBuffer.isView(e):e&&e.buffer&&e.buffer instanceof ArrayBuffer}function c(e){return"string"==typeof e}function f(e){return"number"==typeof e}function p(e){return null!==e&&"object"==typeof e}function d(e){return"[object Date]"===j.call(e)}function l(e){return"[object File]"===j.call(e)}function h(e){return"[object Blob]"===j.call(e)}function m(e){return"[object Function]"===j.call(e)}function y(e){return p(e)&&m(e.pipe)}function g(e){return"undefined"!=typeof URLSearchParams&&e instanceof URLSearchParams}function v(e){return e.replace(/^\s*/,"").replace(/\s*$/,"")}function x(){return("undefined"==typeof navigator||"ReactNative"!==navigator.product&&"NativeScript"!==navigator.product&&"NS"!==navigator.product)&&("undefined"!=typeof window&&"undefined"!=typeof document)}function w(e,t){if(null!==e&&"undefined"!=typeof e)if("object"!=typeof e&&(e=[e]),r(e))for(var n=0,o=e.length;n=200&&e<300}};u.headers={common:{Accept:"application/json, text/plain, */*"}},i.forEach(["delete","get","head"],function(e){u.headers[e]={}}),i.forEach(["post","put","patch"],function(e){u.headers[e]=i.merge(a)}),e.exports=u},function(e,t,n){"use strict";var r=n(2);e.exports=function(e,t){r.forEach(e,function(n,r){r!==t&&r.toUpperCase()===t.toUpperCase()&&(e[t]=n,delete e[r])})}},function(e,t,n){"use strict";var r=n(2),o=n(13),i=n(5),s=n(16),a=n(19),u=n(20),c=n(14);e.exports=function(e){return new Promise(function(t,f){var p=e.data,d=e.headers;r.isFormData(p)&&delete d["Content-Type"];var l=new XMLHttpRequest;if(e.auth){var h=e.auth.username||"",m=e.auth.password||"";d.Authorization="Basic "+btoa(h+":"+m)}var y=s(e.baseURL,e.url);if(l.open(e.method.toUpperCase(),i(y,e.params,e.paramsSerializer),!0),l.timeout=e.timeout,l.onreadystatechange=function(){if(l&&4===l.readyState&&(0!==l.status||l.responseURL&&0===l.responseURL.indexOf("file:"))){var n="getAllResponseHeaders"in l?a(l.getAllResponseHeaders()):null,r=e.responseType&&"text"!==e.responseType?l.response:l.responseText,i={data:r,status:l.status,statusText:l.statusText,headers:n,config:e,request:l};o(t,f,i),l=null}},l.onabort=function(){l&&(f(c("Request aborted",e,"ECONNABORTED",l)),l=null)},l.onerror=function(){f(c("Network Error",e,null,l)),l=null},l.ontimeout=function(){var t="timeout of "+e.timeout+"ms exceeded";e.timeoutErrorMessage&&(t=e.timeoutErrorMessage),f(c(t,e,"ECONNABORTED",l)),l=null},r.isStandardBrowserEnv()){var g=n(21),v=(e.withCredentials||u(y))&&e.xsrfCookieName?g.read(e.xsrfCookieName):void 0;v&&(d[e.xsrfHeaderName]=v)}if("setRequestHeader"in l&&r.forEach(d,function(e,t){"undefined"==typeof p&&"content-type"===t.toLowerCase()?delete d[t]:l.setRequestHeader(t,e)}),r.isUndefined(e.withCredentials)||(l.withCredentials=!!e.withCredentials),e.responseType)try{l.responseType=e.responseType}catch(t){if("json"!==e.responseType)throw t}"function"==typeof e.onDownloadProgress&&l.addEventListener("progress",e.onDownloadProgress),"function"==typeof e.onUploadProgress&&l.upload&&l.upload.addEventListener("progress",e.onUploadProgress),e.cancelToken&&e.cancelToken.promise.then(function(e){l&&(l.abort(),f(e),l=null)}),void 0===p&&(p=null),l.send(p)})}},function(e,t,n){"use strict";var r=n(14);e.exports=function(e,t,n){var o=n.config.validateStatus;!o||o(n.status)?e(n):t(r("Request failed with status code "+n.status,n.config,null,n.request,n))}},function(e,t,n){"use strict";var r=n(15);e.exports=function(e,t,n,o,i){var s=new Error(e);return r(s,t,n,o,i)}},function(e,t){"use strict";e.exports=function(e,t,n,r,o){return e.config=t,n&&(e.code=n),e.request=r,e.response=o,e.isAxiosError=!0,e.toJSON=function(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:this.config,code:this.code}},e}},function(e,t,n){"use strict";var r=n(17),o=n(18);e.exports=function(e,t){return e&&!r(t)?o(e,t):t}},function(e,t){"use strict";e.exports=function(e){return/^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(e)}},function(e,t){"use strict";e.exports=function(e,t){return t?e.replace(/\/+$/,"")+"/"+t.replace(/^\/+/,""):e}},function(e,t,n){"use strict";var r=n(2),o=["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"];e.exports=function(e){var t,n,i,s={};return e?(r.forEach(e.split("\n"),function(e){if(i=e.indexOf(":"),t=r.trim(e.substr(0,i)).toLowerCase(),n=r.trim(e.substr(i+1)),t){if(s[t]&&o.indexOf(t)>=0)return;"set-cookie"===t?s[t]=(s[t]?s[t]:[]).concat([n]):s[t]=s[t]?s[t]+", "+n:n}}),s):s}},function(e,t,n){"use strict";var r=n(2);e.exports=r.isStandardBrowserEnv()?function(){function e(e){var t=e;return n&&(o.setAttribute("href",t),t=o.href),o.setAttribute("href",t),{href:o.href,protocol:o.protocol?o.protocol.replace(/:$/,""):"",host:o.host,search:o.search?o.search.replace(/^\?/,""):"",hash:o.hash?o.hash.replace(/^#/,""):"",hostname:o.hostname,port:o.port,pathname:"/"===o.pathname.charAt(0)?o.pathname:"/"+o.pathname}}var t,n=/(msie|trident)/i.test(navigator.userAgent),o=document.createElement("a");return t=e(window.location.href),function(n){var o=r.isString(n)?e(n):n;return o.protocol===t.protocol&&o.host===t.host}}():function(){return function(){return!0}}()},function(e,t,n){"use strict";var r=n(2);e.exports=r.isStandardBrowserEnv()?function(){return{write:function(e,t,n,o,i,s){var a=[];a.push(e+"="+encodeURIComponent(t)),r.isNumber(n)&&a.push("expires="+new Date(n).toGMTString()),r.isString(o)&&a.push("path="+o),r.isString(i)&&a.push("domain="+i),s===!0&&a.push("secure"),document.cookie=a.join("; ")},read:function(e){var t=document.cookie.match(new RegExp("(^|;\\s*)("+e+")=([^;]*)"));return t?decodeURIComponent(t[3]):null},remove:function(e){this.write(e,"",Date.now()-864e5)}}}():function(){return{write:function(){},read:function(){return null},remove:function(){}}}()},function(e,t,n){"use strict";var r=n(2);e.exports=function(e,t){t=t||{};var n={},o=["url","method","params","data"],i=["headers","auth","proxy"],s=["baseURL","url","transformRequest","transformResponse","paramsSerializer","timeout","withCredentials","adapter","responseType","xsrfCookieName","xsrfHeaderName","onUploadProgress","onDownloadProgress","maxContentLength","validateStatus","maxRedirects","httpAgent","httpsAgent","cancelToken","socketPath"];r.forEach(o,function(e){"undefined"!=typeof t[e]&&(n[e]=t[e])}),r.forEach(i,function(o){r.isObject(t[o])?n[o]=r.deepMerge(e[o],t[o]):"undefined"!=typeof t[o]?n[o]=t[o]:r.isObject(e[o])?n[o]=r.deepMerge(e[o]):"undefined"!=typeof e[o]&&(n[o]=e[o])}),r.forEach(s,function(r){"undefined"!=typeof t[r]?n[r]=t[r]:"undefined"!=typeof e[r]&&(n[r]=e[r])});var a=o.concat(i).concat(s),u=Object.keys(t).filter(function(e){return a.indexOf(e)===-1});return r.forEach(u,function(r){"undefined"!=typeof t[r]?n[r]=t[r]:"undefined"!=typeof e[r]&&(n[r]=e[r])}),n}},function(e,t){"use strict";function n(e){this.message=e}n.prototype.toString=function(){return"Cancel"+(this.message?": "+this.message:"")},n.prototype.__CANCEL__=!0,e.exports=n},function(e,t,n){"use strict";function r(e){if("function"!=typeof e)throw new TypeError("executor must be a function.");var t;this.promise=new Promise(function(e){t=e});var n=this;e(function(e){n.reason||(n.reason=new o(e),t(n.reason))})}var o=n(23);r.prototype.throwIfRequested=function(){if(this.reason)throw this.reason},r.source=function(){var e,t=new r(function(t){e=t});return{token:t,cancel:e}},e.exports=r},function(e,t){"use strict";e.exports=function(e){return function(t){return e.apply(null,t)}}}])}); 3 | //# sourceMappingURL=axios.min.map -------------------------------------------------------------------------------- /cache-request/cacheRequest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * u need axios 3 | * 请注意此处使用axios作为请求库 4 | */ 5 | (function (global, request) { 6 | 7 | // 用于存放缓存数据 8 | const dict = new Map() 9 | 10 | const setCache = (cacheKey, info) => { 11 | dict.set(cacheKey, { 12 | ...(dict.get(cacheKey) || {}), 13 | ...info 14 | }) 15 | } 16 | 17 | const notify = (cacheKey, value) => { 18 | const info = dict.get(cacheKey) 19 | 20 | let queue = [] 21 | 22 | if (info.status === 'SUCCESS') { 23 | queue = info.resolves 24 | } else if (info.status === 'FAIL') { 25 | queue = info.rejects 26 | } 27 | 28 | while(queue.length) { 29 | const cb = queue.shift() 30 | cb(value) 31 | } 32 | 33 | setCache(cacheKey, { resolves: [], rejects: [] }) 34 | } 35 | 36 | const handleRequest = (url, cacheKey) => { 37 | setCache(cacheKey, { 38 | status: 'PENDING', 39 | resolves: [], 40 | rejects: [] 41 | }) 42 | 43 | const ret = request(url) 44 | 45 | return ret.then(res => { 46 | // 返回成功,刷新缓存,广播并发队列 47 | setCache(cacheKey, { 48 | status: 'SUCCESS', 49 | response: res 50 | }) 51 | notify(cacheKey, res) 52 | return Promise.resolve(res) 53 | }).catch(err => { 54 | // 返回失败,刷新缓存,广播错误信息 55 | setCache(cacheKey, { status: 'FAIL' }) 56 | notify(cacheKey, err) 57 | return Promise.reject(err) 58 | }) 59 | } 60 | 61 | /** 62 | * 缓存式请求 63 | * @param {String} target 请求地址 64 | * @param {Object} option 缓存的配置项 65 | * @returns {Promise} 66 | */ 67 | const cacheRequest = function (target, option = {}) { 68 | const cacheKey = option.cacheKey || target 69 | 70 | const cacheInfo = dict.get(cacheKey) 71 | 72 | if (!cacheInfo) { 73 | return handleRequest(target, cacheKey) 74 | } 75 | 76 | const status = cacheInfo.status 77 | // 已缓存成功数据,则返回 78 | if (status === 'SUCCESS') { 79 | return Promise.resolve(cacheInfo.response) 80 | } 81 | // 缓存正在PENDING时,封装单独异步操作,加入队列 82 | if (status === 'PENDING') { 83 | return new Promise((resolve, reject) => { 84 | cacheInfo.resolves.push(resolve) 85 | cacheInfo.rejects.push(reject) 86 | }) 87 | } 88 | // 缓存的请求失败时,重新发起真实请求 89 | return handleRequest(target, cacheKey) 90 | } 91 | 92 | global.cacheRequest = cacheRequest 93 | })(this, axios) -------------------------------------------------------------------------------- /cache-request/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 42 | 43 | -------------------------------------------------------------------------------- /cache-request/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const app = express() 3 | 4 | app.use(express.static(__dirname)) 5 | 6 | app.get('/success', (req, res) => { 7 | setTimeout(() => { 8 | res.send('request ok') 9 | }, 2000) 10 | }) 11 | app.get('/error', (req, res) => { 12 | setTimeout(() => { 13 | res.status(500) 14 | res.send('request error') 15 | }, 2000) 16 | }) 17 | 18 | const port = 11111 19 | app.listen(port, () => { 20 | console.log(`application is listening at http://localhost:${port}`) 21 | }) 22 | -------------------------------------------------------------------------------- /cache-request/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "nodemon index.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "express": "^4.17.1" 15 | }, 16 | "devDependencies": { 17 | "nodemon": "^2.0.2" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /cache-request/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | abbrev@1: 6 | version "1.1.1" 7 | resolved "https://registry.npm.taobao.org/abbrev/download/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" 8 | integrity sha1-+PLIh60Qv2f2NPAFtph/7TF5qsg= 9 | 10 | accepts@~1.3.7: 11 | version "1.3.7" 12 | resolved "https://registry.npm.taobao.org/accepts/download/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" 13 | integrity sha1-UxvHJlF6OytB+FACHGzBXqq1B80= 14 | dependencies: 15 | mime-types "~2.1.24" 16 | negotiator "0.6.2" 17 | 18 | ansi-align@^2.0.0: 19 | version "2.0.0" 20 | resolved "https://registry.npm.taobao.org/ansi-align/download/ansi-align-2.0.0.tgz#c36aeccba563b89ceb556f3690f0b1d9e3547f7f" 21 | integrity sha1-w2rsy6VjuJzrVW82kPCx2eNUf38= 22 | dependencies: 23 | string-width "^2.0.0" 24 | 25 | ansi-regex@^3.0.0: 26 | version "3.0.0" 27 | resolved "https://registry.npm.taobao.org/ansi-regex/download/ansi-regex-3.0.0.tgz?cache=0&sync_timestamp=1570188742860&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fansi-regex%2Fdownload%2Fansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" 28 | integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= 29 | 30 | ansi-styles@^3.2.1: 31 | version "3.2.1" 32 | resolved "https://registry.npm.taobao.org/ansi-styles/download/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" 33 | integrity sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0= 34 | dependencies: 35 | color-convert "^1.9.0" 36 | 37 | anymatch@~3.1.1: 38 | version "3.1.1" 39 | resolved "https://registry.npm.taobao.org/anymatch/download/anymatch-3.1.1.tgz?cache=0&sync_timestamp=1569898504984&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fanymatch%2Fdownload%2Fanymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" 40 | integrity sha1-xV7PAhheJGklk5kxDBc84xIzsUI= 41 | dependencies: 42 | normalize-path "^3.0.0" 43 | picomatch "^2.0.4" 44 | 45 | array-flatten@1.1.1: 46 | version "1.1.1" 47 | resolved "https://registry.npm.taobao.org/array-flatten/download/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" 48 | integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= 49 | 50 | balanced-match@^1.0.0: 51 | version "1.0.0" 52 | resolved "https://registry.npm.taobao.org/balanced-match/download/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 53 | integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= 54 | 55 | binary-extensions@^2.0.0: 56 | version "2.0.0" 57 | resolved "https://registry.npm.taobao.org/binary-extensions/download/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" 58 | integrity sha1-I8DfFPaogHf1+YbA0WfsA8PVU3w= 59 | 60 | body-parser@1.19.0: 61 | version "1.19.0" 62 | resolved "https://registry.npm.taobao.org/body-parser/download/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" 63 | integrity sha1-lrJwnlfJxOCab9Zqj9l5hE9p8Io= 64 | dependencies: 65 | bytes "3.1.0" 66 | content-type "~1.0.4" 67 | debug "2.6.9" 68 | depd "~1.1.2" 69 | http-errors "1.7.2" 70 | iconv-lite "0.4.24" 71 | on-finished "~2.3.0" 72 | qs "6.7.0" 73 | raw-body "2.4.0" 74 | type-is "~1.6.17" 75 | 76 | boxen@^1.2.1: 77 | version "1.3.0" 78 | resolved "https://registry.npm.taobao.org/boxen/download/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b" 79 | integrity sha1-VcbDmouljZxhrSLNh3Uy3rZlogs= 80 | dependencies: 81 | ansi-align "^2.0.0" 82 | camelcase "^4.0.0" 83 | chalk "^2.0.1" 84 | cli-boxes "^1.0.0" 85 | string-width "^2.0.0" 86 | term-size "^1.2.0" 87 | widest-line "^2.0.0" 88 | 89 | brace-expansion@^1.1.7: 90 | version "1.1.11" 91 | resolved "https://registry.npm.taobao.org/brace-expansion/download/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 92 | integrity sha1-PH/L9SnYcibz0vUrlm/1Jx60Qd0= 93 | dependencies: 94 | balanced-match "^1.0.0" 95 | concat-map "0.0.1" 96 | 97 | braces@~3.0.2: 98 | version "3.0.2" 99 | resolved "https://registry.npm.taobao.org/braces/download/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" 100 | integrity sha1-NFThpGLujVmeI23zNs2epPiv4Qc= 101 | dependencies: 102 | fill-range "^7.0.1" 103 | 104 | bytes@3.1.0: 105 | version "3.1.0" 106 | resolved "https://registry.npm.taobao.org/bytes/download/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" 107 | integrity sha1-9s95M6Ng4FiPqf3oVlHNx/gF0fY= 108 | 109 | camelcase@^4.0.0: 110 | version "4.1.0" 111 | resolved "https://registry.npm.taobao.org/camelcase/download/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" 112 | integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0= 113 | 114 | capture-stack-trace@^1.0.0: 115 | version "1.0.1" 116 | resolved "https://registry.npm.taobao.org/capture-stack-trace/download/capture-stack-trace-1.0.1.tgz#a6c0bbe1f38f3aa0b92238ecb6ff42c344d4135d" 117 | integrity sha1-psC74fOPOqC5Ijjstv9Cw0TUE10= 118 | 119 | chalk@^2.0.1: 120 | version "2.4.2" 121 | resolved "https://registry.npm.taobao.org/chalk/download/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" 122 | integrity sha1-zUJUFnelQzPPVBpJEIwUMrRMlCQ= 123 | dependencies: 124 | ansi-styles "^3.2.1" 125 | escape-string-regexp "^1.0.5" 126 | supports-color "^5.3.0" 127 | 128 | chokidar@^3.2.2: 129 | version "3.3.1" 130 | resolved "https://registry.npm.taobao.org/chokidar/download/chokidar-3.3.1.tgz#c84e5b3d18d9a4d77558fef466b1bf16bbeb3450" 131 | integrity sha1-yE5bPRjZpNd1WP70ZrG/FrvrNFA= 132 | dependencies: 133 | anymatch "~3.1.1" 134 | braces "~3.0.2" 135 | glob-parent "~5.1.0" 136 | is-binary-path "~2.1.0" 137 | is-glob "~4.0.1" 138 | normalize-path "~3.0.0" 139 | readdirp "~3.3.0" 140 | optionalDependencies: 141 | fsevents "~2.1.2" 142 | 143 | ci-info@^1.5.0: 144 | version "1.6.0" 145 | resolved "https://registry.npm.taobao.org/ci-info/download/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497" 146 | integrity sha1-LKINu5zrMtRSSmgzAzE/AwSx5Jc= 147 | 148 | cli-boxes@^1.0.0: 149 | version "1.0.0" 150 | resolved "https://registry.npm.taobao.org/cli-boxes/download/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143" 151 | integrity sha1-T6kXw+WclKAEzWH47lCdplFocUM= 152 | 153 | color-convert@^1.9.0: 154 | version "1.9.3" 155 | resolved "https://registry.npm.taobao.org/color-convert/download/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" 156 | integrity sha1-u3GFBpDh8TZWfeYp0tVHHe2kweg= 157 | dependencies: 158 | color-name "1.1.3" 159 | 160 | color-name@1.1.3: 161 | version "1.1.3" 162 | resolved "https://registry.npm.taobao.org/color-name/download/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" 163 | integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= 164 | 165 | concat-map@0.0.1: 166 | version "0.0.1" 167 | resolved "https://registry.npm.taobao.org/concat-map/download/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 168 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 169 | 170 | configstore@^3.0.0: 171 | version "3.1.2" 172 | resolved "https://registry.npm.taobao.org/configstore/download/configstore-3.1.2.tgz#c6f25defaeef26df12dd33414b001fe81a543f8f" 173 | integrity sha1-xvJd767vJt8S3TNBSwAf6BpUP48= 174 | dependencies: 175 | dot-prop "^4.1.0" 176 | graceful-fs "^4.1.2" 177 | make-dir "^1.0.0" 178 | unique-string "^1.0.0" 179 | write-file-atomic "^2.0.0" 180 | xdg-basedir "^3.0.0" 181 | 182 | content-disposition@0.5.3: 183 | version "0.5.3" 184 | resolved "https://registry.npm.taobao.org/content-disposition/download/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" 185 | integrity sha1-4TDK9+cnkIfFYWwgB9BIVpiYT70= 186 | dependencies: 187 | safe-buffer "5.1.2" 188 | 189 | content-type@~1.0.4: 190 | version "1.0.4" 191 | resolved "https://registry.npm.taobao.org/content-type/download/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" 192 | integrity sha1-4TjMdeBAxyexlm/l5fjJruJW/js= 193 | 194 | cookie-signature@1.0.6: 195 | version "1.0.6" 196 | resolved "https://registry.npm.taobao.org/cookie-signature/download/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" 197 | integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= 198 | 199 | cookie@0.4.0: 200 | version "0.4.0" 201 | resolved "https://registry.npm.taobao.org/cookie/download/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" 202 | integrity sha1-vrQ35wIrO21JAZ0IhmUwPr6cFLo= 203 | 204 | create-error-class@^3.0.0: 205 | version "3.0.2" 206 | resolved "https://registry.npm.taobao.org/create-error-class/download/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6" 207 | integrity sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y= 208 | dependencies: 209 | capture-stack-trace "^1.0.0" 210 | 211 | cross-spawn@^5.0.1: 212 | version "5.1.0" 213 | resolved "https://registry.npm.taobao.org/cross-spawn/download/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" 214 | integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk= 215 | dependencies: 216 | lru-cache "^4.0.1" 217 | shebang-command "^1.2.0" 218 | which "^1.2.9" 219 | 220 | crypto-random-string@^1.0.0: 221 | version "1.0.0" 222 | resolved "https://registry.npm.taobao.org/crypto-random-string/download/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" 223 | integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4= 224 | 225 | debug@2.6.9, debug@^2.2.0: 226 | version "2.6.9" 227 | resolved "https://registry.npm.taobao.org/debug/download/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 228 | integrity sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8= 229 | dependencies: 230 | ms "2.0.0" 231 | 232 | debug@^3.2.6: 233 | version "3.2.6" 234 | resolved "https://registry.npm.taobao.org/debug/download/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" 235 | integrity sha1-6D0X3hbYp++3cX7b5fsQE17uYps= 236 | dependencies: 237 | ms "^2.1.1" 238 | 239 | deep-extend@^0.6.0: 240 | version "0.6.0" 241 | resolved "https://registry.npm.taobao.org/deep-extend/download/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" 242 | integrity sha1-xPp8lUBKF6nD6Mp+FTcxK3NjMKw= 243 | 244 | depd@~1.1.2: 245 | version "1.1.2" 246 | resolved "https://registry.npm.taobao.org/depd/download/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" 247 | integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= 248 | 249 | destroy@~1.0.4: 250 | version "1.0.4" 251 | resolved "https://registry.npm.taobao.org/destroy/download/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" 252 | integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= 253 | 254 | dot-prop@^4.1.0: 255 | version "4.2.0" 256 | resolved "https://registry.npm.taobao.org/dot-prop/download/dot-prop-4.2.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdot-prop%2Fdownload%2Fdot-prop-4.2.0.tgz#1f19e0c2e1aa0e32797c49799f2837ac6af69c57" 257 | integrity sha1-HxngwuGqDjJ5fEl5nyg3rGr2nFc= 258 | dependencies: 259 | is-obj "^1.0.0" 260 | 261 | duplexer3@^0.1.4: 262 | version "0.1.4" 263 | resolved "https://registry.npm.taobao.org/duplexer3/download/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" 264 | integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= 265 | 266 | ee-first@1.1.1: 267 | version "1.1.1" 268 | resolved "https://registry.npm.taobao.org/ee-first/download/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" 269 | integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= 270 | 271 | encodeurl@~1.0.2: 272 | version "1.0.2" 273 | resolved "https://registry.npm.taobao.org/encodeurl/download/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" 274 | integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= 275 | 276 | escape-html@~1.0.3: 277 | version "1.0.3" 278 | resolved "https://registry.npm.taobao.org/escape-html/download/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" 279 | integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= 280 | 281 | escape-string-regexp@^1.0.5: 282 | version "1.0.5" 283 | resolved "https://registry.npm.taobao.org/escape-string-regexp/download/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 284 | integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= 285 | 286 | etag@~1.8.1: 287 | version "1.8.1" 288 | resolved "https://registry.npm.taobao.org/etag/download/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" 289 | integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= 290 | 291 | execa@^0.7.0: 292 | version "0.7.0" 293 | resolved "https://registry.npm.taobao.org/execa/download/execa-0.7.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fexeca%2Fdownload%2Fexeca-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" 294 | integrity sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c= 295 | dependencies: 296 | cross-spawn "^5.0.1" 297 | get-stream "^3.0.0" 298 | is-stream "^1.1.0" 299 | npm-run-path "^2.0.0" 300 | p-finally "^1.0.0" 301 | signal-exit "^3.0.0" 302 | strip-eof "^1.0.0" 303 | 304 | express@^4.17.1: 305 | version "4.17.1" 306 | resolved "https://registry.npm.taobao.org/express/download/express-4.17.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fexpress%2Fdownload%2Fexpress-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" 307 | integrity sha1-RJH8OGBc9R+GKdOcK10Cb5ikwTQ= 308 | dependencies: 309 | accepts "~1.3.7" 310 | array-flatten "1.1.1" 311 | body-parser "1.19.0" 312 | content-disposition "0.5.3" 313 | content-type "~1.0.4" 314 | cookie "0.4.0" 315 | cookie-signature "1.0.6" 316 | debug "2.6.9" 317 | depd "~1.1.2" 318 | encodeurl "~1.0.2" 319 | escape-html "~1.0.3" 320 | etag "~1.8.1" 321 | finalhandler "~1.1.2" 322 | fresh "0.5.2" 323 | merge-descriptors "1.0.1" 324 | methods "~1.1.2" 325 | on-finished "~2.3.0" 326 | parseurl "~1.3.3" 327 | path-to-regexp "0.1.7" 328 | proxy-addr "~2.0.5" 329 | qs "6.7.0" 330 | range-parser "~1.2.1" 331 | safe-buffer "5.1.2" 332 | send "0.17.1" 333 | serve-static "1.14.1" 334 | setprototypeof "1.1.1" 335 | statuses "~1.5.0" 336 | type-is "~1.6.18" 337 | utils-merge "1.0.1" 338 | vary "~1.1.2" 339 | 340 | fill-range@^7.0.1: 341 | version "7.0.1" 342 | resolved "https://registry.npm.taobao.org/fill-range/download/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" 343 | integrity sha1-GRmmp8df44ssfHflGYU12prN2kA= 344 | dependencies: 345 | to-regex-range "^5.0.1" 346 | 347 | finalhandler@~1.1.2: 348 | version "1.1.2" 349 | resolved "https://registry.npm.taobao.org/finalhandler/download/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" 350 | integrity sha1-t+fQAP/RGTjQ/bBTUG9uur6fWH0= 351 | dependencies: 352 | debug "2.6.9" 353 | encodeurl "~1.0.2" 354 | escape-html "~1.0.3" 355 | on-finished "~2.3.0" 356 | parseurl "~1.3.3" 357 | statuses "~1.5.0" 358 | unpipe "~1.0.0" 359 | 360 | forwarded@~0.1.2: 361 | version "0.1.2" 362 | resolved "https://registry.npm.taobao.org/forwarded/download/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" 363 | integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= 364 | 365 | fresh@0.5.2: 366 | version "0.5.2" 367 | resolved "https://registry.npm.taobao.org/fresh/download/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" 368 | integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= 369 | 370 | fsevents@~2.1.2: 371 | version "2.1.2" 372 | resolved "https://registry.npm.taobao.org/fsevents/download/fsevents-2.1.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffsevents%2Fdownload%2Ffsevents-2.1.2.tgz#4c0a1fb34bc68e543b4b82a9ec392bfbda840805" 373 | integrity sha1-TAofs0vGjlQ7S4Kp7Dkr+9qECAU= 374 | 375 | get-stream@^3.0.0: 376 | version "3.0.0" 377 | resolved "https://registry.npm.taobao.org/get-stream/download/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" 378 | integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= 379 | 380 | glob-parent@~5.1.0: 381 | version "5.1.0" 382 | resolved "https://registry.npm.taobao.org/glob-parent/download/glob-parent-5.1.0.tgz#5f4c1d1e748d30cd73ad2944b3577a81b081e8c2" 383 | integrity sha1-X0wdHnSNMM1zrSlEs1d6gbCB6MI= 384 | dependencies: 385 | is-glob "^4.0.1" 386 | 387 | global-dirs@^0.1.0: 388 | version "0.1.1" 389 | resolved "https://registry.npm.taobao.org/global-dirs/download/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" 390 | integrity sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU= 391 | dependencies: 392 | ini "^1.3.4" 393 | 394 | got@^6.7.1: 395 | version "6.7.1" 396 | resolved "https://registry.npm.taobao.org/got/download/got-6.7.1.tgz?cache=0&sync_timestamp=1582185103512&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fgot%2Fdownload%2Fgot-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0" 397 | integrity sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA= 398 | dependencies: 399 | create-error-class "^3.0.0" 400 | duplexer3 "^0.1.4" 401 | get-stream "^3.0.0" 402 | is-redirect "^1.0.0" 403 | is-retry-allowed "^1.0.0" 404 | is-stream "^1.0.0" 405 | lowercase-keys "^1.0.0" 406 | safe-buffer "^5.0.1" 407 | timed-out "^4.0.0" 408 | unzip-response "^2.0.1" 409 | url-parse-lax "^1.0.0" 410 | 411 | graceful-fs@^4.1.11, graceful-fs@^4.1.2: 412 | version "4.2.3" 413 | resolved "https://registry.npm.taobao.org/graceful-fs/download/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" 414 | integrity sha1-ShL/G2A3bvCYYsIJPt2Qgyi+hCM= 415 | 416 | has-flag@^3.0.0: 417 | version "3.0.0" 418 | resolved "https://registry.npm.taobao.org/has-flag/download/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" 419 | integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= 420 | 421 | http-errors@1.7.2: 422 | version "1.7.2" 423 | resolved "https://registry.npm.taobao.org/http-errors/download/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" 424 | integrity sha1-T1ApzxMjnzEDblsuVSkrz7zIXI8= 425 | dependencies: 426 | depd "~1.1.2" 427 | inherits "2.0.3" 428 | setprototypeof "1.1.1" 429 | statuses ">= 1.5.0 < 2" 430 | toidentifier "1.0.0" 431 | 432 | http-errors@~1.7.2: 433 | version "1.7.3" 434 | resolved "https://registry.npm.taobao.org/http-errors/download/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" 435 | integrity sha1-bGGeT5xgMIw4UZSYwU+7EKrOuwY= 436 | dependencies: 437 | depd "~1.1.2" 438 | inherits "2.0.4" 439 | setprototypeof "1.1.1" 440 | statuses ">= 1.5.0 < 2" 441 | toidentifier "1.0.0" 442 | 443 | iconv-lite@0.4.24: 444 | version "0.4.24" 445 | resolved "https://registry.npm.taobao.org/iconv-lite/download/iconv-lite-0.4.24.tgz?cache=0&sync_timestamp=1579334008444&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ficonv-lite%2Fdownload%2Ficonv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" 446 | integrity sha1-ICK0sl+93CHS9SSXSkdKr+czkIs= 447 | dependencies: 448 | safer-buffer ">= 2.1.2 < 3" 449 | 450 | ignore-by-default@^1.0.1: 451 | version "1.0.1" 452 | resolved "https://registry.npm.taobao.org/ignore-by-default/download/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" 453 | integrity sha1-SMptcvbGo68Aqa1K5odr44ieKwk= 454 | 455 | import-lazy@^2.1.0: 456 | version "2.1.0" 457 | resolved "https://registry.npm.taobao.org/import-lazy/download/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" 458 | integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= 459 | 460 | imurmurhash@^0.1.4: 461 | version "0.1.4" 462 | resolved "https://registry.npm.taobao.org/imurmurhash/download/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" 463 | integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= 464 | 465 | inherits@2.0.3: 466 | version "2.0.3" 467 | resolved "https://registry.npm.taobao.org/inherits/download/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 468 | integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= 469 | 470 | inherits@2.0.4: 471 | version "2.0.4" 472 | resolved "https://registry.npm.taobao.org/inherits/download/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 473 | integrity sha1-D6LGT5MpF8NDOg3tVTY6rjdBa3w= 474 | 475 | ini@^1.3.4, ini@~1.3.0: 476 | version "1.3.5" 477 | resolved "https://registry.npm.taobao.org/ini/download/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" 478 | integrity sha1-7uJfVtscnsYIXgwid4CD9Zar+Sc= 479 | 480 | ipaddr.js@1.9.1: 481 | version "1.9.1" 482 | resolved "https://registry.npm.taobao.org/ipaddr.js/download/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" 483 | integrity sha1-v/OFQ+64mEglB5/zoqjmy9RngbM= 484 | 485 | is-binary-path@~2.1.0: 486 | version "2.1.0" 487 | resolved "https://registry.npm.taobao.org/is-binary-path/download/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" 488 | integrity sha1-6h9/O4DwZCNug0cPhsCcJU+0Wwk= 489 | dependencies: 490 | binary-extensions "^2.0.0" 491 | 492 | is-ci@^1.0.10: 493 | version "1.2.1" 494 | resolved "https://registry.npm.taobao.org/is-ci/download/is-ci-1.2.1.tgz#e3779c8ee17fccf428488f6e281187f2e632841c" 495 | integrity sha1-43ecjuF/zPQoSI9uKBGH8uYyhBw= 496 | dependencies: 497 | ci-info "^1.5.0" 498 | 499 | is-extglob@^2.1.1: 500 | version "2.1.1" 501 | resolved "https://registry.npm.taobao.org/is-extglob/download/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" 502 | integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= 503 | 504 | is-fullwidth-code-point@^2.0.0: 505 | version "2.0.0" 506 | resolved "https://registry.npm.taobao.org/is-fullwidth-code-point/download/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" 507 | integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= 508 | 509 | is-glob@^4.0.1, is-glob@~4.0.1: 510 | version "4.0.1" 511 | resolved "https://registry.npm.taobao.org/is-glob/download/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" 512 | integrity sha1-dWfb6fL14kZ7x3q4PEopSCQHpdw= 513 | dependencies: 514 | is-extglob "^2.1.1" 515 | 516 | is-installed-globally@^0.1.0: 517 | version "0.1.0" 518 | resolved "https://registry.npm.taobao.org/is-installed-globally/download/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80" 519 | integrity sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA= 520 | dependencies: 521 | global-dirs "^0.1.0" 522 | is-path-inside "^1.0.0" 523 | 524 | is-npm@^1.0.0: 525 | version "1.0.0" 526 | resolved "https://registry.npm.taobao.org/is-npm/download/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4" 527 | integrity sha1-8vtjpl5JBbQGyGBydloaTceTufQ= 528 | 529 | is-number@^7.0.0: 530 | version "7.0.0" 531 | resolved "https://registry.npm.taobao.org/is-number/download/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" 532 | integrity sha1-dTU0W4lnNNX4DE0GxQlVUnoU8Ss= 533 | 534 | is-obj@^1.0.0: 535 | version "1.0.1" 536 | resolved "https://registry.npm.taobao.org/is-obj/download/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" 537 | integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= 538 | 539 | is-path-inside@^1.0.0: 540 | version "1.0.1" 541 | resolved "https://registry.npm.taobao.org/is-path-inside/download/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036" 542 | integrity sha1-jvW33lBDej/cprToZe96pVy0gDY= 543 | dependencies: 544 | path-is-inside "^1.0.1" 545 | 546 | is-redirect@^1.0.0: 547 | version "1.0.0" 548 | resolved "https://registry.npm.taobao.org/is-redirect/download/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24" 549 | integrity sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ= 550 | 551 | is-retry-allowed@^1.0.0: 552 | version "1.2.0" 553 | resolved "https://registry.npm.taobao.org/is-retry-allowed/download/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4" 554 | integrity sha1-13hIi9CkZmo76KFIK58rqv7eqLQ= 555 | 556 | is-stream@^1.0.0, is-stream@^1.1.0: 557 | version "1.1.0" 558 | resolved "https://registry.npm.taobao.org/is-stream/download/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" 559 | integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= 560 | 561 | isexe@^2.0.0: 562 | version "2.0.0" 563 | resolved "https://registry.npm.taobao.org/isexe/download/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" 564 | integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= 565 | 566 | latest-version@^3.0.0: 567 | version "3.1.0" 568 | resolved "https://registry.npm.taobao.org/latest-version/download/latest-version-3.1.0.tgz#a205383fea322b33b5ae3b18abee0dc2f356ee15" 569 | integrity sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU= 570 | dependencies: 571 | package-json "^4.0.0" 572 | 573 | lowercase-keys@^1.0.0: 574 | version "1.0.1" 575 | resolved "https://registry.npm.taobao.org/lowercase-keys/download/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" 576 | integrity sha1-b54wtHCE2XGnyCD/FabFFnt0wm8= 577 | 578 | lru-cache@^4.0.1: 579 | version "4.1.5" 580 | resolved "https://registry.npm.taobao.org/lru-cache/download/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" 581 | integrity sha1-i75Q6oW+1ZvJ4z3KuCNe6bz0Q80= 582 | dependencies: 583 | pseudomap "^1.0.2" 584 | yallist "^2.1.2" 585 | 586 | make-dir@^1.0.0: 587 | version "1.3.0" 588 | resolved "https://registry.npm.taobao.org/make-dir/download/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" 589 | integrity sha1-ecEDO4BRW9bSTsmTPoYMp17ifww= 590 | dependencies: 591 | pify "^3.0.0" 592 | 593 | media-typer@0.3.0: 594 | version "0.3.0" 595 | resolved "https://registry.npm.taobao.org/media-typer/download/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" 596 | integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= 597 | 598 | merge-descriptors@1.0.1: 599 | version "1.0.1" 600 | resolved "https://registry.npm.taobao.org/merge-descriptors/download/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" 601 | integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= 602 | 603 | methods@~1.1.2: 604 | version "1.1.2" 605 | resolved "https://registry.npm.taobao.org/methods/download/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" 606 | integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= 607 | 608 | mime-db@1.43.0: 609 | version "1.43.0" 610 | resolved "https://registry.npm.taobao.org/mime-db/download/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" 611 | integrity sha1-ChLgUCZQ5HPXNVNQUOfI9OtPrlg= 612 | 613 | mime-types@~2.1.24: 614 | version "2.1.26" 615 | resolved "https://registry.npm.taobao.org/mime-types/download/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" 616 | integrity sha1-nJIfwJt+FJpl39wNpNIJlyALCgY= 617 | dependencies: 618 | mime-db "1.43.0" 619 | 620 | mime@1.6.0: 621 | version "1.6.0" 622 | resolved "https://registry.npm.taobao.org/mime/download/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" 623 | integrity sha1-Ms2eXGRVO9WNGaVor0Uqz/BJgbE= 624 | 625 | minimatch@^3.0.4: 626 | version "3.0.4" 627 | resolved "https://registry.npm.taobao.org/minimatch/download/minimatch-3.0.4.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fminimatch%2Fdownload%2Fminimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 628 | integrity sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM= 629 | dependencies: 630 | brace-expansion "^1.1.7" 631 | 632 | minimist@^1.2.0: 633 | version "1.2.5" 634 | resolved "https://registry.npm.taobao.org/minimist/download/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" 635 | integrity sha1-Z9ZgFLZqaoqqDAg8X9WN9OTpdgI= 636 | 637 | ms@2.0.0: 638 | version "2.0.0" 639 | resolved "https://registry.npm.taobao.org/ms/download/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 640 | integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= 641 | 642 | ms@2.1.1: 643 | version "2.1.1" 644 | resolved "https://registry.npm.taobao.org/ms/download/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" 645 | integrity sha1-MKWGTrPrsKZvLr5tcnrwagnYbgo= 646 | 647 | ms@^2.1.1: 648 | version "2.1.2" 649 | resolved "https://registry.npm.taobao.org/ms/download/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 650 | integrity sha1-0J0fNXtEP0kzgqjrPM0YOHKuYAk= 651 | 652 | negotiator@0.6.2: 653 | version "0.6.2" 654 | resolved "https://registry.npm.taobao.org/negotiator/download/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" 655 | integrity sha1-/qz3zPUlp3rpY0Q2pkiD/+yjRvs= 656 | 657 | nodemon@^2.0.2: 658 | version "2.0.2" 659 | resolved "https://registry.npm.taobao.org/nodemon/download/nodemon-2.0.2.tgz#9c7efeaaf9b8259295a97e5d4585ba8f0cbe50b0" 660 | integrity sha1-nH7+qvm4JZKVqX5dRYW6jwy+ULA= 661 | dependencies: 662 | chokidar "^3.2.2" 663 | debug "^3.2.6" 664 | ignore-by-default "^1.0.1" 665 | minimatch "^3.0.4" 666 | pstree.remy "^1.1.7" 667 | semver "^5.7.1" 668 | supports-color "^5.5.0" 669 | touch "^3.1.0" 670 | undefsafe "^2.0.2" 671 | update-notifier "^2.5.0" 672 | 673 | nopt@~1.0.10: 674 | version "1.0.10" 675 | resolved "https://registry.npm.taobao.org/nopt/download/nopt-1.0.10.tgz?cache=0&sync_timestamp=1583732714009&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fnopt%2Fdownload%2Fnopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" 676 | integrity sha1-bd0hvSoxQXuScn3Vhfim83YI6+4= 677 | dependencies: 678 | abbrev "1" 679 | 680 | normalize-path@^3.0.0, normalize-path@~3.0.0: 681 | version "3.0.0" 682 | resolved "https://registry.npm.taobao.org/normalize-path/download/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" 683 | integrity sha1-Dc1p/yOhybEf0JeDFmRKA4ghamU= 684 | 685 | npm-run-path@^2.0.0: 686 | version "2.0.2" 687 | resolved "https://registry.npm.taobao.org/npm-run-path/download/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" 688 | integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= 689 | dependencies: 690 | path-key "^2.0.0" 691 | 692 | on-finished@~2.3.0: 693 | version "2.3.0" 694 | resolved "https://registry.npm.taobao.org/on-finished/download/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" 695 | integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= 696 | dependencies: 697 | ee-first "1.1.1" 698 | 699 | p-finally@^1.0.0: 700 | version "1.0.0" 701 | resolved "https://registry.npm.taobao.org/p-finally/download/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" 702 | integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= 703 | 704 | package-json@^4.0.0: 705 | version "4.0.1" 706 | resolved "https://registry.npm.taobao.org/package-json/download/package-json-4.0.1.tgz#8869a0401253661c4c4ca3da6c2121ed555f5eed" 707 | integrity sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0= 708 | dependencies: 709 | got "^6.7.1" 710 | registry-auth-token "^3.0.1" 711 | registry-url "^3.0.3" 712 | semver "^5.1.0" 713 | 714 | parseurl@~1.3.3: 715 | version "1.3.3" 716 | resolved "https://registry.npm.taobao.org/parseurl/download/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" 717 | integrity sha1-naGee+6NEt/wUT7Vt2lXeTvC6NQ= 718 | 719 | path-is-inside@^1.0.1: 720 | version "1.0.2" 721 | resolved "https://registry.npm.taobao.org/path-is-inside/download/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" 722 | integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= 723 | 724 | path-key@^2.0.0: 725 | version "2.0.1" 726 | resolved "https://registry.npm.taobao.org/path-key/download/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" 727 | integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= 728 | 729 | path-to-regexp@0.1.7: 730 | version "0.1.7" 731 | resolved "https://registry.npm.taobao.org/path-to-regexp/download/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" 732 | integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= 733 | 734 | picomatch@^2.0.4, picomatch@^2.0.7: 735 | version "2.2.1" 736 | resolved "https://registry.npm.taobao.org/picomatch/download/picomatch-2.2.1.tgz#21bac888b6ed8601f831ce7816e335bc779f0a4a" 737 | integrity sha1-IbrIiLbthgH4Mc54FuM1vHefCko= 738 | 739 | pify@^3.0.0: 740 | version "3.0.0" 741 | resolved "https://registry.npm.taobao.org/pify/download/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" 742 | integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= 743 | 744 | prepend-http@^1.0.1: 745 | version "1.0.4" 746 | resolved "https://registry.npm.taobao.org/prepend-http/download/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" 747 | integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= 748 | 749 | proxy-addr@~2.0.5: 750 | version "2.0.6" 751 | resolved "https://registry.npm.taobao.org/proxy-addr/download/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" 752 | integrity sha1-/cIzZQVEfT8vLGOO0nLK9hS7sr8= 753 | dependencies: 754 | forwarded "~0.1.2" 755 | ipaddr.js "1.9.1" 756 | 757 | pseudomap@^1.0.2: 758 | version "1.0.2" 759 | resolved "https://registry.npm.taobao.org/pseudomap/download/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" 760 | integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= 761 | 762 | pstree.remy@^1.1.7: 763 | version "1.1.7" 764 | resolved "https://registry.npm.taobao.org/pstree.remy/download/pstree.remy-1.1.7.tgz#c76963a28047ed61542dc361aa26ee55a7fa15f3" 765 | integrity sha1-x2ljooBH7WFULcNhqibuVaf6FfM= 766 | 767 | qs@6.7.0: 768 | version "6.7.0" 769 | resolved "https://registry.npm.taobao.org/qs/download/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" 770 | integrity sha1-QdwaAV49WB8WIXdr4xr7KHapsbw= 771 | 772 | range-parser@~1.2.1: 773 | version "1.2.1" 774 | resolved "https://registry.npm.taobao.org/range-parser/download/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" 775 | integrity sha1-PPNwI9GZ4cJNGlW4SADC8+ZGgDE= 776 | 777 | raw-body@2.4.0: 778 | version "2.4.0" 779 | resolved "https://registry.npm.taobao.org/raw-body/download/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" 780 | integrity sha1-oc5vucm8NWylLoklarWQWeE9AzI= 781 | dependencies: 782 | bytes "3.1.0" 783 | http-errors "1.7.2" 784 | iconv-lite "0.4.24" 785 | unpipe "1.0.0" 786 | 787 | rc@^1.0.1, rc@^1.1.6: 788 | version "1.2.8" 789 | resolved "https://registry.npm.taobao.org/rc/download/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" 790 | integrity sha1-zZJL9SAKB1uDwYjNa54hG3/A0+0= 791 | dependencies: 792 | deep-extend "^0.6.0" 793 | ini "~1.3.0" 794 | minimist "^1.2.0" 795 | strip-json-comments "~2.0.1" 796 | 797 | readdirp@~3.3.0: 798 | version "3.3.0" 799 | resolved "https://registry.npm.taobao.org/readdirp/download/readdirp-3.3.0.tgz#984458d13a1e42e2e9f5841b129e162f369aff17" 800 | integrity sha1-mERY0ToeQuLp9YQbEp4WLzaa/xc= 801 | dependencies: 802 | picomatch "^2.0.7" 803 | 804 | registry-auth-token@^3.0.1: 805 | version "3.4.0" 806 | resolved "https://registry.npm.taobao.org/registry-auth-token/download/registry-auth-token-3.4.0.tgz#d7446815433f5d5ed6431cd5dca21048f66b397e" 807 | integrity sha1-10RoFUM/XV7WQxzV3KIQSPZrOX4= 808 | dependencies: 809 | rc "^1.1.6" 810 | safe-buffer "^5.0.1" 811 | 812 | registry-url@^3.0.3: 813 | version "3.1.0" 814 | resolved "https://registry.npm.taobao.org/registry-url/download/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942" 815 | integrity sha1-PU74cPc93h138M+aOBQyRE4XSUI= 816 | dependencies: 817 | rc "^1.0.1" 818 | 819 | safe-buffer@5.1.2: 820 | version "5.1.2" 821 | resolved "https://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 822 | integrity sha1-mR7GnSluAxN0fVm9/St0XDX4go0= 823 | 824 | safe-buffer@^5.0.1: 825 | version "5.2.0" 826 | resolved "https://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" 827 | integrity sha1-t02uxJsRSPiMZLaNSbHoFcHy9Rk= 828 | 829 | "safer-buffer@>= 2.1.2 < 3": 830 | version "2.1.2" 831 | resolved "https://registry.npm.taobao.org/safer-buffer/download/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 832 | integrity sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo= 833 | 834 | semver-diff@^2.0.0: 835 | version "2.1.0" 836 | resolved "https://registry.npm.taobao.org/semver-diff/download/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36" 837 | integrity sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY= 838 | dependencies: 839 | semver "^5.0.3" 840 | 841 | semver@^5.0.3, semver@^5.1.0, semver@^5.7.1: 842 | version "5.7.1" 843 | resolved "https://registry.npm.taobao.org/semver/download/semver-5.7.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsemver%2Fdownload%2Fsemver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" 844 | integrity sha1-qVT5Ma66UI0we78Gnv8MAclhFvc= 845 | 846 | send@0.17.1: 847 | version "0.17.1" 848 | resolved "https://registry.npm.taobao.org/send/download/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" 849 | integrity sha1-wdiwWfeQD3Rm3Uk4vcROEd2zdsg= 850 | dependencies: 851 | debug "2.6.9" 852 | depd "~1.1.2" 853 | destroy "~1.0.4" 854 | encodeurl "~1.0.2" 855 | escape-html "~1.0.3" 856 | etag "~1.8.1" 857 | fresh "0.5.2" 858 | http-errors "~1.7.2" 859 | mime "1.6.0" 860 | ms "2.1.1" 861 | on-finished "~2.3.0" 862 | range-parser "~1.2.1" 863 | statuses "~1.5.0" 864 | 865 | serve-static@1.14.1: 866 | version "1.14.1" 867 | resolved "https://registry.npm.taobao.org/serve-static/download/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" 868 | integrity sha1-Zm5jbcTwEPfvKZcKiKZ0MgiYsvk= 869 | dependencies: 870 | encodeurl "~1.0.2" 871 | escape-html "~1.0.3" 872 | parseurl "~1.3.3" 873 | send "0.17.1" 874 | 875 | setprototypeof@1.1.1: 876 | version "1.1.1" 877 | resolved "https://registry.npm.taobao.org/setprototypeof/download/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" 878 | integrity sha1-fpWsskqpL1iF4KvvW6ExMw1K5oM= 879 | 880 | shebang-command@^1.2.0: 881 | version "1.2.0" 882 | resolved "https://registry.npm.taobao.org/shebang-command/download/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" 883 | integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= 884 | dependencies: 885 | shebang-regex "^1.0.0" 886 | 887 | shebang-regex@^1.0.0: 888 | version "1.0.0" 889 | resolved "https://registry.npm.taobao.org/shebang-regex/download/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" 890 | integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= 891 | 892 | signal-exit@^3.0.0, signal-exit@^3.0.2: 893 | version "3.0.2" 894 | resolved "https://registry.npm.taobao.org/signal-exit/download/signal-exit-3.0.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsignal-exit%2Fdownload%2Fsignal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" 895 | integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= 896 | 897 | "statuses@>= 1.5.0 < 2", statuses@~1.5.0: 898 | version "1.5.0" 899 | resolved "https://registry.npm.taobao.org/statuses/download/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" 900 | integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= 901 | 902 | string-width@^2.0.0, string-width@^2.1.1: 903 | version "2.1.1" 904 | resolved "https://registry.npm.taobao.org/string-width/download/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" 905 | integrity sha1-q5Pyeo3BPSjKyBXEYhQ6bZASrp4= 906 | dependencies: 907 | is-fullwidth-code-point "^2.0.0" 908 | strip-ansi "^4.0.0" 909 | 910 | strip-ansi@^4.0.0: 911 | version "4.0.0" 912 | resolved "https://registry.npm.taobao.org/strip-ansi/download/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" 913 | integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= 914 | dependencies: 915 | ansi-regex "^3.0.0" 916 | 917 | strip-eof@^1.0.0: 918 | version "1.0.0" 919 | resolved "https://registry.npm.taobao.org/strip-eof/download/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" 920 | integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= 921 | 922 | strip-json-comments@~2.0.1: 923 | version "2.0.1" 924 | resolved "https://registry.npm.taobao.org/strip-json-comments/download/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" 925 | integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= 926 | 927 | supports-color@^5.3.0, supports-color@^5.5.0: 928 | version "5.5.0" 929 | resolved "https://registry.npm.taobao.org/supports-color/download/supports-color-5.5.0.tgz?cache=0&sync_timestamp=1569558060145&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsupports-color%2Fdownload%2Fsupports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" 930 | integrity sha1-4uaaRKyHcveKHsCzW2id9lMO/I8= 931 | dependencies: 932 | has-flag "^3.0.0" 933 | 934 | term-size@^1.2.0: 935 | version "1.2.0" 936 | resolved "https://registry.npm.taobao.org/term-size/download/term-size-1.2.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fterm-size%2Fdownload%2Fterm-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69" 937 | integrity sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk= 938 | dependencies: 939 | execa "^0.7.0" 940 | 941 | timed-out@^4.0.0: 942 | version "4.0.1" 943 | resolved "https://registry.npm.taobao.org/timed-out/download/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" 944 | integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8= 945 | 946 | to-regex-range@^5.0.1: 947 | version "5.0.1" 948 | resolved "https://registry.npm.taobao.org/to-regex-range/download/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" 949 | integrity sha1-FkjESq58jZiKMmAY7XL1tN0DkuQ= 950 | dependencies: 951 | is-number "^7.0.0" 952 | 953 | toidentifier@1.0.0: 954 | version "1.0.0" 955 | resolved "https://registry.npm.taobao.org/toidentifier/download/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" 956 | integrity sha1-fhvjRw8ed5SLxD2Uo8j013UrpVM= 957 | 958 | touch@^3.1.0: 959 | version "3.1.0" 960 | resolved "https://registry.npm.taobao.org/touch/download/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" 961 | integrity sha1-/jZfX3XsntTlaCXgu3bSSrdK+Ds= 962 | dependencies: 963 | nopt "~1.0.10" 964 | 965 | type-is@~1.6.17, type-is@~1.6.18: 966 | version "1.6.18" 967 | resolved "https://registry.npm.taobao.org/type-is/download/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" 968 | integrity sha1-TlUs0F3wlGfcvE73Od6J8s83wTE= 969 | dependencies: 970 | media-typer "0.3.0" 971 | mime-types "~2.1.24" 972 | 973 | undefsafe@^2.0.2: 974 | version "2.0.3" 975 | resolved "https://registry.npm.taobao.org/undefsafe/download/undefsafe-2.0.3.tgz#6b166e7094ad46313b2202da7ecc2cd7cc6e7aae" 976 | integrity sha1-axZucJStRjE7IgLafsws18xueq4= 977 | dependencies: 978 | debug "^2.2.0" 979 | 980 | unique-string@^1.0.0: 981 | version "1.0.0" 982 | resolved "https://registry.npm.taobao.org/unique-string/download/unique-string-1.0.0.tgz#9e1057cca851abb93398f8b33ae187b99caec11a" 983 | integrity sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo= 984 | dependencies: 985 | crypto-random-string "^1.0.0" 986 | 987 | unpipe@1.0.0, unpipe@~1.0.0: 988 | version "1.0.0" 989 | resolved "https://registry.npm.taobao.org/unpipe/download/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" 990 | integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= 991 | 992 | unzip-response@^2.0.1: 993 | version "2.0.1" 994 | resolved "https://registry.npm.taobao.org/unzip-response/download/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97" 995 | integrity sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c= 996 | 997 | update-notifier@^2.5.0: 998 | version "2.5.0" 999 | resolved "https://registry.npm.taobao.org/update-notifier/download/update-notifier-2.5.0.tgz#d0744593e13f161e406acb1d9408b72cad08aff6" 1000 | integrity sha1-0HRFk+E/Fh5AassdlAi3LK0Ir/Y= 1001 | dependencies: 1002 | boxen "^1.2.1" 1003 | chalk "^2.0.1" 1004 | configstore "^3.0.0" 1005 | import-lazy "^2.1.0" 1006 | is-ci "^1.0.10" 1007 | is-installed-globally "^0.1.0" 1008 | is-npm "^1.0.0" 1009 | latest-version "^3.0.0" 1010 | semver-diff "^2.0.0" 1011 | xdg-basedir "^3.0.0" 1012 | 1013 | url-parse-lax@^1.0.0: 1014 | version "1.0.0" 1015 | resolved "https://registry.npm.taobao.org/url-parse-lax/download/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73" 1016 | integrity sha1-evjzA2Rem9eaJy56FKxovAYJ2nM= 1017 | dependencies: 1018 | prepend-http "^1.0.1" 1019 | 1020 | utils-merge@1.0.1: 1021 | version "1.0.1" 1022 | resolved "https://registry.npm.taobao.org/utils-merge/download/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" 1023 | integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= 1024 | 1025 | vary@~1.1.2: 1026 | version "1.1.2" 1027 | resolved "https://registry.npm.taobao.org/vary/download/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" 1028 | integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= 1029 | 1030 | which@^1.2.9: 1031 | version "1.3.1" 1032 | resolved "https://registry.npm.taobao.org/which/download/which-1.3.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fwhich%2Fdownload%2Fwhich-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" 1033 | integrity sha1-pFBD1U9YBTFtqNYvn1CRjT2nCwo= 1034 | dependencies: 1035 | isexe "^2.0.0" 1036 | 1037 | widest-line@^2.0.0: 1038 | version "2.0.1" 1039 | resolved "https://registry.npm.taobao.org/widest-line/download/widest-line-2.0.1.tgz#7438764730ec7ef4381ce4df82fb98a53142a3fc" 1040 | integrity sha1-dDh2RzDsfvQ4HOTfgvuYpTFCo/w= 1041 | dependencies: 1042 | string-width "^2.1.1" 1043 | 1044 | write-file-atomic@^2.0.0: 1045 | version "2.4.3" 1046 | resolved "https://registry.npm.taobao.org/write-file-atomic/download/write-file-atomic-2.4.3.tgz#1fd2e9ae1df3e75b8d8c367443c692d4ca81f481" 1047 | integrity sha1-H9Lprh3z51uNjDZ0Q8aS1MqB9IE= 1048 | dependencies: 1049 | graceful-fs "^4.1.11" 1050 | imurmurhash "^0.1.4" 1051 | signal-exit "^3.0.2" 1052 | 1053 | xdg-basedir@^3.0.0: 1054 | version "3.0.0" 1055 | resolved "https://registry.npm.taobao.org/xdg-basedir/download/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" 1056 | integrity sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ= 1057 | 1058 | yallist@^2.1.2: 1059 | version "2.1.2" 1060 | resolved "https://registry.npm.taobao.org/yallist/download/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" 1061 | integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= 1062 | -------------------------------------------------------------------------------- /data-structure/.gitignore: -------------------------------------------------------------------------------- 1 | */node_modules -------------------------------------------------------------------------------- /data-structure/lib/parse-tree.js: -------------------------------------------------------------------------------- 1 | const TreeNode = require('./tree-node') 2 | 3 | /** 4 | * 将特定的格式转换为二叉树 5 | * @param {Object} json 6 | * @example 7 | * { 8 | * value: 666, 9 | * left: {}, 10 | * right: {} 11 | * } 12 | */ 13 | function parseTree (json) { 14 | if (!json) return null 15 | 16 | const current = new TreeNode(json.value) 17 | current.left = parseTree(json.left) 18 | current.right = parseTree(json.right) 19 | 20 | return current 21 | } 22 | 23 | module.exports = parseTree -------------------------------------------------------------------------------- /data-structure/lib/tree-node.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 二叉树节点 3 | * @param {Any} data 4 | */ 5 | class TreeNode { 6 | constructor (data) { 7 | this.value = data 8 | this.left = null 9 | this.right = null 10 | } 11 | } 12 | 13 | module.exports = TreeNode -------------------------------------------------------------------------------- /data-structure/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "data-structure", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "directories": { 7 | "lib": "lib", 8 | "test": "test" 9 | }, 10 | "scripts": { 11 | "test": "jest" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "dependencies": { 17 | "jest": "^25.5.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /data-structure/src/kmp.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * 字符串匹配KMP算法: 尽量复用已匹配的字符串 4 | * 1. 根据模式串构建PMT表,如: 5 | * |子字符串  a b a b a b z a b a b a b a 6 | * |最大匹配数 0 0 1 2 3 4 0 1 2 3 4 5 6 ? 7 | * 对应的值为前缀与后缀的最长公共串长度, 8 | * 如上图的aba,前缀为「a, ab」,后缀为「a, ba」,最长为1,即a的长度 9 | * 2. 遍历主字符串,到达不符合的下标时,将之前已匹配的进行查表回溯模式串的指针 10 | * 11 | * 时间复杂度:O(m + n) 12 | */ 13 | 14 | /** 15 | * 创建模式字符串对应的表 16 | * @param {String} pattern 模式字符串 17 | */ 18 | const createPartialMatchTable = (pattern) => { 19 | const table = new Array(pattern.length).fill(0) 20 | let maxLength = 0 21 | 22 | for (let i = 1, len = pattern.length; i < len; i++) { 23 | while ( 24 | maxLength > 0 && 25 | pattern.charAt(maxLength) != pattern.charAt(i) 26 | ) { 27 | maxLength = table[maxLength - 1] 28 | } 29 | 30 | if (pattern.charAt(maxLength) == pattern.charAt(i)) { 31 | maxLength++ 32 | } 33 | 34 | table[i] = maxLength 35 | } 36 | 37 | return table 38 | } 39 | 40 | /** 41 | * 字符串匹配 42 | * @param {String} target 目标字符串 43 | * @param {String} pattern 匹配的模式串 44 | */ 45 | const kmp = (target, pattern) => { 46 | if (!target || !pattern) return -1 47 | 48 | const pmt = createPartialMatchTable(pattern) 49 | 50 | let count = 0 51 | 52 | for (let i = 0, len = target.length; i < len; i++) { 53 | while (count > 0 && pattern.charAt(count) !== target.charAt(i)) { 54 | count = pmt[count - 1] 55 | } 56 | 57 | if (target.charAt(i) === pattern.charAt(count)) { 58 | count++ 59 | } 60 | 61 | if (count === pattern.length) { 62 | // ...此处可优化,记录每一个匹配的index,现暂返回第一个匹配的index 63 | // count = pmt[count - 1] 64 | return i - pattern.length + 1 65 | } 66 | } 67 | 68 | return -1 69 | } 70 | 71 | exports.createPartialMatchTable = createPartialMatchTable 72 | exports.kmp = kmp -------------------------------------------------------------------------------- /data-structure/src/travel-binary-tree.js: -------------------------------------------------------------------------------- 1 | const noop = () => {} 2 | 3 | /* 先序遍历 */ 4 | const preOrderTravel = function (tree, callback = noop) { // 迭代法 5 | if (!tree) return 6 | 7 | const list = [tree] 8 | 9 | while (list.length) { 10 | const current = list.shift() 11 | 12 | callback(current) 13 | current.right && list.unshift(current.right) 14 | current.left && list.unshift(current.left) 15 | } 16 | } 17 | 18 | const preOrderTravelRecurse = function (tree, callback = noop) { // 递归 19 | if (!tree) return 20 | 21 | callback(tree) 22 | preOrderTravelRecurse(tree.left, callback) 23 | preOrderTravelRecurse(tree.right, callback) 24 | } 25 | /* End */ 26 | 27 | 28 | /* 中序遍历 */ 29 | const inOrderTravel = function (tree, callback = noop) { // 迭代法 30 | /** 31 | * 技巧:向左侧遍历,并压栈所有遍历的节点 32 | * 找到第一个没有左孩子的节点,然后进行访问,并开始回溯 33 | * 回溯取栈顶的节点,等同于:上个遍历目标节点的父节点 34 | */ 35 | 36 | if (!tree) return 37 | 38 | const list = [] 39 | 40 | while (true) { 41 | while (tree) { 42 | list.push(tree) 43 | tree = tree.left 44 | } 45 | 46 | if (!list.length) break 47 | 48 | const current = list.pop() 49 | callback(current) 50 | tree = current.right 51 | } 52 | } 53 | 54 | const inOrderTravelRecurse = function (tree, callback = noop) { // 递归 55 | if (!tree) return 56 | 57 | inOrderTravelRecurse(tree.left, callback) 58 | callback(tree) 59 | inOrderTravelRecurse(tree.right, callback) 60 | } 61 | /* End */ 62 | 63 | 64 | /* 后序遍历 */ 65 | const tailOrderTravel = function (tree, callback = noop) { 66 | if (!tree) return 67 | 68 | const list = [] 69 | let pre = null 70 | 71 | while (tree || list.length) { 72 | // 把左侧全部左子压栈,直到无左子结束 73 | if (tree) { 74 | list.push(tree) 75 | tree = tree.left 76 | } else { 77 | // 开始处理右节点 78 | tree = list.pop() 79 | if (!tree.right || tree.right === pre) { 80 | callback(tree) 81 | pre = tree 82 | tree = null 83 | } else { 84 | // 这里需要重新压入原来的父节点 85 | list.push(tree) 86 | tree = tree.right 87 | } 88 | } 89 | } 90 | 91 | } 92 | 93 | const tailOrderTravelRecurse = function (tree, callback = noop) { //递归 94 | if (!tree) return 95 | 96 | tailOrderTravelRecurse(tree.left, callback) 97 | tailOrderTravelRecurse(tree.right, callback) 98 | callback(tree) 99 | } 100 | /* End */ 101 | 102 | 103 | /* 层次遍历 */ 104 | const floorOrderTravel = function (tree, callback = noop) { 105 | if (!tree) return 106 | 107 | const list = [tree] 108 | 109 | while (list.length) { 110 | const current = list.shift() 111 | callback(current) 112 | current.left && list.push(current.left) 113 | current.right && list.push(current.right) 114 | } 115 | } 116 | /* End */ 117 | 118 | exports.preOrderTravel = preOrderTravel 119 | exports.preOrderTravelRecurse = preOrderTravelRecurse 120 | exports.inOrderTravel = inOrderTravel 121 | exports.inOrderTravelRecurse = inOrderTravelRecurse 122 | exports.tailOrderTravel = tailOrderTravel 123 | exports.tailOrderTravelRecurse = tailOrderTravelRecurse 124 | exports.floorOrderTravel = floorOrderTravel -------------------------------------------------------------------------------- /data-structure/test/kmp.test.js: -------------------------------------------------------------------------------- 1 | const { kmp, createPartialMatchTable } = require('../src/kmp') 2 | 3 | test('字符串匹配:kmp', () => { 4 | let target, pattern 5 | 6 | target = 'ababablla' 7 | pattern = 'babll' 8 | expect(createPartialMatchTable(pattern).join('')).toBe('00100') 9 | expect(kmp(target, pattern)).toBe(target.indexOf(pattern)) 10 | 11 | target = 'abbaabbaaba' 12 | pattern = 'abbaaba' 13 | expect(createPartialMatchTable(pattern).join('')).toBe('0001121') 14 | expect(kmp(target, pattern)).toBe(target.indexOf(pattern)) 15 | 16 | target = 'abbaabbaaba' 17 | pattern = '123' 18 | expect(createPartialMatchTable(pattern).join('')).toBe('000') 19 | expect(kmp(target, pattern)).toBe(target.indexOf(pattern)) 20 | }) 21 | -------------------------------------------------------------------------------- /data-structure/test/travel-binary-tree.test.js: -------------------------------------------------------------------------------- 1 | const demo = require('../lib/parse-tree')({ 2 | value: 9, 3 | left: { 4 | value: 8, 5 | left: { 6 | value: 20, 7 | }, 8 | right: { 9 | value: 16, 10 | left: { value: 2 }, 11 | right: { value: 4 } 12 | } 13 | }, 14 | 15 | right: { 16 | value: 7, 17 | left: { value: 11 }, 18 | right: { value: 4 } 19 | } 20 | }) 21 | 22 | const { 23 | preOrderTravel, preOrderTravelRecurse, 24 | inOrderTravel, inOrderTravelRecurse, 25 | tailOrderTravel, tailOrderTravelRecurse, 26 | floorOrderTravel 27 | } = require('../src/travel-binary-tree') 28 | 29 | const travel = (fn, tree) => { 30 | let result = [] 31 | fn(tree, ({value}) => { 32 | result.push(value) 33 | }) 34 | return result 35 | } 36 | 37 | test('前序遍历', () => { 38 | expect(travel(preOrderTravel, demo).join(',')).toBe('9,8,20,16,2,4,7,11,4') 39 | expect(travel(preOrderTravelRecurse, demo).join(',')).toBe('9,8,20,16,2,4,7,11,4') 40 | }) 41 | 42 | test('中序遍历', () => { 43 | expect(travel(inOrderTravel, demo).join(',')).toBe('20,8,2,16,4,9,11,7,4') 44 | expect(travel(inOrderTravelRecurse, demo).join(',')).toBe('20,8,2,16,4,9,11,7,4') 45 | }) 46 | 47 | test('后序遍历', () => { 48 | expect(travel(tailOrderTravel, demo).join(',')).toBe('20,2,4,16,8,11,4,7,9') 49 | expect(travel(tailOrderTravelRecurse, demo).join(',')).toBe('20,2,4,16,8,11,4,7,9') 50 | }) 51 | 52 | test('层次遍历', () => { 53 | expect(travel(floorOrderTravel, demo).join(',')).toBe('9,8,7,20,16,11,4,2,4') 54 | }) 55 | 56 | -------------------------------------------------------------------------------- /promise/promise.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 三种状态,且只能从pending转换为另外两种,不可逆 3 | */ 4 | const FULFILLED = 'fulfilled' 5 | const REJECTED = 'rejected' 6 | const PENDING = 'pending' 7 | 8 | class Promise { 9 | constructor (executor) { 10 | this.state = PENDING 11 | 12 | this.value = undefined // 成功的值 13 | this.reason = undefined // 失败的原因 14 | 15 | this.onResolvedQueue = [] // 存放then传入的resolve(array保证可多次重复then) 16 | this.onRejectedQueue = [] // 存放then传入的reject 17 | 18 | // 用于作为函数参数的resolve 19 | const resolve = value => { 20 | if (this.state === PENDING) { 21 | this.value = value 22 | this.state = FULFILLED 23 | this.onResolvedQueue.forEach(fn => fn(value)) 24 | } 25 | } 26 | 27 | // 用于作为函数参数的reject 28 | const reject = reason => { 29 | if (this.state === PENDING) { 30 | this.reason = reason 31 | this.state = REJECTED 32 | this.onRejectedQueue.forEach(fn => fn(reason)) 33 | } 34 | } 35 | 36 | // 执行目标函数 37 | try { 38 | executor && executor(resolve, reject) 39 | } catch (error) { 40 | reject(error) 41 | } 42 | } 43 | 44 | then (onFulfilled, onRejected) { 45 | // onFulfilled如果不是函数,就忽略onFulfilled,直接返回value 46 | onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value 47 | // onRejected如果不是函数,就忽略onRejected,直接扔出错误 48 | onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err } 49 | 50 | const newPromise = new Promise((resolve, reject) => { 51 | try { 52 | if (this.state === FULFILLED) { 53 | setTimeout(() => { 54 | const ret = onFulfilled(this.value) 55 | resolvePromise(newPromise, ret, resolve, reject) 56 | }, 0) 57 | } 58 | 59 | if (this.state === REJECTED) { 60 | setTimeout(() => { 61 | const ret = onRejected(this.reason) 62 | resolvePromise(newPromise, ret, resolve, reject) 63 | }, 0) 64 | } 65 | 66 | if (this.state === PENDING) { 67 | this.onResolvedQueue.push(() => { 68 | setTimeout(() => { 69 | const ret = onFulfilled(this.reason) 70 | resolvePromise(newPromise, ret, resolve, reject) 71 | }, 0) 72 | }) 73 | this.onRejectedQueue.push(() => { 74 | setTimeout(() => { 75 | const ret = onRejected(this.reason) 76 | resolvePromise(newPromise, ret, resolve, reject) 77 | }, 0) 78 | }) 79 | } 80 | } catch (error) { 81 | reject(error) 82 | } 83 | }) 84 | 85 | return newPromise 86 | } 87 | } 88 | 89 | function resolvePromise (newPromise, ret, resolve, reject) { 90 | if (ret === newPromise) { 91 | return reject(new TypeError('Chaining cycle detected for promise')) 92 | } 93 | 94 | // 防止多次调用 95 | let called 96 | // x不是null 且x是对象或者函数 97 | if (ret != null && (typeof ret === 'object' || typeof ret === 'function')) { 98 | try { 99 | // A+规定,声明then = ret的then方法 100 | const then = ret.then 101 | // 如果then是函数,就默认是promise了 102 | if (typeof then === 'function') { 103 | // 就让then执行 第一个参数是this 后面是成功的回调 和 失败的回调 104 | then.call(ret, y => { 105 | // 成功和失败只能调用一个 106 | if (called) return 107 | called = true 108 | // resolve的结果依旧是promise 那就继续解析 109 | resolvePromise(newPromise, y, resolve, reject) 110 | }, err => { 111 | // 成功和失败只能调用一个 112 | if (called) return 113 | called = true 114 | reject(err)// 失败了就失败了 115 | }) 116 | } else { 117 | resolve(ret) // 直接成功即可 118 | } 119 | } catch (e) { 120 | // 也属于失败 121 | if (called) return 122 | called = true 123 | // 取then出错了那就不要在继续执行了 124 | reject(e) 125 | } 126 | } else { 127 | resolve(ret) 128 | } 129 | } 130 | 131 | Promise.resolve = val => { 132 | return new Promise(resolve => { 133 | resolve(val) 134 | }) 135 | } 136 | 137 | Promise.reject = reason => { 138 | return new Promise((resolve, reject) => { 139 | reject(reason) 140 | }) 141 | } 142 | 143 | Promise.race = promises => { 144 | return new Promise((resolve, reject) => { 145 | for (let i = 0; i < promises.length; i++) { 146 | promises.then(resolve, reject) 147 | } 148 | }) 149 | } 150 | 151 | Promise.all = promises => { 152 | const len = promises.length 153 | const ret = new Array(len) 154 | 155 | let i = 1 156 | 157 | return new Promise((resolve, reject) => { 158 | promises.forEach((pm, index) => { 159 | pm.then(val => { 160 | ret[index] = val 161 | if (++i === len) { 162 | resolve(ret) 163 | } 164 | }, reject) 165 | }) 166 | }) 167 | } 168 | 169 | module.exports = Promise 170 | -------------------------------------------------------------------------------- /uploader/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules -------------------------------------------------------------------------------- /uploader/README.md: -------------------------------------------------------------------------------- 1 | ### 前言 2 | 3 | 本文将带你基于ES6的面向对象,脱离框架使用原生JS,从设计到代码实现一个Uploader基础类,再到实际投入使用。通过本文,你可以了解到一般情况下根据需求是如何合理构造出一个工具类lib。 4 | 5 | ### 需求描述 6 | 7 | 相信很多人都用过/写过上传的逻辑,无非就是创建`input[type=file]`标签,监听`onchange`事件,添加到`FormData`发起请求。 8 | 9 | 但是,想引入开源的工具时觉得**增加了许多体积且定制性不满足**,每次写上传逻辑又会写很多**冗余性代码**。在不同的toC业务上,还要重新编写自己的上传组件样式。 10 | 11 | 此时编写一个Uploader基础类,供于业务组件二次封装,就显得很有必要。 12 | 13 | 下面我们来分析下使用场景与功能: 14 | 15 | - **选择文件后可根据配置,自动/手动上传,定制化传参数据,接收返回。** 16 | - **可对选择的文件进行控制,如:文件个数,格式不符,超出大小限制等等。** 17 | - **操作已有文件,如:二次添加、失败重传、删除等等。** 18 | - **提供上传状态反馈,如:上传中的进度、上传成功/失败。** 19 | - **可用于拓展更多功能,如:拖拽上传、图片预览、大文件分片等。** 20 | 21 | 然后,我们可以根据需求,大概设计出想要的API效果,再根据API推导出内部实现。 22 | 23 | #### 可通过配置实例化 24 | 25 | ```javascript 26 | const uploader = new Uploader({ 27 | url: '', 28 | // 用于自动添加input标签的容器 29 | wrapper: null, 30 | 31 | // 配置化的功能,多选、接受文件类型、自动上传等等 32 | multiple: true, 33 | accept: '*', 34 | limit: -1, // 文件个数 35 | autoUpload: false 36 | 37 | // xhr配置 38 | header: {}, // 适用于JWT校验 39 | data: {} // 添加额外参数 40 | withCredentials: false 41 | }); 42 | ``` 43 | 44 | #### 状态/事件监听 45 | 46 | ```javascript 47 | // 链式调用更优雅 48 | uploader 49 | .on('choose', files => { 50 | // 用于接受选择的文件,根据业务规则过滤 51 | }) 52 | .on('change', files => { 53 | // 添加、删除文件时的触发钩子,用于更新视图 54 | // 发起请求后状态改变也会触发 55 | }) 56 | .on('progress', e => { 57 | // 回传上传进度 58 | }) 59 | .on('success', ret => {/*...*/}) 60 | .on('error', ret => {/*...*/}) 61 | ``` 62 | 63 | #### 外部调用方法 64 | 65 | 这里主要暴露一些可能通过交互才触发的功能,如选择文件、手动上传等 66 | 67 | ```javascript 68 | uploader.chooseFile(); 69 | 70 | // 独立出添加文件函数,方便拓展 71 | // 可传入slice大文件后的数组、拖拽添加文件 72 | uploader.loadFiles(files); 73 | 74 | // 相关操作 75 | uploader.removeFile(file); 76 | uploader.clearFiles() 77 | 78 | // 凡是涉及到动态添加dom,事件绑定 79 | // 应该提供销毁API 80 | uploader.destroy(); 81 | ``` 82 | 83 | 至此,可以大概设计完我们想要的uploader的大致效果,接着根据API进行内部实现。 84 | 85 | ### 内部实现 86 | 87 | 使用ES6的class构建uploader类,把功能进行内部方法拆分,使用下划线开头标识内部方法。 88 | 89 | 然后可以给出以下大概的内部接口: 90 | 91 | ```javascript 92 | class Uploader { 93 | // 构造器,new的时候,合并默认配置 94 | constructor (option = {}) {} 95 | // 根据配置初始化,绑定事件 96 | _init () {} 97 | 98 | // 绑定钩子与触发 99 | on (evt) {} 100 | _callHook (evt) {} 101 | 102 | // 交互方法 103 | chooseFile () {} 104 | loadFiles (files) {} 105 | removeFile (file) {} 106 | clear () {} 107 | 108 | // 上传处理 109 | upload (file) {} 110 | // 核心ajax发起请求 111 | _post (file) {} 112 | } 113 | ``` 114 | 115 | #### 构造器 - constructor 116 | 117 | 代码比较简单,这里目标主要是定义默认参数,进行参数合并,然后调用初始化函数 118 | 119 | ```javascript 120 | class Uploader { 121 | constructor (option = {}) { 122 | const defaultOption = { 123 | url: '', 124 | // 若无声明wrapper, 默认为body元素 125 | wrapper: document.body, 126 | multiple: false, 127 | limit: -1, 128 | autoUpload: true, 129 | accept: '*', 130 | 131 | headers: {}, 132 | data: {}, 133 | withCredentials: false 134 | } 135 | this.setting = Object.assign(defaultOption, option) 136 | this._init() 137 | } 138 | } 139 | ``` 140 | 141 | #### 初始化 - _init 142 | 143 | 这里初始化做了几件事:维护一个内部文件数组`uploadFiles`,构建`input`标签,绑定`input`标签的事件,挂载dom。 144 | 145 | 为什么需要用一个数组去维护文件,因为从需求上看,我们的每个文件需要一个状态去追踪,所以我们选择内部维护一个数组,而不是直接将文件对象交给上层逻辑。 146 | 147 | 由于逻辑比较混杂,分多了一个函数`_initInputElement`进行初始化`input`的属性。 148 | 149 | ```javascript 150 | class Uploader { 151 | // ... 152 | 153 | _init () { 154 | this.uploadFiles = []; 155 | this.input = this._initInputElement(this.setting); 156 | // input的onchange事件处理函数 157 | this.changeHandler = e => { 158 | // ... 159 | }; 160 | this.input.addEventListener('change', this.changeHandler); 161 | this.setting.wrapper.appendChild(this.input); 162 | } 163 | 164 | _initInputElement (setting) { 165 | const el = document.createElement('input'); 166 | Object.entries({ 167 | type: 'file', 168 | accept: setting.accept, 169 | multiple: setting.multiple, 170 | hidden: true 171 | }).forEach(([key, value]) => { 172 | el[key] = value; 173 | })'' 174 | return el; 175 | } 176 | } 177 | ``` 178 | 179 | 看完上面的实现,有两点需要说明一下: 180 | 181 | 1. 为了考虑到`destroy()`的实现,我们需要在`this`属性上暂存`input`标签与绑定的事件。后续方便直接取来,解绑事件与去除dom。 182 | 2. 其实把`input`事件函数`changeHandler`单独抽离出去也可以,更方便维护。但是会有this指向问题,因为handler里我们希望将this指向本身实例,若抽离出去就需要使用`bind`绑定一下当前上下文。 183 | 184 | 上文中的`changeHanler`,来单独分析实现,这里我们要读取文件,响应实例choose事件,将文件列表作为参数传递给`loadFiles`。 185 | 186 | 为了更加贴合业务需求,可以通过事件返回结果来判断是中断,还是进入下一流程。 187 | 188 | ```javascript 189 | this.changeHandler = e => { 190 | const files = e.target.files; 191 | const ret = this._callHook('choose', files); 192 | if (ret !== false) { 193 | this.loadFiles(ret || e.target.files); 194 | } 195 | }; 196 | ``` 197 | 198 | 通过这样的实现,如果显式返回`false`,我们则不响应下一流程,否则拿返回结果||文件列表。这样我们就将判断**格式不符,超出大小限制**等等这样的逻辑交给上层实现,响应样式控制。如以下例子: 199 | 200 | ```javascript 201 | uploader.on('choose', files => { 202 | const overSize = [].some.call(files, item => item.size > 1024 * 1024 * 10) 203 | if (overSize) { 204 | setTips('有文件超出大小限制') 205 | return false; 206 | } 207 | return files; 208 | }); 209 | ``` 210 | 211 | #### 状态事件绑定与响应 212 | 213 | 简单实现上文提到的`_callHook`,将事件挂载在实例属性上。因为要涉及到单个choose事件结果控制。没有按照标准的发布/订阅模式的事件中心来做,有兴趣的同学可以看看[tiny-emitter](https://github.com/scottcorgan/tiny-emitter)的实现。 214 | 215 | ```javascript 216 | class Uploader { 217 | // ... 218 | on (evt, cb) { 219 | if (evt && typeof cb === 'function') { 220 | this['on' + evt] = cb; 221 | } 222 | return this; 223 | } 224 | 225 | _callHook (evt, ...args) { 226 | if (evt && this['on' + evt]) { 227 | return this['on' + evt].apply(this, args); 228 | } 229 | return; 230 | } 231 | } 232 | ``` 233 | 234 | #### 装载文件列表 - loadFiles 235 | 236 | 传进来文件列表参数,判断个数响应事件,其次就是要封装出内部列表的数据格式,方便追踪状态和对应对象,这里我们要用一个外部变量生成id,再根据`autoUpload`参数选择是否自动上传。 237 | 238 | ```javascript 239 | let uid = 1 240 | 241 | class Uploader { 242 | // ... 243 | loadFiles (files) { 244 | if (!files) return false; 245 | 246 | if (this.limit !== -1 && 247 | files.length && 248 | files.length + this.uploadFiles.length > this.limit 249 | ) { 250 | this._callHook('exceed', files); 251 | return false; 252 | } 253 | // 构建约定的数据格式 254 | this.uploadFiles = this.uploadFiles.concat([].map.call(files, file => { 255 | return { 256 | uid: uid++, 257 | rawFile: file, 258 | fileName: file.name, 259 | size: file.size, 260 | status: 'ready' 261 | } 262 | })) 263 | 264 | this._callHook('change', this.uploadFiles); 265 | this.setting.autoUpload && this.upload() 266 | 267 | return true 268 | } 269 | } 270 | ``` 271 | 272 | 到这里其实还没完善,因为`loadFiles`可以用于别的场景下添加文件,我们再增加些许类型判断代码。 273 | 274 | ```diff 275 | class Uploader { 276 | // ... 277 | loadFiles (files) { 278 | if (!files) return false; 279 | 280 | + const type = Object.prototype.toString.call(files) 281 | + if (type === '[object FileList]') { 282 | + files = [].slice.call(files) 283 | + } else if (type === '[object Object]' || type === '[object File]') { 284 | + files = [files] 285 | + } 286 | 287 | if (this.limit !== -1 && 288 | files.length && 289 | files.length + this.uploadFiles.length > this.limit 290 | ) { 291 | this._callHook('exceed', files); 292 | return false; 293 | } 294 | 295 | + this.uploadFiles = this.uploadFiles.concat(files.map(file => { 296 | + if (file.uid && file.rawFile) { 297 | + return file 298 | + } else { 299 | return { 300 | uid: uid++, 301 | rawFile: file, 302 | fileName: file.name, 303 | size: file.size, 304 | status: 'ready' 305 | } 306 | } 307 | })) 308 | 309 | this._callHook('change', this.uploadFiles); 310 | this.setting.autoUpload && this.upload() 311 | 312 | return true 313 | } 314 | } 315 | ``` 316 | 317 | #### 上传文件列表 - upload 318 | 319 | 这里可根据传进来的参数,判断是上传当前列表,还是单独重传一个,建议是每一个文件单独走一次接口(有助于失败时的文件追踪)。 320 | 321 | ```javascript 322 | upload (file) { 323 | if (!this.uploadFiles.length && !file) return; 324 | 325 | if (file) { 326 | const target = this.uploadFiles.find( 327 | item => item.uid === file.uid || item.uid === file 328 | ) 329 | target && target.status !== 'success' && this._post(target) 330 | } else { 331 | this.uploadFiles.forEach(file => { 332 | file.status === 'ready' && this._post(file) 333 | }) 334 | } 335 | } 336 | ``` 337 | 338 | 当中涉及到的`_post`函数,我们往下再单独实现。 339 | 340 | #### 交互方法 341 | 342 | 这里都是些供给外部操作的方法,实现比较简单就直接上代码了。 343 | 344 | ```javascript 345 | class Uploader { 346 | // ... 347 | chooseFile () { 348 | // 每次都需要清空value,否则同一文件不触发change 349 | this.input.value = '' 350 | this.input.click() 351 | } 352 | 353 | removeFile (file) { 354 | const id = file.id || file 355 | const index = this.uploadFiles.findIndex(item => item.id === id) 356 | if (index > -1) { 357 | this.uploadFiles.splice(index, 1) 358 | this._callHook('change', this.uploadFiles); 359 | } 360 | } 361 | 362 | clear () { 363 | this.uploadFiles = [] 364 | this._callHook('change', this.uploadFiles); 365 | } 366 | 367 | destroy () { 368 | this.input.removeEventHandler('change', this.changeHandler) 369 | this.setting.wrapper.removeChild(this.input) 370 | } 371 | // ... 372 | } 373 | ``` 374 | 375 | 有一点要注意的是,主动调用`chooseFile`,需要在用户交互之下才会触发选择文件框,就是说要在某个按钮点击事件回调里,进行调用`chooseFile`。否则会出现以下这样的提示: 376 | 377 | ![](https://user-gold-cdn.xitu.io/2020/3/1/170961f4e3232cce?w=954&h=114&f=png&s=29920) 378 | 379 | 写到这里,我们可以根据已有代码尝试一下,打印`upload`时的内部`uploadList`,结果正确。 380 | 381 | ![](https://user-gold-cdn.xitu.io/2020/3/1/170961fbd2df53ee?w=1076&h=452&f=png&s=93871) 382 | 383 | #### 发起请求 - _post 384 | 385 | 这个是比较关键的函数,我们用原生`XHR`实现,因为`fetch`并不支持`progress`事件。简单描述下要做的事: 386 | 387 | 1. 构建`FormData`,将文件与配置中的`data`进行添加。 388 | 2. 构建`xhr`,设置配置中的header、withCredentials,配置相关事件 389 | 390 | - onload事件:处理响应的状态,返回数据并改写文件列表中的状态,响应外部`change`等相关状态事件。 391 | - onerror事件:处理错误状态,改写文件列表,抛出错误,响应外部`error`事件 392 | - onprogress事件:根据返回的事件,计算好百分比,响应外部`onprogress`事件 393 | 394 | 3. 因为xhr的返回格式不太友好,我们需要额外编写两个函数处理http响应:`parseSuccess`、`parseError` 395 | 396 | ```javascript 397 | _post (file) { 398 | if (!file.rawFile) return 399 | 400 | const { headers, data, withCredentials } = this.setting 401 | const xhr = new XMLHttpRequest() 402 | const formData = new FormData() 403 | formData.append('file', file.rawFile, file.fileName) 404 | 405 | Object.keys(data).forEach(key => { 406 | formData.append(key, data[key]) 407 | }) 408 | Object.keys(headers).forEach(key => { 409 | xhr.setRequestHeader(key, headers[key]) 410 | }) 411 | 412 | file.status = 'uploading' 413 | 414 | xhr.withCredentials = !!withCredentials 415 | xhr.onload = () => { 416 | /* 处理响应 */ 417 | if (xhr.status < 200 || xhr.status >= 300) { 418 | file.status = 'error' 419 | this._callHook('error', parseError(xhr), file, this.uploadFiles) 420 | } else { 421 | file.status = 'success' 422 | this._callHook('success', parseSuccess(xhr), file, this.uploadFiles) 423 | } 424 | } 425 | 426 | xhr.onerror = e => { 427 | /* 处理失败 */ 428 | file.status = 'error' 429 | this._callHook('error', parseError(xhr), file, this.uploadFiles) 430 | } 431 | 432 | xhr.upload.onprogress = e => { 433 | /* 处理上传进度 */ 434 | const { total, loaded } = e 435 | e.percent = total > 0 ? loaded / total * 100 : 0 436 | this._callHook('progress', e, file, this.uploadFiles) 437 | } 438 | 439 | xhr.open('post', this.setting.url, true) 440 | xhr.send(formData) 441 | } 442 | ``` 443 | 444 | ##### parseSuccess 445 | 446 | 将响应体尝试JSON反序列化,失败的话再返回原样文本 447 | 448 | ```javascript 449 | const parseSuccess = xhr => { 450 | let response = xhr.responseText 451 | if (response) { 452 | try { 453 | return JSON.parse(response) 454 | } catch (error) {} 455 | } 456 | return response 457 | } 458 | ``` 459 | 460 | ##### parseError 461 | 462 | 同样的,JSON反序列化,此处还要抛出个错误,记录错误信息。 463 | 464 | ```javascript 465 | const parseError = xhr => { 466 | let msg = '' 467 | let { responseText, responseType, status, statusText } = xhr 468 | if (!responseText && responseType === 'text') { 469 | try { 470 | msg = JSON.parse(responseText) 471 | } catch (error) { 472 | msg = responseText 473 | } 474 | } else { 475 | msg = `${status} ${statusText}` 476 | } 477 | 478 | const err = new Error(msg) 479 | err.status = status 480 | return err 481 | } 482 | ``` 483 | 484 | 至此,一个完整的Upload类已经构造完成,整合下来大概200行代码多点,由于篇幅问题,[完整的代码](https://github.com/impeiran/Blog/blob/master/uploader/uploader.js)已放在个人github里。 485 | 486 | ### 测试与实践 487 | 488 | 写好一个类,当然是上手实践一下,由于测试代码并不是本文关键,所以采用截图的方式呈现。为了呈现良好的效果,把chrome里的network调成自定义降速,并在测试失败重传时,关闭网络。 489 | 490 | 491 | ![](https://user-gold-cdn.xitu.io/2020/3/1/170962ebabc3b945?w=480&h=305&f=gif&s=4454775) 492 | 493 | #### 服务端 494 | 495 | 这里用node搭建了一个小的http服务器,用`multiparty`处理文件接收。 496 | 497 | ![](https://user-gold-cdn.xitu.io/2020/3/1/17096214064f2f2b?w=1536&h=1442&f=png&s=290075) 498 | 499 | #### 客户端 500 | 501 | 简单的用html结合vue实现了一下,会发现将业务代码跟基础代码分开实现后,简洁明了不少 502 | 503 | ![](https://user-gold-cdn.xitu.io/2020/3/1/170962181a83adcf?w=1508&h=1908&f=png&s=382305) 504 | 505 | #### 拓展拖拽上传 506 | 507 | 拖拽上传注意两个事情就是 508 | 509 | 1. 监听drop事件,获取`e.dataTransfer.files` 510 | 2. 监听dragover事件,并执行`preventDefault()`,防止浏览器弹窗。 511 | 512 | ##### 更改客户端代码如下: 513 | 514 | 515 | ![](https://user-gold-cdn.xitu.io/2020/3/1/1709622ef252bcf7?w=974&h=808&f=png&s=98062) 516 | 517 | ##### 效果图GIF 518 | 519 | ![](https://user-gold-cdn.xitu.io/2020/3/1/17096220e064c2ff?w=640&h=451&f=gif&s=2500732) 520 | 521 | ### 优化与总结 522 | 523 | 本文涉及的全部源代码以及测试代码均已上传到[github仓库](https://github.com/impeiran/Blog/tree/master/uploader)中,有兴趣的同学可自行查阅。 524 | 525 | 代码当中还存在不少需要的优化项以及争论项,等待各位读者去斟酌改良: 526 | 527 | - 文件大小判断是否应该结合到类里面?看需求,因为有时候可能会有根据`.zip`压缩包的文件,可以允许更大的体积。 528 | - 是否应该提供可重写ajax函数的配置项? 529 | - 参数是否应该可传入一个函数动态确定? 530 | - ... 531 | -------------------------------------------------------------------------------- /uploader/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | project 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 | 18 | 19 | 20 |

21 | 22 | 25 | 26 | 27 | 28 | 31 | 32 | 33 | 34 | 35 | 36 | 39 | 40 | 41 |
42 |
43 | 78 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /uploader/index.js: -------------------------------------------------------------------------------- 1 | const http = require("http"); 2 | const path = require("path"); 3 | const fs = require("fs"); 4 | const multiparty = require("multiparty"); 5 | const server = http.createServer(); 6 | 7 | server.on("request", async (req, res) => { 8 | 9 | if (req.url === '/' || /\w+\.\w+$/.test(req.url)) { 10 | const filename = req.url === '/' ? 'index.html' : req.url.slice(1) 11 | fs.readFile(filename, (err, data) => { 12 | if (err) { 13 | res.writeHead(404) 14 | res.end() 15 | } 16 | res.end(data) 17 | }) 18 | } 19 | 20 | if (req.url === '/upload' && req.method === 'POST') { 21 | const form = new multiparty.Form({ 22 | uploadDir: path.resolve(__dirname, 'static') 23 | }); 24 | 25 | form.parse(req, (err, fields, files) => { 26 | if (err) return 27 | 28 | files.file.forEach(item => { 29 | fs.rename(item.path, './static/' + new Date().getTime() + item.originalFilename, () => {}) 30 | }) 31 | 32 | res.end(JSON.stringify({ 33 | success: 1, 34 | data: fields || {} 35 | })); 36 | }); 37 | } 38 | }); 39 | 40 | server.listen(3000, () => console.log("application is listening at http://localhost:3000")); -------------------------------------------------------------------------------- /uploader/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "mce.js", 6 | "scripts": { 7 | "start": "node index.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "multiparty": "^4.2.1" 15 | } 16 | } -------------------------------------------------------------------------------- /uploader/static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/impeiran/Blog/106e1b8b32704846fa3d5f674c36bd95dcdc3248/uploader/static/.gitkeep -------------------------------------------------------------------------------- /uploader/uploader.js: -------------------------------------------------------------------------------- 1 | let uid = 1 2 | 3 | const parseError = xhr => { 4 | let msg = '' 5 | let { responseText, responseType, status, statusText } = xhr 6 | if (!responseText && responseType === 'text') { 7 | try { 8 | msg = JSON.parse(responseText) 9 | } catch (error) { 10 | msg = responseText 11 | } 12 | } else { 13 | msg = `${status} ${statusText}` 14 | } 15 | 16 | const err = new Error(msg) 17 | err.status = status 18 | return err 19 | } 20 | 21 | const parseSuccess = xhr => { 22 | let response = xhr.responseText 23 | if (response) { 24 | try { 25 | return JSON.parse(response) 26 | } catch (error) {} 27 | } 28 | 29 | return response 30 | } 31 | 32 | class Uploader { 33 | constructor (option = {}) { 34 | 35 | const defaultOption = { 36 | url: '', 37 | 38 | wrapper: document.body, 39 | multiple: false, 40 | limit: -1, 41 | autoUpload: true, 42 | accept: '*', 43 | 44 | headers: {}, 45 | data: {}, 46 | withCredentials: false 47 | } 48 | 49 | this.setting = Object.assign(defaultOption, option) 50 | 51 | this._init() 52 | } 53 | 54 | _init () { 55 | this.uploadFiles = [] 56 | 57 | this.input = this._initInputElement(this.setting) 58 | 59 | this.changeHandler = e => { 60 | const files = e.target.files 61 | const ret = this._callHook('choose', files) 62 | if (ret !== false) { 63 | this.loadFiles(ret || e.target.files) 64 | } 65 | } 66 | 67 | this.input.addEventListener('change', this.changeHandler) 68 | 69 | this.setting.wrapper.appendChild(this.input) 70 | } 71 | 72 | _initInputElement (setting) { 73 | const el = document.createElement('input') 74 | 75 | Object.entries({ 76 | type: 'file', 77 | accept: setting.accept, 78 | multiple: setting.multiple, 79 | hidden: true 80 | }).forEach(([key, value]) => { 81 | el[key] = value 82 | }) 83 | 84 | return el; 85 | } 86 | 87 | on (evt, cb) { 88 | if (evt && typeof cb === 'function') { 89 | this['on' + evt] = cb; 90 | } 91 | return this; 92 | } 93 | 94 | _callHook (evt, ...args) { 95 | if (evt && this['on' + evt]) { 96 | return this['on' + evt].apply(this, args) 97 | } 98 | 99 | return; 100 | } 101 | 102 | chooseFile () { 103 | this.input.value = '' 104 | this.input.click() 105 | } 106 | 107 | loadFiles (files) { 108 | if (!files) return false; 109 | 110 | const type = Object.prototype.toString.call(files) 111 | if (type === '[object FileList]') { 112 | files = [].slice.call(files) 113 | } else if (type === '[object Object]' || type === '[object File]') { 114 | files = [files] 115 | } 116 | 117 | if (this.limit !== -1 && 118 | files.length && 119 | files.length + this.uploadFiles.length > this.limit 120 | ) { 121 | this._callHook('exceed', files); 122 | return false; 123 | } 124 | 125 | this.uploadFiles = this.uploadFiles.concat(files.map(file => { 126 | if (file.uid && file.rawFile) { 127 | return file 128 | } else { 129 | return { 130 | uid: uid++, 131 | rawFile: file, 132 | fileName: file.name, 133 | size: file.size, 134 | status: 'ready' 135 | } 136 | } 137 | })) 138 | 139 | this._callHook('change', this.uploadFiles); 140 | this.setting.autoUpload && this.upload() 141 | 142 | return true 143 | } 144 | 145 | removeFile (file) { 146 | const id = file.id || file 147 | const index = this.uploadFiles.findIndex(item => item.id === id) 148 | if (index > -1) { 149 | this.uploadFiles.splice(index, 1) 150 | this._callHook('change', this.uploadFiles); 151 | } 152 | } 153 | 154 | clear () { 155 | this.uploadFiles = [] 156 | this._callHook('change', this.uploadFiles); 157 | } 158 | 159 | upload (file) { 160 | if (!this.uploadFiles.length && !file) return; 161 | 162 | if (file) { 163 | const target = this.uploadFiles.find(item => item.uid === file.uid || item.uid === file) 164 | target && target.status !== 'success' && this._post(target) && console.log(111) 165 | } else { 166 | this.uploadFiles.forEach(file => { 167 | file.status === 'ready' && this._post(file) 168 | }) 169 | } 170 | } 171 | 172 | _post (file) { 173 | if (!file.rawFile) return 174 | 175 | const { headers, data, withCredentials } = this.setting 176 | const xhr = new XMLHttpRequest() 177 | 178 | const formData = new FormData() 179 | formData.append('file', file.rawFile, file.fileName) 180 | 181 | Object.keys(data).forEach(key => { 182 | formData.append(key, data[key]) 183 | }) 184 | Object.keys(headers).forEach(key => { 185 | xhr.setRequestHeader(key, headers[key]) 186 | }) 187 | 188 | file.status = 'uploading' 189 | 190 | xhr.withCredentials = !!withCredentials 191 | 192 | xhr.onload = () => { 193 | if (xhr.status < 200 || xhr.status >= 300) { 194 | file.status = 'error' 195 | this._callHook('error', parseError(xhr), file, this.uploadFiles) 196 | } else { 197 | file.status = 'success' 198 | this._callHook('success', parseSuccess(xhr), file, this.uploadFiles) 199 | } 200 | } 201 | 202 | xhr.onerror = e => { 203 | file.status = 'error' 204 | this._callHook('error', parseError(xhr), file, this.uploadFiles) 205 | } 206 | 207 | xhr.upload.onprogress = e => { 208 | const { total, loaded } = e 209 | e.percent = total > 0 ? loaded / total * 100 : 0 210 | this._callHook('progress', e, file, this.uploadFiles) 211 | } 212 | 213 | xhr.open('post', this.setting.url, true) 214 | xhr.send(formData) 215 | } 216 | 217 | destroy () { 218 | this.input.removeEventHandler('change', this.changeHandler) 219 | this.setting.wrapper.removeChild(this.input) 220 | } 221 | } -------------------------------------------------------------------------------- /uploader/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | depd@~1.1.2: 6 | version "1.1.2" 7 | resolved "https://registry.npm.taobao.org/depd/download/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" 8 | integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= 9 | 10 | fd-slicer@1.1.0: 11 | version "1.1.0" 12 | resolved "https://registry.npm.taobao.org/fd-slicer/download/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" 13 | integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4= 14 | dependencies: 15 | pend "~1.2.0" 16 | 17 | http-errors@~1.7.0: 18 | version "1.7.3" 19 | resolved "https://registry.npm.taobao.org/http-errors/download/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" 20 | integrity sha1-bGGeT5xgMIw4UZSYwU+7EKrOuwY= 21 | dependencies: 22 | depd "~1.1.2" 23 | inherits "2.0.4" 24 | setprototypeof "1.1.1" 25 | statuses ">= 1.5.0 < 2" 26 | toidentifier "1.0.0" 27 | 28 | inherits@2.0.4: 29 | version "2.0.4" 30 | resolved "https://registry.npm.taobao.org/inherits/download/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 31 | integrity sha1-D6LGT5MpF8NDOg3tVTY6rjdBa3w= 32 | 33 | multiparty@^4.2.1: 34 | version "4.2.1" 35 | resolved "https://registry.npm.taobao.org/multiparty/download/multiparty-4.2.1.tgz#d9b6c46d8b8deab1ee70c734b0af771dd46e0b13" 36 | integrity sha1-2bbEbYuN6rHucMc0sK93HdRuCxM= 37 | dependencies: 38 | fd-slicer "1.1.0" 39 | http-errors "~1.7.0" 40 | safe-buffer "5.1.2" 41 | uid-safe "2.1.5" 42 | 43 | pend@~1.2.0: 44 | version "1.2.0" 45 | resolved "https://registry.npm.taobao.org/pend/download/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" 46 | integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= 47 | 48 | random-bytes@~1.0.0: 49 | version "1.0.0" 50 | resolved "https://registry.npm.taobao.org/random-bytes/download/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b" 51 | integrity sha1-T2ih3Arli9P7lYSMMDJNt11kNgs= 52 | 53 | safe-buffer@5.1.2: 54 | version "5.1.2" 55 | resolved "https://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 56 | integrity sha1-mR7GnSluAxN0fVm9/St0XDX4go0= 57 | 58 | setprototypeof@1.1.1: 59 | version "1.1.1" 60 | resolved "https://registry.npm.taobao.org/setprototypeof/download/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" 61 | integrity sha1-fpWsskqpL1iF4KvvW6ExMw1K5oM= 62 | 63 | "statuses@>= 1.5.0 < 2": 64 | version "1.5.0" 65 | resolved "https://registry.npm.taobao.org/statuses/download/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" 66 | integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= 67 | 68 | toidentifier@1.0.0: 69 | version "1.0.0" 70 | resolved "https://registry.npm.taobao.org/toidentifier/download/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" 71 | integrity sha1-fhvjRw8ed5SLxD2Uo8j013UrpVM= 72 | 73 | uid-safe@2.1.5: 74 | version "2.1.5" 75 | resolved "https://registry.npm.taobao.org/uid-safe/download/uid-safe-2.1.5.tgz#2b3d5c7240e8fc2e58f8aa269e5ee49c0857bd3a" 76 | integrity sha1-Kz1cckDo/C5Y+Komnl7knAhXvTo= 77 | dependencies: 78 | random-bytes "~1.0.0" 79 | -------------------------------------------------------------------------------- /vue2-reactivity-data/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 |
计数器:{{counter}}
12 |
当前时间戳:{{currentDate}}
13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 43 | 44 | -------------------------------------------------------------------------------- /vue2-reactivity-data/observe.js: -------------------------------------------------------------------------------- 1 | let depId = 0 2 | class Dep { 3 | constructor () { 4 | this.id = depId++ 5 | this.subs = [] 6 | } 7 | 8 | addSub (sub) { 9 | this.subs.push(sub) 10 | } 11 | 12 | removeSub (sub) { 13 | const index = this.subs.findIndex(sub) 14 | if (index !== -1) { 15 | this.subs.splice(index, 1) 16 | } 17 | } 18 | 19 | depend () { 20 | if (Dep.target) { 21 | Dep.target.addDep(this) 22 | } 23 | } 24 | 25 | notify () { 26 | this.subs.forEach(sub => { 27 | sub.update() 28 | }) 29 | } 30 | } 31 | Dep.target = null 32 | 33 | const targetStack = [] 34 | 35 | function pushTarget (target) { 36 | targetStack.push(target) 37 | Dep.target = target 38 | } 39 | 40 | function popTarget () { 41 | targetStack.pop() 42 | Dep.target = targetStack[targetStack.length - 1] 43 | } 44 | 45 | // 将状态代理到实例上 46 | function proxy (source, sourceKey, k) { 47 | Object.defineProperty(source, k, { 48 | enumerable: true, 49 | configurable: true, 50 | get: function () { 51 | return this[sourceKey][k] 52 | }, 53 | set: function (val) { 54 | this[sourceKey][k] = val 55 | } 56 | }) 57 | } 58 | 59 | 60 | // 递归遍历 劫持属性 61 | function observe (obj) { 62 | const keys = Object.keys(obj) 63 | for (const key of keys) { 64 | const dep = new Dep() 65 | 66 | let val = obj[key] 67 | 68 | if (Object.prototype.toString.call(val) === 'object') { 69 | observe(val) 70 | } 71 | 72 | Object.defineProperty(obj, key, { 73 | enumerable: true, 74 | configurable: true, 75 | get: function () { 76 | if (Dep.target) { 77 | dep.depend() 78 | } 79 | return val 80 | }, 81 | 82 | set: function (newVal) { 83 | if (newVal === val) return 84 | val = newVal 85 | dep.notify() 86 | } 87 | }) 88 | } 89 | } -------------------------------------------------------------------------------- /vue2-reactivity-data/vue.js: -------------------------------------------------------------------------------- 1 | class Vue { 2 | constructor (option) { 3 | this._option = option 4 | this._el = document.querySelector(option.el) 5 | this._template = this._el.innerHTML 6 | 7 | this._initState(this) 8 | 9 | new Watcher(this, function update () { 10 | this._mount() 11 | }) 12 | } 13 | 14 | _initState () { 15 | const data = this._data = this._option.data 16 | ? this._option.data() : {} 17 | 18 | const keys = Object.keys(data) 19 | let i = keys.length 20 | while (i--) { 21 | const key = keys[i] 22 | proxy(this, '_data', key) 23 | } 24 | observe(data) 25 | } 26 | 27 | _mount () { 28 | const _this = this 29 | let template = _this._template 30 | 31 | // 替换差值表达式 32 | let matchText 33 | while ((matchText = /\{\{((\w)+?)\}\}/.exec(template))) { 34 | template = template.replace(matchText[0], _this._data[matchText[1]]) 35 | } 36 | 37 | _this._el.innerHTML = template 38 | } 39 | } -------------------------------------------------------------------------------- /vue2-reactivity-data/watcher.js: -------------------------------------------------------------------------------- 1 | let watchId = 0 2 | class Watcher { 3 | constructor (vm, expOrFn) { 4 | this.id = watchId++ 5 | this.vm = vm 6 | this.getter = expOrFn 7 | 8 | // 用于处理依赖 9 | this.deps = [] 10 | this.newDeps = [] 11 | this.depIds = new Set() 12 | this.newDepIds = new Set() 13 | 14 | this.value = this.get() 15 | } 16 | 17 | addDep (dep) { 18 | const id = dep.id 19 | if (!this.newDepIds.has(id)) { 20 | this.newDepIds.add(id) 21 | this.newDeps.push(dep) 22 | if (!this.depIds.has(id)) { 23 | dep.addSub(this) 24 | } 25 | } 26 | } 27 | 28 | cleanupDeps () { 29 | let i = this.deps.length 30 | while (i--) { 31 | const dep = this.deps[i] 32 | if (!this.newDepIds.has(dep.id)) { 33 | dep.removeSub(this) 34 | } 35 | } 36 | let tmp = this.depIds 37 | this.depIds = this.newDepIds 38 | this.newDepIds = tmp 39 | this.newDepIds.clear() 40 | tmp = this.deps 41 | this.deps = this.newDeps 42 | this.newDeps = tmp 43 | this.newDeps.length = 0 44 | } 45 | 46 | get () { 47 | pushTarget(this) 48 | const value = this.getter.call(this.vm) 49 | popTarget() 50 | 51 | return value 52 | } 53 | 54 | update () { 55 | Promise.resolve().then(() => { 56 | this.get() 57 | }) 58 | } 59 | } --------------------------------------------------------------------------------