├── .gitignore ├── README.md ├── code ├── algorithm │ ├── search │ │ └── binary-search.js │ └── sort │ │ ├── bubble.js │ │ ├── insert.js │ │ ├── quick.js │ │ └── selection.js ├── bigData │ └── hive.md ├── canvas-smooth-curve │ ├── canvas-smooth-curve-1.js │ ├── canvas-smooth-curve-2.js │ └── canvas-smooth-curve.md ├── clickhouse │ └── clickhouse.md ├── clipboard │ └── clipboard.js ├── code-snippet │ └── code-snippet.md ├── detect-leave-page │ └── detect-leave-page.md ├── detect-page-visibility │ └── detect-page-visibility.md ├── dom-operation │ ├── dom-operation.js │ └── render-dom-template.js ├── elasticsearch │ └── elasticsearch.md ├── gulp-config │ └── gulpfile.js ├── hide-scroll-bar │ └── hide-scroll-bar.html ├── image-processing │ ├── convertBase64ToBlob.js │ ├── imgCompression.js │ ├── previewImg.js │ └── resetImgOrientation.js ├── img-to-base64 │ └── img-to-base64.js ├── kafka │ └── kafka.md ├── linux │ ├── debug.md │ ├── env-variable-config.md │ ├── linux-init-simple.sh │ ├── linux-init.sh │ ├── linux-shell-example.md │ ├── linux.md │ └── lrzsz.md ├── mobile-meta │ └── mobile-meta.html ├── mysql │ └── mysql.md ├── nginx │ └── nginx.md ├── parse-string-path-to-tree │ ├── data.js │ └── parse-string-path-to-tree.js ├── puppeteer │ └── puppeteer.md ├── rich-text-editor │ ├── getCursorPosition.js │ └── getFontStyle.js ├── rollup-tpl │ └── rollup-tpl.md ├── rsyslog │ └── rsyslog.md ├── simple-mvvm │ ├── README.md │ ├── index.html │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ ├── index.js │ │ └── mvvm │ │ │ ├── compiler │ │ │ ├── compilerUtil.js │ │ │ ├── index.js │ │ │ └── updater.js │ │ │ ├── dep.js │ │ │ ├── index.js │ │ │ ├── observer.js │ │ │ └── watcher.js │ └── yarn.lock ├── smartCode │ ├── getDeepObjectData.js │ ├── getMonthDays.js │ ├── gradientColors.js │ └── throttle-debounce.js ├── wakeUpApp │ └── wakeUpMap.html ├── weChat │ └── fullScreenVideo.html └── website │ └── website.md ├── notes ├── ajax.md ├── java.md ├── notes.md ├── readding-notes │ ├── 深入理解 tcp 协议.md │ └── 编写可读代码的艺术.md └── rollup.md ├── solution ├── H5-animation │ ├── H5-animation.md │ └── principles-for-smooth-animation.md ├── bayesian │ ├── bayesian.md │ └── img │ │ ├── compute.png │ │ ├── deviceinfo.png │ │ └── evercookie.png ├── bury │ └── bury.md ├── deep-link │ └── deep-link.md ├── docker │ └── docker.md ├── dynamic-insert-js │ └── dynamic-insert-js.md ├── facebook-login │ └── facebook-login.md ├── fileDownload │ └── fileDownload.md ├── fileUpload │ └── fileUpload.md ├── git-multi-ssh │ └── git-multi-ssh.md ├── inspect-webview │ └── inspect-webview.md ├── install-llvm │ └── install-llvm.md ├── install-nodejs │ └── install-nodejs.md ├── linux-send-mail │ └── linux-send-mail.md ├── load-time-summary │ └── load-time-summary.md ├── mocha-es6-in-browser │ ├── mocha-es6-in-browser.md │ └── project │ │ ├── package.json │ │ └── test │ │ ├── index.html │ │ ├── test-code │ │ └── index.test.js │ │ └── webpack.config.js ├── network-information │ └── network-information.md ├── nodejs-framework-design │ └── nodejs-framework-design.md ├── npm-system-cmd │ └── npm-system-cmd.md ├── performance-optimization │ ├── performance-optimization.md │ └── 前端性能优化概述.xmind ├── react-css-modules │ └── react-css-modules.md ├── react │ └── react.md ├── screenshot │ └── screenshot.md ├── typescript │ └── typescript.md ├── unit-test │ └── unit-test.md ├── upgrade-apache │ └── upgrade-apache.md └── upgrade-gcc │ └── upgrade-gcc.md ├── system ├── env-config-file.md ├── mac-auto-proxy.md ├── mac.md └── windows-remote-access.md └── weird-problem └── weird-problem.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode 3 | test* 4 | /**/*/node_modules 5 | /**/*/dist -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Code-Collection 2 | 3 | - 个人博客:[前端杂货铺](https://segmentfault.com/blog/whale) 4 | - [问题的解决方案](#解决方案) 5 | - [古怪问题汇总](#古怪问题) 6 | - [代码片段](#代码片段) 7 | - [算法理解](#算法理解) 8 | - [读书笔记](#读书笔记) 9 | - [其他](#其他) 10 | 11 | ### 解决方案 12 | 13 | - [h5 动画性能优化](https://github.com/Youjingyu/Code-Collection/tree/master/solution/H5-animation) 14 | - [前端性能优化](https://github.com/Youjingyu/Code-Collection/blob/master/solution/performance-optimization) 15 | - [基于贝叶斯推断的用户唯一性识别](https://github.com/Youjingyu/Code-Collection/blob/master/solution/bayesian/bayesian.md) 16 | - [动态插入 js 并按顺序执行](https://github.com/Youjingyu/Code-Collection/blob/master/solution/dynamic-insert-js/dynamic-insert-js.md) 17 | - [linux 定时邮件](https://github.com/Youjingyu/Code-Collection/blob/master/solution/linux-send-mail/linux-send-mail.md) 18 | - [基于 npm 的系统 cli](https://github.com/Youjingyu/Code-Collection/blob/master/solution/npm-system-cmd/npm-system-cmd.md) 19 | - [nodejs 编译安装](https://github.com/Youjingyu/Code-Collection/blob/master/solution/install-nodejs/install-nodejs.md) 20 | - [windows 调试安卓 webview](https://github.com/Youjingyu/Code-Collection/blob/master/solution/inspect-webview/inspect-webview.md) 21 | - [升级 gcc](https://github.com/Youjingyu/Code-Collection/blob/master/solution/upgrade-gcc/upgrade-gcc.md) 22 | - [安装 llvm](https://github.com/Youjingyu/Code-Collection/blob/master/solution/install-llvm/install-llvm.md) 23 | - [git 多 ssh 账户](https://github.com/Youjingyu/Code-Collection/blob/master/solution/git-multi-ssh/git-multi-ssh.md) 24 | - [页面埋点](https://github.com/Youjingyu/Code-Collection/blob/master/solution/load-time-summary/load-time-summary.md) 25 | - [获取网络信息](https://github.com/Youjingyu/Code-Collection/blob/master/solution/network-information/network-information.md) 26 | - [页面截图](https://github.com/Youjingyu/Code-Collection/blob/master/solution/screenshot/screenshot.md) 27 | - [文件下载](https://github.com/Youjingyu/Code-Collection/blob/master/solution/fileDownload/fileDownload.md) 28 | - [文件上传](https://github.com/Youjingyu/Code-Collection/blob/master/solution/fileUpload/fileUpload.md) 29 | 30 | ### 古怪问题 31 | 32 | - [古怪问题汇总](https://github.com/Youjingyu/Code-Collection/blob/master/weird-problem/weird-problem.md) 33 | 34 | ### 代码片段 35 | 36 | - [图片预处理](https://github.com/Youjingyu/Code-Collection/tree/master/code/image-processing) 37 | - [mvvm 实现](https://github.com/Youjingyu/Code-Collection/tree/master/code/simple-mvvm) 38 | - [dom 操作封装](https://github.com/Youjingyu/Code-Collection/tree/master/code/dom-operation) 39 | - [剪切板操纵](https://github.com/Youjingyu/Code-Collection/tree/master/code/clipboard/clipboard.js) 40 | - [mocha 在浏览器中测试 es6 代码](https://github.com/Youjingyu/Code-Collection/blob/master/solution/mocha-es6-in-browser/project) 41 | - [监听用户离开页面](https://github.com/Youjingyu/Code-Collection/tree/master/code/detect-leave-page/detect-leave-page.md) 42 | - [监听页面是否可见](https://github.com/Youjingyu/Code-Collection/blob/master/code/detect-page-visibility/detect-page-visibility.md) 43 | - [gulp 配置](https://github.com/Youjingyu/Code-Collection/tree/master/code/gulp-config) 44 | - [kafka](https://github.com/Youjingyu/Code-Collection/blob/master/code/kafka/kafka.md) 45 | - [clickhouse](https://github.com/Youjingyu/Code-Collection/blob/master/code/clickhouse/clickhouse.md) 46 | - [elasticsearch](https://github.com/Youjingyu/Code-Collection/blob/master/code/elasticsearch/elasticsearch.md) 47 | - [mysql](https://github.com/Youjingyu/Code-Collection/blob/master/code/mysql/mysql.md) 48 | - [linux](https://github.com/Youjingyu/Code-Collection/blob/master/code/linux) 49 | - [nginx](https://github.com/Youjingyu/Code-Collection/blob/master/code/nginx/nginx.md) 50 | - [rsyslog](https://github.com/Youjingyu/Code-Collection/blob/master/code/rsyslog/rsyslog.md) 51 | - [隐藏浏览器滚动条](https://github.com/Youjingyu/Code-Collection/blob/master/code/hide-scroll-bar/hide-scroll-bar.html) 52 | - [meta 标签配置](https://github.com/Youjingyu/Code-Collection/blob/master/code/mobile-meta/mobile-meta.html) 53 | - [路径解析为树形结构](https://github.com/Youjingyu/Code-Collection/blob/master/code/parse-string-path-to-tree/parse-string-path-to-tree.js) 54 | - [富文本编辑](https://github.com/Youjingyu/Code-Collection/tree/master/code/rich-text-editor) 55 | - [工具代码](https://github.com/Youjingyu/Code-Collection/tree/master/code/smartCode) 56 | - [h5 唤醒 app](https://github.com/Youjingyu/Code-Collection/tree/master/code/wakeUpApp) 57 | - [微信相关](https://github.com/Youjingyu/Code-Collection/tree/master/code/weChat) 58 | - [网站相关](https://github.com/Youjingyu/Code-Collection/tree/master/code/website) 59 | - [图片转 base64](https://github.com/Youjingyu/Code-Collection/blob/master/code/img-to-base64/img-to-base64.js) 60 | - [绘制平滑的 canvas 曲线](https://github.com/Youjingyu/Code-Collection/tree/master/code/canvas-smooth-curve) 61 | 62 | ### 算法理解 63 | - 排序 64 | - [冒泡排序](https://github.com/Youjingyu/Code-Collection/tree/master/code/algorithm/sort/bubble.js) 65 | - [快速排序](https://github.com/Youjingyu/Code-Collection/tree/master/code/algorithm/sort/quick.js) 66 | - [插入排序](https://github.com/Youjingyu/Code-Collection/tree/master/code/algorithm/sort/insert.js) 67 | - [选择排序](https://github.com/Youjingyu/Code-Collection/tree/master/code/algorithm/sort/secletion.js) 68 | - 查找 69 | - [二分查找](https://github.com/Youjingyu/Code-Collection/tree/master/code/algorithm/search/binary-search.js) 70 | 71 | ### 读书笔记 72 | 73 | - [编写可读代码的艺术](https://github.com/Youjingyu/Code-Collection/blob/master/notes/readding-notes/编写可读代码的艺术.md) 74 | 75 | ### 其他 76 | 77 | - [笔记](https://github.com/Youjingyu/Code-Collection/blob/master/notes/notes.md) 78 | - [电脑配置](https://github.com/Youjingyu/Code-Collection/tree/master/system) 79 | - [基于正态分布的前端性能数据分析(一)](https://zhuanlan.zhihu.com/p/32482391) 80 | - [基于正态分布的前端性能数据分析(二)](https://zhuanlan.zhihu.com/p/32682197) 81 | - [快应用开发框架 vue-hap-tools 实现原理](https://zhuanlan.zhihu.com/p/36304914) 82 | - [Ajax 入门](https://github.com/Youjingyu/Code-Collection/blob/master/notes/ajax.md) 83 | - [rollup 入门](https://github.com/Youjingyu/Code-Collection/blob/master/notes/rollup.md) 84 | -------------------------------------------------------------------------------- /code/algorithm/search/binary-search.js: -------------------------------------------------------------------------------- 1 | // 需要循环的次数最差等于 arr.length 能够除对少次 2 2 | // 也就是时间复杂度为 2 为底的对数,最终是 O(log n) 3 | function binarySearch (target, arr) { 4 | let start = arr[0] 5 | let end = arr.length - 1 6 | while (start <= end) { 7 | let mid = Math.floor((start + end) / 2) 8 | if (target === arr[mid]) return mid 9 | if (target > arr[mid]) start = mid + 1 10 | if (target < arr[mid]) end = mid - 1 11 | } 12 | return -1 13 | } 14 | 15 | console.log(binarySearch(7, [ 0, 2, 9, 10, 12, 15, 29, 40, 50 ])) 16 | -------------------------------------------------------------------------------- /code/algorithm/sort/bubble.js: -------------------------------------------------------------------------------- 1 | // 冒泡排序 2 | // 1. 依次比较相邻的两个数,如果不符合排序规则,则调换两个数的位置。这样一遍比较下来,能够保证最大(或最小)的数排在最后一位。 3 | // 2. 再对最后一位以外的数组,重复前面的过程,直至全部排序完成。 4 | // 时间复杂度:平均o(n^2) 最差o(n^2) 最好o(n) 5 | // 空间复杂度:o(1) 6 | function bubble (arr) { 7 | const len = arr.length 8 | for (let i = 0; i < len; i++) { 9 | for (let j = 0; j < len - 1 - i; j++) { 10 | const temp = arr[j] 11 | if (temp > arr[j + 1]) { 12 | arr[j] = arr[j + 1] 13 | arr[j + 1] = temp 14 | } 15 | } 16 | } 17 | return arr 18 | } 19 | // n -1 + n - 2 + n - 3 + ... + n - (n - 1) = n^2 + n*(n - 1)/2 20 | 21 | // 优化:记住最后一次交换的位置pos,下次交换到pos截止 22 | function bubbleOptimize1 (arr) { 23 | const len = arr.length 24 | let pos = len - 1 25 | for (let i = 0; i < len; i++) { 26 | // 这里需要保存pos,不直接用pos作为j循环的边界 27 | // 因为pos在当前循环中会改变 28 | let stop = pos 29 | for (let j = 0; j < stop; j++) { 30 | const temp = arr[j] 31 | if (temp > arr[j + 1]) { 32 | arr[j] = arr[j + 1] 33 | arr[j + 1] = temp 34 | pos = j 35 | } 36 | } 37 | } 38 | return arr 39 | } 40 | 41 | // 优化: 42 | console.log(bubbleOptimize1([9, 3, 5, 3, 7, 9, 6, 4, 6, 0, 1, 5, 32, 67, 238, 81, 12, 345, 54, 34])) 43 | -------------------------------------------------------------------------------- /code/algorithm/sort/insert.js: -------------------------------------------------------------------------------- 1 | // 插入排序 2 | // 第一个元素视为已排序数组,选出后面的第一个元素插入已排序数组 3 | function insert (arr) { 4 | const len = arr.length 5 | let sortedArrLen = 1 6 | for (let i = 1; i < len; i++) { 7 | // 关键:insertIndex用于标记已经插入顺序数组的位置 8 | let insertIndex = i 9 | for (let j = sortedArrLen - 1; j >= 0; j--) { 10 | // 只要在顺序数组中找到比自己大的,就插入 11 | // 但不一定插入了最小的位置,所以需要继续遍历 12 | if (arr[j] > arr[insertIndex]) { 13 | const temp = arr[j] 14 | arr[j] = arr[insertIndex] 15 | arr[insertIndex] = temp 16 | insertIndex = j 17 | } 18 | } 19 | sortedArrLen++ 20 | } 21 | return arr 22 | } 23 | 24 | // 使用二分查找找到插入位置,再插入 25 | function insertOptimize (arr) { 26 | const len = arr.length 27 | let sortedArrLen = 1 28 | for (let i = 1; i < len; i++) { 29 | let left = 0 30 | let right = sortedArrLen - 1 31 | let itemToInsert = arr[i] 32 | while (left <= right) { 33 | const mid = Math.floor((left + right) / 2) 34 | if (itemToInsert > arr[mid]) { 35 | left = mid + 1 36 | } else { 37 | right = mid - 1 38 | } 39 | } 40 | sortedArrLen++ 41 | } 42 | return arr 43 | } 44 | 45 | console.log(insert([9, 3, 5, 3, 7, 9, 6, 4, 6, 0, 1, 5, 32, 67, 238, 81, 12, 345, 54, 34])) -------------------------------------------------------------------------------- /code/algorithm/sort/quick.js: -------------------------------------------------------------------------------- 1 | // 快排 2 | // 快排必须要用到递归,采用分而治之的思路 3 | // 选择一个基准值(通常选最中间的值,一定概率上可以降低复杂度) 4 | // 声明两个数组 less、greater,比基准值大的放入 greater,小的放入 less 5 | // 递归地再对 less、greater 进行相同的操作,并把结果 concat 起来得到最终结果 6 | // 其实每次递归,都需要 n 次操作,但是总递归次数,取决于基准值的选择 7 | // 比如对于已排序的数组,选择 arr[0] 做基准值 8 | // 那每次递归,只有一个元素放到 less,剩余的元素都放到 greater 9 | // 也就是最终的递归次数是 n,时间复杂度为 O(n^2) 10 | // 如果选择 arr[Math.floor(arr.length / 2)] 作为基准值, 11 | // 那 less、greater 每次都分别能放入一半的次数 12 | // 也就总递归次数为 2 为底的对数,时间复杂度是 n*O(log n) 13 | 14 | // 时间复杂度:平均n*O(log n) 最差O(n^2) 最好n*O(log n) 15 | // 空间复杂度 O(log n) 16 | function quickSort (arr) { 17 | if (arr.length < 2) return arr 18 | const midIndex = Math.floor(arr.length / 2) 19 | const mid = arr[midIndex] 20 | // 需要从数组中移除元素,因为下面的大小写判断是用的 else 21 | // 不移除的话,该元素会重复进入数组 22 | arr.splice(midIndex, 1) 23 | const less = [] 24 | const greater = [] 25 | for (let i = 0; i < arr.length; i++) { 26 | if (arr[i] < mid) less.push(arr[i]) 27 | else greater.push(arr[i]) 28 | } 29 | return quickSort(less).concat([mid], quickSort(greater)) 30 | } 31 | 32 | console.log(quickSort([2, 9, 0, 40, 50, 29, 12, 15, 10])) 33 | -------------------------------------------------------------------------------- /code/algorithm/sort/selection.js: -------------------------------------------------------------------------------- 1 | // 选择排序 2 | // 挑出后续最小的元素,放到最前面 3 | // 时间复杂度:平均o(n^2) 最差o(n^2) 最好o(n^2) 4 | // 空间复杂度:o(1) 5 | function selectoin1 (arr) {w 6 | const len = arr.length 7 | for (let i = 0; i < len; i++) { 8 | // 关键:记住最小元素的index,而不是值 9 | let minIndex = i 10 | for (let j = i + 1; j < len; j++) { 11 | if (arr[minIndex] > arr[j]) minIndex = j 12 | } 13 | if (minIndex !== i) { 14 | const temp = arr[i] 15 | arr[i] = arr[minIndex] 16 | arr[minIndex] = temp 17 | } 18 | } 19 | return arr 20 | } 21 | // 22 | 23 | // 这种实现,交换次数会变多 24 | function selectoin2 (arr) { 25 | const len = arr.length 26 | for (let i = 0; i < len; i++) { 27 | // 找出后面最小的 28 | for (let j = i + 1; j < len; j++) { 29 | const tempI = arr[i] 30 | const tempJ = arr[j] 31 | if (tempI > tempJ) { 32 | arr[i] = tempJ 33 | arr[j] = tempI 34 | } 35 | } 36 | } 37 | return arr 38 | } 39 | 40 | console.log(selectoin1([9, 3, 5, 3, 7, 9, 6, 4, 6, 0, 1, 5, 32, 67, 238, 81, 12, 345, 54, 34])) 41 | -------------------------------------------------------------------------------- /code/bigData/hive.md: -------------------------------------------------------------------------------- 1 | ## hive 2 | 3 | ### 注意事项 4 | - 字段不能删除,只能在末尾 append 新字段 5 | - select 出来的字段和 hive 字段必须按顺序一一对应 6 | 7 | ### 修改表结构步骤 8 | - 删除分区数据 alter table t_td_ad_server_overview_temp drop partition(ds=${YYYYMMDD}); 9 | - 在 s3 删除对应的数据 10 | - 整体替换字段 alter table t_td_ad_server_overview_temp replace columns (xxx) 11 | 12 | ### emr 13 | - 复制代码到 emr aws s3 cp s3://bucket-id/path/ . --recursive && sh xxx.sh -------------------------------------------------------------------------------- /code/canvas-smooth-curve/canvas-smooth-curve-1.js: -------------------------------------------------------------------------------- 1 | // 实现简单,但不够精确 2 | 3 | var ctx = document.getElementById('canvas').getContext('2d'); 4 | var points = [ 5 | {x: 10, y: 100}, 6 | {x: 60, y: 200}, 7 | {x: 110, y: 150}, 8 | {x: 160, y: 160}, 9 | {x: 210, y: 120}, 10 | {x: 260, y: 100}, 11 | {x: 310, y: 150}, 12 | ]; 13 | for (var j = 0; j < points.length; j ++) { 14 | ctx.font = "20px serif"; 15 | ctx.fillText(String(points[j].y), points[j].x, points[j].y); 16 | } 17 | ctx.beginPath(); 18 | ctx.moveTo(points[0][0], points[0][1]); 19 | for (var i = 0; i < points.length - 2; i ++) { 20 | var xc = (points[i].x + points[i + 1].x) / 2; 21 | var yc = (points[i].y + points[i + 1].y) / 2; 22 | ctx.quadraticCurveTo(points[i].x, points[i].y, xc, yc); 23 | } 24 | 25 | // curve through the last two points 26 | ctx.quadraticCurveTo(points[i].x, points[i].y, points[i+1].x,points[i+1].y); 27 | ctx.stroke(); -------------------------------------------------------------------------------- /code/canvas-smooth-curve/canvas-smooth-curve-2.js: -------------------------------------------------------------------------------- 1 | // 实现稍微复杂,但更精确 2 | 3 | var ctx = document.getElementById("canvas").getContext("2d"); 4 | function drawCurve(ctx, ptsa, tension, isClosed, numOfSegments, showPoints) { 5 | ctx.beginPath(); 6 | drawLines(ctx, getCurvePoints(ptsa, tension, isClosed, numOfSegments)); 7 | if (showPoints) { 8 | ctx.beginPath(); 9 | for(var i=0;i { 7 | onLeave('beforeunload') 8 | }) 9 | // 在ios下,只有刷新才会触发unload、beforeunload,关闭页面、点击连接跳转均不会触发 10 | // ios中貌似已经弃用unload和onload事件 11 | // 参考[Safari Web Content Guide](https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html#//apple_ref/doc/uid/TP40006511-SW5) 12 | // 官方推荐使用pagehide事件(ios下点击连接跳转、关闭页面时触发,其他平台beforeunload先于pagehide触发) 13 | win.addEventListener('pagehide', () => { 14 | onLeave('pagehide') 15 | }) 16 | win.addEventListener('pageshow', (e) => { 17 | // ios下后退,js代码不会重新执行,后台时,需要将isLeaving置为false 18 | // e.persisted === true代表有缓存,说明是后退 19 | if (e.persisted === true) { 20 | isLeaving = false 21 | } 22 | }) 23 | function () { 24 | if (isLeaving) return 25 | // do something when leave 26 | } 27 | ``` -------------------------------------------------------------------------------- /code/detect-page-visibility/detect-page-visibility.md: -------------------------------------------------------------------------------- 1 | ## 检测页面可见性 2 | ```javascript 3 | let doc = document 4 | 5 | let hidden 6 | let state 7 | let visibilityChange 8 | // 各种浏览器兼容 9 | if (typeof doc.hidden !== 'undefined') { 10 | hidden = 'hidden' 11 | visibilityChange = 'visibilitychange' 12 | state = 'visibilityState' 13 | } else if (typeof doc.mozHidden !== 'undefined') { 14 | hidden = 'mozHidden' 15 | visibilityChange = 'mozvisibilitychange' 16 | state = 'mozVisibilityState' 17 | } else if (typeof doc.msHidden !== 'undefined') { 18 | hidden = 'msHidden' 19 | visibilityChange = 'msvisibilitychange' 20 | state = 'msVisibilityState' 21 | } else if (typeof doc.webkitHidden !== 'undefined') { 22 | hidden = 'webkitHidden' 23 | visibilityChange = 'webkitvisibilitychange' 24 | state = 'webkitVisibilityState' 25 | } 26 | 27 | doc.addEventListener(visibilityChange, function () { 28 | if (doc[state] === hidden) { 29 | // hidden 30 | } else { 31 | // visible 32 | } 33 | }) 34 | ``` -------------------------------------------------------------------------------- /code/dom-operation/dom-operation.js: -------------------------------------------------------------------------------- 1 | /****** dom operation *******/ 2 | (function(Dom, doc){ 3 | Dom.wrap = wrap; 4 | Dom.getEle = getEle; 5 | 6 | // wrap dom to operate dom easily 7 | function wrap(dom) { 8 | dom.getEle = function (css_selector) { 9 | // use current element as parent to query children 10 | return getEle(css_selector, this); 11 | } 12 | dom.on = addEvent; 13 | dom.addClass = addClass; 14 | dom.removeClass = removeClass; 15 | dom.hasClass = hasClass; 16 | dom.indexOfParent = indexOfParent; 17 | dom.parent = parent; 18 | dom.hide = hide; 19 | dom.show = show; 20 | // return dom to chain call 21 | return dom; 22 | } 23 | // get element by tagname, class and id selector 24 | function getEle(css_selector, parent) { 25 | parent = parent || doc; 26 | var dom = null; 27 | // only return one element for id selector 28 | if(/^#/.test(css_selector)){ 29 | dom = parent.querySelector(css_selector); 30 | wrap(dom); 31 | } else { 32 | dom = parent.querySelectorAll(css_selector); 33 | // wrap each element 34 | [].forEach.call(dom, function (ele) { 35 | wrap(ele); 36 | }); 37 | // allow to add event for element set 38 | dom.on = function (type, target_sel, callback) { 39 | [].forEach.call(dom, function(ele) { 40 | addEvent.call(ele, type, target_sel, callback); 41 | }); 42 | } 43 | } 44 | return dom; 45 | } 46 | // add event 47 | function addEvent(type, target_sel, callback) { 48 | var _this = this; 49 | if(typeof target_sel === 'function'){ 50 | this.addEventListener(type, function (event) { 51 | target_sel.call(_this, event); 52 | }) 53 | } else if(typeof target_sel === 'string'){ 54 | // only execute addEventListener once on the same agent element, 55 | // for listener added later, just use selector as key to cache callback function in this[eventCache] 56 | var eventCache = 'data-' + type + '-obj'; 57 | // if a listener has been added, there will be eventCache 58 | if(!this[eventCache]){ 59 | this[eventCache] = {}; 60 | // save callback function 61 | this[eventCache][target_sel] = callback; 62 | this.addEventListener(type, function (event) { 63 | var target = event.target; 64 | // bug/feature: if the parent node/target was removed from dom tree, 65 | // traversal will break 66 | while(target){ 67 | // traverse all cached event callback function in the agent elment, 68 | // and check whether the target matches the key(ie selector) 69 | for(var sel in _this[eventCache]){ 70 | if(testSelector(sel, target)){ 71 | // execute cached callback function 72 | _this[eventCache][sel] && _this[eventCache][sel].call(target, event); 73 | } 74 | } 75 | target = target.parentNode; 76 | // traverse until the agent element 77 | if(target && target.isEqualNode(_this)){ 78 | break; 79 | } 80 | } 81 | }); 82 | } 83 | 84 | // use target selector to save callback function 85 | this[eventCache][target_sel] = callback; 86 | } 87 | } 88 | // remove class 89 | function removeClass(clas_name) { 90 | var cur_class = this.getAttribute('class'); 91 | if(cur_class){ 92 | // not yet deal with the spaces left by replacing className 93 | this.setAttribute('class', cur_class.replace(new RegExp(clas_name), '')); 94 | } 95 | } 96 | // add class 97 | function addClass(clas_name) { 98 | var cur_class = this.getAttribute('class'); 99 | this.setAttribute('class', cur_class ? cur_class + ' ' + clas_name : clas_name); 100 | } 101 | // whether element has the class 102 | function hasClass(clas_name) { 103 | return new RegExp(clas_name).test(this.getAttribute('class')); 104 | } 105 | // get element position of parent 106 | function indexOfParent() { 107 | var children = this.parentNode.childNodes; 108 | // filter text node 109 | var filter_children = [].filter.call(children, function (ele) { 110 | return ele.nodeType === 1; 111 | }) 112 | for(var i = 0; i < filter_children.length; i++){ 113 | if(this.isEqualNode(filter_children[i])){ 114 | return i 115 | } 116 | }; 117 | return -1; 118 | } 119 | // get parents by selector 120 | function parent(selector) { 121 | var parent = this.parentNode; 122 | if(selector){ 123 | while (parent){ 124 | if(testSelector(selector, parent)){ 125 | return wrap(parent); 126 | } 127 | parent = parent.parentNode; 128 | }; 129 | return null; 130 | } else { 131 | return wrap(parent); 132 | } 133 | } 134 | // hide element 135 | function hide() { 136 | this.setAttribute('style', 'display: none'); 137 | } 138 | // show element 139 | function show() { 140 | // only support block element 141 | this.setAttribute('style', 'display: block'); 142 | } 143 | 144 | // whether the element matches the selector 145 | function testSelector(selctor, target) { 146 | // only surpport tagName, id, and class selector 147 | if(/^#/.test(selctor)){ 148 | // id selector 149 | return '#' + target.getAttribute('id') === selctor; 150 | } else if(/^\./.test(selctor)){ 151 | // class selector 152 | return new RegExp(selctor.replace('.', '')).test(target.className); 153 | } else { 154 | // tagname selector 155 | return target.tagName && target.tagName.toLowerCase() === selctor; 156 | } 157 | } 158 | })(window.Dom || (window.Dom = {}), document); -------------------------------------------------------------------------------- /code/dom-operation/render-dom-template.js: -------------------------------------------------------------------------------- 1 | /******** rendering function **********/ 2 | (function(Dom, doc){ 3 | // cache template function 4 | var cache = {}; 5 | // cache the parent node of the template tag, 6 | // and use the parent node to render html every time 7 | var templ_parent_node_cache = {}; 8 | 9 | // rendering function 10 | Dom.render = function render(str, data){ 11 | var $ele = doc.getElementById(str); 12 | 13 | // calculate the rendering function if not cached 14 | var fn = cache[str] = cache[str] || new Function("obj", 15 | // the rendering function references jquery teml 16 | "var p=[],print=function(){p.push.apply(p,arguments);};" + 17 | "with(obj){var data = obj;p.push('" + 18 | $ele.innerHTML 19 | .replace(/[\r\t\n]/g, " ") 20 | .split("<%").join("\t") 21 | .replace(/((^|%>)[^\t]*)'/g, "$1\r") 22 | .replace(/\t=(.*?)%>/g, "',$1,'") 23 | .split("\t").join("');") 24 | .split("%>").join("p.push('") 25 | .split("\r").join("\\'") 26 | + "');}return p.join('');"); 27 | 28 | // cache the parent node 29 | if($ele){ 30 | templ_parent_node_cache[str] = $ele.parentNode; 31 | } 32 | templ_parent_node_cache[str].innerHTML = fn(data); 33 | }; 34 | })(window.Dom || (window.Dom = {}), document); -------------------------------------------------------------------------------- /code/elasticsearch/elasticsearch.md: -------------------------------------------------------------------------------- 1 | ### elasticsearch查询、存储、聚合、计数、排序 2 | npm 有elasticsearch的包,可以用于与es服务器交互,但也可以手动调用es的result api 3 | 我主要用到了查询和以及聚合,两者是并列关系,可以结合使用。es中字符串数据默认是会分词的,因此在保存数据之前,必须使用mapping type设置每个字段的数据类型、以及禁用分词。 4 | - 查询 5 | es可以使用查询表达式,参考文档https://www.elastic.co/guide/cn/elasticsearch/guide/current/query-dsl-intro.html 6 | ```javascript 7 | request({ 8 | url: 'esServer/index/type/_search', 9 | json: true, 10 | body: { 11 | "size" : 100, // es默认只返回10条数据 12 | "query": { 13 | "bool": { 14 | "must": [ 15 | {"match": {"message.log_master": 'performance'}}, // 对于嵌套数据使用.号连接 16 | {"match": {"message.type": 'page'}}, 17 | ], 18 | "must": { "match": { "tweet": "elasticsearch" }}, 19 | "filter": { "range": { "age" : { "gt" : 30 }} } 20 | } 21 | } 22 | }, (error, response, body)=> { 23 | if(!error){ 24 | if(response.statusCode == 200){ 25 | console.info('success'); 26 | }); 27 | } else { 28 | console.error(response.statusCode + ' error'); 29 | } 30 | } 31 | }); 32 | ``` 33 | - 聚合 34 | ```javascript 35 | request({ 36 | url: 'esServer/index/type/_search', 37 | json: true, 38 | body: { 39 | "size" : 0, // 我只需要聚合后的数据分类,不需要返回数据 40 | "query": { 41 | "bool": { 42 | "must": { 43 | "match": { 44 | "message.log_master": 'log_master' 45 | } 46 | } 47 | } 48 | }, 49 | // 在查询结果的基础上聚合 50 | "aggs" : { 51 | "hosts" : { // 聚合后的分类的名字,随意设置 52 | "terms" : { 53 | "field" : "message.host" // 要聚合的数据 54 | } 55 | } 56 | } 57 | }}, function (error, response, body) { 58 | 59 | }); 60 | ``` 61 | 聚合还可以计算数值类型的百分位,文档https://www.elastic.co/guide/cn/elasticsearch/guide/current/percentiles.html 62 | ```javascript 63 | request({ 64 | url: 'esServer/index/type/_search', 65 | json: true, 66 | body: { 67 | "size" : 0, // 我只需要聚合后的数据分类,不需要返回数据 68 | "query": { 69 | "bool": { 70 | "must": { 71 | "match": { 72 | "message.log_master": 'log_master' 73 | } 74 | } 75 | } 76 | }, 77 | "aggs" : { 78 | "percentile" : { // 聚合百分位 79 | "percentiles" : { 80 | "field": "message.dC", // 要计算百分位信息的数据 81 | "percents": ["1", "5", "25", "50", "75", "95", "99"], // 返回各个百分位段的数据值,如90%的数据在多少以下 82 | "script": { 83 | "inline": `_value - doc['message.dI'].value` // 还可以计算,这里聚合dC-dI值的百分位 84 | } 85 | } 86 | } 87 | } 88 | }}, function (error, response, body) { 89 | 90 | }); 91 | ``` 92 | - 存储数据 93 | ```javascript 94 | request.post({ 95 | url: 'esServer/index/type', 96 | json: true, 97 | body: data 98 | }, function (error, response, body) { 99 | if(!error){ 100 | if(response.statusCode == 201){ 101 | console.info('success'); 102 | } else { 103 | console.error(response.statusCode + ' error'); 104 | } 105 | } 106 | }); 107 | ``` 108 | - 计数 109 | ```javascript 110 | request({ 111 | url: 'esServer/index/type/_count', 112 | json: true, 113 | body: { 114 | "query": { 115 | "range" : { 116 | "date" : date_range 117 | } 118 | } 119 | } 120 | }, function (error, response, body) { 121 | if(!error){ 122 | if(response.statusCode == 201){ 123 | console.info('success'); 124 | } else { 125 | console.error(response.statusCode + ' error'); 126 | } 127 | } 128 | }); 129 | ``` 130 | - 排序 131 | ```javascript 132 | request({ 133 | url: 'esServer/index/type/_search', 134 | json: true, 135 | body: { 136 | "query": { 137 | 138 | } 139 | "sort": [{ 140 | "date": { // 以时间排序 141 | "order": "asc" //asc正序(默认) desc倒序 142 | } 143 | }] 144 | } 145 | }, function (error, response, body) { 146 | if(!error){ 147 | if(response.statusCode == 201){ 148 | console.info('success'); 149 | } else { 150 | console.error(response.statusCode + ' error'); 151 | } 152 | } 153 | }); 154 | ``` -------------------------------------------------------------------------------- /code/gulp-config/gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | gulpSequence = require('gulp-sequence'), 3 | uglify = require('gulp-uglify'), 4 | autoprefixer = require('gulp-autoprefixer'), 5 | cleanCSS = require('gulp-clean-css'), 6 | htmlMin = require('gulp-htmlmin'), 7 | htmlImgBase64 = require('gulp-img64'), 8 | base64 = require('gulp-base64'); 9 | var fs = require('fs'); 10 | 11 | 12 | gulp.task('jsmin', function () { 13 | return gulp.src('yijian/common.js') 14 | .pipe(uglify()) 15 | .pipe(gulp.dest('yijian/dist/')); 16 | }); 17 | 18 | gulp.task('handleCss', function () { 19 | return gulp.src('yijian/common.css') 20 | .pipe(base64({ 21 | baseDir: 'yijian', 22 | extensions: ['svg', 'png', /\.jpg#datauri$/i], 23 | exclude: [], 24 | excludeDec: 'yijian/build/img', 25 | maxImageSize: 20*1024, 26 | debug: true 27 | })) 28 | .pipe(autoprefixer()) 29 | .pipe(cleanCSS()) 30 | .pipe(gulp.dest('yijian/dist/')); 31 | }); 32 | 33 | gulp.task('htmlMin', function () { 34 | return gulp.src('yijian/index.html') 35 | .pipe(htmlImgBase64({ 36 | exclude: ['title.png'], 37 | excludeDec: 'yijian/build/img' 38 | })) 39 | .pipe(htmlMin({collapseWhitespace: true})) 40 | .pipe(gulp.dest('yijian/dist/')); 41 | }); 42 | 43 | gulp.task('htmlConcat', ['jsmin', 'handleCss', 'htmlMin'], function () { 44 | var html = fs.readFileSync('yijian/dist/index.html').toString(); 45 | var css = fs.readFileSync('yijian/dist/common.css').toString(); 46 | var js = fs.readFileSync('yijian/dist/common.js').toString(); 47 | html = html.replace('', ''); 48 | html = html.replace('', ''); 49 | // fs.rmdir("/yijian/build/", function () { 50 | // fs.mkdir("/yijian/build/",function(){ 51 | // fs.writeFile('yijian/build/start.html', html, function () {}); 52 | // }); 53 | // }) 54 | fs.writeFile('yijian/build/start.html', html, function () {}); 55 | }); 56 | 57 | gulp.task('default', ['htmlConcat']); 58 | 59 | // gulp.task('default', gulpSequence(['jsmin', 'handleCss', 'htmlMin'], 'htmlConcat')); -------------------------------------------------------------------------------- /code/hide-scroll-bar/hide-scroll-bar.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 隐藏浏览器滚动条 7 | 8 | 31 | 32 | 33 |
34 |
35 | 36 | -------------------------------------------------------------------------------- /code/image-processing/convertBase64ToBlob.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 将base64转换为文件对象 3 | * (即用文件上传输入框上传文件得到的对象) 4 | * @param {String} base64 base64字符串 5 | */ 6 | function convertBase64UrlToBlob(base64){ 7 | var base64Arr = base64.split(','); 8 | if(base64Arr.length > 1){ 9 | //如果是图片base64,去掉头信息 10 | base64 = base64Arr[1]; 11 | } 12 | // 将base64解码 13 | var bytes = atob(base64); 14 | var bytesCode = new ArrayBuffer(bytes.length); 15 | // 将base64转换为ascii码 16 | for (var i = 0; i < bytes.length; i++) { 17 | bytesCode[i] = bytes.charCodeAt(i); 18 | } 19 | // 转换为类型化数组 20 | var byteArray = new Uint8Array(bytesCode); 21 | // 生成Blob对象(文件对象) 22 | return new Blob( [byteArray] , {type : 'image/png'}); 23 | } -------------------------------------------------------------------------------- /code/image-processing/imgCompression.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 压缩图片 3 | * (压缩后返回的是base64,可以参照本目录下的convertBase64ToBlob.js,将base64还原为file input读取得到的文件对象) 4 | * @param {Dom Object} $fileInput 文件上传输入框 5 | * @param {Function} callback 压缩完成后的回调函数 6 | * @return {Object} options 指定压缩到指定体积以下或按比率压缩,不指定不会压缩 7 | */ 8 | function compressImg($fileInput, callback, options) { 9 | options = options || {}; 10 | // 绑定change事件 11 | $fileInput.onchange = function ($event) { 12 | var $target = $event.target; 13 | if ($target.files && $target.files[0]) { 14 | // 用FileReader读取文件 15 | var reader = new FileReader(); 16 | // 将图片读取为base64 17 | reader.readAsDataURL($fileInput.files[0]); 18 | reader.onload = function(evt){ 19 | var base64 = evt.target.result; 20 | // 创建图片对象 21 | var img = new Image(); 22 | // 用图片对象加载读入的base64 23 | img.src = base64; 24 | img.onload = function () { 25 | var that = this, 26 | canvas = document.createElement('canvas'), 27 | ctx = canvas.getContext('2d'); 28 | canvas.setAttribute('width', that.width); 29 | canvas.setAttribute('height', that.height); 30 | // 将图片画入canvas 31 | ctx.drawImage(that, 0, 0, that.width, that.height); 32 | // 压缩到指定体积以下(M) 33 | if(options.size){ 34 | var scale = 0.9; 35 | while (base64.length / 1024 / 1024 > options.size) { 36 | // 用canvas的toDataURL方法实现压缩 37 | base64 = canvas.toDataURL('image/jpeg', scale); 38 | // 降低压缩比率,直到压缩结果小于指定大小 39 | scale = scale - 0.1; 40 | } 41 | } else if(options.scale){ 42 | // 按比率压缩 43 | base64 = canvas.toDataURL('image/jpeg', options.scale); 44 | } 45 | callback(base64); 46 | } 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /code/image-processing/previewImg.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 上传之前预览图片 3 | * @param {Dom Object} $fileInput 文件上传输入框 4 | * @param {Dom Object} $previewImg 预览图片的image元素 5 | */ 6 | function previewImg($fileInput, $previewImg) { 7 | $fileInput.onchange = function ($event) { 8 | var $target = $event.target; 9 | if ($target.files && $target.files[0]) { 10 | var reader = new FileReader(); 11 | reader.readAsDataURL($target.files[0]); 12 | reader.onload = function(evt){ 13 | var base64 = evt.target.result; 14 | $previewImg.setAttribute('src', base64); 15 | } 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /code/image-processing/resetImgOrientation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 将图片旋转到正确的角度 3 | * (解决移动端上传的图片角度不正确的问题) 4 | * (旋转后返回的是base64,可以参照本目录下的convertBase64ToBlob.js,将base64还原为file input读取得到的文件对象) 5 | * @param {Dom Object} $fileInput 文件上传输入框 6 | * @param {Function} callback 旋转完成后的回调函数 7 | */ 8 | function resetImgOrientation($fileInput, callback) { 9 | // 绑定change事件 10 | $fileInput.onchange = function ($event) { 11 | var $target = $event.target; 12 | if ($target.files && $target.files[0]) { 13 | // 获取图片旋转角度 14 | getOrientation($target.files[0], function (orientation) { 15 | var reader = new FileReader(); 16 | reader.readAsDataURL($target.files[0]); 17 | reader.onload = function(evt){ 18 | var base64 = evt.target.result; 19 | // 将图片旋转到正确的角度 20 | resetOrientation(base64, orientation, function (resultBase64) { 21 | callback(resultBase64); 22 | }); 23 | } 24 | }); 25 | } 26 | } 27 | } 28 | 29 | // 获取图片旋转的角度 30 | function getOrientation(file, callback) { 31 | var reader = new FileReader(); 32 | reader.readAsArrayBuffer(file); 33 | reader.onload = function(e) { 34 | var view = new DataView(e.target.result); 35 | if (view.getUint16(0, false) != 0xFFD8) return callback(-2); 36 | var length = view.byteLength, offset = 2; 37 | while (offset < length) { 38 | var marker = view.getUint16(offset, false); 39 | offset += 2; 40 | if (marker == 0xFFE1) { 41 | if (view.getUint32(offset += 2, false) != 0x45786966) return callback(-1); 42 | var little = view.getUint16(offset += 6, false) == 0x4949; 43 | offset += view.getUint32(offset + 4, little); 44 | var tags = view.getUint16(offset, little); 45 | offset += 2; 46 | for (var i = 0; i < tags; i++) 47 | if (view.getUint16(offset + (i * 12), little) == 0x0112) 48 | return callback(view.getUint16(offset + (i * 12) + 8, little)); 49 | } 50 | else if ((marker & 0xFF00) != 0xFF00) break; 51 | else offset += view.getUint16(offset, false); 52 | } 53 | return callback(-1); 54 | }; 55 | } 56 | // 将图片旋转到正确的角度 57 | function resetOrientation(srcBase64, srcOrientation, callback) { 58 | var img = new Image(); 59 | img.onload = function() { 60 | var width = img.width, 61 | height = img.height, 62 | canvas = document.createElement('canvas'), 63 | ctx = canvas.getContext("2d"); 64 | // set proper canvas dimensions before transform & export 65 | if ([5,6,7,8].indexOf(srcOrientation) > -1) { 66 | canvas.width = height; 67 | canvas.height = width; 68 | } else { 69 | canvas.width = width; 70 | canvas.height = height; 71 | } 72 | // transform context before drawing image 73 | switch (srcOrientation) { 74 | case 2: ctx.transform(-1, 0, 0, 1, width, 0); break; 75 | case 3: ctx.transform(-1, 0, 0, -1, width, height ); break; 76 | case 4: ctx.transform(1, 0, 0, -1, 0, height ); break; 77 | case 5: ctx.transform(0, 1, 1, 0, 0, 0); break; 78 | case 6: ctx.transform(0, 1, -1, 0, height , 0); break; 79 | case 7: ctx.transform(0, -1, -1, 0, height , width); break; 80 | case 8: ctx.transform(0, -1, 1, 0, 0, width); break; 81 | default: ctx.transform(1, 0, 0, 1, 0, 0); 82 | } 83 | // draw image 84 | ctx.drawImage(img, 0, 0); 85 | // export base64 86 | callback(canvas.toDataURL('image/jpeg')); 87 | }; 88 | img.src = srcBase64; 89 | }; -------------------------------------------------------------------------------- /code/img-to-base64/img-to-base64.js: -------------------------------------------------------------------------------- 1 | // 由于浏览器跨域的限制,只能转同域下的图片,或者服务器设置图片的header:Access-Control-Allow-Origin: * 2 | // 基于ajax的方式会使图片体积增大30%左右,基于canvas的会增大2到3倍 3 | function basedOnAjax(url, cb) { 4 | const xhr = new XMLHttpRequest(), fileReader = new FileReader(); 5 | xhr.open("GET", url); 6 | // responseType设置为blob的兼容性没有arraybuffer好 7 | // 可以先设置为arraybuffer,再手动转blob 8 | xhr.responseType = "blob"; 9 | xhr.addEventListener("load", ()=>{ 10 | if (xhr.status === 200) { 11 | fileReader.onload = (evt)=>{ 12 | var result = evt.target.result; 13 | cb && cb(result); 14 | }; 15 | // 转为字符串 16 | fileReader.readAsDataURL(xhr.response); 17 | } 18 | }); 19 | xhr.send(); 20 | } 21 | function basedOnAjaxArrayBuffer(url, cb) { 22 | const xhr = new XMLHttpRequest(), fileReader = new FileReader(); 23 | let blobBuilder = new (window.BlobBuilder || window.MozBlobBuilder || window.WebKitBlobBuilder || window.OBlobBuilder || window.msBlobBuilder); 24 | let blob; 25 | xhr.open("GET", url); 26 | xhr.responseType = "arraybuffer"; 27 | xhr.addEventListener("load", ()=>{ 28 | if (xhr.status === 200) { 29 | // 将响应数据放入blobBuilder中 30 | blobBuilder.append(xhr.response); 31 | const resType = xhr.getResponseHeader('content-type'); 32 | // 用文件类型创建blob对象 33 | blob = blobBuilder.getBlob(resType); 34 | // 转为字符串 35 | fileReader.readAsDataURL(blob); 36 | fileReader.onload = (evt)=>{ 37 | var result = evt.target.result; 38 | cb && cb(result); 39 | }; 40 | } 41 | }); 42 | xhr.send(); 43 | } 44 | 45 | function basedOnCanvas(url, cb) { 46 | const img = new Image(); 47 | img.src = url; 48 | img.onload = function () { 49 | const imgCanvas = document.createElement("canvas"), 50 | imgContext = imgCanvas.getContext("2d"); 51 | // 确保canvas尺寸和图片一致 52 | imgCanvas.width = img.width; 53 | imgCanvas.height = img.height; 54 | // 在canvas中绘制图片 55 | imgContext.drawImage(img, 0, 0, img.width, img.height); 56 | // 将图片保存为Data URI 57 | // 这里可以设置图片的清晰度,减小base64体积 58 | cb && cb(imgCanvas.toDataURL("image/png")); 59 | } 60 | } -------------------------------------------------------------------------------- /code/kafka/kafka.md: -------------------------------------------------------------------------------- 1 | ### 使用node-kafka链接kafka服务器 2 | node-kafka编译安装过程遇到的问题参考:https://github.com/Youjingyu/problem-summary 3 | ```javascript 4 | const Kafka = require('node-rdkafka'); 5 | // topicid、groupid、bootstrap.servers需要向kafka服务方确定 6 | let consumer = new Kafka.KafkaConsumer( 7 | { 8 | 'client.id': 'push_monitor', 9 | 'group.id': '',//groupid 10 | 'enable.auto.offset.store': true, 11 | 'offset.store.method': 'none', 12 | 'session.timeout.ms': 30000, 13 | 'fetch.min.bytes': 1024, 14 | // 订阅服务器 15 | 'bootstrap.servers': '10.39.14.43:9110,10.39.14.47:9110,10.39.14.25:9110,10.39.14.27:9110,10.39.14.31:9110', 16 | 'security.protocol': 'sasl_plaintext', 17 | 'sasl.mechanisms': 'PLAIN', 18 | 'sasl.username': '', // 账户 19 | 'sasl.password': '', // 密码 20 | 'api.version.request': true 21 | }, 22 | { 23 | 'auto.offset.reset': 'latest', 24 | 'enable.auto.commit': true, 25 | 'auto.commit.interval.ms': 3000 26 | } 27 | ) 28 | consumer.on('ready', () => { 29 | consumer.subscribe(['']) //topicid 30 | consumer.consume() 31 | }).on('data', msg => { 32 | 33 | }) 34 | consumer.connect(); 35 | 36 | ``` 37 | -------------------------------------------------------------------------------- /code/linux/debug.md: -------------------------------------------------------------------------------- 1 | ## linux调试 2 | 系统状态 3 | ```bash 4 | # 指标: 5 | # VIRT 进程申请但可能没有使用的内存; 6 | # RES 实际使用的内存; 7 | # SHR 与其他进程共享的内存(比如两个进程复用了一个系统so) 8 | top 9 | ``` 10 | 11 | 查看单个进程状态 12 | ```bash 13 | top -p 14 | ``` 15 | 16 | 查看内存占用 17 | ```bash 18 | free -m 19 | total used free shared buffers cached 20 | Mem: 49406140 48352948 1053192 0 96524 35946452 21 | -/+ buffers/cache: 12309972 37096168 22 | Swap: 0 0 0 23 | 24 | # 49406140:总物理内存 25 | # 48352948:当前被系统调度的内存 26 | # 1053192:未被系统调度的内存 27 | # 96524:用于读取文件时用作缓存的内存 28 | # 35946452:系统会将一大部分内存用于cache,比如用于文件预读等,与buffer相同,这部分内存可以被系统重复调度给新的进程 29 | # 12309972:真正被占用,不能被系统调度的内存 30 | # 37096168:可以被系统调度的内存,等于1053192 + 96524 + 35946452(free + buffers + cached) 31 | ``` 32 | 33 | 查看进程详细的系统调用 34 | ```bash 35 | strace -t -o ./process_2_log node index.js 36 | # mac可使用dtruss 37 | dtruss 38 | ``` 39 | 40 | 追踪线程信息 41 | ```bash 42 | pstack 43 | ``` 44 | 45 | 查看进程操作的fd 46 | ```bash 47 | ls /proc//fd 48 | ``` 49 | 50 | 罗列进程对应的线程,输出的spid就是线程id,linux的线程其实是进程模拟的 51 | ```bash 52 | ps -T -p 53 | ``` 54 | 55 | 查看进程的内存映像信息 56 | ```bash 57 | pmap -x 58 | ``` -------------------------------------------------------------------------------- /code/linux/env-variable-config.md: -------------------------------------------------------------------------------- 1 | ## 环境变量配置 2 | 系统环境变量修改```/etc/profile```,用户环境变量修改```~/.bash_profile``` 3 | 一般模板: 4 | ```bash 5 | # 项目目录 6 | export JMETER_HOME=/usr/local/jmeter/apache-jmeter-3.1 7 | export CLASSPATH=$JMETER_HOME/lib/ext/ApacheJMeter_core.jar:$JMETER_HOME/lib/jorphan.jar:$JMETER_HOME/lib/logkit-2.0.jar:$CLASSPATH 8 | # bin目录 9 | export PATH=$JMETER_HOME/bin:$PATH:$HOME/bin 10 | ``` 11 | 然后刷新profile: 12 | ```bash 13 | source /etc/profile 14 | # 或 15 | source /etc/profile 16 | ``` -------------------------------------------------------------------------------- /code/linux/linux-init-simple.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Author:xiaojue 3 | # 2018-02-13 4 | # 脚本适用于centos6.5 sce环境 5 | 6 | #设置iptables 开放端口 7 | PORTS=80,443 8 | BASHDIR=`pwd` 9 | 10 | cat << EOF 11 | +-----------------------------------------------------------------------------------------+ 12 | | ********** Welcome to CentOS 6.5 System init for Sina SCE by xiaojue ********** | 13 | +-----------------------------------------------------------------------------------------+ 14 | EOF 15 | 16 | #成功提示 17 | function success(){ 18 | echo -e "\033[32m Success!!!\033[0m\n" 19 | echo "#########################################################" 20 | } 21 | 22 | #检查权限,运行必须是root用户 23 | [ `whoami` != "root" ] && echo "please use root exec this file" && exit 1 24 | 25 | #更新yum为阿里云的源 26 | echo "set yum mirrirs with aliyun" 27 | cd /etc/yum.repos.d/ 28 | mv CentOS-Base.repo CentOS-Base.repo.bak 29 | wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-6.repo 30 | wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-6.repo 31 | yum clean all 32 | yum makecache 33 | success 34 | 35 | #安装中文 36 | # echo "install pingfang ttc" 37 | # mkdir /usr/share/fonts 38 | # mkdir /usr/share/fonts/chinese 39 | # cd /usr/share/fonts/chinese 40 | # wget https://gitlab.weibo.cn/SINA_MFE/initCentos/raw/master/PingFang.ttc 41 | # chmod -R 755 PingFang.ttc 42 | # yum install mkfontscale fontconfig -y 43 | # mkfontscale 44 | # mkfontdir 45 | # fc-cache -fv 46 | # source /etc/profile 47 | # success 48 | 49 | #自动更新服务器时间 50 | echo "set ntptime" 51 | ntpdate cn.pool.ntp.org 52 | echo '*/5 * * * * /usr/sbin/ntpdate cn.pool.ntp.org &>/dev/null' >> /etc/crontab 53 | hwclock -w 54 | success 55 | 56 | #设置文件句柄数 57 | echo "Set ulimit 65535" 58 | cat << EOF > /etc/security/limits.conf 59 | * soft nofile 65535 60 | * hard nofile 65535 61 | * soft nproc 65535 62 | * hard nproc 65535 63 | EOF 64 | sed -i 's/65535/1024000/g' /etc/security/limits.d/90-nproc.conf 65 | success 66 | 67 | #设置iptables 68 | echo "set iptables" 69 | iptables -F 70 | iptables -A INPUT -p tcp -m multiport --dports $PORTS -j ACCEPT 71 | iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT 72 | iptables -A INPUT -i lo -j ACCEPT 73 | iptables -A OUTPUT -m state --state NEW,ESTABLISHED -j ACCEPT 74 | iptables -P INPUT DROP 75 | iptables -P FORWARD DROP 76 | iptables -P OUTPUT ACCEPT 77 | service iptables save 78 | success 79 | 80 | #安装系统必需软件包 81 | echo "install system pack" 82 | yum -y install make gcc-c++ cmake bison-devel ncurses-devel net-snmp sysstat dstat iotop lrzsz flex byacc libpcap libpcap-devel nfs-utils ntp zip unzip xz wget vim lsof bison openssh-clients htop lftp 83 | success 84 | 85 | #安装系统套件软件包 86 | echo "install development tools" 87 | yum -y groupinstall "Development Tools" "Server Platform Development" 88 | success 89 | 90 | #安装git命令行 91 | echo "install git" 92 | yum install curl-devel expat-devel gettext-devel openssl-devel zlib-devel gcc perl-ExtUtils-MakeMaker -y 93 | yum remove git -y 94 | cd /usr/src 95 | wget https://www.kernel.org/pub/software/scm/git/git-2.5.0.tar.gz 96 | tar -zxvf git-2.5.0.tar.gz 97 | cd git-2.5.0 98 | ./configure 99 | make prefix=/usr/local/git all 100 | make prefix=/usr/local/git install 101 | echo "export PATH=$PATH:/usr/local/git/bin" >> /etc/bashrc 102 | source /etc/bashrc 103 | success 104 | 105 | #安装node和npm 106 | echo "install node & npm" 107 | yum -y install gcc make gcc-c++ openssl-devel wget 108 | cd /usr/src 109 | wget https://nodejs.org/dist/v8.9.4/node-v8.9.4-linux-x64.tar.xz 110 | tar xvJf node-v8.9.4-linux-x64.tar.xz 111 | cd node-v8.9.4-linux-x64 112 | echo "export PATH=$PATH:/usr/src/node-v8.9.4-linux-x64/bin" >> /etc/bashrc 113 | source /etc/bashrc 114 | success 115 | 116 | #安装cnpm和pm2 117 | echo "install cnpm & pm2" 118 | npm install -g cnpm --registry=https://registry.npm.taobao.org 119 | cnpm install -g pm2 120 | success 121 | 122 | # 安装一堆其他的图形依赖 123 | # echo "install image deps" 124 | # yum install ipa-gothic-fonts xorg-x11-fonts-100dpi xorg-x11-fonts-75dpi xorg-x11-utils xorg-x11-fonts-cyrillic xorg-x11-fonts-Type1 xorg-x11-fonts-misc -y 125 | # yum install pango.x86_64 libXcomposite.x86_64 libXcursor.x86_64 libXdamage.x86_64 libXext.x86_64 libXi.x86_64 libXtst.x86_64 cups-libs.x86_64 libXScrnSaver.x86_64 libXrandr.x86_64 GConf2.x86_64 alsa-lib.x86_64 atk.x86_64 gtk3.x86_64 -y 126 | # success 127 | 128 | # 可能需要安装字体 129 | # https://www.jianshu.com/p/26e9892ae692 130 | -------------------------------------------------------------------------------- /code/linux/linux-init.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Author:xiaojue 3 | # 2018-02-13 4 | # 脚本适用于centos6.5 sce环境 5 | 6 | #设置iptables 开放端口 7 | PORTS=80,443 8 | BASHDIR=`pwd` 9 | 10 | cat << EOF 11 | +-----------------------------------------------------------------------------------------+ 12 | | ********** Welcome to CentOS 6.5 System init for Sina SCE by xiaojue ********** | 13 | +-----------------------------------------------------------------------------------------+ 14 | EOF 15 | 16 | #成功提示 17 | function success(){ 18 | echo -e "\033[32m Success!!!\033[0m\n" 19 | echo "#########################################################" 20 | } 21 | 22 | #检查权限,运行必须是root用户 23 | [ `whoami` != "root" ] && echo "please use root exec this file" && exit 1 24 | 25 | #更新yum为阿里云的源 26 | # echo "set yum mirrirs with aliyun" 27 | # cd /etc/yum.repos.d/ 28 | # mv CentOS-Base.repo CentOS-Base.repo.bak 29 | # wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-6.repo 30 | # wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-6.repo 31 | # yum clean all 32 | # yum makecache 33 | # success 34 | 35 | #安装中文 36 | # echo "install pingfang ttc" 37 | # mkdir /usr/share/fonts 38 | # mkdir /usr/share/fonts/chinese 39 | # cd /usr/share/fonts/chinese 40 | # wget https://gitlab.weibo.cn/SINA_MFE/initCentos/raw/master/PingFang.ttc 41 | # chmod -R 755 PingFang.ttc 42 | # yum install mkfontscale fontconfig -y 43 | # mkfontscale 44 | # mkfontdir 45 | # fc-cache -fv 46 | # source /etc/profile 47 | # success 48 | 49 | #自动更新服务器时间 50 | echo "set ntptime" 51 | ntpdate cn.pool.ntp.org 52 | echo '*/5 * * * * /usr/sbin/ntpdate cn.pool.ntp.org &>/dev/null' >> /etc/crontab 53 | hwclock -w 54 | success 55 | 56 | #设置文件句柄数 57 | echo "Set ulimit 65535" 58 | cat << EOF > /etc/security/limits.conf 59 | * soft nofile 65535 60 | * hard nofile 65535 61 | * soft nproc 65535 62 | * hard nproc 65535 63 | EOF 64 | sed -i 's/65535/1024000/g' /etc/security/limits.d/90-nproc.conf 65 | success 66 | 67 | #设置iptables 68 | echo "set iptables" 69 | iptables -F 70 | iptables -A INPUT -p tcp -m multiport --dports $PORTS -j ACCEPT 71 | iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT 72 | iptables -A INPUT -i lo -j ACCEPT 73 | iptables -A OUTPUT -m state --state NEW,ESTABLISHED -j ACCEPT 74 | iptables -P INPUT DROP 75 | iptables -P FORWARD DROP 76 | iptables -P OUTPUT ACCEPT 77 | service iptables save 78 | success 79 | 80 | #安装系统必需软件包 81 | echo "install system pack" 82 | yum -y install make gcc-c++ cmake bison-devel ncurses-devel net-snmp sysstat dstat iotop lrzsz flex byacc libpcap libpcap-devel nfs-utils ntp zip unzip xz wget vim lsof bison openssh-clients htop lftp 83 | success 84 | 85 | #安装系统套件软件包 86 | echo "install development tools" 87 | yum -y groupinstall "Development Tools" "Server Platform Development" 88 | success 89 | 90 | #安装git命令行 91 | echo "install git" 92 | yum install curl-devel expat-devel gettext-devel openssl-devel zlib-devel gcc perl-ExtUtils-MakeMaker -y 93 | yum remove git -y 94 | cd /usr/src 95 | wget https://www.kernel.org/pub/software/scm/git/git-2.5.0.tar.gz 96 | tar -zxvf git-2.5.0.tar.gz 97 | cd git-2.5.0 98 | ./configure 99 | make prefix=/usr/local/git all 100 | make prefix=/usr/local/git install 101 | echo "export PATH=$PATH:/usr/local/git/bin" >> /etc/bashrc 102 | source /etc/bashrc 103 | success 104 | 105 | #安装node和npm 106 | echo "install node & npm" 107 | yum -y install gcc make gcc-c++ openssl-devel wget 108 | cd /usr/src 109 | wget https://nodejs.org/dist/v8.9.4/node-v8.9.4-linux-x64.tar.xz 110 | tar xvJf node-v8.9.4-linux-x64.tar.xz 111 | cd node-v8.9.4-linux-x64 112 | echo "export PATH=$PATH:/usr/src/node-v8.9.4-linux-x64/bin" >> /etc/bashrc 113 | source /etc/bashrc 114 | success 115 | 116 | #安装cnpm和pm2 117 | echo "install cnpm & pm2" 118 | npm install -g cnpm --registry=https://registry.npm.taobao.org 119 | cnpm install -g pm2 120 | success 121 | 122 | # 安装一堆其他的图形依赖 123 | # echo "install image deps" 124 | # yum install ipa-gothic-fonts xorg-x11-fonts-100dpi xorg-x11-fonts-75dpi xorg-x11-utils xorg-x11-fonts-cyrillic xorg-x11-fonts-Type1 xorg-x11-fonts-misc -y 125 | # yum install pango.x86_64 libXcomposite.x86_64 libXcursor.x86_64 libXdamage.x86_64 libXext.x86_64 libXi.x86_64 libXtst.x86_64 cups-libs.x86_64 libXScrnSaver.x86_64 libXrandr.x86_64 GConf2.x86_64 alsa-lib.x86_64 atk.x86_64 gtk3.x86_64 -y 126 | # success 127 | 128 | # 可能需要安装字体 129 | # https://www.jianshu.com/p/26e9892ae692 130 | -------------------------------------------------------------------------------- /code/linux/linux-shell-example.md: -------------------------------------------------------------------------------- 1 | ### 检查硬盘容量,并邮件占用过大的部分 2 | ```bash 3 | #!/bin/bash 4 | mail=fuqiang6@staff.sina.com.cn,jingyu16@staff.sina.com.cn 5 | num=` df |awk '{print $5}'|grep -c -E "^[7-9][0-9]|^100"` 6 | #通过df命令查看硬盘使用情况,并通过awk只查看第五个域的内容,再通过grep只筛选以7到9开头的双位数(即50到99之间的任意数)或以100开头的行,最终把匹配的行数赋值给num。 grep的-c选项是用来计算匹配行的行数。 7 | if [ $num -gt 0 ] #如果num的值大于0 8 | then 9 | df |grep -E "[7-9][0-9]%|100%"|mail -v -s "disk warning" $mail - #把使用率超过70%的硬盘情况通过邮件发出去。 10 | fi 11 | ``` 12 | ### 判断进程是否存在,不存在重启 13 | ```bash 14 | #!/bin/sh 15 | cd `dirname $0` 16 | BIN_DIR=`pwd` 17 | ES_ID=`ps -ef |grep elasticsearch |grep '/usr/share/elasticsearch'|grep -v 'grep'|awk '{print $2}'` 18 | # 日志输出 19 | ESMonitorLog=$BIN_DIR/es-master-monitor.log 20 | NOW=`date '+%Y-%m-%d %H:%M:%S'` 21 | Monitor() 22 | { 23 | if [[ $ES_ID ]];then # 这里判断ES进程是否存在 24 | echo "[info][$NOW] 当前ES进程ID为:$ES_ID" 25 | else 26 | date '+%Y-%m-%d %H:%M:%S' 27 | echo "[error][$NOW] ES进程不存在!ES开始自动重启..." 28 | /etc/init.d/elasticsearch start 29 | fi 30 | } 31 | ``` 32 | 使用contab定时执行上述任务 33 | ```bash 34 | */1 * * * * /bin/bash /path-to/checkdisk.sh 35 | */15 * * * * /bin/sh /path-to/es_monitor.sh 36 | ``` 37 | -------------------------------------------------------------------------------- /code/linux/linux.md: -------------------------------------------------------------------------------- 1 | - 批量替换目录下所有文件的特定字符 2 | ```bash 3 | // 将文件中的\t 替换为`,-P参数使grep将后续匹配符识别为正则 4 | sed -i "s/\t /\`/g" `grep -P '\t\ ' -rl ./dir`; 5 | ``` 6 | - 简单命令 7 | ```bash 8 | // watch日志 9 | tail -f logfile 10 | // 搜索含有特定字符的行 11 | grep match_text logfile 12 | // 编辑crontab 13 | crontab -e 14 | // 重启crontab 15 | service crond restart 16 | // 查看文件、文件夹大小,-h 以易于人阅读的形式输出 17 | du -h logfile 18 | du -h logdir 19 | // 查看系统盘使用情况 20 | df -h 21 | // 查看系统状态,内存占用等 22 | top 23 | // pm2 24 | pm2 list 25 | pm2 start app.js --name app 26 | pm2 restart all 27 | pm2 restart 1 28 | pm2 restart app 29 | // 查看大文件 30 | find . -type f -size +800M 31 | // 查看大目录 32 | du -h --max-depth=1 33 | // 软链接 34 | ln -s file copyfile 35 | // 硬链接 36 | ln file copyfile 37 | // 查看开机启动项 38 | chkconfig 39 | // 就是查看文件里有多少行 40 | wc -l filename 41 | // 从服务器下载文件(参数 -r处理文件夹,-av --progress 显示进度) 42 | // 客户端和服务端都需要安装rsync 43 | rsync username@serverIp:serverFilePath localFileName 44 | // 上传文件到服务器 45 | rsync localFileName username@serverIp:serverFilePath 46 | // 查看进程详细信息 47 | ll /proc/进程pid 48 | ``` 49 | - centos 7开启端口 50 | ```bash 51 | firewall-cmd --list-all 52 | firewall-cmd --zone=public --add-port=80/tcp --permanent 53 | firewall-cmd --zone=public --add-port=443 /tcp --permanent 54 | firewall-cmd --reload 55 | ``` 56 | - crontab 规则 57 | ```bash 58 | # 每 10 小时一次 59 | # 注意,由于 crontab 的计算周期是一天 60 | # 所以,比如现在的事件时 16:00:00,接下来触发定时器的时间是 00:00:00、10:00:00、20:00:00、00:00:00... 61 | 0 */10 * * * 62 | ``` 63 | 64 | - grep 正则 65 | ```bash 66 | # 匹配 "sImsi":" 后的第一个字符不是 " 的行 67 | grep -n '"sImsi":"[^"]' filename 68 | ``` -------------------------------------------------------------------------------- /code/linux/lrzsz.md: -------------------------------------------------------------------------------- 1 | ## lrzsz 2 | 在mac item2中使用linux上传下载工具lrzsz 3 | mac安装:```brew install lrzsz``` 4 | centos安装:```sudo yum -y install lrzsz``` 5 | 将https://github.com/mmastrac/iterm2-zmodem下的两个sh文件复制到mac的/usr/local/bin,然后添加执行权限: 6 | ```bash 7 | chmod +x /usr/local/bin/iterm2-send-zmodem.sh 8 | chmod +x /usr/local/bin/iterm2-recv-zmodem.sh 9 | ``` 10 | 配置item2:打开iTerm2 -> Preferences -> Profiles 选择 Advanced 设置 Triggers ,点击 Edit,添加两项: 11 | ```bash 12 | Regular expression: \*\*B0100 13 | Action: Run Silent Coprocess 14 | Parameters: /usr/local/bin/iterm2-send-zmodem.sh 15 | 16 | Regular expression: \*\*B00000000000000 17 | Action: Run Silent Coprocess 18 | Parameters: /usr/local/bin/iterm2-recv-zmodem.sh 19 | ``` 20 | 如图: 21 | ![item2](https://user-images.githubusercontent.com/15033260/40297193-8d83be8c-5d11-11e8-9282-2aca8542fcae.png) 22 | 重启item2,在服务器的命令行中输入`rz`,就可以选择本地文件上传了。在服务器上输入`sz filename filename1 ... filenameN`,弹出目录选择框,选择本地的存储目录,等待下载 -------------------------------------------------------------------------------- /code/mobile-meta/mobile-meta.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 经纪人回应女子夜访胡歌豪宅_手机新浪网 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /code/mysql/mysql.md: -------------------------------------------------------------------------------- 1 | - mac 下 mysql 服务操作 2 | ```bash 3 | # 启动 4 | mysql.server start 5 | # 停止 6 | mysql.server stop 7 | # 重启 8 | mysql.server restart 9 | ``` 10 | 11 | - mysql 连接 12 | 13 | ```bash 14 | # 创建用户,host 如果为 %,则通配所有域名 15 | CREATE USER 'username'@'host' IDENTIFIED BY 'password'; 16 | # 例子: 17 | CREATE USER 'pig'@'192.168.1.101_' IDENDIFIED BY '123456'; 18 | CREATE USER 'pig'@'%' IDENTIFIED BY '123456'; 19 | 20 | # 授权,privileges 包括 SELECT,INSERT,UPDATE,ALL 等,这个授权操作会创建一个用户 21 | GRANT privileges ON databasename.tablename TO 'username'@'host' identified by 'password'; 22 | # 列子 23 | GRANT SELECT, INSERT ON test.user TO 'pig'@'%'; 24 | GRANT ALL ON *.* TO 'pig'@'%'; 25 | 26 | # 刷新权限 27 | flush privileges; 28 | 29 | # 使创建的用户能够对其他用户授权 30 | GRANT privileges ON databasename.tablename TO 'username'@'host' identified by 'password' WITH GRANT OPTION; 31 | # 设置与更改用户密码 32 | SET PASSWORD FOR 'username'@'host' = PASSWORD('newpassword'); 33 | # 删除用户 34 | DROP USER 'username'@'host'; 35 | 36 | # 添加非删除权限 37 | GRANT CREATE,INDEX,SELECT,INSERT,UPDATE,CREATE VIEW,SHOW VIEW,ALTER ROUTINE,CREATE ROUTINE,EXECUTE,CREATE TEMPORARY TABLES ON *.* TO 'developer'@'%'; 38 | 39 | # 取消权限 40 | Revoke SELECT,INSERT,UPDATE *.* from 'developer'@'%'; 41 | 42 | # 删除权限 43 | 44 | # 显示权限 45 | SHOW GRANTS FOR 'developer'@'%'; 46 | 47 | # 显示所有用户 48 | select User from mysql.user; 49 | 50 | # 报错 ER_NOT_SUPPORTED_AUTH_MODE: Client does not support authentication protocol requested by server; consider upgrading MySQL client 51 | ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '123456789'; 52 | ``` 53 | 54 | - 自增id被搞混乱过后,还原为自增id 55 | ```bash 56 | # 删除原有主键id 57 | ALTER TABLE `table_name` DROP `id`; 58 | # 再重建主键id 59 | ALTER TABLE `table_name` ADD 'id' mediumint(6) PRIMARY KEY NOT NULL AUTO_INCREMENT FIRST; 60 | (在myslq中,上述的单引号不需要) 61 | 62 | # 查看自增 id 增长到的数值 63 | SELECT `AUTO_INCREMENT` FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'DatabaseName' AND TABLE_NAME = 'TableName'; 64 | # 查看自增的字段 65 | DESCRIBE `table_name` 66 | ``` 67 | - 合并表,并使id自增(如果不处理,id会冲撞) 68 | ```bash 69 | # 方式一,将表二的id依次加上表一的长度,再将表二合并到表一 70 | update table2 SET ID=ID+(SELECT MAX(ID) FROM table1); 71 | insert into table2 select * from table1; 72 | # 方式二,直接将表二的非id数据导入表一,表一的id自动增加 73 | # 注意所有列都要写出,并且列顺序一致 74 | insert into table2 select name,sex,age from table1; 75 | # 或不要求顺序一致 76 | insert into table2(sex,name,age) select sex,name,age from table1; 77 | ``` 78 | - 统计表中一对多的个数 79 | ```bash 80 | # 统计一个vid对应多个uid的vid数目 81 | # (括号里的语句相当于返回一个新表,并命名为A) 82 | select count(*) 83 | from ( 84 | select vid, count(distinct uid) as cnt 85 | from table 86 | group by vid 87 | having cnt > 1) A; 88 | ``` 89 | - 一次建立建立索引 90 | ```bash 91 | ALTER TABLE table1 ADD INDEX ip_index(ip), ADD INDEX fp_index(fp), ADD INDEX vid_index(vid), ADD INDEX stime_index(stime); 92 | ``` 93 | - 复制表 94 | ```bash 95 | create table table12 like table1; 96 | insert into table12 select * from table1; 97 | ``` 98 | - 从另一个表插入数据 99 | ``` bash 100 | insert into t_nations_info (sCode, sCNName, sENName, sRegion) select code,zh_name,en_name,region from t_r_country_info_list t where code in ('KE') ON DUPLICATE KEY UPDATE sRegion=t.region; 101 | ``` 102 | - 新建列 103 | ```bash 104 | ALTER TABLE table1 ADD isoverwrite int(8) DEFAULT 0 NULL; 105 | ``` 106 | - 创建表 107 | ```bash 108 | CREATE TABLE `tabel1` (`id` bigint(20) NOT NULL AUTO_INCREMENT, `sdate` date DEFAULT NULL, `stime` time DEFAULT NULL, `ip` varchar(32) DEFAULT NULL, `uid` bigint(20) DEFAULT NULL, `vid` varchar(16) DEFAULT NULL, `isgen` int(8) DEFAULT 0, `fp` varchar(66) DEFAULT NULL, `ustat` varchar(60) DEFAULT NULL, `ua` text, `ref` text, `err` text, `clientua` text, `fpua` varchar(32) DEFAULT NULL, `fpdetail` text, `cvid` varchar(16) DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_uid` (`uid`)) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; 109 | ``` 110 | - 从文件日志导入数据库 111 | ```bash 112 | LOAD DATA LOCAL INFILE "D:/user-unique/20180125data/c_20180125.txt" REPLACE INTO TABLE `c_201801。。` FIELDS TERMINATED BY "\t" ENCLOSED BY "" ESCAPED BY "\\" LINES TERMINATED BY "\n" (sdate, cvid, vid, isgen, isgen2, uid, fp, ustat, ip, ua, ref, err, clientua, fpua, fpdetail); 113 | ``` 114 | - 多表联合查询 115 | ```bash 116 | # 计算表一二中,每个唯一id的数目,去掉group,则统计所有vid的数目 117 | # (括号里的语句相当于返回一个新表,并命名为x) 118 | select count(*) from 119 | (select * from table1 union all select * table2) x 120 | where isgen=1 group by x.vid; 121 | ``` 122 | 123 | - 查询昨天的数据 124 | ```bash 125 | select * from table_name where DATE(insert_time) = CURDATE()-1; 126 | ``` 127 | 128 | - 编码查看与配置 129 | ```bash 130 | # 查看数据库编码 131 | show variables like '%char%'; 132 | status; 133 | # 查看 table 编码 134 | show create table <表名>; 135 | # 查看字段编码 136 | show full columns from <表名>; 137 | # 设置数据库编码 138 | alter database <数据库名> character set utf8mb4; 139 | # 设置表编码 140 | alter table <表名> character set utf8mb4; 141 | # 设置字段编码 142 | ALTER TABLE <表名> MODIFY COLUMN <字段名> <字段类型> CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; 143 | ``` 144 | 145 | - 修改字段 146 | ```bash 147 | # 修改字段名以及数据类型 148 | ALTER TABLE 表名 CHANGE 旧字段名 新字段名 新数据类型; 149 | # 修改字段数据类型 150 | ALTER TABLE 表名 MODIFY COLUMN 字段名 新数据类型 新类型长度 新默认值 151 | # 修改表的默认字符集和所有列的字符集 152 | ALTER TABLE table_name CONVERT TO CHARACTER SET character_name 153 | ``` 154 | 155 | - 移动表到另一个数据库 156 | ```bash 157 | alter table database.table rename database1.table 158 | ``` 159 | 160 | - join 161 | left: 表1左连接表2,以左为主,表示以表1为主,关联上表2的数据,查出来的结果显示左边的所有数据,然后右边显示的是和左边有交集部分的数据 162 | right join: 表1右连接表2,以右为主,表示以表2为主,关联查询表1的数据,查出表2所有数据以及表1和表2有交集的数据 163 | inner join: 表示以两个表的交集为主,查出来是两个表有交集的部分,其余没有关联就不额外显示出来 164 | full outer join: 合并两个表的内容 165 | 参考:https://mp.weixin.qq.com/s/HC0GTEaVDiQj2w19yu3_DQ 166 | ```bash 167 | # 在表 A 中排除表 B 中的部分:取出 A + A&B,然后排除 B 存在值的部分 168 | select * from tableA A left join tableB B on A.key=B.key where B.key is null 169 | # 取出 A + B,排除 A、B 的交集 170 | select * from tableA A full outer join tableB B on A.key=B.key where B.key is null and A.key is null 171 | 172 | - 根据一个表的一列更新另一个表的一列 173 | update t_push_info t1 inner join t_push_rule_config t2 on t1.push_rule=t2.iRuleId set t1.push_rule_config_id=concat('rule_pushRule_', t2.id) 174 | ``` -------------------------------------------------------------------------------- /code/nginx/nginx.md: -------------------------------------------------------------------------------- 1 | ## nginx启动https反向代理 2 | /etc/nginx/nginx.config 3 | ```bash 4 | user nginx; 5 | worker_processes auto; 6 | 7 | error_log /var/log/nginx/error.log warn; 8 | error_log /var/log/nginx/error.log notice; 9 | error_log /var/log/nginx/error.log info; 10 | pid /var/run/nginx.pid; 11 | 12 | 13 | events { 14 | worker_connections 1024; 15 | } 16 | 17 | 18 | http { 19 | include /etc/nginx/mime.types; 20 | default_type application/octet-stream; 21 | 22 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 23 | '$status $body_bytes_sent "$http_referer" ' 24 | '"$http_user_agent" "$http_x_forwarded_for"'; 25 | 26 | access_log /var/log/nginx/access.log main; 27 | 28 | sendfile on; 29 | #tcp_nopush on; 30 | 31 | #配置共享会话缓存大小,视站点访问情况设定 32 | ssl_session_cache shared:SSL:10m; 33 | #配置会话超时时间 34 | ssl_session_timeout 10m; 35 | #gzip on; 36 | # 忽略原始服务器的cache响应头 37 | proxy_ignore_headers X-Accel-Expires; 38 | proxy_ignore_headers Expires; 39 | proxy_ignore_headers Cache-Control; 40 | # 缓存路径 41 | proxy_cache_path /etc/nginx/cache/api levels=1:2 keys_zone=api_cache:10m max_size=5g inactive=6h use_temp_path=off; 42 | proxy_cache_path /etc/nginx/cache/raw levels=1:2 keys_zone=raw_cache:10m max_size=5g inactive=6h use_temp_path=off; 43 | # 不同状态码的缓存时间 44 | # 与inactive配置是独立的,inactive代表文件从不被访问开始,保留的时间 45 | # 如果一直被访问,则会一直保存 46 | proxy_cache_valid 200 301 302 6h; 47 | proxy_cache_valid any 5m; 48 | 49 | include /etc/nginx/conf.d/*.conf; 50 | } 51 | ``` 52 | /etc/nginx/conf.d/*.conf 53 | ```bash 54 | server { 55 | listen 443 ssl; 56 | server_name www.test.com; 57 | ssl_certificate path/server.crt; 58 | ssl_certificate_key path/server.key; 59 | 60 | keepalive_timeout 70; 61 | # ssl_protocols SSLv2 SSLv3 TLSv1; 62 | # ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP; 63 | ssl_prefer_server_ciphers on; 64 | 65 | #减少点击劫持 66 | add_header X-Frame-Options DENY; 67 | #禁止服务器自动解析资源类型 68 | add_header X-Content-Type-Options nosniff; 69 | #防XSS攻擊 70 | add_header X-Xss-Protection 1; 71 | 72 | #charset koi8-r; 73 | # access_log /var/log/nginx/host.access.log main; 74 | 75 | location /repos/ { 76 | proxy_cache raw_cache; 77 | proxy_pass https://api.github.com; 78 | rewrite ^(.*)$ $1?client_id=xxxx&xxxxx break; 79 | } 80 | location /myraw/ { 81 | proxy_cache raw_cache; 82 | proxy_pass https://raw.githubusercontent.com; 83 | rewrite /myraw/(.*) /$1 break; 84 | } 85 | 86 | #error_page 404 /404.html; 87 | 88 | # redirect server error pages to the static page /50x.html 89 | # 90 | error_page 500 502 503 504 /50x.html; 91 | location = /50x.html { 92 | root /usr/share/nginx/html; 93 | } 94 | 95 | # proxy the PHP scripts to Apache listening on 127.0.0.1:80 96 | # 97 | #location ~ \.php$ { 98 | # proxy_pass http://127.0.0.1; 99 | #} 100 | 101 | # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 102 | # 103 | #location ~ \.php$ { 104 | # root html; 105 | # fastcgi_pass 127.0.0.1:9000; 106 | # fastcgi_index index.php; 107 | # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; 108 | # include fastcgi_params; 109 | #} 110 | 111 | # deny access to .htaccess files, if Apache's document root 112 | # concurs with nginx's one 113 | # 114 | #location ~ /\.ht { 115 | # deny all; 116 | #} 117 | } 118 | ``` -------------------------------------------------------------------------------- /code/parse-string-path-to-tree/data.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | 'path': '.eslintignore', 4 | 'mode': '100644', 5 | 'type': 'blob', 6 | 'sha': '0effd54813556590823e0827768d6578c8ea1544', 7 | 'size': 62, 8 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/0effd54813556590823e0827768d6578c8ea1544' 9 | }, 10 | { 11 | 'path': '.eslintrc.js', 12 | 'mode': '100644', 13 | 'type': 'blob', 14 | 'sha': '7f3fdb962a5adf2c83192b11f8271a3dbb0ac6a1', 15 | 'size': 519, 16 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/7f3fdb962a5adf2c83192b11f8271a3dbb0ac6a1' 17 | }, 18 | { 19 | 'path': '.gitignore', 20 | 'mode': '100644', 21 | 'type': 'blob', 22 | 'sha': 'dfb4167f9a5c27e756cbf779da6af25b6a8b89a3', 23 | 'size': 213, 24 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/dfb4167f9a5c27e756cbf779da6af25b6a8b89a3' 25 | }, 26 | { 27 | 'path': '.npmignore', 28 | 'mode': '100644', 29 | 'type': 'blob', 30 | 'sha': '9b12ae5e3a3d9eeb6ba90d820fe47c09aab9af21', 31 | 'size': 225, 32 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/9b12ae5e3a3d9eeb6ba90d820fe47c09aab9af21' 33 | }, 34 | { 35 | 'path': 'README.md', 36 | 'mode': '100644', 37 | 'type': 'blob', 38 | 'sha': '8f29e1d72cf32cc926bafc5535ba6dec01c16c1c', 39 | 'size': 1697, 40 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/8f29e1d72cf32cc926bafc5535ba6dec01c16c1c' 41 | }, 42 | { 43 | 'path': 'bin', 44 | 'mode': '040000', 45 | 'type': 'tree', 46 | 'sha': '47ae6457630872c17063dc0888c6a0b6dc2cbbc3', 47 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/trees/47ae6457630872c17063dc0888c6a0b6dc2cbbc3' 48 | }, 49 | { 50 | 'path': 'bin/vue-hap.js', 51 | 'mode': '100755', 52 | 'type': 'blob', 53 | 'sha': '77cd3a1cee0e858c3eb14af08e3c4af230420fdf', 54 | 'size': 1287, 55 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/77cd3a1cee0e858c3eb14af08e3c4af230420fdf' 56 | }, 57 | { 58 | 'path': 'convert', 59 | 'mode': '040000', 60 | 'type': 'tree', 61 | 'sha': '87ac892577f0e168fa9415869af36081610ec85e', 62 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/trees/87ac892577f0e168fa9415869af36081610ec85e' 63 | }, 64 | { 65 | 'path': 'convert/app', 66 | 'mode': '040000', 67 | 'type': 'tree', 68 | 'sha': '8179b489c68035fd1e817480f289599833f7d53f', 69 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/trees/8179b489c68035fd1e817480f289599833f7d53f' 70 | }, 71 | { 72 | 'path': 'convert/app/bind-watch.js', 73 | 'mode': '100644', 74 | 'type': 'blob', 75 | 'sha': 'fa82817fa70f28aba4bfe0e6d55d3677a0a8c701', 76 | 'size': 874, 77 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/fa82817fa70f28aba4bfe0e6d55d3677a0a8c701' 78 | }, 79 | { 80 | 'path': 'convert/app/export.js', 81 | 'mode': '100644', 82 | 'type': 'blob', 83 | 'sha': 'bf786c0169be2dd861b0cd1132eccf0b9b75b14b', 84 | 'size': 676, 85 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/bf786c0169be2dd861b0cd1132eccf0b9b75b14b' 86 | }, 87 | { 88 | 'path': 'convert/app/import.js', 89 | 'mode': '100644', 90 | 'type': 'blob', 91 | 'sha': '2e1be9048504945ffe19a9489fab5f1ebc91b27f', 92 | 'size': 616, 93 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/2e1be9048504945ffe19a9489fab5f1ebc91b27f' 94 | }, 95 | { 96 | 'path': 'convert/app/index.js', 97 | 'mode': '100644', 98 | 'type': 'blob', 99 | 'sha': '8ab1cc79a835bf5d054b3bc15b0750769e9c0bab', 100 | 'size': 1553, 101 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/8ab1cc79a835bf5d054b3bc15b0750769e9c0bab' 102 | }, 103 | { 104 | 'path': 'convert/app/router-hack.js', 105 | 'mode': '100644', 106 | 'type': 'blob', 107 | 'sha': '2c923eae140aa1e243374c7f7816bc9ed4417b62', 108 | 'size': 945, 109 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/2c923eae140aa1e243374c7f7816bc9ed4417b62' 110 | }, 111 | { 112 | 'path': 'convert/app/vue-mount.js', 113 | 'mode': '100644', 114 | 'type': 'blob', 115 | 'sha': '1ca56df8d2e6b816c78a175df1165ef97e58beb7', 116 | 'size': 239, 117 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/1ca56df8d2e6b816c78a175df1165ef97e58beb7' 118 | }, 119 | { 120 | 'path': 'convert/index.js', 121 | 'mode': '100644', 122 | 'type': 'blob', 123 | 'sha': '2731a9e5ed2796526348b54453f1eca743bad3ea', 124 | 'size': 1141, 125 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/2731a9e5ed2796526348b54453f1eca743bad3ea' 126 | }, 127 | { 128 | 'path': 'convert/js', 129 | 'mode': '040000', 130 | 'type': 'tree', 131 | 'sha': '058a01347a76a1de811a38dfcbdd8f74112d4168', 132 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/trees/058a01347a76a1de811a38dfcbdd8f74112d4168' 133 | }, 134 | { 135 | 'path': 'convert/js/code-gen', 136 | 'mode': '040000', 137 | 'type': 'tree', 138 | 'sha': '386cb352c7cb7e45d1609e8d4bfe6597193b776c', 139 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/trees/386cb352c7cb7e45d1609e8d4bfe6597193b776c' 140 | }, 141 | { 142 | 'path': 'convert/js/code-gen/export.js', 143 | 'mode': '100644', 144 | 'type': 'blob', 145 | 'sha': '8790669b8059018178023f025ce41fd086dd2c7e', 146 | 'size': 1268, 147 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/8790669b8059018178023f025ce41fd086dd2c7e' 148 | }, 149 | { 150 | 'path': 'convert/js/code-gen/index.js', 151 | 'mode': '100644', 152 | 'type': 'blob', 153 | 'sha': 'c83edfb968737d55e193b55c39caa53c1148dc0c', 154 | 'size': 634, 155 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/c83edfb968737d55e193b55c39caa53c1148dc0c' 156 | }, 157 | { 158 | 'path': 'convert/js/code-gen/vue-options.js', 159 | 'mode': '100644', 160 | 'type': 'blob', 161 | 'sha': 'c49f4c0d9d86bff7e9e014ca780747206c4ecd40', 162 | 'size': 398, 163 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/c49f4c0d9d86bff7e9e014ca780747206c4ecd40' 164 | }, 165 | { 166 | 'path': 'convert/js/code-parse', 167 | 'mode': '040000', 168 | 'type': 'tree', 169 | 'sha': 'e9b67c706ef3ddc1f2f536c754c7b3a86484e604', 170 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/trees/e9b67c706ef3ddc1f2f536c754c7b3a86484e604' 171 | }, 172 | { 173 | 'path': 'convert/js/code-parse/export.js', 174 | 'mode': '100644', 175 | 'type': 'blob', 176 | 'sha': 'c47a6ce9914e9303e097ebaf748df86aa8ef46bf', 177 | 'size': 1435, 178 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/c47a6ce9914e9303e097ebaf748df86aa8ef46bf' 179 | }, 180 | { 181 | 'path': 'convert/js/code-parse/index.js', 182 | 'mode': '100644', 183 | 'type': 'blob', 184 | 'sha': 'd57347ed27267474459d59a24084faf5f52893f1', 185 | 'size': 1348, 186 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/d57347ed27267474459d59a24084faf5f52893f1' 187 | }, 188 | { 189 | 'path': 'convert/js/index.js', 190 | 'mode': '100644', 191 | 'type': 'blob', 192 | 'sha': '76fae7597d742d27b9a17c9616b5092bc8d59fc0', 193 | 'size': 715, 194 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/76fae7597d742d27b9a17c9616b5092bc8d59fc0' 195 | }, 196 | { 197 | 'path': 'convert/style', 198 | 'mode': '040000', 199 | 'type': 'tree', 200 | 'sha': 'ff9c07c581ca7bfbe8742b139060ffcb0a946f68', 201 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/trees/ff9c07c581ca7bfbe8742b139060ffcb0a946f68' 202 | }, 203 | { 204 | 'path': 'convert/style/hack-selector.js', 205 | 'mode': '100644', 206 | 'type': 'blob', 207 | 'sha': 'f19b4fc924a5b0aa4540cd075c19acbf2f46278f', 208 | 'size': 1415, 209 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/f19b4fc924a5b0aa4540cd075c19acbf2f46278f' 210 | }, 211 | { 212 | 'path': 'convert/style/index.js', 213 | 'mode': '100644', 214 | 'type': 'blob', 215 | 'sha': '07896ac364fad7143e2c34fe55b3092ef8e8b255', 216 | 'size': 883, 217 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/07896ac364fad7143e2c34fe55b3092ef8e8b255' 218 | }, 219 | { 220 | 'path': 'convert/style/rem-to-px.js', 221 | 'mode': '100644', 222 | 'type': 'blob', 223 | 'sha': '210b64831a347d12373fc6ee0ef4d462341f7991', 224 | 'size': 289, 225 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/210b64831a347d12373fc6ee0ef4d462341f7991' 226 | }, 227 | { 228 | 'path': 'convert/tpl', 229 | 'mode': '040000', 230 | 'type': 'tree', 231 | 'sha': 'fc3433e24d7f770714cc68b916348df3169e61e0', 232 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/trees/fc3433e24d7f770714cc68b916348df3169e61e0' 233 | }, 234 | { 235 | 'path': 'convert/tpl/directive.js', 236 | 'mode': '100644', 237 | 'type': 'blob', 238 | 'sha': '2ec1a73cbe4f19622339e552837af3ed6aeb7d6e', 239 | 'size': 2331, 240 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/2ec1a73cbe4f19622339e552837af3ed6aeb7d6e' 241 | }, 242 | { 243 | 'path': 'convert/tpl/index.js', 244 | 'mode': '100644', 245 | 'type': 'blob', 246 | 'sha': '44738d0161a0b46cb98609e27fe2d1b6eef08434', 247 | 'size': 457, 248 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/44738d0161a0b46cb98609e27fe2d1b6eef08434' 249 | }, 250 | { 251 | 'path': 'convert/tpl/process.js', 252 | 'mode': '100644', 253 | 'type': 'blob', 254 | 'sha': 'fa3d7aafefdb809836925254c53548089613d829', 255 | 'size': 5561, 256 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/fa3d7aafefdb809836925254c53548089613d829' 257 | }, 258 | { 259 | 'path': 'convert/tpl/self-close-tag.js', 260 | 'mode': '100644', 261 | 'type': 'blob', 262 | 'sha': '04ee8c868e3c6745c05174c10823c7bf62f5b2a1', 263 | 'size': 613, 264 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/04ee8c868e3c6745c05174c10823c7bf62f5b2a1' 265 | }, 266 | { 267 | 'path': 'convert/tpl/tag-map.js', 268 | 'mode': '100644', 269 | 'type': 'blob', 270 | 'sha': '492735f0dd618f30f23572bf5dbe80fd0568b464', 271 | 'size': 787, 272 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/492735f0dd618f30f23572bf5dbe80fd0568b464' 273 | }, 274 | { 275 | 'path': 'convert/utils', 276 | 'mode': '040000', 277 | 'type': 'tree', 278 | 'sha': '0d64cec07b2dd979fa806f97f930efd806c7bf65', 279 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/trees/0d64cec07b2dd979fa806f97f930efd806c7bf65' 280 | }, 281 | { 282 | 'path': 'convert/utils/comment-delete.js', 283 | 'mode': '100644', 284 | 'type': 'blob', 285 | 'sha': '14e2d741172f5288201377658d457e7e216aeb91', 286 | 'size': 680, 287 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/14e2d741172f5288201377658d457e7e216aeb91' 288 | }, 289 | { 290 | 'path': 'convert/utils/css-what-stringify.js', 291 | 'mode': '100644', 292 | 'type': 'blob', 293 | 'sha': '174efce1d7b5aed9433caed6d399cbf43f90493b', 294 | 'size': 1506, 295 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/174efce1d7b5aed9433caed6d399cbf43f90493b' 296 | }, 297 | { 298 | 'path': 'convert/utils/index.js', 299 | 'mode': '100644', 300 | 'type': 'blob', 301 | 'sha': 'e389d8c690d9ba18feffd718271d5e41d5ca8da6', 302 | 'size': 3025, 303 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/e389d8c690d9ba18feffd718271d5e41d5ca8da6' 304 | }, 305 | { 306 | 'path': 'docs', 307 | 'mode': '040000', 308 | 'type': 'tree', 309 | 'sha': 'b5cd3efba2be9135e4efd906abbf7b0abbf31f83', 310 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/trees/b5cd3efba2be9135e4efd906abbf7b0abbf31f83' 311 | }, 312 | { 313 | 'path': 'docs/doc.md', 314 | 'mode': '100644', 315 | 'type': 'blob', 316 | 'sha': 'd4e82175e1de8fef773705f29bb5c1dd8e1cd798', 317 | 'size': 3896, 318 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/d4e82175e1de8fef773705f29bb5c1dd8e1cd798' 319 | }, 320 | { 321 | 'path': 'docs/example.js', 322 | 'mode': '100644', 323 | 'type': 'blob', 324 | 'sha': '89c93be0c2d1c7e6854525f3b24f2782634c580e', 325 | 'size': 2160, 326 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/89c93be0c2d1c7e6854525f3b24f2782634c580e' 327 | }, 328 | { 329 | 'path': 'docs/explanation.md', 330 | 'mode': '100644', 331 | 'type': 'blob', 332 | 'sha': 'ccbb0e3b5383d8171514b664f2c6f2cbba218e89', 333 | 'size': 3502, 334 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/ccbb0e3b5383d8171514b664f2c6f2cbba218e89' 335 | }, 336 | { 337 | 'path': 'docs/flow-chart.png', 338 | 'mode': '100644', 339 | 'type': 'blob', 340 | 'sha': '77dd9199dcf34a5eb72703b225b9159d4aae9b05', 341 | 'size': 32420, 342 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/77dd9199dcf34a5eb72703b225b9159d4aae9b05' 343 | }, 344 | { 345 | 'path': 'docs/knownIssues.md', 346 | 'mode': '100644', 347 | 'type': 'blob', 348 | 'sha': '3201404644f4cddb48775e182d9ff2ab33e8148c', 349 | 'size': 5318, 350 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/3201404644f4cddb48775e182d9ff2ab33e8148c' 351 | }, 352 | { 353 | 'path': 'docs/quick-app-web.gif', 354 | 'mode': '100644', 355 | 'type': 'blob', 356 | 'sha': 'a49a32c116dcf77d50730557eec30146f398fb13', 357 | 'size': 2176333, 358 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/a49a32c116dcf77d50730557eec30146f398fb13' 359 | }, 360 | { 361 | 'path': 'docs/quick-app.gif', 362 | 'mode': '100644', 363 | 'type': 'blob', 364 | 'sha': '860753dfdbc0f65300edb9fd7390e3105ca21d9f', 365 | 'size': 4971138, 366 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/860753dfdbc0f65300edb9fd7390e3105ca21d9f' 367 | }, 368 | { 369 | 'path': 'index.js', 370 | 'mode': '100644', 371 | 'type': 'blob', 372 | 'sha': 'd34d420283f2d0dfcedeed44142424fee729c5a4', 373 | 'size': 1326, 374 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/d34d420283f2d0dfcedeed44142424fee729c5a4' 375 | }, 376 | { 377 | 'path': 'package.json', 378 | 'mode': '100644', 379 | 'type': 'blob', 380 | 'sha': '088399fbc0556c57d22182d5166ca9f6a07f9388', 381 | 'size': 1793, 382 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/088399fbc0556c57d22182d5166ca9f6a07f9388' 383 | }, 384 | { 385 | 'path': 'sign', 386 | 'mode': '040000', 387 | 'type': 'tree', 388 | 'sha': 'c3bbaa23205756a1ba7422a02c8a3477efbbf4c8', 389 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/trees/c3bbaa23205756a1ba7422a02c8a3477efbbf4c8' 390 | }, 391 | { 392 | 'path': 'sign/debug', 393 | 'mode': '040000', 394 | 'type': 'tree', 395 | 'sha': '73fdf44ef6b36d6a3e0951fceae359be4fa37437', 396 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/trees/73fdf44ef6b36d6a3e0951fceae359be4fa37437' 397 | }, 398 | { 399 | 'path': 'sign/debug/certificate.pem', 400 | 'mode': '100644', 401 | 'type': 'blob', 402 | 'sha': 'fe635b145ed348ba166935f2f9fb88b5c0b25341', 403 | 'size': 1168, 404 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/fe635b145ed348ba166935f2f9fb88b5c0b25341' 405 | }, 406 | { 407 | 'path': 'sign/debug/private.pem', 408 | 'mode': '100644', 409 | 'type': 'blob', 410 | 'sha': '50d0707f36120e134a997792202baa73c21664fa', 411 | 'size': 1704, 412 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/50d0707f36120e134a997792202baa73c21664fa' 413 | }, 414 | { 415 | 'path': 'test', 416 | 'mode': '040000', 417 | 'type': 'tree', 418 | 'sha': 'a0abad12ca15cd3a97cd60a726b580f8bebbf765', 419 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/trees/a0abad12ca15cd3a97cd60a726b580f8bebbf765' 420 | }, 421 | { 422 | 'path': 'test/convert', 423 | 'mode': '040000', 424 | 'type': 'tree', 425 | 'sha': 'af7ee0a2da6e755dc4c2d50ea4585ad2f03ad315', 426 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/trees/af7ee0a2da6e755dc4c2d50ea4585ad2f03ad315' 427 | }, 428 | { 429 | 'path': 'test/convert/convert-vue', 430 | 'mode': '040000', 431 | 'type': 'tree', 432 | 'sha': 'b471cee7aa70bb996983e9b04b50b0781e4681d1', 433 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/trees/b471cee7aa70bb996983e9b04b50b0781e4681d1' 434 | }, 435 | { 436 | 'path': 'test/convert/convert-vue/code-tpl', 437 | 'mode': '040000', 438 | 'type': 'tree', 439 | 'sha': '75e4a69296ff71eb02ab478e5b829bdb488ccad6', 440 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/trees/75e4a69296ff71eb02ab478e5b829bdb488ccad6' 441 | }, 442 | { 443 | 'path': 'test/convert/convert-vue/code-tpl/index.res.vue', 444 | 'mode': '100644', 445 | 'type': 'blob', 446 | 'sha': 'ef6f27fa0fcd95bc6ba7ebc2935212c93f3dd570', 447 | 'size': 2162, 448 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/ef6f27fa0fcd95bc6ba7ebc2935212c93f3dd570' 449 | }, 450 | { 451 | 'path': 'test/convert/convert-vue/code-tpl/index.vue', 452 | 'mode': '100644', 453 | 'type': 'blob', 454 | 'sha': 'be721ab33dd7aa36a8a06ae54d9f0cdf08748e7a', 455 | 'size': 2044, 456 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/be721ab33dd7aa36a8a06ae54d9f0cdf08748e7a' 457 | }, 458 | { 459 | 'path': 'test/convert/convert-vue/index.test.js', 460 | 'mode': '100644', 461 | 'type': 'blob', 462 | 'sha': 'f75f07b30b41fe9a7b4386693cc7bc8ed964d3de', 463 | 'size': 602, 464 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/f75f07b30b41fe9a7b4386693cc7bc8ed964d3de' 465 | }, 466 | { 467 | 'path': 'test/convert/js', 468 | 'mode': '040000', 469 | 'type': 'tree', 470 | 'sha': '44103e15e0c6ab291dbc08b12dd38fb994fcd5eb', 471 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/trees/44103e15e0c6ab291dbc08b12dd38fb994fcd5eb' 472 | }, 473 | { 474 | 'path': 'test/convert/js/code-tpl', 475 | 'mode': '040000', 476 | 'type': 'tree', 477 | 'sha': '6992ffb65dc5449a3cc2a623f53c6c850d382f4f', 478 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/trees/6992ffb65dc5449a3cc2a623f53c6c850d382f4f' 479 | }, 480 | { 481 | 'path': 'test/convert/js/code-tpl/components', 482 | 'mode': '040000', 483 | 'type': 'tree', 484 | 'sha': 'afb3c0309467fbe68c5ef1aef9be71583d8421f3', 485 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/trees/afb3c0309467fbe68c5ef1aef9be71583d8421f3' 486 | }, 487 | { 488 | 'path': 'test/convert/js/code-tpl/components/index.js', 489 | 'mode': '100644', 490 | 'type': 'blob', 491 | 'sha': 'f2d8511df95bbaece191fe186abac76ddf167dec', 492 | 'size': 216, 493 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/f2d8511df95bbaece191fe186abac76ddf167dec' 494 | }, 495 | { 496 | 'path': 'test/convert/js/code-tpl/components/index.res.js', 497 | 'mode': '100644', 498 | 'type': 'blob', 499 | 'sha': 'e9bb1e54e477a8edbedd2c601a5737268a5bf92b', 500 | 'size': 65, 501 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/e9bb1e54e477a8edbedd2c601a5737268a5bf92b' 502 | }, 503 | { 504 | 'path': 'test/convert/js/code-tpl/computed', 505 | 'mode': '040000', 506 | 'type': 'tree', 507 | 'sha': 'ce9b3cbf28afc5a5e311200b8c29b7181e1f4585', 508 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/trees/ce9b3cbf28afc5a5e311200b8c29b7181e1f4585' 509 | }, 510 | { 511 | 'path': 'test/convert/js/code-tpl/computed/index.js', 512 | 'mode': '100644', 513 | 'type': 'blob', 514 | 'sha': '28ced8d0eeb573373c1a0f16a34b98ebffc80332', 515 | 'size': 144, 516 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/28ced8d0eeb573373c1a0f16a34b98ebffc80332' 517 | }, 518 | { 519 | 'path': 'test/convert/js/code-tpl/computed/index.res.js', 520 | 'mode': '100644', 521 | 'type': 'blob', 522 | 'sha': '153decc5861d9b4231e34e8ce3ba7d31cd0ea33c', 523 | 'size': 431, 524 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/153decc5861d9b4231e34e8ce3ba7d31cd0ea33c' 525 | }, 526 | { 527 | 'path': 'test/convert/js/code-tpl/event-callback', 528 | 'mode': '040000', 529 | 'type': 'tree', 530 | 'sha': '3e485fea6e6670a242358ae394992598b895eec6', 531 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/trees/3e485fea6e6670a242358ae394992598b895eec6' 532 | }, 533 | { 534 | 'path': 'test/convert/js/code-tpl/event-callback/index.js', 535 | 'mode': '100644', 536 | 'type': 'blob', 537 | 'sha': '04611246a69d4c5a43ec33d79952e4c9bcb55e23', 538 | 'size': 107, 539 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/04611246a69d4c5a43ec33d79952e4c9bcb55e23' 540 | }, 541 | { 542 | 'path': 'test/convert/js/code-tpl/event-callback/index.res.js', 543 | 'mode': '100644', 544 | 'type': 'blob', 545 | 'sha': 'a8be6fde8bb0203082f88e6a84432056a77311dd', 546 | 'size': 310, 547 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/a8be6fde8bb0203082f88e6a84432056a77311dd' 548 | }, 549 | { 550 | 'path': 'test/convert/js/code-tpl/lifecycle', 551 | 'mode': '040000', 552 | 'type': 'tree', 553 | 'sha': 'f84c7924a3f261c7e9e45c26469be35aa0ece964', 554 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/trees/f84c7924a3f261c7e9e45c26469be35aa0ece964' 555 | }, 556 | { 557 | 'path': 'test/convert/js/code-tpl/lifecycle/index.js', 558 | 'mode': '100644', 559 | 'type': 'blob', 560 | 'sha': 'c0f9dec84ba443c57bda2565d13d8149d73859e2', 561 | 'size': 78, 562 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/c0f9dec84ba443c57bda2565d13d8149d73859e2' 563 | }, 564 | { 565 | 'path': 'test/convert/js/code-tpl/lifecycle/index.res.js', 566 | 'mode': '100644', 567 | 'type': 'blob', 568 | 'sha': 'da803a5e7265f87088e847a7534a4ca2b8415bbf', 569 | 'size': 99, 570 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/da803a5e7265f87088e847a7534a4ca2b8415bbf' 571 | }, 572 | { 573 | 'path': 'test/convert/js/code-tpl/methods', 574 | 'mode': '040000', 575 | 'type': 'tree', 576 | 'sha': '07334accc08bfb8355634fff6f0d9156e9193bf3', 577 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/trees/07334accc08bfb8355634fff6f0d9156e9193bf3' 578 | }, 579 | { 580 | 'path': 'test/convert/js/code-tpl/methods/index.js', 581 | 'mode': '100644', 582 | 'type': 'blob', 583 | 'sha': '8adc5e3132ce8b9bfc0c9613871d1fca5509c3af', 584 | 'size': 95, 585 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/8adc5e3132ce8b9bfc0c9613871d1fca5509c3af' 586 | }, 587 | { 588 | 'path': 'test/convert/js/code-tpl/methods/index.res.js', 589 | 'mode': '100644', 590 | 'type': 'blob', 591 | 'sha': '062a5672f136c67b300875a8697d48ee6a2c85ce', 592 | 'size': 75, 593 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/062a5672f136c67b300875a8697d48ee6a2c85ce' 594 | }, 595 | { 596 | 'path': 'test/convert/js/code-tpl/router', 597 | 'mode': '040000', 598 | 'type': 'tree', 599 | 'sha': '937ba6424af395cac7fe8484bcb3aa764d9a2e5f', 600 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/trees/937ba6424af395cac7fe8484bcb3aa764d9a2e5f' 601 | }, 602 | { 603 | 'path': 'test/convert/js/code-tpl/router/index.js', 604 | 'mode': '100644', 605 | 'type': 'blob', 606 | 'sha': '020150e5a8dd9b157427207464c14c02c6285954', 607 | 'size': 241, 608 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/020150e5a8dd9b157427207464c14c02c6285954' 609 | }, 610 | { 611 | 'path': 'test/convert/js/code-tpl/router/index.res.js', 612 | 'mode': '100644', 613 | 'type': 'blob', 614 | 'sha': 'a55fbea3eb6f3997361f318a84a754a82cf17252', 615 | 'size': 538, 616 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/a55fbea3eb6f3997361f318a84a754a82cf17252' 617 | }, 618 | { 619 | 'path': 'test/convert/js/code-tpl/watch', 620 | 'mode': '040000', 621 | 'type': 'tree', 622 | 'sha': '9645caacae045f3738bd014e785f509549d92d17', 623 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/trees/9645caacae045f3738bd014e785f509549d92d17' 624 | }, 625 | { 626 | 'path': 'test/convert/js/code-tpl/watch/index.js', 627 | 'mode': '100644', 628 | 'type': 'blob', 629 | 'sha': 'e19e99773dc779e09355ede634d862e24eb2a931', 630 | 'size': 137, 631 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/e19e99773dc779e09355ede634d862e24eb2a931' 632 | }, 633 | { 634 | 'path': 'test/convert/js/code-tpl/watch/index.res.js', 635 | 'mode': '100644', 636 | 'type': 'blob', 637 | 'sha': '4c42200d951f137365839e9144475de2c01dbc79', 638 | 'size': 212, 639 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/4c42200d951f137365839e9144475de2c01dbc79' 640 | }, 641 | { 642 | 'path': 'test/convert/js/index.test.js', 643 | 'mode': '100644', 644 | 'type': 'blob', 645 | 'sha': '80c2284f7a28af414c3a5d1e7a3b4462a38bcb60', 646 | 'size': 1860, 647 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/80c2284f7a28af414c3a5d1e7a3b4462a38bcb60' 648 | }, 649 | { 650 | 'path': 'test/convert/style', 651 | 'mode': '040000', 652 | 'type': 'tree', 653 | 'sha': '2d2ea69f25a4f0c7582528f3b4240daccdf8d2e6', 654 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/trees/2d2ea69f25a4f0c7582528f3b4240daccdf8d2e6' 655 | }, 656 | { 657 | 'path': 'test/convert/style/index.test.js', 658 | 'mode': '100644', 659 | 'type': 'blob', 660 | 'sha': 'bb51dc29e4e0f0229dba62a909682c30351627eb', 661 | 'size': 791, 662 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/bb51dc29e4e0f0229dba62a909682c30351627eb' 663 | }, 664 | { 665 | 'path': 'test/convert/tpl', 666 | 'mode': '040000', 667 | 'type': 'tree', 668 | 'sha': 'e833baaf4aaee96cb25a9db10f8d5d3e55ed0d0a', 669 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/trees/e833baaf4aaee96cb25a9db10f8d5d3e55ed0d0a' 670 | }, 671 | { 672 | 'path': 'test/convert/tpl/index.test.js', 673 | 'mode': '100644', 674 | 'type': 'blob', 675 | 'sha': 'dfbec6c9185126ed9ea601c9acd8995ffb4c2ceb', 676 | 'size': 3826, 677 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/dfbec6c9185126ed9ea601c9acd8995ffb4c2ceb' 678 | }, 679 | { 680 | 'path': 'utils', 681 | 'mode': '040000', 682 | 'type': 'tree', 683 | 'sha': '293317ffb97b5c7a1b5a87f4233d91b5c808afce', 684 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/trees/293317ffb97b5c7a1b5a87f4233d91b5c808afce' 685 | }, 686 | { 687 | 'path': 'utils/doConvert.js', 688 | 'mode': '100644', 689 | 'type': 'blob', 690 | 'sha': 'e816f5f2f6c71033877492b561478ac909aab0cf', 691 | 'size': 1336, 692 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/e816f5f2f6c71033877492b561478ac909aab0cf' 693 | }, 694 | { 695 | 'path': 'utils/fixSrcDir.js', 696 | 'mode': '100644', 697 | 'type': 'blob', 698 | 'sha': '4985e59428a2fa015bd0b462178f6626ebb2e452', 699 | 'size': 1265, 700 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/4985e59428a2fa015bd0b462178f6626ebb2e452' 701 | }, 702 | { 703 | 'path': 'utils/getHapPath.js', 704 | 'mode': '100644', 705 | 'type': 'blob', 706 | 'sha': '0503e022501a74d8a27f6d4f077eab5b62b3e9d0', 707 | 'size': 316, 708 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/0503e022501a74d8a27f6d4f077eab5b62b3e9d0' 709 | }, 710 | { 711 | 'path': 'utils/getSrcDir.js', 712 | 'mode': '100644', 713 | 'type': 'blob', 714 | 'sha': '86be45c2f8cf749bbba76ff007866ad8662c29f7', 715 | 'size': 135, 716 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/86be45c2f8cf749bbba76ff007866ad8662c29f7' 717 | }, 718 | { 719 | 'path': 'utils/logger.js', 720 | 'mode': '100644', 721 | 'type': 'blob', 722 | 'sha': '7ef774836709e82758f688044289a3010f8dc57d', 723 | 'size': 729, 724 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/7ef774836709e82758f688044289a3010f8dc57d' 725 | }, 726 | { 727 | 'path': 'utils/watchFile.js', 728 | 'mode': '100644', 729 | 'type': 'blob', 730 | 'sha': 'd500d397af08990cb37ddcaa79fc30349bddbd59', 731 | 'size': 253, 732 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/d500d397af08990cb37ddcaa79fc30349bddbd59' 733 | }, 734 | { 735 | 'path': 'yarn.lock', 736 | 'mode': '100644', 737 | 'type': 'blob', 738 | 'sha': 'b180e7d64be7e905e0e25bc46fa49ba71fce4d2f', 739 | 'size': 189393, 740 | 'url': 'https://api.github.com/repos/Youjingyu/vue-hap-tools/git/blobs/b180e7d64be7e905e0e25bc46fa49ba71fce4d2f' 741 | } 742 | ] 743 | -------------------------------------------------------------------------------- /code/parse-string-path-to-tree/parse-string-path-to-tree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 将一系列字符串表示的文件路径,解析为树结构,并输出与原始顺序一致的数组 3 | * 数据结构见:https://api.github.com/repos/Youjingyu/Code-Collection/git/trees/eb2334713e90342e6f90f9a4288075d588845d72?recursive=1 4 | * @param {Array} data 原始数据 5 | */ 6 | 7 | function parseData (data) { 8 | // 将路径解析为用对象表示的树,用节点名作为key 9 | // 因为遍历对象不能保证顺序,因此用length、index属性模拟数组, 10 | // 最后使用_objToArr方法将模拟的数组转换为真数组 11 | const treeObj = { 12 | length: 0, 13 | index: 0 14 | } 15 | data.forEach((item) => { 16 | const pathArr = item.path.split('/') 17 | let delegateObj = treeObj 18 | // 遍历到到倒数第二个路径(最后一个路径单独处理) 19 | for (var i = 0; i < pathArr.length - 1; i++) { 20 | if (!delegateObj[pathArr[i]]) { 21 | // 如果节点不存在,为节点赋值 22 | delegateObj[pathArr[i]] = { 23 | length: 0, 24 | index: delegateObj.length // 当前节点在父节点中的索引 25 | } 26 | delegateObj.length++ // 添加当前节点后,父节点长度需要加1 27 | } 28 | // 保存当前节点,循环递归 29 | delegateObj = delegateObj[pathArr[i]] 30 | } 31 | // 对于最后一个节点,判断是否还有子节点 32 | // 如果有子节点,则同上赋值 33 | // 如果没有子节点,说明到达终点,将内容保存到该节点 34 | if (item.type === 'tree' && !delegateObj[pathArr[i]]) { 35 | delegateObj[pathArr[i]] = { 36 | length: 0, 37 | index: delegateObj.length 38 | } 39 | delegateObj.length++ 40 | } else { 41 | delegateObj[pathArr[i]] = { 42 | index: delegateObj.length, 43 | item 44 | } 45 | delegateObj.length++ 46 | } 47 | }) 48 | return _objToArr(treeObj) 49 | } 50 | 51 | function _objToArr (obj) { 52 | return objToArr(obj, []) 53 | // 将上述模拟出的数组转换为真数组 54 | function objToArr (obj, arr) { 55 | // 删除对象中的index、length属性,避免遍历到 56 | // 遍历到的key即为节点名 57 | delete obj.index 58 | delete obj.length 59 | let nodeObj, index 60 | for (let key in obj) { 61 | // 保存节点的index,后续使用该index插入数组,达到按顺序遍历数组的目的 62 | index = obj[key].index 63 | nodeObj = {} 64 | // 如果是遍历到树的最底部,一对象的形式保存(因为后续会用到对象的key) 65 | if (obj[key].item) { 66 | nodeObj[key] = obj[key] 67 | } else { 68 | // 如果还没有到达树的底部,继续递归 69 | nodeObj[key] = objToArr(obj[key], []) 70 | } 71 | // 插入数组 72 | arr[index] = nodeObj 73 | } 74 | return arr 75 | } 76 | } 77 | 78 | console.log(parseData(require('./data'))) 79 | -------------------------------------------------------------------------------- /code/puppeteer/puppeteer.md: -------------------------------------------------------------------------------- 1 | ### linux 安装 puppeteer 2 | 3 | ```bash 4 | npm i puppeteer 5 | # 安装相关缺少的依赖 6 | # https://luodao.me/post/puppeteer-pakeng.html 7 | sudo yum install pango.x86_64 libXcomposite.x86_64 libXcursor.x86_64 libXdamage.x86_64 libXext.x86_64 libXi.x86_64 libXtst.x86_64 cups-libs.x86_64 libXScrnSaver.x86_64 libXrandr.x86_64 GConf2.x86_64 alsa-lib.x86_64 atk.x86_64 gtk3.x86_64 nss.x86_64 -y 8 | 9 | sudo yum install ipa-gothic-fonts xorg-x11-fonts-100dpi xorg-x11-fonts-75dpi xorg-x11-utils xorg-x11-fonts-cyrillic xorg-x11-fonts-Type1 xorg-x11-fonts-misc -y 10 | 11 | # 安装中文字体 12 | COPY fonts/*.* /usr/share/fonts/chinese/ 13 | sudo -S chmod -R 755 /usr/share/fonts/chinese && cd /usr/share/fonts && sudo -S ttmkfdir -e /usr/share/X11/fonts/encodings/encodings.dir 14 | vi /etc/fonts/fonts.conf 15 | # https://www.jianshu.com/p/f2ba4f5b8f36 16 | # 下载微软雅黑字体 http://www.font5.com/download.php?id=8982&designated=1231818422 17 | # 在 xml 添加一行: /usr/local/share/fonts/chinese 18 | # 19 | # /usr/share/fonts 20 | # /usr/share/X11/fonts/Type1 21 | # /usr/share/X11/fonts/TTF 22 | # /usr/local/share/fonts 23 | # /usr/local/share/fonts/chinese 24 | # fonts 25 | # 26 | # ~/.fonts 27 | ``` 28 | 29 | #### docker file 30 | ```docker 31 | FROM 685235515338.dkr.ecr.eu-west-1.amazonaws.com/tars-node:stable 32 | RUN echo "mima" | sudo -S yum install -y install pango.x86_64 libXcomposite.x86_64 libXcursor.x86_64 libXdamage.x86_64 libXext.x86_64 libXi.x86_64 libXtst.x86_64 cups-libs.x86_64 libXScrnSaver.x86_64 libXrandr.x86_64 GConf2.x86_64 alsa-lib.x86_64 atk.x86_64 gtk3.x86_64 nss.x86_64 && yum clean all 33 | RUN echo "mima" | sudo -S yum install -y ipa-gothic-fonts xorg-x11-fonts-100dpi xorg-x11-fonts-75dpi xorg-x11-utils xorg-x11-fonts-cyrillic xorg-x11-fonts-Type1 xorg-x11-fonts-misc && yum clean all 34 | COPY fonts/*.* /usr/share/fonts/chinese/ 35 | RUN echo $(ls /usr/share/fonts/chinese) 36 | RUN echo "mima" | sudo -S chmod -R 755 /usr/share/fonts/chinese && cd /usr/share/fonts && sudo -S ttmkfdir -e /usr/share/X11/fonts/encodings/encodings.dir 37 | COPY fonts.conf /etc/fonts/ 38 | RUN echo $(cat /etc/fonts/fonts.conf) 39 | ``` -------------------------------------------------------------------------------- /code/rich-text-editor/getCursorPosition.js: -------------------------------------------------------------------------------- 1 | // 获取可编辑div中的光标坐标 2 | function getSelectionCoords(win) { 3 | win = win || window; 4 | var doc = win.document; 5 | var sel = doc.selection, range, rects, rect; 6 | var x = 0, y = 0; 7 | if (sel) { 8 | if (sel.type != "Control") { 9 | range = sel.createRange(); 10 | range.collapse(true); 11 | x = range.boundingLeft; 12 | y = range.boundingTop; 13 | } 14 | } else if (win.getSelection) { 15 | sel = win.getSelection(); 16 | if (sel.rangeCount) { 17 | range = sel.getRangeAt(0).cloneRange(); 18 | if (range.getClientRects) { 19 | range.collapse(true); 20 | rects = range.getClientRects(); 21 | if (rects.length > 0) { 22 | rect = rects[0]; 23 | } 24 | // 光标在行首时,rect为undefined 25 | if(rect){ 26 | x = rect.left; 27 | y = rect.top; 28 | } 29 | } 30 | // Fall back to inserting a temporary element 31 | if ((x == 0 && y == 0) || rect === undefined) { 32 | var span = doc.createElement("span"); 33 | if (span.getClientRects) { 34 | // Ensure span has dimensions and position by 35 | // adding a zero-width space character 36 | span.appendChild( doc.createTextNode("\u200b") ); 37 | range.insertNode(span); 38 | rect = span.getClientRects()[0]; 39 | x = rect.left; 40 | y = rect.top; 41 | var spanParent = span.parentNode; 42 | spanParent.removeChild(span); 43 | 44 | // Glue any broken text nodes back together 45 | spanParent.normalize(); 46 | } 47 | } 48 | } 49 | } 50 | return { x: x, y: y }; 51 | } -------------------------------------------------------------------------------- /code/rich-text-editor/getFontStyle.js: -------------------------------------------------------------------------------- 1 | // 获取光标所在位置的字体样式 2 | document.queryCommandValue("ForeColor"); // 字体颜色 3 | document.queryCommandValue("FontSize"); // 字体大小 4 | document.queryCommandValue("bold"); // 是否加粗 5 | document.queryCommandValue("italic"); // 是否是斜体 -------------------------------------------------------------------------------- /code/rollup-tpl/rollup-tpl.md: -------------------------------------------------------------------------------- 1 | ### rollup打包模板 2 | ```json 3 | # package.json 4 | { 5 | "scripts": { 6 | "build": "rm -rf ./dist && NODE_ENV=production rollup -c rollup.config.js", 7 | "dev": "rm -rf ./dist &&NODE_ENV=development rollup -c rollup.config.js -w ./src", 8 | } 9 | } 10 | ``` 11 | ```javascript 12 | // rollup.config.js 13 | import babel from 'rollup-plugin-babel' 14 | // import multiEntry from 'rollup-plugin-multi-entry' 15 | import serve from 'rollup-plugin-serve' 16 | import livereload from 'rollup-plugin-livereload' 17 | import vue from 'rollup-plugin-vue2' 18 | import css from 'rollup-plugin-css-only' 19 | import copy from 'rollup-plugin-copy' 20 | import uglify from 'rollup-plugin-uglify' 21 | import commonjs from 'rollup-plugin-commonjs' 22 | import resolve from 'rollup-plugin-node-resolve' 23 | import replace from 'rollup-plugin-replace' 24 | import path from 'path' 25 | 26 | const isProduction = process.env.NODE_ENV === 'production' 27 | const globby = require('globby') 28 | 29 | var plugins = [ 30 | replace({ 31 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), 32 | 'process.env.VUE_ENV': JSON.stringify('browser') 33 | }), 34 | babel({ 35 | babelrc: false, 36 | exclude: 'node_modules/**', 37 | presets: ['es2015-rollup', 'stage-0'], 38 | plugins: ['transform-class-properties'] 39 | }), 40 | resolve({ 41 | browser: true, 42 | jsnext: true, 43 | main: true 44 | }), commonjs() 45 | ] 46 | 47 | if (isProduction) { 48 | plugins = plugins.concat([ 49 | uglify() 50 | ]) 51 | } else { 52 | plugins = plugins.concat([ 53 | serve({ 54 | contentBase: 'dist', 55 | open: true 56 | }), 57 | livereload('dist') 58 | ]) 59 | } 60 | 61 | copy({ 62 | 'src/ui/buryConsole/index.html': 'dist/ui/buryConsole/index.html', 63 | 'src/ui/configList/index.html': 'dist/ui/configList/index.html', 64 | 'src/ui/groupList/index.html': 'dist/ui/groupList/index.html', 65 | 'src/index.html': 'dist/index.html', 66 | 'src/sdk/benchmark.html': 'dist/sdk/benchmark.html', 67 | 'test': 'dist/test', 68 | 'node_modules/element-ui/lib/theme-chalk/fonts': 'dist/ui/configList/fonts', 69 | verbose: true 70 | }).ongenerate() 71 | 72 | const configs = globby.sync(['src/sdk/index.js', 'src/ui/*/index.js', '!src/backend']).map((inputFile) => { 73 | var name = path.basename(inputFile, '.js') 74 | return { 75 | input: inputFile, 76 | output: { 77 | file: inputFile.replace(/src\/(.*?)\/(.*?)\.js/, 'dist/$1/$2.js'), 78 | name: name, 79 | format: 'umd' 80 | }, 81 | // 对于多个入口,vue、css插件需要单独实例化 82 | // 否则css不能分别打包到各个入口的dist目录 83 | plugins: [vue(), css()].concat(plugins) 84 | } 85 | }) 86 | 87 | module.exports = configs 88 | 89 | ``` -------------------------------------------------------------------------------- /code/rsyslog/rsyslog.md: -------------------------------------------------------------------------------- 1 | ## rsyslog简单使用(v8.x) 2 | 3 | 推送端 4 | ```bash 5 | # /etc/rsyslog.conf 6 | 7 | # 加载文件处理模块 8 | module(load="imfile") 9 | # 定义出入 10 | input(type="imfile" 11 | # 输入文件的路径 12 | File="/var/log/performance-log/*.log.*" 13 | Facility="user" 14 | Severity="error" 15 | Tag="web_access" 16 | PersistStateInterval="1" 17 | # 使用的规则 18 | Ruleset="ruleset1") 19 | # 定义输出规则 20 | ruleset(name="ruleset1"){ 21 | # 将日志推送到10.88.132.120:10515 22 | action(type="omfwd" Protocol="tcp" Target="12.88.132.120" Port="10515") stop 23 | } 24 | ``` 25 | 26 | 接收端(12.88.132.120) 27 | ```bash 28 | # /etc/rsyslog.conf 29 | 30 | # 加载tcp模块 31 | module(load="imtcp") 32 | # 加载json parse模块 33 | module(load="mmjsonparse") 34 | # 加载elasticsearch模块 35 | module(load="omelasticsearch") 36 | # 加载kafka模块 37 | module(load="omkafka") 38 | 39 | # 定义template 40 | # 推送的elasticsearch的template 41 | template(name="es-tpl" type="list"){ 42 | constant(value="{\"@timestamp\":\"") property(name="timereported" dateFormat="rfc3339") 43 | constant(value="\",") property(name="$!all-json" position.from="2") 44 | } 45 | # 推送的kafka的template 46 | template(name="kafka-tpl" type="string" string="%msg%") 47 | # 定义输入 48 | # 从10515端口接收 49 | input(type="imtcp" Port="10515" Ruleset="ruleset1") 50 | # 定义输出规则 51 | ruleset(name="performance") { 52 | # 推送到kafka 53 | action(type="omkafka" 54 | topic="" 55 | broker="12.88.132.120:9110,12.88.132.121:9110" 56 | confParam=["sasl.mechanisms=PLAIN","security.protocol=sasl_plaintext","sasl.username=","sasl.password=","api.version.request=true"] 57 | Template="kafka-tpl" 58 | partitions.auto="on" 59 | errorFile="/var/log/kafka/kafka-err.log") 60 | # 解析为json后推送到elasticsearch 61 | action(type="mmjsonparse") 62 | action(type="omfile" DynaFile="performanceDynFile") 63 | action(type="omelasticsearch" 64 | template="es-tpl" 65 | searchIndex="your_index" 66 | dynSearchIndex="on" 67 | bulkmode="on" 68 | searchType="logs" 69 | queue.type="LinkedList" 70 | queue.size="1000" 71 | queue.dequeuebatchsize="300" 72 | action.resumeretrycount="-1" 73 | server="12.88.132.119" 74 | serverport="62320") stop 75 | } 76 | ``` 77 | 78 | 验证rsyslog配置是否正确: 79 | ```bash 80 | rsyslogd -N 1 81 | ``` 82 | 在debug模式下运行 83 | ```bash 84 | rsyslogd -dn 85 | ``` -------------------------------------------------------------------------------- /code/simple-mvvm/README.md: -------------------------------------------------------------------------------- 1 | ## 简单的 mvvm 实现 2 | 3 | 简单的 vue mvvm 实现,用于理解 vue 原理,查看效果: 4 | 5 | ```bash 6 | npm i 7 | npm run dev 8 | ``` 9 | 10 | 代码主要分为下面几个部分: 11 | 12 | - Mvvm 类,即入口,执行 observer、compiler 13 | - observe,递归数据,使用 defineProperty 收集依赖该数据的 watcher,并在状态改变时通知 watcher 14 | - watcher,每个数据都对应一个 watcher 实例,watcher 实例会保存数据更新后要执行的回调,在回调中会更新对应的视图。一个关键的点是,observer 到数据改变后,要通知哪个 watcher 呢?watcher 实例化时,会将 Dep 类静态属性 Dep.target 修改为自己,并且会立即获取一次数据,从而会执行 observer 中的 getter,getter 中会将 Dep.target 加入自己的依赖,从而知道自己的数据改变后要通知的 watcher 15 | - dep,一个简单的发布-订阅器,用于 watcher 和 observer 之间的沟通 16 | - compiler,解析模板,并为模板中用到的每个数据实例化一个 watcher 17 | 18 | 除 compiler 之外的部分,都比较简单,不再赘述 19 | 20 | #### compiler 21 | 22 | compiler 首先将 options.el 元素中的子元素剪切到 documentFragment 中以提高 dom 操作性能。然后递归解析 fragment 中的 vue 指令。 23 | 24 | 数据绑定:解析到数据绑定时,会实例化一个 watcher,watcher 的回调是更新对应 fragment 相应数据的函数(observer 到数据变化时,就会执行 watcher 的回调,从而实现了数据到视图的自动更新) 25 | 26 | 事件绑定:直接 addEventListener 就行了,参数就是 vm.method 中的对应的函数 -------------------------------------------------------------------------------- /code/simple-mvvm/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | mvvm 8 | 9 | 10 |
11 | 12 |

{{word}}

13 | 14 |
15 | 16 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /code/simple-mvvm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple-mvvm", 3 | "version": "0.0.1", 4 | "description": "simple mvvm", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "dev": "rollup -c rollup.config.js -w ./src" 8 | }, 9 | "author": "whale", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "rollup": "^1.2.0", 13 | "rollup-plugin-livereload": "^1.0.0", 14 | "rollup-plugin-serve": "^1.0.1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /code/simple-mvvm/rollup.config.js: -------------------------------------------------------------------------------- 1 | const serve = require('/usr/local/lib/node_modules/rollup-plugin-serve') 2 | const livereload = require('/usr/local/lib/node_modules/rollup-plugin-livereload') 3 | 4 | export default { 5 | input: './src/index.js', 6 | output: { 7 | format: 'iife', 8 | name: 'mvvm', 9 | file: './dist/bundle.js' 10 | }, 11 | plugins: [ 12 | serve(), 13 | livereload() 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /code/simple-mvvm/src/index.js: -------------------------------------------------------------------------------- 1 | import Mvvm from './mvvm/index' 2 | 3 | window.Mvvm = Mvvm 4 | -------------------------------------------------------------------------------- /code/simple-mvvm/src/mvvm/compiler/compilerUtil.js: -------------------------------------------------------------------------------- 1 | import Watcher from '../watcher' 2 | import updater from './updater' 3 | 4 | // 指令处理集合 5 | export default { 6 | text (node, vm, exp) { 7 | this.bind(node, vm, exp, 'text') 8 | }, 9 | 10 | html (node, vm, exp) { 11 | this.bind(node, vm, exp, 'html') 12 | }, 13 | 14 | model (node, vm, exp) { 15 | this.bind(node, vm, exp, 'model') 16 | 17 | let val = this._getVMVal(vm, exp) 18 | node.addEventListener('input', (e) => { 19 | const newValue = e.target.value 20 | if (val === newValue) { 21 | return 22 | } 23 | 24 | this._setVMVal(vm, exp, newValue) 25 | val = newValue 26 | }) 27 | }, 28 | 29 | class (node, vm, exp) { 30 | this.bind(node, vm, exp, 'class') 31 | }, 32 | 33 | bind (node, vm, exp, dir) { 34 | const updaterFn = updater[dir + 'Updater'] 35 | updaterFn && updaterFn(node, this._getVMVal(vm, exp)) 36 | 37 | new Watcher(vm, exp, function (value, oldValue) { 38 | updaterFn && updaterFn(node, value, oldValue) 39 | }) 40 | }, 41 | 42 | // 事件处理 43 | eventHandler (node, vm, exp, dir) { 44 | const eventType = dir.split(':')[1] 45 | const fn = vm.$options.methods && vm.$options.methods[exp] 46 | 47 | if (eventType && fn) { 48 | node.addEventListener(eventType, fn.bind(vm), false) 49 | } 50 | }, 51 | 52 | _getVMVal (vm, exp) { 53 | let val = vm 54 | exp = exp.split('.') 55 | exp.forEach(function (k) { 56 | val = val[k] 57 | }) 58 | return val 59 | }, 60 | 61 | _setVMVal (vm, exp, value) { 62 | let val = vm 63 | exp = exp.split('.') 64 | exp.forEach(function (k, i) { 65 | // 非最后一个key,更新val的值 66 | if (i < exp.length - 1) { 67 | val = val[k] 68 | } else { 69 | val[k] = value 70 | } 71 | }) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /code/simple-mvvm/src/mvvm/compiler/index.js: -------------------------------------------------------------------------------- 1 | import compileUtil from './compilerUtil' 2 | 3 | export default class Compiler { 4 | constructor (vm, el) { 5 | this.$vm = vm 6 | this.$el = this.isElementNode(el) ? el : document.querySelector(el) 7 | // 在 fragment 中操作,提高性能 8 | this.$fragment = this.node2Fragment(this.$el) 9 | this.compileElement(this.$fragment) 10 | this.$el.appendChild(this.$fragment) 11 | } 12 | node2Fragment (el) { 13 | const fragment = document.createDocumentFragment() 14 | let child 15 | // eslint-disable-next-line 16 | while (child = el.firstChild) { 17 | fragment.appendChild(child) 18 | } 19 | return fragment 20 | } 21 | compileElement (el) { 22 | const childNodes = el.childNodes 23 | 24 | Array.from(childNodes).forEach((node) => { 25 | const text = node.textContent 26 | const reg = /\{\{(.*)\}\}/ 27 | 28 | if (this.isElementNode(node)) { 29 | this.compile(node) 30 | } else if (this.isTextNode(node) && reg.test(text)) { 31 | this.compileText(node, RegExp.$1) 32 | } 33 | 34 | if (node.childNodes && node.childNodes.length) { 35 | this.compileElement(node) 36 | } 37 | }) 38 | } 39 | compile (node) { 40 | const nodeAttrs = node.attributes 41 | 42 | Array.from(nodeAttrs).forEach((attr) => { 43 | const attrName = attr.name 44 | if (this.isDirective(attrName)) { 45 | const exp = attr.value 46 | const dir = attrName.substring(2) 47 | // 事件指令 48 | if (this.isEventDirective(dir)) { 49 | compileUtil.eventHandler(node, this.$vm, exp, dir) 50 | // 普通指令 51 | } else { 52 | compileUtil[dir] && compileUtil[dir](node, this.$vm, exp) 53 | } 54 | 55 | node.removeAttribute(attrName) 56 | } 57 | }) 58 | } 59 | 60 | compileText (node, exp) { 61 | compileUtil.text(node, this.$vm, exp) 62 | } 63 | 64 | isDirective (attr) { 65 | return attr.indexOf('v-') === 0 66 | } 67 | 68 | isEventDirective (dir) { 69 | return dir.indexOf('on') === 0 70 | } 71 | isElementNode (node) { 72 | return node.nodeType === 1 73 | } 74 | 75 | isTextNode (node) { 76 | return node.nodeType === 3 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /code/simple-mvvm/src/mvvm/compiler/updater.js: -------------------------------------------------------------------------------- 1 | export default { 2 | textUpdater (node, value) { 3 | node.textContent = typeof value === 'undefined' ? '' : value 4 | }, 5 | 6 | htmlUpdater (node, value) { 7 | node.innerHTML = typeof value === 'undefined' ? '' : value 8 | }, 9 | 10 | classUpdater (node, value, oldValue) { 11 | let className = node.className 12 | className = className.replace(oldValue, '').replace(/\s$/, '') 13 | 14 | const space = className && String(value) ? ' ' : '' 15 | 16 | node.className = className + space + value 17 | }, 18 | 19 | modelUpdater (node, value, oldValue) { 20 | node.value = typeof value === 'undefined' ? '' : value 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /code/simple-mvvm/src/mvvm/dep.js: -------------------------------------------------------------------------------- 1 | class Dep { 2 | constructor () { 3 | this.subs = [] 4 | } 5 | addSub (sub) { 6 | this.subs.push(sub) 7 | } 8 | notify () { 9 | this.subs.forEach((sub) => { 10 | sub.update() 11 | }) 12 | } 13 | } 14 | 15 | Dep.target = null 16 | 17 | export default Dep 18 | -------------------------------------------------------------------------------- /code/simple-mvvm/src/mvvm/index.js: -------------------------------------------------------------------------------- 1 | import Compiler from './compiler/index' 2 | import observer from './observer' 3 | 4 | export default class Mvvm { 5 | constructor (options) { 6 | this.$options = options 7 | const data = this._data = options.data 8 | Object.keys(data).forEach((key) => { 9 | this._proxy(key) 10 | }) 11 | observer(data) 12 | this.$compiler = new Compiler(this, options.el) 13 | } 14 | _proxy (key) { 15 | const self = this 16 | Object.defineProperty(self, key, { 17 | configurable: false, 18 | enumerable: true, 19 | get () { 20 | return self._data[key] 21 | }, 22 | set (newVal) { 23 | self._data[key] = newVal 24 | } 25 | }) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /code/simple-mvvm/src/mvvm/observer.js: -------------------------------------------------------------------------------- 1 | import Dep from './dep' 2 | 3 | export default function observer (data) { 4 | Object.keys(data).forEach((key) => { 5 | let val = data[key] 6 | if (Object.prototype.toString.call(val) === '[object Object]') { 7 | observer(val) 8 | } else { 9 | const dep = new Dep() 10 | Object.defineProperty(data, key, { 11 | get () { 12 | Dep.target && dep.addSub(Dep.target) 13 | Dep.target = null 14 | return val 15 | }, 16 | set (newVal) { 17 | if (val === newVal) return 18 | val = newVal 19 | dep.notify() 20 | } 21 | }) 22 | } 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /code/simple-mvvm/src/mvvm/watcher.js: -------------------------------------------------------------------------------- 1 | import Dep from './dep' 2 | 3 | export default class Watcher { 4 | constructor (vm, exp, cb) { 5 | this.$vm = vm 6 | this.$exp = exp 7 | this.$cb = cb 8 | Dep.target = this 9 | // 这里会触发 observer 中的 getter 10 | // 然后将该 watcher 加入 dep 11 | this.value = vm[exp] 12 | } 13 | update () { 14 | const oldVal = this.value 15 | const newVal = this.$vm[this.$exp] 16 | if (oldVal === newVal) return 17 | this.value = newVal 18 | this.$cb.call(this.$vm, newVal, oldVal) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /code/smartCode/getDeepObjectData.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 获取深层数据结构中的数据 3 | * @param {Array} keyArr 查找数据所需key组成的数组 4 | * @param {Object} dataObject 数据对象 5 | */ 6 | function getDeepObjectData(keyArr, dataObject) { 7 | return keyArr.reduce(function (data, key) { 8 | return (data && data[key]) ? data[key] : null 9 | }, dataObject) 10 | } 11 | 12 | // 使用示例 13 | var testData = { 14 | a: { 15 | b: { 16 | c:{ 17 | d: 2, 18 | h: 5 19 | }, 20 | j: 4 21 | }, 22 | f:{ 23 | g: 1 24 | } 25 | }, 26 | r: { 27 | l:{ 28 | o: 2 29 | } 30 | } 31 | } 32 | 33 | // 通常做法 34 | testData['a'] && 35 | testData['a']['b'] && 36 | testData['a']['b']['c'] && 37 | testData['a']['b']['c']['h'] 38 | 39 | // 使用上述函数 40 | getDeepObjectData(['a', 'b', 'c', 'h'], testData) // 5 -------------------------------------------------------------------------------- /code/smartCode/getMonthDays.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 获取某年某月的天数 3 | * @param {Date} date 日期 4 | */ 5 | function getDeepObjectData(date) { 6 | var year = date.getYear(), month = date.getMonth() + 1; 7 | // 判断是否是闰年 8 | var isLeapYear = (year % 4 == 0) && (year % 100 != 0 || year % 400 == 0); 9 | // 获取当月天数 10 | return (month === 2) ? (28 + isLeapYear) : 31 - (month - 1) % 7 % 2; 11 | } -------------------------------------------------------------------------------- /code/smartCode/gradientColors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 获取两个颜色之间的渐变色 3 | * @param {String} start 开始颜色 4 | * @param {String} end 结束颜色 5 | * @param {Number} steps 步长 6 | * @param {Number} gamma 色彩校正 7 | */ 8 | function gradientColors(start, end, steps, gamma) { 9 | var i, j, ms, me, output = [], so = []; 10 | gamma = gamma || 1; 11 | var normalize = function (channel) { 12 | return Math.pow(channel / 255, gamma); 13 | }; 14 | start = parseColor(start).map(normalize); 15 | end = parseColor(end).map(normalize); 16 | for (i = 0; i < steps; i++) { 17 | ms = i / (steps - 1); 18 | me = 1 - ms; 19 | for (j = 0; j < 3; j++) { 20 | so[j] = pad(Math.round(Math.pow(start[j] * me + end[j] * ms, 1 / gamma) * 255).toString(16)); 21 | } 22 | output.push('#' + so.join('')); 23 | } 24 | return output; 25 | // convert #hex notation to rgb array 26 | function parseColor(hexStr) { 27 | return hexStr.length === 4 ? hexStr.substr(1).split('').map(function (s) { return 0x11 * parseInt(s, 16); }) : [hexStr.substr(1, 2), hexStr.substr(3, 2), hexStr.substr(5, 2)].map(function (s) { return parseInt(s, 16); }) 28 | }; 29 | 30 | // zero-pad 1 digit to 2 31 | function pad(s) { 32 | return (s.length === 1) ? '0' + s : s; 33 | }; 34 | 35 | }; 36 | 37 | // try if it works 38 | console.log(gradientColors('#00ff00', '#ff0000', 100)); 39 | 40 | console.log(gradientColors('#000', '#fff', 100, 2.2)); -------------------------------------------------------------------------------- /code/smartCode/throttle-debounce.js: -------------------------------------------------------------------------------- 1 | // 节流 2 | function throttle (frequency, func) { 3 | let timer 4 | return function () { 5 | if (timer) return 6 | timer = setTimeout(() => { 7 | func.apply(this, arguments) 8 | timer = null 9 | }, frequency) 10 | } 11 | } 12 | 13 | // 防抖 14 | function debounce (delay, func) { 15 | let timer 16 | return function () { 17 | clearTimeout(timer) 18 | timer = setTimeout(function () { 19 | func.apply(this, arguments) 20 | }, delay) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /code/wakeUpApp/wakeUpMap.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Wake Up Map App 6 | 7 | 8 | 9 | 高德地图 18 | 19 | 百度地图 23 | 24 | 25 | 高德地图 31 | 32 | 百度地图 36 | 37 | -------------------------------------------------------------------------------- /code/weChat/fullScreenVideo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Full screen video 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /code/website/website.md: -------------------------------------------------------------------------------- 1 | - 世界和主要国家地图信息json文件: http://www.ourd3js.com/wordpress/668/ -------------------------------------------------------------------------------- /notes/ajax.md: -------------------------------------------------------------------------------- 1 | # Ajax 入门 2 | 3 | ### 前言 4 | 5 | 在 Ajax 出现之前,网页想要和服务器通信,最常用的方式是使用 form 表单;用户提交表单后,浏览器就开始跳转,服务器接收表单并处理,然后将新的网页返回给浏览器;整个过程用户都只有等待,用户之前的操作状态会丢失,并且服务器返回的新网页常常和之前网页的大部分内容相同,浪费带宽;可见,使用表单来进行网页和服务器的交互,会做很多无谓的工作,浪费资源,用户体验还差。 6 | 7 | Ajax 是 Asynchronous JavaScript and XML(异步的 JavaScript 与 XML 技术)的缩写,并不是 JavaScript 的一部分,而是网页与服务器通信的一系列技术的总称。网页使用 Ajax 与服务器通信,可以规避上述 form 表单存在的问题,页面不会刷新,用户不用等待请求的返回,可以继续在我们的网页上“冲浪”。第一个大规模使用 Ajax 的网页应用是 Gmail,Gmail 的出现让大家意识到网页还能这么玩,网页也能做得像桌面应用一样,打破了大家对网页应用的认知,可以说 Ajax 为 web 技术注入了灵魂。 8 | 9 | ### 使用 10 | 11 | 浏览器为我们提供了 XMLHttpRequest 对象(低版本 IE 使用 ActiveXObject 对象),让我们能够方便地使用 Ajax。下面我们就用 Ajax 来和服务器打声招呼: 12 | 13 | ```javascript 14 | var xhr 15 | // 实例化一个 XMLHttpRequest 对象 16 | if (window.XMLHttpRequest) { 17 | xhr = new XMLHttpRequest(); 18 | } else if (window.ActiveXObject) { // IE 6及以下 19 | xhr = new ActiveXObject("Microsoft.XMLHTTP"); 20 | } 21 | // 绑定 xhr.readyState 改变时调用的回调 22 | xhr.onreadystatechange = function () { 23 | if (xhr.readyState === 4) { 24 | if (xhr.status === 200) { 25 | console.log(xhr.responseText) 26 | console.log('请求成功') 27 | } else { 28 | console.log('请求错误') 29 | } 30 | } 31 | } 32 | // 初始化请求 33 | xhr.open('GET', '/api/hello'); 34 | // 设置请求头(可选) 35 | xhr.setRequestHeader('Accept', '*/*') 36 | // 发出请求 37 | xhr.send(); 38 | ``` 39 | 40 | 可见,发送一个最简单的 Ajax 请求,只需几步:实例化一个 XMLHttpRequest 对象,绑定 readyState 改变时调用的回调,最后使用 open、send 方法发出请求即可。 41 | 42 | 上面的代码涉及到 XMLHttpRequest 对象的常用属性、方法,接下来依次解释它们的作用。 43 | 44 | 注:为了方便阅读,下面用 xhr 指代 XMLHttpRequest 45 | 46 | `xhr.onreadystatechange` 47 | 48 | 请求发出后,只要 xhr.readyState 发生变化,就会调用通过 xhr.onreadystatechange 绑定的函数。 49 | 50 | `xhr.readyState` 51 | 52 | xhr.readyState 的值代表 xhr 对象所处的状态,可能的状态如下: 53 | 54 | | 值 | 状态 | 描述 | 55 | |----------|----------|----------| 56 | | 0 | UNSENT | xhr已被创建,但还没有调用 open 方法 | 57 | | 1 | OPENED | open 方法被调用 | 58 | | 2 | HEADERS_RECEIVED | send 方法被调用,并且可以获取响应头部以及响应状态码 | 59 | | 3 | LOADING | 正在下载响应内容 | 60 | | 4 | DONE | 下载完成 | 61 | 62 | 就像上面的示例一样,一般我们在 xhr.onreadystatechange 绑定的函数中判断 xhr.readyState 的值,当值变为4的时候,我们再做进一步处理。 63 | 64 | `xhr.status` 65 | 66 | xhr.status 代表服务器响应的 http 状态码,比如上面的示例中,我们认为 xhr.status 等于200的时候,服务器正常返回了我们想要的内容,否则认为请求发生错误。 67 | 68 | `xhr.responseText` 69 | 70 | xhr.responseText 的值即为服务器响应的内容。 71 | 72 | `xhr.open(method, url, async, user, password)` 73 | 74 | xhr.open方法,用于初始化请求,可以接受5个参数,后三个参数都是可选的,通常我们也很少使用 75 | 76 | - method:要使用的HTTP方法,比如 GET、POST、PUT、DELETE 等 77 | - url:请求的url 78 | - async(可选):是否发起异步请求,默认值为 true;注意,新版本的浏览器已经不建议将该值设置为 false 来发起同步请求 79 | - user(可选):用户名,用于认证 80 | - password(可选):用户密码,用于认证 81 | 82 | `xhr.setRequestHeader(header, value)` 83 | 84 | xhr.setRequestHeader 用于设置 http 请求的 header。需要注意的是,该方法只能在调用 xhr.open 初始化请求后,并且在调用 xhr.send 发出请求之前调用,否则会抛出错误。该方法接收两个参数 85 | 86 | - header: 设置的 header 头的名字 87 | - value:设置的 header 头的值 88 | 89 | `xhr.send(content)` 90 | 91 | xhr.send 方法用于发出请求。注意,如果发出的是同步请求,该方法会阻塞 js 的执行。xhr.send 接收一个参数 92 | 93 | - content:请求发送的内容。如果是 GET 或 HEAD 请求,应当不传这个参数或者传null 94 | 95 | ### XMLHttpRequest Level 2 96 | 97 | 在 HTML5 之前,虽然各家浏览器都实现了 XMLHttpRequest 接口,但由于没有统一的规范,导致各个浏览器的实现或多或少有些差异。HTML5 将 XMLHttpRequest 纳入了规范,并在原来的基础上做了升级,提出了 XMLHttpRequest Level 2。 98 | 99 | XMLHttpRequest Level 2 相较于老版本的 XMLHttpRequest 主要新增了如下内容: 100 | 101 | - 可以设置HTTP请求的超时时间 102 | - 可以通过 FormData 发送表单数据 103 | - 可以上传文件 104 | - 支持跨域请求 105 | - 可以获取服务器端的二进制数据 106 | - 可以获得数据传输的进度信息 107 | 108 | #### 设置HTTP请求的超时时间 109 | 110 | `xhr.timeout` 111 | 112 | 和 xhr.setRequestHeader 一样,xhr.timeout的值只能在调用 xhr.open 之后且在 xhr.send 之前设置 113 | 114 | ```javascript 115 | var xhr = new XMLHttpRequest() 116 | xhr.open('GET', '/api/hello') 117 | xhr.timeout = 2000 // 超时时间,单位是毫秒 118 | xhr.ontimeout = function (e) { 119 | // XMLHttpRequest 超时,在此做超时的处理 120 | } 121 | xhr.send(null) 122 | ``` 123 | 124 | #### 发送表单数据 125 | 126 | HTML5 新增了一个 FormData 对象,用于模拟表单。我们可以结合 FormData 与 xhr 发送表单数据 127 | 128 | ```javascript 129 | var xhr = new XMLHttpRequest() 130 | // 实例化一个 FormData 对象 131 | var formData = new FormData() 132 | // 向 FormData 添加数据 133 | formData.append('username', 'whale') 134 | formData.append('age', '18') 135 | xhr.open('POST', '/api/form') 136 | // 发送表单数据 137 | xhr.send(formData) 138 | ``` 139 | 140 | #### 上传文件 141 | 142 | FormData 除了可以添加字符串数据,也可以添加 [blob](https://developer.mozilla.org/zh-CN/docs/Web/API/Blob)、[file](https://developer.mozilla.org/zh-CN/docs/Web/API/FormData/Using_FormData_Objects) 类型的数据,因此可以用于上传文件。在浏览器中,一般是通过文件上传输入框来获取 file 对象,比如: 143 | 144 | ```html 145 | 146 | ``` 147 | 148 | 然后监听 input 的 change 事件,获取 file 对象: 149 | 150 | ```javascript 151 | document.getElementById('upload-file').addEventListener('change', function () { 152 | formData.append('uploadFile', this.files[0]) 153 | xhr.send(formData) 154 | }) 155 | ``` 156 | 157 | #### 跨域请求 158 | 159 | 默认情况下,浏览器出于安全考虑不允许发送[跨域请求](https://www.zhihu.com/question/26376773),但有时候向不同域的服务器发送请求是必要的。为了标准化跨域请求流程,W3C 提出了[跨域资源共享](http://www.ruanyifeng.com/blog/2016/04/cors.html)(Cross-origin resource sharing,简称 CORS)标准,在 CORS 出现之前,通常是使用 [JSONP](https://baike.baidu.com/item/JSONP) 来取巧地解决跨域问题,但由于 JSONP 存在各种限制,因此在支持 CORS 的浏览器中(IE10 以下不支持)还是推荐使用 CORS。 160 | 161 | 要使用 CORS,默认情况下,前端不用修改任何代码,如果浏览器发现 XMLHttpRequest 发出了跨域请求,会帮我们做相应的处理,但服务器需要返回 `Access-Control-Allow-Origin` 响应头,指定允许进行跨域请求的域。 162 | 163 | CORS 请求默认是不发送 Cookie 的,如果想要发送 cookie,服务器需要返回 `Access-Control-Allow-Credentials: true`,并且前端需要设置 withCredentials 属性: 164 | 165 | ```JavaScript 166 | xhr.withCredentials = true 167 | ``` 168 | 169 | #### 接收二进制数据 170 | 171 | XMLHttpRequest level 1 只能接收文本数据,新版本 XMLHttpRequest 添加了接收二进制数据的能力。要接收二进制数据,一般有两种方式。 172 | 173 | 一种是使用 `overrideMimeType` 方法覆写服务器指定的 [MIME 类型](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types),从而改变浏览器解析数据的方式。 174 | 175 | `xhr.overrideMimeType(mimeType)` 176 | 177 | - mimeType:要设置的 MIME 类型 178 | 179 | 比如: 180 | 181 | ```javascript 182 | // 告诉浏览器,服务器响应的内容是用户自定义的字符集 183 | xhr.overrideMimeType('text/plain; charset=x-user-defined') 184 | ``` 185 | 186 | 执行上面的代码后,浏览器就会将服务器返回的二进制数据当成文本处理,我们需要做进一步的转换才能拿到真实的数据: 187 | 188 | ```javascript 189 | // 获取二进制数据的第 i 位的值 190 | var byte = xhr.responseText.charCodeAt(i) & 0xff 191 | ``` 192 | 193 | 针对 "& 0xff" 运算,参考[阮一峰的文章](http://www.ruanyifeng.com/blog/2012/09/xmlhttprequest_level_2.html)解释如下: 194 | 195 | > "& 0xff" 运算,表示在每个字符的两个字节之中,只保留后一个字节,将前一个字节扔掉。原因是浏览器解读字符的时候,会把字符自动解读成Unicode的0xF700-0xF7ff区段。 196 | 197 | 在较新的浏览器中,可以采用另一种接收二进制数据的方式。 198 | 199 | xhr.responseType 用于设置服务器返回的数据的类型。我们可以将返回类型设置为 [blob](https://developer.mozilla.org/zh-CN/docs/Web/API/Blob) 或者 [arraybuffer](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer),然后就可以从 xhr.response 属性获取到对应类型的服务器返回数据。比如: 200 | 201 | ```javascript 202 | xhr.responseType = 'arraybuffer' 203 | xhr.onload = function () { 204 | var arrayBuffer = oReq.response 205 | // 接下来对 arrayBuffer 做进一步处理... 206 | } 207 | ``` 208 | 209 | #### 数据传输进度信息 210 | 211 | 新版本的 XMLHttpRequest 允许我们监听数据传输的详细状态,上面的示例代码,我们就使用 onload 监听了一个数据传输完成的事件。可以监听的事件如下: 212 | 213 | | 事件 | 描述 | 214 | |----------|----------| 215 | | onloadstart | 获取数据开始 | 216 | | onprogress | 数据传输过程中 | 217 | | onabort | 数据获取被取消 | 218 | | onerror | 获取数据错误 | 219 | | onload | 获取数据成功 | 220 | | ontimeout | 获取数据超时 | 221 | | onloadend | 获取完成(无论成功或失败)| 222 | 223 | ### 总结 224 | 225 | 上面我们总结了 XMLHttpRequest 的大部分内容,可见XMLHttpRequest 已经非常强大了,通过它我们可以和服务器做复杂的交互,从而为我们开发出功能强大的 web 应用创造了条件。XMLHttpRequest 的 api 并不多,也不复杂,感兴趣的同学可以查看 [MDN XMLHttpRequest](https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest) 文档,了解更多内容。 226 | 227 | ### 参考资料 228 | 229 | - [MDN XMLHttpRequest](https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest) 230 | - [XMLHttpRequest Level 2 使用指南](http://www.ruanyifeng.com/blog/2012/09/xmlhttprequest_level_2.html) 231 | - [Sending and Receiving Binary Data](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Sending_and_Receiving_Binary_Data) -------------------------------------------------------------------------------- /notes/java.md: -------------------------------------------------------------------------------- 1 | ## java 笔记 2 | 3 | - 计算机内存的最小存储单元是字节(byte),一个字节就是一个8位二进制数,即8个bit。它的二进制表示范围从00000000~11111111,换算成十进制是0~255,换算成十六进制是00~ff 4 | - 对于整型类型,java用最高位代表正负,0 代表正,1 代表负;java 数据类型占用的字节: 5 | - byte:1b,-128 ~ 127 6 | - short: 2b,-32768 ~ 32767 7 | - int: 4b,-2147483648 ~ 2147483647 8 | - long: 8b, -9223372036854775808 ~ 9223372036854775807 9 | - float:4b 10 | - double:8b 11 | - char:2b -------------------------------------------------------------------------------- /notes/readding-notes/深入理解 tcp 协议.md: -------------------------------------------------------------------------------- 1 | # 深入理解 tcp 协议:从原理到实战 2 | 3 | - 网络分层模型:每一层之间通过接口沟通,从而提高可维护性。应用层(http)、传输层(tcp)、网络互联层(ip)、网路接口层(ip和硬件部分的沟通,太网、Wifi、蓝牙工作在这一层) 4 | 5 | - TCP 是一个可靠的(reliable)、面向连接的(connection-oriented)、基于字节流(byte-stream)、全双工的(full-duplex)协议 6 | - 面向连接:面向连接的协议要求正式发送数据之前需要通过「握手」建立一个逻辑连接,结束通信时也是通过有序的四次挥手来断开连接。而无连接,可以认为不建连就开始发送数据,不关注对方是否能收到 7 | - IP 是一种无连接、不可靠的协议:它尽最大可能将数据报从发送者传输给接收者,但并不保证包到达的顺序会与它们被传输的顺序一致,也不保证包是否重复,甚至都不保证包是否会达到接收者。因此 tcp 为了保证可靠必须解决包完整性校验、乱序、重复、超时重传、流量控制等问题 8 | - 字节流协议:流的含义是没有固定的报文边界。应用层向 socket 写入的数据,tcp 层会按照自己优化规则,将写入的数据分一个、多个报文发送,接收方(应用层)不知道发送方每次发了多少数据,因此只能在应用层定义包的边界(比如 http 的换行符)。比如,应用层分两次写入 500 + 800 字节的数据,tcp 拆分为 300 + 1000发送,接收方(tcp)按照 400 + 900 接收 9 | - 全双工的协议:在 TCP 中发送端和接收端可以是客户端/服务端,也可以是服务器/客户端,通信的双方在任意时刻既可以是接收数据也可以是发送数据,每个方向的数据流都独立管理序列号、滑动窗口大小、MSS 等信息 -------------------------------------------------------------------------------- /notes/readding-notes/编写可读代码的艺术.md: -------------------------------------------------------------------------------- 1 | # 编写可读代码的艺术 2 | ## 命名、注释及审美 3 | ### 可读性基本定理 4 | 代码的写法应当使别人理解他所花时间最小化 5 | ```php 6 | for(node = node->head; node != null; node = node->next){ 7 | print(node->data) 8 | } 9 | // 优于: 10 | node = node->head 11 | if(node == null) return 12 | while(node.next != null){ 13 | print(node->data) 14 | node = node.next 15 | } 16 | if(node != null) print(node->data) 17 | ``` 18 | ### 把信息装进名字 19 | #### 选择专业的词 20 | 比如从互联网中获取页面:```get(url) -> getPage(url) -> fetchPage(url) | downloadPage(url)``` 21 | #### 有表现力的词 22 | |单词|更多选择| 23 | |-----|-----| 24 | |send|deliver、dispatch、announce、distribute、route| 25 | |find|search、extract、locate、recover| 26 | |start|launch、create、begin、open| 27 | |make|create、set up、build、generate、compose、add、new 28 | #### 循环迭代 29 | 循环时,不一定必须用i、j、k,可以使用有意义的下标:club_i、user_j、members_k 30 | #### 用具体名字代替抽象 31 | 比如,一个检测服务是否可以监听某个给定tcp/ip端口的函数ServerCanStart(),并没有表示出监听可用端口,可以修改为CanListenOnPort() 32 | #### 带单位的值 33 | 给度量变量名附加单位: 34 | ```javascript 35 | var start_ms = (new Date()).getTime() 36 | var size_mb = localStorage.get('data').length / 1024 /1024 37 | ``` 38 | #### 附加更多信息 39 | 比如,以url编码的输入数据:data -> data_urlenc,纯文本密码:password -> plaintext_password 40 | #### 名字长度 41 | - 小作用域里可以使用段命名 42 | - 丢掉没用的词:convertToString -> toString,doServerLoop -> serverLoop 43 | #### 避免歧义 44 | - 上下限用max、min 45 | - 包含范围用first、last 46 | - 包含/排除范围使用begin、end 47 | - 使用is、has、use标识布尔值,避免使用反义词,如disable 48 | - 站在用户的角度想,如get()、size()用户会认为是轻量操作, 49 | ### 审美 50 | - 对于前端,使用eslint强制代码风格 51 | - 保持列对齐 52 | ```javascript 53 | // 比如使用空格,使注释对齐 54 | const config = { 55 | color: '#fff' // 颜色 56 | fontSize: '20px' // 字体 57 | } 58 | ``` 59 | ### 注释 60 | - 能用命名解决的不要用注释 61 | - 不要对能很快从代码本身就能看出来的结果注释 62 | - 添加对代码的特别说明,如TODO、已知问题,为什么这样做、危险、注意事项 63 | - 添加文件级别注释、代码块的注释 64 | - 注释言简意赅 65 | - 注意不明确的代词 66 | ## 简化循环和逻辑 67 | - 比较时,把改变的值放在左边,稳定的值放在右边 68 | - 减少嵌套,尽量使代码是线性的,通常提前返回可以减少嵌套,比如函数顶部处理后直接返回 69 | ```javascript 70 | // 优化前 71 | if(user_result === 'success'){ 72 | if(permission_result !== 'success'){ 73 | reply.writeErrors('permission deny') 74 | reply.done() 75 | return 76 | } 77 | return reply.writeErrors('') 78 | } else { 79 | reply.writeErrors(user_result) 80 | } 81 | reply.done() 82 | // 优化后 83 | if(user_result !== 'success'){ 84 | reply.writeErrors(user_result) 85 | reply.done() 86 | return 87 | } 88 | if(permission_result !== 'success'){ 89 | reply.writeErrors('permission deny') 90 | reply.done() 91 | return 92 | } 93 | reply.writeErrors(')' 94 | reply.done() 95 | ``` 96 | - 对于循环,使用continue来减少嵌套 97 | ## 拆分超长表达式 98 | - 使用额外的变量来达到解释作用 99 | ```javascript 100 | // 优化前 101 | if(line.split(':')[0].strip()){} 102 | // 优化后 103 | const user_name = line.split(':')[0].strip() 104 | if(user_name){} 105 | ``` 106 | - 使用总结变量 107 | ```javascript 108 | // 优化前 109 | if(request.user.id == document.owner.id){} 110 | if(request.user.id != document.owner.id){} 111 | // 优化后 112 | const user_owns_document = request.user.id == document.owner.id 113 | if(user_owns_document){} 114 | if(!user_owns_document){} 115 | ``` 116 | - 使用德摩根定理 117 | ```javascript 118 | not (a or b or c) <=> (not a) and (not b) and (not c) 119 | not (a and b and c) <=> (not a) or (not b) or (not c) 120 | // 比如 121 | if(!(a && !b)){} 122 | // 转换为 123 | if(!a || b){} 124 | ``` 125 | - 当判断逻辑变多时,需要停下来看,是否可以优化。有时候反过来想,可能会有意想不到的发现 126 | ## 重新组织你的代码 127 | ### 抽取不相关的子问题 128 | 所谓工程学就是关于把大问题拆分为小问题再把这些问题的解决方案放回一起,这个原则应用于代码能够使代码更健壮、易读。 129 | 尽可能将代码剥离为一个个不相关的子问题 130 | ### 一次只做一件事 131 | 1. 列出代码的所有任务 132 | 2. 尽量将这些任务拆分到不同的函数中 133 | ### 把想法变成代码 134 | 1. 用自然语言描述代码要做的事 135 | 2. 注意描述中的关键词和短语 136 | 3. 写出和描述匹配的代码 137 | ### 编写测试 138 | - 测试用例失败时,提供足够的错误信息 139 | - 为测试函数做有意义的命名 140 | - 测试驱动开发,一般在写测试时遇到困难,说明你的代码可能有问题 141 | - 代码有明确的接口、没有过多状态设置 142 | - 不要依赖全局变量,否则每次测试都需要重置全局变量 143 | - 依赖大量外部组件 144 | -------------------------------------------------------------------------------- /notes/rollup.md: -------------------------------------------------------------------------------- 1 | # rollup 入门 2 | 3 | ### 前言 4 | 5 | webpack 虽然非常火热,但后起之秀 rollup 却慢慢地蚕食着 webpack 的市场;我们已经有了几乎无所不能的 webpack,为什么还要 rollup 呢? 6 | 7 | webpack 的配置实在是太复杂了,所以前端社区常戏谑“每个前端团队都需要招聘一个 webpack 配置工程师”;如果我们只是写一个 JavaScript 工具或者库,并不需要打包 image、css,也不需要代码拆分等特性,使用 webpack 未免太过繁琐;这时使用 rollup 就非常适合,rollup 专注于打包 JavaScript,简单明了,易于上手,事实上很多前端流行的库都是使用 rollup 打包的,比如 React、Vue、Moment 等。 8 | 9 | ### 快速上手 10 | 11 | 就像其他前端工具一样,我们可以通过 [npm](https://www.npmjs.cn/) 全局安装 rollup,然后在命令行中使用它。 12 | 13 | ```bash 14 | npm install --global rollup 15 | ``` 16 | 17 | 安装完成后就可以使用 rollup 来打包我们的代码了。 18 | 19 | 首先我们新建一个名为 test-rollup 的目录,然后在命令行中进入该目录,并用 npm 初始化: 20 | 21 | ```bash 22 | cd test-rollup 23 | npm init # npm会让你输入一些信息,直接回车就行 24 | ``` 25 | 26 | 接着新建两个 js 文件 main.js、foo.js,分别写入如下代码: 27 | 28 | ```javascript 29 | // foo.js 30 | export default 'hi rollup' 31 | ``` 32 | 33 | ```javascript 34 | // main.js 35 | import foo from './foo.js' 36 | export default function () { 37 | console.log(foo) 38 | } 39 | ``` 40 | 41 | 现在我们使用 rollup 将这两个 js 文件打包为可以直接在浏览器中执行的文件: 42 | 43 | ```bash 44 | rollup main.js --format iife --name foo --file bundle.js 45 | ``` 46 | 47 | 在命令行中执行上面的语句后,就会生成可以直接在浏览器中执行的文件 bundle.js。命令的参数的意义如下: 48 | 49 | - main.js 入口文件,rollup会以入口文件为起点分析 JavaScript 代码,将所有代码打包为一个文件 50 | - --format 定义输出的 bundle.js 的格式,我们这里使用的是 iife,rollup 会将代码包裹在一个自执行函数中,便于在浏览器中执行;rollup 还支持 cjs(针对 Node.js)、umd(针对 Node.js 和浏览器)等输出格式 51 | - --name 定义输出的包的名字,这里是 foo 52 | - --file 定义打包出的文件的名字,这里是 bundle.js 53 | 54 | ### 使用配置文件 55 | 56 | 如果想使用更加复杂的配置,或者不想每次都在命令行中输入各种参数,我们可以使用配置文件,rollup 使用 --config 参数来定义要使用的配置文件。比如,我们创建一个名为 rollup.config.js 的文件来描述上面示例中的命令行参数,从而达到同样的效果: 57 | 58 | ```javascript 59 | // rollup.config.js 60 | export default { 61 | input: 'main.js', 62 | output: { 63 | format: 'iife', 64 | name: 'foo', 65 | file: 'bundle.js' 66 | } 67 | } 68 | ``` 69 | 70 | 然后开始打包,最终效果和上面的示例一样: 71 | 72 | ```bash 73 | rollup --config rollup.config.js 74 | ``` 75 | 76 | 可以看到 rollup 的配置文件也是一个 js 文件,该文件需要导出 rollup 需要的各项配置。 77 | 78 | ### 使用插件 79 | 80 | rollup 只提供打包的核心功能,如果你还想使用 babel 编译代码、压缩代码等功能,可以使用 rollup 社区提供的丰富插件,参考 [Awesome Rollup](https://github.com/rollup/awesome)。接下来我们就使用 rollup 的压缩插件 [rollup-plugin-uglify](https://github.com/TrySound/rollup-plugin-uglify) 来压缩代码。 81 | 82 | 首先我们使用 npm 安装 rollup-plugin-uglify,在命令行执行: 83 | 84 | ```bash 85 | npm i --save-dev rollup-plugin-uglify 86 | ``` 87 | 88 | 然后将 rollup.config.js 修改为下面的样子: 89 | 90 | ```javascript 91 | // rollup.config.js 92 | import { uglify } from 'rollup-plugin-uglify' 93 | export default { 94 | input: 'main.js', 95 | output: { 96 | format: 'iife', 97 | name: 'foo', 98 | file: 'bundle.js' 99 | }, 100 | plugins: [ uglify() ] 101 | } 102 | ``` 103 | 104 | 最后开始打包: 105 | 106 | ```bash 107 | rollup --config rollup.config.js 108 | ``` 109 | 110 | 可以看到,这次打包输出的 bundle.js 已经是被压缩过的了。rollup 的插件使用 plugins 字段来定义,plugins 是一个数组,打包时,rollup 会使用数组中的插件来进一步处理代码。 111 | 112 | ### 总结 113 | 114 | 虽然这篇文章定位为 rollup 简介,但也几乎涵盖了 rollup 的全貌,相较于 webpack 简单得多,我们不需要理解纷繁复杂的概念,配置也简单明了,符合人的直观思维。但 webpack 肯定也有自己的优势,两者定位不同,通常来说,打包 JavaScript 工具、库,推荐使用 rollup,打包 web 应用,推荐使用 webpack。 -------------------------------------------------------------------------------- /solution/H5-animation/H5-animation.md: -------------------------------------------------------------------------------- 1 | ### 高效H5动画及性能优化 2 | 3 | - 动画实现方案 4 | - GIF。最多256种颜色,只支持全透明、不支持半透明,动画质量与资源体积成正比 5 | - Animated PNG。支持度低。 6 | - Video。使用autoplay、loop实现。视频不能太长,也不能太短导致一直循环播放 7 | - SVG。无失真,复杂动画、复杂位图效果难以呈现 8 | - Canvas、WebGL。 9 | - JS。requestAnimationFrame 10 | - CSS3。transition、keyframe。渲染引擎会做优化。 11 | - 屏幕适配 12 | - 页面主体,在不同屏幕下只缩放(rem、scale) 13 | - 边缘元素(按钮),绝对定位 14 | - 工具 15 | - 性能检测,stats.js 16 | - tinypng 17 | - 图片合并(可预览动画):https://tonytony.club/tool/dongdong/p 18 | - textureMerger 19 | - 性能优化 20 | - 避免重绘、重排 21 | - 使用scale替代width、height 22 | - 使用translate(x, y)替代margin 23 | - 元素创建后,立即设置translateZ(0)或translate3D(0,0,0) 24 | - 使用css3会创建单独的layer层 25 | - 性能:canvas>js+css(background)>css(translate)>css(background) 26 | - 使用css的will-change让浏览器预优化 27 | - 懒加载、猜测用户动作预加载 28 | 大的雪碧图会导致GPU压力大,导致掉帧 29 | - http 2.0 30 | - 弃用雪碧图:使用zip,前端使用jszip解压出ArrayBuffer,再转base64。再使用CDN缓存、IndexDB 31 | css3动画控制 32 | - settimeout等待动画完成,误差大 33 | - animationStart-animationEnd,兼容性不好 34 | - 降级使用JS 35 | FPS控制 36 | - settimeout/setinterval 37 | - requestAnimationFrame,它会尽量保持与浏览器的刷新频率一致,但也可以使用DOMHighResTimeStamp控制帧率 38 | - 500px的图画到250的canvas上会模糊 39 | - 先绘制到500px的canvas再scale缩小 40 | ```javascript 41 | var context = canvas.getContext('2d'); 42 | context.scale(radio, radio) 43 | ``` 44 | - 性能评分 45 | - 创建200个div,宽高100,然后做随机transform、opacity,计算动画时间 46 | 47 | 48 | -------------------------------------------------------------------------------- /solution/H5-animation/principles-for-smooth-animation.md: -------------------------------------------------------------------------------- 1 | ### 实现丝滑H5动画需要坚守的原则 2 | 3 | - 除了透明度(opacity)和切换(transform),不要改变任何属性。这两个属性不会改变页面布局,并且浏览器会做优化,而修改margin、width、height会造成页面布局改变,造成浏览器渲染压力大。 4 | - 使用opacity隐藏元素,并使用pointer-events:off禁止元素可点击。这样做不会造成回流。但因为动画的时机并不总那么完美 ,比如一个元素在不可见状态下仍然可以点击或者覆盖了其他内容,或者只有当元素淡入显示完全的时候才可以点击。下面会有解决方案。 5 | - 不要一次开始所有动画,这很可能会造成卡顿。 6 | - 可以编排动画,是动画之间有部分重叠,而不是一个接一个地链式动画,可以sass/less和js实现编排 7 | ```sass 8 | @for $i from 1 through 7 9 | &:nth-child(#{$i}) 10 | .loaded& 11 | .text 12 | transition-delay: 500 + (42ms*$i) 13 | .bottom 14 | transition-delay: 500 + (42ms*$i) 15 | ``` 16 | ```javascript 17 | $('el').each(n, el, function() { 18 | setTimeout(function() { 19 | $(el).addClass('showing'); 20 | }, 350 + 64*n); 21 | }) 22 | ``` 23 | - 开发时,放慢动作来观察动画不流畅的地方 24 | - 将动画录像,在视频中观察动画不妥的地方 25 | - 将动画效果在页面加载后延迟零点几秒将会对性能有很大的提升 26 | - 不要直接绑定滚动事件,如实现视差滚动等特效时。 27 | - 尽早在不同屏幕、分辨率等各类屏幕上测试,以发现实现问题、性能问题 -------------------------------------------------------------------------------- /solution/bayesian/bayesian.md: -------------------------------------------------------------------------------- 1 | ## 基于贝叶斯推断的用户唯一性识别 2 | 对于弱登陆web应用(如新闻、博客、品牌电商官网等),要做个性化推送、广告推送,其前提是能够唯一性的识别不同的用户,常规识别用户的做法是基于cookie、本地存储,但在移动端,这些信息经常丢失,导致用户识别错误率偏高。 3 | 在本地信息丢失的情况下,可以使用ip(内网、外网)、fp(根据用户设备信息计算出的hash,参考文末的备注)识别用户,但识别成功率仍不理想。但我们可以使用贝叶斯推断来提高正确率。 4 | 对于贝叶斯推断本身,可以参考阮一峰的系列文章: 5 | - [贝叶斯推断及其互联网应用(一):定理简介](http://www.ruanyifeng.com/blog/2011/08/bayesian_inference_part_one.html) 6 | - [贝叶斯推断及其互联网应用(二):过滤垃圾邮件](http://www.ruanyifeng.com/blog/2011/08/bayesian_inference_part_two.html) 7 | - [贝叶斯推断及其互联网应用(三):拼写检查](http://www.ruanyifeng.com/blog/2012/10/spelling_corrector.html) 8 | 9 | 可以简单地理解贝叶斯推断为,我们先预估一个"先验概率",然后加入实验结果,看这个实验到底是增强还是削弱了"先验概率",由此得到更接近事实的"后验概率"。如果该实验结果不断加入,就会到导致后验概率越来越大。也就是说,事件B发生时,事件A也发生的次数越多,我们就越有信心认为事件B发生时,事件A会发生。B发生时,A发生的概率记做P(A|B),则有: 10 | ```bash 11 | P(A|B) = P(B|A)*P(A)/P(B) 12 | ``` 13 | 对应到我们这里的场景,假设用户张三来访问我们的页面,页面会上报他的ip和fp,该ip-fp组合出现即为事件B;一段时间后,该ip-fp组合再次出现时,对应的用户恰好是张三即为事件A。对于同样的ip-fp组合,在cookie没有丢失的情况下,我们可以知道该组合对应的用户是张三还是李四。如果更多时候是对应的张三,并且多到了一定程度,在cookie丢失时,如果再次发现该ip-fp组合,我们就有信心认为该用户就是张三。这个‘程度’用概率来衡量,即某ip-fp组合出现时,恰好是某用户的概率。如果我们为每个来访用户分配随机的vid(随机hash,会存入cookie),在cookie丢失时,对于某一ip-fp组合,恰好是某一用户的概率P(vid|ip-fp)为: 14 | ```bash 15 | P(vid|ip-fp) = P(ip-fp|vid)*P(vid)/P(ip-fp) 16 | ``` 17 | 当一个没有cookie的用户来访问我们的页面,页面上报ip-fp,后台可以根据数据库得到该ip-fp出现的概率P(ip-fp);由于该ip-fp组合可能对应张三、李四或更多用户,我们可以计算每个用户出现的概率P(vid);由于用户的ip、fp都可能变化,在该用户出现时,其对应的ip-fp组合恰好是此时的ip-fp组合的概率P(ip-fp|vid)也可以计算。最后,便可以计算出每个可能用户的P(vid|ip-fp),概率最大者,比如张三的概率最大,我们就可以认为来访用户就是概率最大的用户。为了提高识别的准确度,我们还需要为P(vid|ip-fp)定一个阈值,比如0.3,即概率最大的用户的概率大于0.3,我们才有信心认为来访用户就是概率最大的用户,否则就按新用户处理,分配新的vid。 18 | 为了验证上述处理方式的正确率并确定阈值,可能还是需要依赖强登陆的产品。因为用户登陆了,因此可以验证我们找出的可能性最大的用户是不是正确的,从而可以得到一个正确率,可以用唯一性和稳定性来衡量。假设注册用户具有唯一标示uid,唯一性、稳定性计算方式如下: 19 | ![唯一性、稳定性计算](https://github.com/Youjingyu/Code-Collection/blob/master/solution/bayesian/img/compute.png) 20 | 通过取不同的阈值,可以得到不同的稳定性、唯一性,从而得到最合适的阈值。从上述的过程可以看出,数据库越大,识别得越准确,因此数据变多时,可以适当提高阈值。 21 | 在找到合适的阈值后,完全可以推广到同类产品,因为这个算法本质上是规避cookie丢失的问题,不受产品本身影响。 22 | 23 | 备注: 24 | fp信息组成如下: 25 | ![设备信息](https://github.com/Youjingyu/Code-Collection/blob/master/solution/bayesian/img/deviceinfo.png) 26 | evercookie 27 | ![evercookie成功率](https://github.com/Youjingyu/Code-Collection/blob/master/solution/bayesian/img/evercookie.png) -------------------------------------------------------------------------------- /solution/bayesian/img/compute.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Youjingyu/Code-Collection/3c702ee5f8760b37301e4c857ab1d1d09aaa8e80/solution/bayesian/img/compute.png -------------------------------------------------------------------------------- /solution/bayesian/img/deviceinfo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Youjingyu/Code-Collection/3c702ee5f8760b37301e4c857ab1d1d09aaa8e80/solution/bayesian/img/deviceinfo.png -------------------------------------------------------------------------------- /solution/bayesian/img/evercookie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Youjingyu/Code-Collection/3c702ee5f8760b37301e4c857ab1d1d09aaa8e80/solution/bayesian/img/evercookie.png -------------------------------------------------------------------------------- /solution/bury/bury.md: -------------------------------------------------------------------------------- 1 | ### 埋点日志服务器返回 img 图片 2 | ```javascript 3 | const gifPath = path.resolve(__dirname, './read.gif'); 4 | const gifLength = fs.statSync(gifPath).size.toString(); 5 | const gif = fs.readFileSync(gifPath); // buffer 6 | 7 | ctx.set({ 8 | // cache control 控制参考自 facebook.com 9 | // private 阻止 cdn、浏览器缓存 10 | 'Cache-Control': 'private, no-cache, no-store, must-revalidate', 11 | 'Expires': 'Sat, 01 Jan 2000 00:00:00 GMT', 12 | 'Pragma': 'no-cache', 13 | 'Content-Type': 'image/gif', 14 | 'Content-Length': gifLength, 15 | }); 16 | ctx.body = gif; 17 | 18 | ``` -------------------------------------------------------------------------------- /solution/deep-link/deep-link.md: -------------------------------------------------------------------------------- 1 | ## DeepLink 2 | 所谓DeepLink(深度链接)就是支持在移动App自由跳转的技术,可简单理解为基于scheme的跳转 3 | 针对不同的系统、场景,跳转流程如下: 4 | ![deep-link](https://user-images.githubusercontent.com/15033260/40275501-e6179036-5c22-11e8-9d2a-d94856276690.png) 5 | ![deep-link2](https://user-images.githubusercontent.com/15033260/40275510-7336e05c-5c23-11e8-848b-ee095e9c8dde.png) 6 | -------------------------------------------------------------------------------- /solution/docker/docker.md: -------------------------------------------------------------------------------- 1 | ## docker 2 | - 配置docker加速镜像 3 | Docker For Mac 4 | 右键点击桌面顶栏的 docker 图标,选择 Preferences ,在 Daemon 标签(Docker 17.03 之前版本为 Advanced 标签)下的 Registry mirrors 列表中加入镜像地址: http://密匙.m.daocloud.io,密匙是http://www.daocloud.io提供的 5 | 或者在pull时加上镜像地址: 6 | ```bash 7 | docker pull registry.docker-cn.com/myname/myrepo:mytag 8 | ``` -------------------------------------------------------------------------------- /solution/dynamic-insert-js/dynamic-insert-js.md: -------------------------------------------------------------------------------- 1 | ## 动态注入js 2 | 为了提前页面的onload、DOMContentLoaded时间,从而让浏览器尽早脱离loading的状态(我们的项目中,image是通过js懒加载的),可以将js的type设置为text/async-script,在onload之后再注入js。需要注意注入方式,如果循环创建script依次append进页面,不能保证js执行顺序,如果在每个js的onload中依次append,就不能利用浏览器的并行加载。拼成script标签字符串,再innerHTML,不会执行。最终可以使用fragment插入,并保证js的执行顺序。 3 | ```javascript 4 | window.addEventListener('load', function () { 5 | // 保证js按照顺序执行、并且可以并行加载,使用fragment一次性将js加入页面 6 | var docfrag = document.createDocumentFragment(); 7 | 8 | [].forEach.call(document.getElementsByTagName('script'), function(scriptTag){ 9 | if(scriptTag.getAttribute('type') === 'text/async-script'){ 10 | var src = scriptTag.getAttribute('data-src'); 11 | var script = document.createElement('script'); 12 | if(src){ 13 | script.src = src 14 | } else { 15 | // 行内js 16 | script.textContent = scriptTag.textContent 17 | } 18 | docfrag.appendChild(script); 19 | } 20 | }); 21 | 22 | document.getElementsByTagName('body')[0].appendChild(docfrag); 23 | }); 24 | ``` -------------------------------------------------------------------------------- /solution/facebook-login/facebook-login.md: -------------------------------------------------------------------------------- 1 | ## 接入 facebook 第三方授权登录经验 2 | 3 | > 公司很多项目都有出海谋求进一步增长的需求,为了在海外环境下方便用户登录,接入 facebook、google 等用户基数非常大的第三方账号也就成了自然而然的需求,这篇文章就对 facebook 的授权登录流程做一个梳理。 4 | 5 | 其实 facebook 对第三方授权登录有比较详尽的[登录文档](https://developers.facebook.com/docs/facebook-login),针对常见的平台,比如 android、ios、web,都有现成的 sdk,非常方便接入。针对想接入 facebook 账号体系的客户端应用,如果引入客户端 sdk ,会增加 app 包体积,如果应用对包体积比较敏感,则不太能接受。因此我们今天主要使用[手动构建登录流程](https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow)的方式,这种方式不需要接入客户端 sdk,并且可以利用 web 的灵活性。 6 | 7 | ### 创建 app 并配置重定向 url 8 | 9 | 在开始之前,我们创建 app 并配置重定向 url。 10 | 11 | facebook 支持多种环境下的登录方式,不管哪种方式都需要我们有一个 facebook 账号,facebook 账号注册流程就不赘述了,注册好账号后,在[应用面板](https://developers.facebook.com/apps)中创建一个 app,注意,app 的名字会在认证时展示给用户,所以一定要取一个正式的名字。 12 | 13 | 创建 app 后,我们点击[应用面板](https://developers.facebook.com/apps)中的 app 进入控制面板,然后在 `添加产品` 部分设置 `facebook 登录`,我们只需要设置 `有效 OAuth 跳转 URI` 就行了,其它配置你可以视情况自行配置,在开发阶段,为了方便调试,你可以将跳转 URI 设置为本地地址,比如 `https:localhost:8080`,注意就算是本地地址,也必须是 https 地址。 14 | 15 | ### 引导用户登录 16 | 17 | 用户在你的 app 种点击登录按钮后,app 需要打开 webview 加载如下的 url 从而进入 facebook 的登录流程: 18 | 19 | ```bash 20 | https://www.facebook.com/v3.2/dialog/oauth? 21 | client_id={app-id} 22 | &redirect_uri={redirect-uri} 23 | &state={state-param} 24 | &response_type={type} 25 | &scope={scope} 26 | ``` 27 | 28 | url 参数意义如下: 29 | 30 | - client_id:是创建 app 时分配的应用 id,直接在[应用面板](https://developers.facebook.com/apps)中就可以查看 31 | - redirect_uri:上面配置的 `有效 OAuth 跳转 URI` 32 | - state:跳转到你配置的 `有效 OAuth 跳转 URI` 时,facebook 会在 URI 中将该值回传给你,你可以用这个值验证,用户确实是从你的 app 跳转过来的,避免跨站请求伪造。这个值取决于你的安全性要求,你不处理,facebook 也不会有任何警告。比如 uc mini 就没有处理这个值。 33 | - response_type:可选,具体取值你可以查看[文档](https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow#login),我们使用默认值就行了 34 | - scope:可选,要向应用用户请求的权限列表,列表项目以逗号或空格分隔。你可以不传这个值,默认情况下就能拿到 id、email、name、first name、last name、picture,但如果需要其他权限,需要显示地声明,并且需要通过 facebook 审核,详见[权限参考文档](https://developers.facebook.com/docs/facebook-login/permissions/#----)。另外,由于用户可能是通过手机号码注册的, email 不一定能拿到。 35 | 36 | app 打开上述 url 后,会进入 facebook 的登录流程,用户完成登录后,就会重定向到我们配置的 URI,到我们的页面后,前端通过 js 拿到页面参数中的 code,然后通过 hybrid 或者其它方式将 code 传给客户端,客户端再拿着 code 到服务端换取用户信息。 37 | 38 | ### 获取 access_token 与用户信息 39 | 40 | 因为这一步需要用到 app 密匙,而密匙是不能暴露在前端或者客户端的,所以这一步必须在服务器进行。客户端带着上一步获取到的 code,请求服务器接口,服务器再通过如下 url 请获取 access_token: 41 | 42 | ```bash 43 | https://graph.facebook.com/v3.2/oauth/access_token? 44 | client_id={app-id} 45 | &redirect_uri={redirect-uri} 46 | &client_secret={app-secret} 47 | &code={code-parameter} 48 | ``` 49 | 50 | 参数意义: 51 | 52 | - client_id:同上 53 | - redirect_uri:同上 54 | - client_secret:app 密匙,在 `app 控制面 > 设置 > 基本` 里可以查看 55 | - code:上一步获取到的 code 56 | 57 | 拿到 access_token 后就可以去获取用户信息了,请求如下 url 即可: 58 | 59 | ```bash 60 | https://graph.facebook.com/v3.2/me? 61 | access_token={access_token} 62 | &fields={id,email,name,name_format,middle_name,first_name,picture} 63 | ``` 64 | 65 | - 参数意义: 66 | 67 | - access_token:上一步拿到的 token 68 | - fields:需要获取的用户信息字段 69 | 70 | > 注意,从上面的流程可以看出,其实在用户完成登录后,会跳转到我们配置的页面,这个页面是请求我们的服务器后输出的,服务器完全可以在这一步就拿到 code 了,然后服务器就可以去请求用户信息了,但是请求结果我们只能输出到页面中,然后再通过页面 js 代码传递给客户端,因为我们想将 access_token 传给客户端,但又不希望直接在页面中输出 access_token 等敏感信息,因此采用了客户端拿着 code 去请求服务端接口的方式(我们的客户端和服务器之间有 rpc 通道)。 71 | 72 | ### 登出 73 | 74 | 因为我们的 app 是一个浏览器,在用户使用 facebook 账号登录我们的 app 后,在网页中打开 facebook 主页会直接登录;反之,如果用户先在网页中登录了 facebook 账号,然后在我们 app 的登录流程中也会直接登录(用户第一次登录时,facebook 会要求用户授权,之后都会直接登录)。 75 | 76 | 因此针对登出,取决于你期望的产品交互流程,分为两种情况: 77 | 78 | 1. 退出你的产品,但不退出 facebook 79 | 2. 同时退出 facebook 和你的产品 80 | 81 | 官方期望的是,你自己保存用户是否登出你的应用的标识,然后用户退出时,只退出你的应用,不要退出 facebook;比如,用户登出后,再次进入个人信息等需要登录才能查看的页面,你判断到用户已经登出,因此引导用户到登录页,然后用户点击使用 facebook 登录的按钮,用户就直接登录了(不会出现用户名、密码输入界面,因为用户没有登出 facebook,并且已经授权过你的应用了) 82 | 83 | 但对于我们的浏览器产品来说,首先我们暂时还没有保存用户的登录标识(完全依赖 facebook 了标识,并且由于早期的设计不合理,登录界面是原生的,webview 中不能控制登录界面),如果只登出我们的应用,不登出 facebook,用户再次登录时,会用上次的登录账号直接登录,导致用户无法切换账号;并且直接登录的交互,会给用户造成没有登出的假象。因此我们采用了第二种方案。 84 | 85 | #### 第一种方案 86 | 87 | 其实第一种方案,官方并没有提供直接的 api,我们经过各种搜索,都没有找到合适的方式,只能借助于 facebook 的网页 sdk 了。最开始我们不想引入 sdk,我们只需要模拟 sdk 的行为就行了;但在查看 sdk 的源码以及登出时的请求后,发现要模拟非常麻烦,而且各种参数易于出错,鉴于 facebook 的网页 sdk 只有 1.9 KB,引入 sdk 完全可以接受,我们就直接使用这个 sdk 好了。 88 | 89 | 代码如下: 90 | 91 | ```html 92 | 93 | 94 | 95 | Facebook Logout 96 | 97 | 98 | 99 | 117 | 118 | 119 | ``` 120 | 121 | 这里有两个坑: 122 | 123 | - 调用 `FB.logout` 会报错 `Refused to display 'https://www.facebook.com/home.php' in a frame because it set 'X-Frame-Options' to 'deny'`,需要在 `app 控制面板 设置 > 基本 > 添加平台 > 网站` 中添加执行 `FB.logout` 方法的域名才能退出。 124 | - 不能在 `localhost` 域名下调用 `FB.logout` ,否则刷新页面后,用户仍旧是登录状态。你可以本地绑定 host,然后换一个域名测试即可。 125 | 126 | #### 第二种方案 127 | 128 | 第二种方案相对来说容易一些。一种简单的方式是访问如下的 url 就能够登出: 129 | 130 | ```bash 131 | https://www.facebook.com/logout.php? 132 | access_token={token} 133 | next={redirect-url} 134 | ``` 135 | 136 | 参数意义: 137 | 138 | - access_token:上面步骤中获取到的 token 139 | - next:必须是我们在上面配置的 `有效 OAuth 跳转 URI` 140 | 141 | 如果你不想再次打开一个 webview 访问上面的 url,你也可以直接操作 cookie,需要注意,facebook 在很多子域下都种有 cookie(包括 https://facebook.com、https://m.facebook.com、https://staticxxx.facebook.com),我们发现强行使这些域名下的 cookie 过期并不生效。最后发现了官方的处理方式,从而实现登出: 142 | 143 | ```bash 144 | set-cookie: c_user=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=-1555934745; path=/; domain=.facebook.com 145 | set-cookie: xs=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=-1555934745; path=/; domain=.facebook.com; httponly 146 | set-cookie: spin=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=-1555934745; path=/; domain=.facebook.com; httponly 147 | ``` 148 | 149 | ### 补充 150 | 151 | 其实 Google 账号的登录流程差不多,因为 facebook 和 google 都是采用的 OAuth 2.0 标准。 152 | 153 | 针对 google 的登出,也有三种方式: 154 | 155 | 1. 使用官方 [sdk](https://developers.google.com/identity/sign-in/web/) 156 | 2. 访问 url:https://www.google.com/accounts/Logout?continue=https://appengine.google.com/_ah/logout?continue=redirect_url 157 | 3. 清除 cookie 158 | 159 | 第一种方式和 facebook 一样,只能退出你的应用,不能退出 google。第二种方式的问题是,在跳转到你的 redirect_url 之前需要用户再次确认,交互上会多一步,并且如果用户拒绝跳转到你的 redirect_url,相当于你拿不到登出成功的通知。第三种方式也和 facebook 类似,不过操作 cookie 的方式不同: 160 | 161 | ```bash 162 | set-cookie: SID=EXPIRED; Domain=.google.com; Expires=Tue, 26-Jul-2016 12:09:29 GMT; Path=/; Secure 163 | set-cookie: HSID=EXPIRED; Domain=.google.com; Expires=Tue, 26-Jul-2016 12:09:29 GMT; Path=/; Secure 164 | set-cookie: SSID=EXPIRED; Domain=.google.com; Expires=Tue, 26-Jul-2016 12:09:29 GMT; Path=/; Secure 165 | set-cookie: APISID=EXPIRED; Domain=.google.com; Expires=Tue, 26-Jul-2016 12:09:29 GMT; Path=/; Secure 166 | set-cookie: SAPISID=EXPIRED; Domain=.google.com; Expires=Tue, 26-Jul-2016 12:09:29 GMT; Path=/; Secure 167 | set-cookie: LSID=EXPIRED; Expires=Tue, 26-Jul-2016 12:09:29 GMT; Path=/; Secure 168 | set-cookie: TAID=EXPIRED; Domain=.google.com; Expires=Tue, 26-Jul-2016 12:09:29 GMT; Path=/ads/measurement; Secure 169 | set-cookie: SIDCC=EXPIRED; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=.google.com; priority=high 170 | ``` -------------------------------------------------------------------------------- /solution/fileDownload/fileDownload.md: -------------------------------------------------------------------------------- 1 | ### 微博端文件下载 2 | 3 | - 后端设置header 4 | ```javascript 5 | header('Content-type: image/jpeg'); 6 | header("Content-Disposition: attachment; filename='download.jpg'"); 7 | ``` 8 | - a标签的download属性,download的值为下载后的文件名 9 | ```html 10 | 下载 11 | ``` -------------------------------------------------------------------------------- /solution/fileUpload/fileUpload.md: -------------------------------------------------------------------------------- 1 | ### 文件上传 2 | 参考文章[前端本地文件操作与上传](https://zhuanlan.zhihu.com/p/31401799?group_id=917810126737256448) -------------------------------------------------------------------------------- /solution/git-multi-ssh/git-multi-ssh.md: -------------------------------------------------------------------------------- 1 | - git多个ssh 2 | ```bash 3 | ssh-keygen -t rsa -C "github 账号" 4 | ssh-keygen -t rsa -C "公司 gitlab 账号" 5 | # 记得在交互式行中输入生成的密匙文件的名字(包含完整路径) 6 | ``` 7 | 生成密匙,不要输入密码,否则每次pull、push都需要密码 8 | ```bash 9 | ssh-add ~/.ssh/github_rsa 10 | ssh-add ~/.ssh/gitlab_rsa 11 | // 复制公匙到github和gitlab 12 | cat ~/.ssh/github_rsa.pub 13 | cat ~/.ssh/gitlab_rsa.pub 14 | ``` 15 | 添加config文件,```vi ~/.ssh/config```,输入如下内容 16 | ```bash 17 | #github 18 | Host github.com 19 | HostName github.com 20 | User 735284268@qq.com 21 | IdentityFile ~/.ssh/github_rsa 22 | 23 | #gitlab 24 | Host gitlab.weibo.cn 25 | HostName gitlab.weibo.cn 26 | User jingyu16@staff.sina.com.cn 27 | IdentityFile ~/.ssh/gitlab_rsa 28 | ``` 29 | 测试 30 | ```bash 31 | ssh -T git@github.com 32 | ``` -------------------------------------------------------------------------------- /solution/inspect-webview/inspect-webview.md: -------------------------------------------------------------------------------- 1 | ## Windows使用Chrome调试安卓webview 2 | - 手机打开usb调试 3 | - 手机下载chrome 4 | - 用数据线将手机和电脑连接,电脑可能会自己下载驱动,如果下载失败,需要手动安装驱动 5 | - 参考:https://developer.android.com/studio/run/oem-usb?hl=zh-cn 6 | - 在上面的页面底部下载设备对应的驱动,解压 7 | - 右键计算机,选择管理,选择设备,再选择其它设备,里面会有你的手机,右键手机,更新驱动,从本地安装,选择你刚才解压的驱动,然后完成安装(期间如果弹出警告,点确定即可) 8 | - 驱动安装好后,电脑chrome访问```chrome://inspect``` 9 | - 手机使用webview访问一个页面,在电脑的chrome界面中就会出现该页面的选项 -------------------------------------------------------------------------------- /solution/install-llvm/install-llvm.md: -------------------------------------------------------------------------------- 1 | llnode依赖lldb,安装lldb需要llvm 2 | ## centos安装llvm 3 | ### centos7 4 | ```bash 5 | # Install CentOS SCLo RH Testing repository: 6 | yum install centos-release-scl-rh 7 | #Install llvm-toolset-7-lldb rpm package: 8 | yum --enablerepo=centos-sclo-rh-testing install llvm-toolset-7-lldb 9 | # https://www.jianshu.com/p/f965bbba6eb1 10 | ``` 11 | ### centos6.5 12 | 13 | #### 一键安装lldb 14 | 新建文件`/etc/yum.repos.d/llvm.repo`,写入如下内容: 15 | ```bash 16 | [llvm] 17 | name=LLVM for CentOS-$releasever 18 | baseurl=https://llvm.cpp.fail/CentOS/$releasever/llvm/$basearch/ 19 | enabled=1 20 | gpgcheck=1 21 | repo_gpgcheck=1 22 | gpgkey=https://llvm.cpp.fail/gpg.key 23 | ``` 24 | 然后`yum install lldb`即可 25 | 26 | ### 编译安装llvm 27 | #### 升级gcc 28 | 在安装之前先[升级gcc](https://github.com/Youjingyu/Code-Collection/blob/master/solution/upgrade-gcc/upgrade-gcc.md) 29 | 30 | #### 安装Python2.7+ 31 | ```bash 32 | sudo yum install centos-release-scl 33 | sudo yum install python27 34 | scl enable python27 bash 35 | ``` 36 | 37 | #### 安装cmake 38 | ```bash 39 | sudo yum install cmake 40 | # 如果在执行后续步骤时提示cmake版本过低,需要升级cmake 41 | wget https://cmake.org/files/v3.5/cmake-3.5.2.tar.gz 42 | tar xvf cmake-3.5.2.tar.gz 43 | cd cmake-3.5.2 44 | ./bootstrap --prefix=/usr 45 | gmake 46 | gmake install 47 | cmake --version 48 | ``` 49 | ### 选择llvm版本 50 | ```bash 51 | # 列出所有版本 52 | # llnode要求版本高于3.9 53 | # 我选择了4.0.0 54 | svn ls http://llvm.org/svn/llvm-project/llvm/tags | grep RELEASE 55 | ``` 56 | 57 | ### 安装llvm 58 | ```bash 59 | svn co http://llvm.org/svn/llvm-project/llvm/tags/RELEASE_400/final llvm_RELEASE_400 60 | cd llvm_RELEASE_400/tools 61 | svn co http://llvm.org/svn/llvm-project/cfe/tags/RELEASE_400/final clang 62 | cd ../projects 63 | svn co http://llvm.org/svn/llvm-project/compiler-rt/tags/RELEASE_400/final compiler-rt 64 | svn co http://llvm.org/svn/llvm-project/libcxx/tags/RELEASE_400/final libcxx 65 | svn co http://llvm.org/svn/llvm-project/libcxxabi/tags/RELEASE_400/final libcxxabi 66 | cd .. 67 | svn update 68 | 69 | # 开始编译 70 | mkdir ../llvm_RELEASE_360_build 71 | cd ../llvm_RELEASE_360_build 72 | # 注意替换gcc、g++的路径(which gcc、which g++) 73 | # 这一步在我的1G 1核的机器上停滞在 check clang version 或 configuring done,请耐心等待 74 | # 如果是多核心机器可以将下面的 && make && 替换为 && make -j <核心数> &&,从提高编译速度 75 | cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/local/bin/gcc -DCMAKE_CXX_COMPILER=/usr/local/bin/g++ ../llvm_RELEASE_400 && make && sudo make install && echo success 76 | # 查看是否安装成功 77 | clang --version 78 | clang++ --version 79 | which clang 80 | which clang++ 81 | # https://www.vultr.com/docs/how-to-install-llvm-and-clang-on-centos-6 82 | ``` -------------------------------------------------------------------------------- /solution/install-nodejs/install-nodejs.md: -------------------------------------------------------------------------------- 1 | ### 通过官方的方式 2 | 有可能因为网络问题,没有下载成功,导致安装的是0.10版本 3 | ```bash 4 | curl --silent --location https://rpm.nodesource.com/setup_8.x | sudo bash - 5 | sudo yum -y install nodejs 6 | ``` 7 | ### 压缩包安装1 8 | ```bash 9 | cd /usr/src 10 | wget https://nodejs.org/dist/v8.9.4/node-v8.9.4-linux-x64.tar.xz 11 | tar xvJf node-v8.9.4-linux-x64.tar.xz 12 | cd node-v8.9.4-linux-x64 13 | echo "export PATH=$PATH:/usr/src/node-v8.9.4-linux-x64/bin" >> /etc/bashrc 14 | source /etc/bashrc 15 | ``` 16 | ### 压缩包安装2 17 | ```bash 18 | cd /usr/src 19 | wget https://nodejs.org/dist/latest-v9.x/node-v9.4.0-linux-x64.tar.xz 20 | tar -xvJf node-v9.4.0-linux-x64.tar.xz 21 | mv node-v9.4.0-linux-x64 /usr/local/node-v9 22 | ln -s /usr/local/node-v9/bin/node /bin/node 23 | ln -s /usr/local/node-v9/bin/npm /bin/npm 24 | echo 'export PATH=/usr/local/node-v9/bin:$PATH' >> /etc/profile 25 | source /etc/profile 26 | ``` -------------------------------------------------------------------------------- /solution/linux-send-mail/linux-send-mail.md: -------------------------------------------------------------------------------- 1 | ### Linux定时邮件发送 2 | 基于nodejs、nodemailer、sendmail 3 | #### node js 4 | ```javascript 5 | const nodemailer = require('nodemailer'); 6 | let transporter = nodemailer.createTransport({ 7 | sendmail:true, 8 | newline:'unix', 9 | path:'/usr/sbin/sendmail' 10 | }); 11 | transporter.sendMail({ 12 | to: 'xxxx@xx.com', 13 | subject: 'Message', 14 | text: 'mac has been there' 15 | }, (err, info) => { 16 | console.log(info.envelope); 17 | console.log(info.messageId); 18 | }); 19 | ``` 20 | #### linux 21 | ```bash 22 | yum -y install sendmail 23 | service sendmail start 24 | ``` 25 | 查看sendmail服务状态,如果出现sendmail dead but subsys locked,说明postfix和sendmail冲突了 26 | ```bash 27 | postfix stop 28 | ``` 29 | 测试邮件 30 | ```bash 31 | echo "this is my test mail" | mail -s 'mail test' xxx@yyy.com 32 | # 或 33 | mail -s 'mail test' xxx@yyy.com < con.txt 34 | ``` 35 | 查看邮件日志 36 | ```bash 37 | tail -f /var/log/maillog 38 | ``` 39 | 等一会,如果出现超时,可能是25端口没有打开 40 | ``` 41 | iptables -A INPUT -p tcp --dport 25 -j ACCEPT 42 | ``` 43 | 对于腾讯云服务器,还需要到云控制台手动打开25端口的解禁,腾讯的协议要求只能使用smtp发送邮件,因此不能再sendemail 44 | ```javascript 45 | let transporter = nodemailer.createTransport({ 46 | host: 'smtp.qq.com', 47 | secure: true, 48 | auth: { 49 | user: 'xxx@qq.com', 50 | pass: '***' 51 | } 52 | }); 53 | transporter.sendMail({ 54 | from: 'xxx@qq.com', 55 | to: 'xxx@qq.com', 56 | subject: 'Message', 57 | text: 'mac has been there' 58 | }, (err, info) => { 59 | console.log(info.envelope); 60 | console.log(info.messageId); 61 | }); 62 | ``` -------------------------------------------------------------------------------- /solution/load-time-summary/load-time-summary.md: -------------------------------------------------------------------------------- 1 | ### 前端静态文件加载时间埋点统计 2 | - 使用Date 3 | 在资源加载开始之前,执行 4 | ```javascript 5 | new Date().getTime() // 获取开始时间 6 | ``` 7 | 在每个资源的onload事件中统计时间 8 | ````html 9 | 10 | 11 | 12 | ```` 13 | 最后通过各个时间节点计算每个资源的加载时间 14 | 15 | - 使用Resource Timing API 16 | 使用```window.performance.getEntriesByType("resource")```收集页面资源加载的具体信息。 17 | 详情参考[使用Resource Timing API分析前端性能探索](http://fe.sina.cn/2016/11/04/shi-yong-resource-timing-apifen-xi-qian-duan-xing-neng-tan-suo-yi/)、 18 | [Resource Timing (资源计时) 使用技巧](http://ju.outofmemory.cn/entry/110290) 19 | -------------------------------------------------------------------------------- /solution/mocha-es6-in-browser/mocha-es6-in-browser.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Youjingyu/Code-Collection/3c702ee5f8760b37301e4c857ab1d1d09aaa8e80/solution/mocha-es6-in-browser/mocha-es6-in-browser.md -------------------------------------------------------------------------------- /solution/mocha-es6-in-browser/project/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "version": "0.0.1", 4 | "main": "lib/index.cjs.js", 5 | "module": "src/index.js", 6 | "scripts": { 7 | "test": "webpack-dev-server --config ./test/webpack.config.js --hot" 8 | }, 9 | "devDependencies": { 10 | "webpack": "*", 11 | "webpack-dev-server": "*" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /solution/mocha-es6-in-browser/project/test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | suda and sima tests 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 16 | 17 | -------------------------------------------------------------------------------- /solution/mocha-es6-in-browser/project/test/test-code/index.test.js: -------------------------------------------------------------------------------- 1 | import SUDA from '../../src/suda/core/suda' 2 | 3 | const chai = window.chai 4 | 5 | describe('sum', function () { 6 | it('new SUDA', function () { 7 | const suda = new SUDA() 8 | chai.expect(suda.sudaCount).to.equal(1) 9 | }); 10 | }); -------------------------------------------------------------------------------- /solution/mocha-es6-in-browser/project/test/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: ['./test/test-code/index.test.js'], 3 | output: { 4 | filename: 'test/bundle.js' 5 | }, 6 | module: { 7 | rules: [ 8 | { 9 | test: /\.js$/, 10 | exclude: /(node_modules|bower_components)/, 11 | use: { 12 | loader: 'babel-loader', 13 | options: { 14 | presets: [ 15 | ['env', { 16 | 'modules': false, 17 | 'targets': { 18 | 'browsers': ['> 1%', 'last 2 versions', 'not ie <= 8'] 19 | } 20 | }] 21 | ] 22 | } 23 | } 24 | } 25 | ] 26 | }, 27 | devServer: { 28 | contentBase: __dirname, 29 | open: true, 30 | port: 9000, 31 | noInfo: true 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /solution/network-information/network-information.md: -------------------------------------------------------------------------------- 1 | ### 获取手机网络状态 2 | 使用Network Information API: 3 | ```javascript 4 | //得到网络链接类型 5 | var type = navigator.connection.type; 6 | 7 | // 在第一次网络跳跃的时候得到下行最大比特率 8 | var max = navigator.connection.downlinkMax; 9 | 10 | function changeHandler(e) { 11 | // 网络链接改变时处理函数 12 | } 13 | 14 | // 注册网络链接改变事件 15 | navigator.connection.onchange = changeHandler; 16 | 17 | // 另一种注册方式 18 | navigator.connection.addEventListener('change', changeHandler); 19 | ``` 20 | 具体兼容性问题参考[Network Information API通过JS判断网络状态测试说明](http://fe.sina.cn/2017/07/27/untitled-12/)、 21 | [Network Information API](http://wicg.github.io/netinfo/#) -------------------------------------------------------------------------------- /solution/nodejs-framework-design/nodejs-framework-design.md: -------------------------------------------------------------------------------- 1 | ## Node.js 框架设计 2 | 3 | > 总结自 Node.js web 框架 [Daruk](https://github.com/daruk-framework/daruk) 的设计与开发 4 | 5 | 在开始之前,我们需要明确框架设计的目标。我们并不是想造一个底层框架,比如 koa、express 这种,一个原因是我们必须拥抱社区生态,新的轮子在生态方面可能会非常欠缺;另一方面,经过社区筛选后的成熟框架,肯定是更加合理的并且能够满足各种业务形态,新造的轮子可能会存在各种坑。因此我们这里所说的框架设计,是面向业务的,基于底层 Node.js 框架的;目标在于降低业务开发成本,约束代码风格,并提供性能分析、监控、上线部署、日志、单元测试等周边设施,最理想的情况是,业务同学只需要关注业务,其他方便面都交给框架来做。 6 | 7 | ### 业界框架 8 | 9 | 底层框架主要有: 10 | 11 | - koa 12 | - Express 13 | - hapi 14 | - restify 15 | - fastify 16 | - nest.js 17 | 18 | 这里所说的底层框架,肯定也是有相应的周边配套设施的,只是没有提供开箱即用的方式,也没有统一的规范,它们都可以通过简短的代码启动一个 http server。 19 | 20 | 上层框架: 21 | 22 | - egg 23 | - think.js 24 | - midway 25 | - meteor 26 | - feathers.js 27 | 28 | 需要说明的是,上面只是对部分 Node.js 框架的简单划分,因为我并不熟悉所有框架,可能划分并不准确。 29 | 30 | 我们想要达到的目的,可能和 egg、think.js 比较接近,他们都基于约定的目录结构,有自己的开发规范,从而不同的开发人员写出来的代码都非常类似,易于维护;并且他们还提供了数据库连接、cookie、session、日志、多进程支持等特性。 31 | 32 | ### 设计思路 33 | 34 | 接下来,想一想,我们的框架要怎样设计呢?从我个人的角度来说,我比较想达到如下目标: 35 | 36 | - 统一代码规范 37 | - 框架概念少,易于上手,开发人员写着顺手,代码简洁 38 | - 拥抱社区,社区工具不需要 Adapter 就能直接使用 39 | - 提供周边工具 40 | 41 | 接下来我们一点点实现这些目标 42 | 43 | #### 统一代码规范 44 | 45 | 对于统一代码规范,我们只需要约定目录结构,并对目录中导出的模块做一定要求就行了。对于常见的 Node.js http server,通常需要下面的目录: 46 | 47 | ```bash 48 | ├── router 49 | │ └── index.js 50 | ├── middleware 51 | │ └── index.js 52 | ├── controller 53 | │ └── index.js 54 | └── service 55 | └── index.js 56 | ``` 57 | 58 | router 目录用于定义路由,controller 目录用于定义路由 handle,service 目录用于抽象、复用 controller 逻辑。 59 | 60 | 除了上面的必要目录,还可以约定一些工具类目录,比如 config(项目配置)、timers(定时器)、utils(工具函数)等目录。 61 | 62 | 对于目录中的模块可以要求必须导出一个类、一个函数(用于后续自动加载约定目录中的模块)。我们会将约定目录中的模块挂载到 context 或者框架实例 app 上,比如,用户使用的时候就可以用 `ctx.service.service1` 或者 `app.service.service1` 63 | 64 | #### 使用 typescript 65 | 66 | typescript 在社区里越来越流行,在大型应用中,ts 对多方协作、bug 规避确实有好处,对于后端应用来说,应用的稳定性更加重要,而强类型可以在开发阶段就发现各种 bug。因此我们选择 ts 来开发,框架设计上也要向 ts 靠拢。首先是框架本身用 ts 编写,自动加载的模块不能丢失类型声明,利用上 ts 的装饰器。 67 | 68 | ##### 约定目录模块的类型声明 69 | 70 | 约定目录中的内容由框架自动加载,然后将内容挂载到 context 或者框架实例 app 上,不同于直接 `import` 约定目录中的内容,模块的挂载实际上依赖于 js 的动态性,ts 是不知道 context、app 上拥对应的属性的。因此我们需要将这些属性告知 ts,从而既能保有自动加载的便捷和严格的类型声明。为了保有类型声明,我们需要 ts 的 declare merging 特性,该特性可以将多个模块声明合并。比如我们在模块内部声明了 context,然后在用户的目录下又可以声明 context,两者会合并,从而将用户写的约定目录中的代码又挂在到 context 上: 71 | 72 | ```typescript 73 | // 框架内部的声明 74 | declare 'daruk' { 75 | Interface Context {} 76 | } 77 | ``` 78 | 79 | ```typescript 80 | // 用户的声明文件 81 | // 为了避免用户手动维护这些声明文件 82 | // 我们可以提供工具自动生成这些文件 83 | import service1 from './src/service/service1.ts' 84 | 85 | declare 'daruk' { 86 | Interface Context { 87 | service1: service1 88 | // ... 89 | } 90 | } 91 | ``` 92 | 93 | 然后用户通过 `ctx.service.service1` 访问 `service1` 的时候就会有 `service1` 完整的类型声明。 94 | 95 | ##### 装饰器 96 | 97 | ts 中的装饰器可以进一步对类、类属性、类方法进行修饰,我们可以利用上这一点。在上述的目录结构约定中,我们划分了 controller、router 目录,其实 router 只是一个描述信息,真正执行逻辑的是 controller,那么时候可以将两者合并呢?一个路由定义包含三个信息:路由 path + 路由的 http method + 处理路由的 handle;我们可以利用上装饰器,将这三个信息与 controller 的定义合并;首先我们用 controller 的目录结构来描述路由信息,用 http method 名作为装饰器的名字来修饰路由 handle,比如我们定义一个路径为 `/user/list` 的 get 请求路由: 98 | 99 | ```typescript 100 | // ./controller/user/list.ts 101 | import { get } from 'daruk' 102 | 103 | class UserList { 104 | @get('/') 105 | index (ctx) { 106 | ctx.body = 'user list' 107 | } 108 | } 109 | ``` 110 | 111 | 当然这种是装饰器的一种用处,还有其他用处可以查看 [Daruk 装饰器](https://daruk-framework.github.io/daruk.org/decorator.html#http-method-%E8%A3%85%E9%A5%B0%E5%99%A8) 112 | 113 | #### 链路追踪 114 | 115 | 与 php(每次请求对应一个线程并同步执行代码)、java(通过线程局部变量与某个请求绑定) 等语言搭建的 web 服务不同,Node.js 是异步编程模型,并没有顺序的调用链路,调用链是”混乱“的,因此我们的代码是不知道自己的上下文的(也就是不能对应到某个请求),当然基于 koa 的变成模型,我们在中间件和路由的 handle 中我们都能很方便地拿到上下文(即 context),但是中间件或者路由中的更深层调用呢?比如在 service 中访问 context;最简单的方式是,将 context 一层层传递下去,但是对于业务方来说,过于繁琐,难以接受。 116 | 117 | 针对上述的问题,业界也有很多解决方案,首先是官方的 async_hooks,但是直到现在的稳定版,都还有性能问题,一直处于实验性阶段;另外有一个 zone.js,zone.js 几乎覆写了所有 js 中可能存在的异步操作,然后在异步调用中传递 context;还有一种思路是通过 ast 编译,将 context 以参数的形式自动在调用链中传递;后两种思路,都太“重”了,那有没有比较轻量的方式呢? 118 | 119 | 其实我们已经约定了代码规范,只要用户按照一定方式书写代码,我们有可能在框架层面直接注入 context 的。具体来说就是,用户获取 service 和 controller 时都从 ctx 上去获取,比如 `ctx.service.service1`,`ctx.controller.controller1`,我们可以劫持 ctx、ctx.service、ctx.controller 的 getter,在 getter 中实现 ctx 的注入,从而业务方可以在各处拿到 context。伪代码如下: 120 | 121 | ```typescript 122 | class HelpContext { 123 | constructor (context) { 124 | this._ctx = context 125 | Object.defineProperty(this, 'service1', { 126 | get () { 127 | // 用户定义的 service 会保存在 app.module 中 128 | // 每次实例化 service 都会传入对应的 ctx 129 | return new context.app.module['service1'](this._ctx) 130 | } 131 | }) 132 | } 133 | } 134 | 135 | // 用户拿到的 context 是被框架修改后的 136 | context.service = new HelpContext(context) 137 | ``` 138 | 139 | ### 周边工具 140 | 141 | 为了方便用户快速开始使用,以及最佳实践,我们需要为用户准备开箱即用的 cli 工具,可以参照 [daruk-cli](https://github.com/daruk-framework/daruk-cli)。当然你也可以围绕你的框架,提供一系列其它周边库,参照 [daruk 的周边库列表](https://github.com/daruk-framework) 142 | 143 | ### 单元测试 144 | 145 | 单元测试对于持续的迭代维护是必不可少的,我们可以使用 nyc 计算测试覆盖率。 -------------------------------------------------------------------------------- /solution/npm-system-cmd/npm-system-cmd.md: -------------------------------------------------------------------------------- 1 | ## 基于npm的系统命令开发 2 | 使npm全局安装包后,添加系统命令 3 | #### package.json添加bin字段 4 | ```json 5 | { 6 | "cmd-name": "./bin/index.js" 7 | } 8 | ``` 9 | 添加后,全局安装该包时,npm会将命令```cmd-name```加入系统命令 10 | #### 获取命令行参数 11 | 命令行执行```cmd-name parama1 parama2```相当于执行```node ./bin/index.js parama1 parama2``` 12 | 在```./bin/index.js```中获取命令行参数 13 | ```javascript 14 | #!/usr/bin/env node 15 | 16 | console.log(process.argv) // ["node", "./bin/index.js", “ parama1”, “parama2”] 17 | ``` 18 | #### 输出带颜色的log 19 | ```javascript 20 | // 输出黄色log 21 | console.log('\x1b[33m', log, '\x1b[0m') 22 | ``` 23 | ```\x1b[0m```代表清除前面设置的格式,不清除的话,后面的所有输出都会变色 24 | 颜色列表: 25 | Reset = "\x1b[0m" 26 | Bright = "\x1b[1m" 27 | Dim = "\x1b[2m" 28 | Underscore = "\x1b[4m" 29 | Blink = "\x1b[5m" 30 | Reverse = "\x1b[7m" 31 | Hidden = "\x1b[8m" 32 | 33 | FgBlack = "\x1b[30m" 34 | FgRed = "\x1b[31m" 35 | FgGreen = "\x1b[32m" 36 | FgYellow = "\x1b[33m" 37 | FgBlue = "\x1b[34m" 38 | FgMagenta = "\x1b[35m" 39 | FgCyan = "\x1b[36m" 40 | FgWhite = "\x1b[37m" 41 | 42 | BgBlack = "\x1b[40m" 43 | BgRed = "\x1b[41m" 44 | BgGreen = "\x1b[42m" 45 | BgYellow = "\x1b[43m" 46 | BgBlue = "\x1b[44m" 47 | BgMagenta = "\x1b[45m" 48 | BgCyan = "\x1b[46m" 49 | BgWhite = "\x1b[47m" -------------------------------------------------------------------------------- /solution/performance-optimization/performance-optimization.md: -------------------------------------------------------------------------------- 1 | ## 前端性能优化 2 | 性能优化目标主要是让用户尽快看到页面内容、页面能够尽快对用户操作做出反馈。 3 | 所以主要关注下面几个指标 4 | - First Paint 白屏时间:浏览器从白屏到第一次视觉变化。responseStart - navigationStart 5 | - First Meaningful Paint 首次有效渲染:文字、图像和主要内容都已可见。performance.getEntriesByType('paint'),兼容性不好。 6 | - Visually Complete 视觉完整:视口中的所有内容都可见。根据业务情况情况埋点,或者获取首屏所有图片loaded的时间 7 | - Time to Interactive 可交互时间:视口中的所有内容都可见,并且可以进行交互(JavaScript 主线程停止活动)。domInteractive - requestStart 8 | ### 资源优化 9 | - 主动缓存 10 | - 启用压 11 | - 使用 CDN 12 | - 优先关键资源 13 | - 浏览器有默认的关键请求(如果请求包含用户视口渲染所需的资源,那该请求就是关键请求。), 比如HTML、必要的 CSS、LOGO、网络字体,也可能是图片。但许多其他不相关的(JavaScript、追踪代码、广告等)也可能被请求。 14 | - 通过,我们可以手动强制设置资源的优先级,来确保所期望的内容按时渲染。 15 | - chrome控制台可以查看浏览器默认的请求优先级。 16 | ### 图片优化 17 | - 选择正确的格式。 18 | - JPEG:色彩丰富的图片(例如照片)。PNG–8:色彩不是很丰富的图片。PNG–24:具有部分透明度的图片。GIF:动画图片 19 | - 尽可能使用矢量图 20 | - 如果变化不明显,则降低质量 21 | - 尝试新格式,Google 的 WebP,Apple 的 JPEG 2000 和 Microsoft 的 JPEG-XR 22 | - WebP,74%浏览器支持,并有降级方案 23 | - 使用工具和算法进行优化 24 | - 使用 srcset 属性和 picture 元素,85%+ 的浏览器支持率 25 | - 使用图片 CDN 26 | ### 优化网络字体 27 | - 选择正确的格式 28 | - 字体选择评测 29 | - 使用 Unicode-range 子集 30 | - 建立字体加载策略 31 | ### 优化 JavaScript 32 | 低端机器解析执行JavaScript的时间可能是高端机器的2-5倍 33 | - 监控 JavaScript 传输 34 | - 移除不必要的依赖 35 | - 实施代码分割 36 | - 考虑框架选择 37 | 38 | 在上述优化的基础上,还可以通过chrome dev tools 分析资源加载、解析、执行顺序对关键指标的影响 39 | 40 | #### 参考链接 41 | - https://github.com/xitu/gold-miner/blob/master/TODO/front-end-performance-checklist-2018-1.md 42 | - https://www.smashingmagazine.com/2018/01/front-end-performance-checklist-2018-pdf-pages/ 43 | - https://mp.weixin.qq.com/s/g1hNWleW00ACQ5u1oU2_cQ 44 | - https://developer.akamai.com/blog/2017/12/04/beware-performancetiming-dominteractive/ 45 | - https://juejin.im/post/5a5ed5f3f265da3e261bfb94 46 | - https://fed.renren.com/2017/10/29/chrome-fetch-resource/ 47 | - https://developers.google.com/web/fundamentals/performance/critical-rendering-path/analyzing-crp 48 | -------------------------------------------------------------------------------- /solution/performance-optimization/前端性能优化概述.xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Youjingyu/Code-Collection/3c702ee5f8760b37301e4c857ab1d1d09aaa8e80/solution/performance-optimization/前端性能优化概述.xmind -------------------------------------------------------------------------------- /solution/react-css-modules/react-css-modules.md: -------------------------------------------------------------------------------- 1 | ## react + typescript + css modules + scss 2 | 3 | 在 typescript 写的 react 项目中使用 css modules + scss。 4 | 5 | 首先配置 .tsx 文件的 webpack loader: 6 | 7 | ```js 8 | // webpack.config.js 9 | module.exports = { 10 | module: { 11 | rules: [ 12 | { 13 | test: /\.(t|j)sx?$/, 14 | include: paths.appSrc, 15 | use: [ 16 | { 17 | loader: require.resolve('babel-loader'), 18 | options: { 19 | 'presets': ['@babel/react'], 20 | 'plugins': [ 21 | // babel-plugin-react-css-modules 配置 22 | ['react-css-modules', { 23 | 'filetypes': { 24 | '.scss': { 25 | 'syntax': 'postcss-scss' 26 | } 27 | }, 28 | // 默认值就是这个,一定要与 css-loader 的 localIdentName 配置一致 29 | generateScopedName: '[path]___[name]__[local]___[hash:base64:5]' 30 | }] 31 | ] 32 | } 33 | }, 34 | { 35 | loader: require.resolve('ts-loader'), 36 | options: { 37 | // disable type checker - we will use it in fork plugin 38 | transpileOnly: true 39 | } 40 | } 41 | ] 42 | } 43 | ] 44 | } 45 | } 46 | ``` 47 | 48 | 然后需要配置 tsconfig.json: 49 | 50 | ```json 51 | // tsconfig.json 52 | { 53 | "compilerOptions": { 54 | // 让 ts compiler 不要编译 jsx,并将文件后缀修改为 .jsx,后续交给 babel-loader 处理 55 | "jsx": "preserve" 56 | } 57 | } 58 | ``` 59 | 60 | 接下来是配置 .scss 文件的 loader: 61 | 62 | ```js 63 | // webpack.config.js 64 | module.exports = { 65 | module: { 66 | rules: [ 67 | { 68 | test: /\.scss$/, 69 | use: [ 70 | require.resolve('style-loader'), 71 | { 72 | loader: require.resolve('css-loader'), 73 | options: { 74 | importLoaders: 1, 75 | modules: true, 76 | // 一定要与 babel-plugin-react-css-modules 的 generateScopedName 配置一致 77 | localIdentName: '[path]___[name]__[local]___[hash:base64:5]' 78 | } 79 | }, 80 | { 81 | loader: require.resolve('postcss-loader'), 82 | options: { 83 | // ... 84 | } 85 | }, 86 | { 87 | loader: require.resolve('sass-loader'), 88 | options: { 89 | } 90 | } 91 | ] 92 | } 93 | ] 94 | } 95 | } 96 | ``` 97 | 98 | 另外为了让 ts 能够识别 .scss 文件,需要添加 .scss 的声明文件: 99 | 100 | ```typescript 101 | // types/scss.d.ts 102 | declare module "*.scss" { 103 | interface IClassNames { 104 | [className: string]: string; 105 | } 106 | const classNames: IClassNames; 107 | export default classNames; 108 | } 109 | ``` 110 | 111 | 最后还剩一个问题,tsx 的标签中是不能写 styleName 属性的,因为 react 的声明文件中没有这个属性,所以需要利用 ts 的声明合并,帮助 react 声明: 112 | 113 | ```typescript 114 | // types/react.d.ts 115 | declare namespace React { 116 | interface HTMLAttributes { 117 | styleName?: string 118 | } 119 | } 120 | ``` 121 | 122 | 然后就可以愉快地搬砖啦: 123 | 124 | ```tsx 125 | import * as React from 'react'; 126 | // 只能有一个匿名导入 127 | import './base.scss'; 128 | import header from './header.scss'; 129 | 130 | // 必须要进行一次赋值,否则 ts 会认为 header 未使用,从而删除 header 的导入 131 | // @ts-ignore 132 | const h = header 133 | 134 | export default class Header extends React.Component { 135 | public render () { 136 | return ( 137 |
138 | // ./base.scss 中的 style 139 |
140 | // ./header.scss 中的 style 141 |
142 |
143 | ); 144 | } 145 | } 146 | ``` 147 | 148 | 如果编译过程中报错 babel 兼容性问题,可以使用下列命令一键修复: 149 | 150 | ```bash 151 | # https://github.com/babel/babel-upgrade/blob/master/readme.md 152 | npx babel-upgrade --write --install 153 | ``` -------------------------------------------------------------------------------- /solution/react/react.md: -------------------------------------------------------------------------------- 1 | ### react 性能优化 2 | https://www.zhihu.com/search?type=content&q=react%20%E4%BC%98%E5%8C%96 3 | 4 | - 列表的事件绑定 5 | ```jsx 6 | function Parent () { 7 | return ( 8 |
9 | { 10 | list.map((item, index) =>{ 11 | // bad 12 | // return onClick(index)} /> 13 | // 尽量保证 props 不变,因此通过子组件回传 index 14 | // 如果子组件是 dom 或者 第三方组件,可以尝试传递 data-*=index 15 | // 然后通过 event target 获取 data-* 16 | // good 17 | return 18 | }) 19 | } 20 |
21 | ) 22 | } 23 | function Child (props) { 24 | return ( 25 |
26 | ) 27 | } 28 | ``` 29 | 30 | - react hooks 状态问题 31 | 如果 react 的子组件使用了 React.memo 阻止渲染,子组件的 props 引用的父组件 function 也不会随着每次渲染更新,也就是永远都是父组件上一次的function 32 | ```jsx 33 | function Parent () { 34 | [state, setState] = useState({}); 35 | 36 | const handler = () => { 37 | console.log(state); 38 | } 39 | return ( 40 | 41 | 42 | ) 43 | } 44 | React.memo(function Child (props) { 45 | // 只要 child 不重新渲染,props.onClick 永远都是这次渲染的值 46 | // 也就是父组件中引用的 state 永远都是这次渲染的 state 47 | return ( 48 |
49 | ) 50 | }, () => { 51 | // 阻止渲染 52 | }) 53 | 54 | // 解决方式,如果需要在 handler 中 setState,可以使用 setState 的 callback 方案 55 | function Parent () { 56 | [state, setState] = useState({}); 57 | 58 | const handler = () => { 59 | // 永远都是老的 state 60 | console.log(state); 61 | setState((preState) => { 62 | // react 会将最新的 state 放到这里 63 | console.log(preState) 64 | }) 65 | } 66 | return ( 67 | 68 | 69 | ) 70 | } 71 | ``` 72 | 73 | - react 懒加载原理 74 | - react-loadable 原理:使用 import 语法,让 webpack 拆包,然后调用 import(..).then(component => {}),组件加载成功后,就将 loading 替换为真正的组件。但组件没有被用到时(比如在if else 的另一分支用到),不会执行 import(xxx),也就是不会加载。react-loadable 没有用 Suspense 75 | - suspense 原理:React.lazy 异步加载的组件会 throw 一个 Promise 出来,然后 Suspense 在 componentDidCatch 中将该 Promise 捕获,从而拿到子组件的加载状态,从而决定是渲染 loading 还是渲染子组件。如果 Suspense 有多个 lazy 子组件,每个子组件的 Promise resolve 都会重新 render,然后在 render 的时候,重新获取子组件的值,就是 resolve 后的具体组件内容了 76 | ```jsx 77 | // React.lazy 伪代码 78 | React.lazy = function (loader) { 79 | const comp = loader(); 80 | switch (comp.status) { 81 | case Pending: { 82 | // suspense 会捕获到这个 Promise 83 | throw comp; 84 | } 85 | case Resolved: { 86 | return comp; 87 | } 88 | case Rejected: { 89 | throw error; 90 | } 91 | } 92 | } 93 | ``` 94 | - React.forwardRef 95 | React.forwardRef 解决高阶组件无法访问 wrapped component 的 ref 的问题 96 | ```jsx 97 | function Hoc (WrappedComponent) { 98 | class InnerComp extends React.Component { 99 | render () { 100 | return 101 | } 102 | } 103 | return InnerComp; 104 | } 105 | // 外部引用的时候,无法访问到 SomeComponent 的 ref 106 | export default Hoc(SomeComponent); 107 | 108 | // 解决 109 | function Hoc (WrappedComponent) { 110 | class InnerComp extends React.Component { 111 | render () { 112 | return 113 | } 114 | } 115 | return React.forwardRef((props, ref) => { 116 | // 外部可以通过 forwardedRef 访问到 WrappedComponent 的 ref 117 | return ; 118 | }); 119 | } 120 | ``` 121 | - useImperativeHandle 122 | 函数组件没有 ref(其实只有 class 组件和 dom 才能绑定 ref),useImperativeHandle 可以自定义暴露给父组件的 ref 的值 123 | ```jsx 124 | function FancyInput(props, ref) { 125 | const inputRef = useRef(); 126 | // 父组件的拿到的 ref 就是 {focus: () => void} 127 | useImperativeHandle(ref, () => ({ 128 | focus: () => { 129 | inputRef.current.focus(); 130 | } 131 | })); 132 | return ; 133 | } 134 | FancyInput = forwardRef(FancyInput); 135 | ``` -------------------------------------------------------------------------------- /solution/screenshot/screenshot.md: -------------------------------------------------------------------------------- 1 | ### 使用canvas对图片、视频(截取为gif)截图 2 | 3 | - 主使用[jsgif](https://github.com/antimatter15/jsgif) 4 | - 具体可参考文章[移动端canvas实现视频截取图片并下载为gif](http://fe.sina.cn/2017/11/24/yi-dong-duan-canvasshi-xian-shi-pin-jie-qu-tu-pian-bing-xia-zai-wei-gifshi-li-diao-yan-shuo-ming/) -------------------------------------------------------------------------------- /solution/typescript/typescript.md: -------------------------------------------------------------------------------- 1 | ### 类型抽取 2 | ```typescript 3 | interface Dictionary { 4 | [key: string]: T; 5 | } 6 | 7 | type StrDict = Dictionary 8 | // 定义一个含有 Dictionary 中的 T 的类型,即 V 9 | type DictMember = T extends Dictionary ? V : never 10 | type StrDictMember = DictMember // string 11 | ``` 12 | ```typescript 13 | // 以 promise 的类型抽取为例 14 | async function stringPromise() { 15 | return 'Hello, Semlinker!'; 16 | } 17 | 18 | interface Person { 19 | name: string; 20 | age: number; 21 | } 22 | 23 | async function personPromise() { 24 | return { name: 'Semlinker', age: 30 }; 25 | } 26 | 27 | // 先定义一个含有 Promise 的辅助类型 28 | type PromiseType = (args: any[]) => Promise; 29 | type UnPromisify = T extends PromiseType ? U : never; 30 | 31 | type extractStringPromise = UnPromisify; // string 32 | type extractPersonPromise = UnPromisify; // Person 33 | ``` 34 | 35 | ### nodejs callback 被 promisify 包装后,获取其类型 36 | ```typescript 37 | type TPromiseOne = 38 | T extends ((arg1:infer T1, callback:(err:any, result:infer TResult) => void) => boolean) ? (arg:T1) => Promise 39 | : T extends ((arg1:infer T1, arg2:infer T2, callback:(err:any, result:infer TResult) => void) => boolean) ? (arg:T1, arg2:T2) => Promise 40 | : T extends ((arg1:infer T1, arg2:infer T2, arg3:infer T3, callback:(err:any, result:infer TResult) => void) => boolean) ? (arg:T1, arg2:T2, arg3:T3) => Promise 41 | : T extends ((arg1:infer T1, arg2:infer T2, arg3:infer T3, arg4:infer T4, callback:(err:any, result:infer TResult) => void) => boolean) ? (arg:T1, arg2:T2, arg3:T3, arg4:T4) => Promise 42 | : T extends ((arg1:infer T1, arg2:infer T2, arg3:infer T3, arg4:infer T4, arg5:infer T5, callback:(err:any, result:infer TResult) => void) => boolean) ? (arg:T1, arg2:T2, arg3:T3, arg4:T4, arg5:T5) => Promise 43 | : never; 44 | ``` -------------------------------------------------------------------------------- /solution/unit-test/unit-test.md: -------------------------------------------------------------------------------- 1 | 特殊的单元测试方式 2 | 3 | 下面的测试方式都依赖 (sinon)[https://sinonjs.org/releases/v7.3.0/] 4 | 下面的测试都在 mocha 环境下 5 | 6 | ### process.exit 的回调测试 7 | 8 | 如果想测试程序在 process 的 exit 事件中的行为符合预期,比如下面的代码,我们需要手动触发 exit 事件,但又不想导致测试进程退出: 9 | 10 | ```javascript 11 | const doSomeThing = () => {} 12 | process.on('exit', doSomeThing) 13 | ``` 14 | 15 | 1. 通过 process.kill 16 | 17 | ```javascript 18 | const sinon = require('sinon') 19 | 20 | describe('process exit', () => { 21 | let stubExit 22 | before(() => { 23 | // 劫持 process.exit 方法,避免进程真正退出 24 | stubExit = sinon.stub(process , 'exit') 25 | }) 26 | after(() => { 27 | // 还原 process.exit 28 | stubExit.restore() 29 | }) 30 | it('should call exit callback', () => { 31 | // 向进程发送关机信号 SIGTERM,从而会触发 process 的 exit 事件 32 | // 不能使用 SIGINT 信号,因为 mocha 监听这个信号判断进程退出 33 | process.kill(process.pid, 'SIGTERM') 34 | }) 35 | }) 36 | ``` 37 | 38 | 2. process.listeners 39 | 40 | ```javascript 41 | describe('process exit', () => { 42 | it('should call exit callback', () => { 43 | // 直接执行 exit 事件的回调函数 44 | process.listeners('exit')[0](0) 45 | }) 46 | }) 47 | ``` 48 | 49 | 通过上述 process.kill 的方式不能触发 uncaughtException 事件的回调,但可以通过这里的方式: 50 | 51 | ```javascript 52 | describe('process uncaughtException', () => { 53 | it('should call uncaughtException callback', () => { 54 | // 直接执行 uncaughtException 事件的回调函数 55 | process.listeners('uncaughtException')[0](new Error('mockError')) 56 | }) 57 | }) 58 | ``` 59 | 60 | ### 测试函数调用次数 61 | 62 | sinon 有丰富的 api,这里只是简单地示例使用: 63 | 64 | ```javascript 65 | const sinon = require('sinon') 66 | const assert = require('assert) 67 | 68 | describe('some test', () => { 69 | it('should call logger with string error level', () => { 70 | const stubLogger = sinon.stub(logger, 'info').callsFake(() => { 71 | console.log(arguments) // 'msg', { level: 'error' } 72 | }) 73 | stubLogger('msg', { level: 'error' }) 74 | // 执行了一次 75 | assert(stubLogger.callCount === 1) 76 | console.log(stubLogger.getCall(0).args) // 'msg', { level: 'error' } 77 | }) 78 | }) 79 | ``` -------------------------------------------------------------------------------- /solution/upgrade-apache/upgrade-apache.md: -------------------------------------------------------------------------------- 1 | ### mac下编译升级Apache 2 | 3 | 编译httpd需要依赖apr、apr-util 4 | 5 | 安装apr 6 | ```bash 7 | # http://apr.apache.org/download.cgi 8 | wget http://mirror.bit.edu.cn/apache//apr/apr-1.6.5.tar.gz 9 | tar -zxvf apr-1.6.5.tar.gz 10 | cd apr-1.6.5 11 | ./configure --prefix=/usr/local/apr 12 | make -j4 13 | make -j4 install 14 | ``` 15 | 16 | 安装apr-util 17 | ```bash 18 | # http://apr.apache.org/download.cgi 19 | wget http://mirror.bit.edu.cn/apache//apr/apr-util-1.6.1.tar.gz 20 | tar -zxvf apr-util-1.6.1.tar.gz 21 | cd apr-util-1.6.1 22 | ./configure --prefix=/usr/local/apr-util --with-apr=/usr/local/apr 23 | make -j4 24 | make -j4 install 25 | ``` 26 | 27 | 安装httpd 28 | ```bash 29 | # http://httpd.apache.org/download.cgi 30 | wget http://mirrors.hust.edu.cn/apache//httpd/httpd-2.4.37.tar.gz 31 | tar -zxvf httpd-2.4.37.tar.gz 32 | cd httpd-2.4.37 33 | ./configure --with-apr=/usr/local/apr --with-apr-util=/usr/local/apr-util 34 | make -j4 35 | make -j4 install 36 | ``` -------------------------------------------------------------------------------- /solution/upgrade-gcc/upgrade-gcc.md: -------------------------------------------------------------------------------- 1 | ## mac升级管理gcc 2 | 下载[port](https://www.macports.org/install.php) 3 | ```bash 4 | # 添加环境变量 5 | vi ~/.bash_profile 6 | # 然后写入 7 | export PATH=/opt/local/bin:/opt/local/sbin:$PATH 8 | # source一下 9 | source ~/.bash_profile 10 | # 查找gcc库 11 | port search gcc 12 | sudo port install gcc6 13 | port select --list gcc 14 | sudo port select --set gcc mp-gcc6 15 | # 清空bash缓存 16 | hash -r 17 | ``` 18 | ## centos升级gcc 19 | 一键升级GCC到gcc 7: 20 | ```bash 21 | sudo yum install centos-release-scl 22 | sudo yum install devtoolset-7-gcc* 23 | scl enable devtoolset-7 bash 24 | which gcc 25 | # 得到gcc路径:/opt/rh/devtoolset-7/root/usr/bin/gcc 26 | # source该路径上上上层目录中的/opt/rh/devtoolset-7/enable 27 | source /opt/rh/devtoolset-7/enable 28 | gcc --version 29 | ``` -------------------------------------------------------------------------------- /system/env-config-file.md: -------------------------------------------------------------------------------- 1 | ### profile文件 2 | - ./etc/profile:是全局profile文件,设置后会影响到所有用户 3 | - /home/username/.profile或.bash_profile是针对特定用户的,可以针对用户,来配置自己的环境变量 4 | 注意:profile是unix上才有的;bash_profile是Linux下有的(Linux下,用户目录没有.profile文件) 5 | 6 | ### profile文件加载顺序 7 | - 先执行全局的/etc/profile 8 | - 接着bash会检查使用者的HOME目录中,是否有 .bash_profile 或者 .bash_login或者 .profile,若有,则会执行其中一个,执行顺序为:.bash_profile 最优先 > .bash_login其次 > .profile 最后 9 | 10 | profile类文件是用户登录是会加载的文件 11 | 12 | ### ~/.bashrc、~/zshrc 13 | 14 | rc类文件是打开bash、zsh命令行时,会加载的文件 15 | -------------------------------------------------------------------------------- /system/mac-auto-proxy.md: -------------------------------------------------------------------------------- 1 | ## mac下命令行自动设置代理 2 | 由于shadowsSocks会覆盖系统的代理,关闭shadowsSocks后,代理不会切换回来,所以希望命令行自动切换: 3 | ```bash 4 | sudo networksetup -setautoproxyurl 网络名 代理地址 5 | # 比如 6 | sudo networksetup -setautoproxyurl Wi-Fi http://10.210.97.118/proxy.pac 7 | # 执行后,命令行会要求输入电脑密码 8 | ``` 9 | 为了不每次都输入一长串命令,简化如下: 10 | 为了在shell中输入交互式的密码,需要安装expect 11 | ```bash 12 | brew install expect 13 | ``` 14 | 然后新建```~/.set_proxy```文件,写入如下内容: 15 | ```bash 16 | #!/bin/expect 17 | set timeout 30 18 | spawn sudo networksetup -setautoproxyurl Wi-Fi http://10.210.97.118/proxy.pac 19 | expect { 20 | "*Password:" { send "密码\r" } 21 | } 22 | interact 23 | ``` 24 | 在```~/.bash_profile```文件中添加: 25 | ```bash 26 | alias proxy="expect ~/.set_proxy" 27 | ``` 28 | 然后执行: 29 | ```bash 30 | source ~/.bash_profile 31 | ``` 32 | 最后就可以在命令行执行```proxy```命令来设置代理了 33 | 另外 networksetup 还可以单独设置http、https代理,具体使用networksetup -help查看 34 | -------------------------------------------------------------------------------- /system/mac.md: -------------------------------------------------------------------------------- 1 | ### bash 和 zsh 互切 2 | 3 | bash 切为 zsh 4 | 5 | ```bash 6 | chsh -s /bin/zsh 7 | ``` 8 | 9 | zsh 切为bash 10 | 11 | ```bash 12 | chsh -s /bin/bash 13 | ``` -------------------------------------------------------------------------------- /system/windows-remote-access.md: -------------------------------------------------------------------------------- 1 | ### 局域网中用户实现远程访问 2 | - 外网访问需使用VPN或者将内网ip映射到外网ip 3 | - 右键计算机 -> 管理 -> 本地用户和组 -> 用户 下的用户可以直接访问 4 | - 对于域用户,需要在 右键计算机 -> 管理 -> 本地用户和组 -> 组 -> Remote Desk User 中添加要远程访问的域用户 -------------------------------------------------------------------------------- /weird-problem/weird-problem.md: -------------------------------------------------------------------------------- 1 | # weird problem 2 | 平时遇到的古怪问题的总结 3 | 4 | - 功能键在非Firefox浏览器不触发keypress事件的问题 5 | - 比如,按下backspace键在Firefox下会触发keydown、keypress事件,而在chrome下只会触发keydown事件 6 | - 参考[键盘事件keydown、keypress、keyup随笔整理总结](http://www.cnblogs.com/xcsn/p/3413074.html) 7 | - 可以使用DOM 3级的KeyboardEvent来模拟keypress事件,但有部分兼容性问题,参考[KeyboardEvent](https://developer.mozilla.org/zh-CN/docs/Web/API/KeyboardEvent) 8 | - 如果canvas中绘制了跨域图片,canvas.toDataURL会报错,可以用img.setAttribute('cross0rigin', 'Anonymous')解决,但前提是服务器允许跨域请求图片 9 | - 呼出键盘后,页面元素被遮挡,可以设置页面高度为document.getClientHeight来解决,https://segmentfault.com/a/1190000006243816 10 | - 在ios下,呼出键盘后,页面元素的位置会调整,但绝对定位元素中的输入框的焦点却还在原来的位置。可以在呼出键盘后,手动使绝对定位元素中的input获得一次焦点来解决(focus方法) 11 | - ios下,不会执行keyup事件回调函数中的异步请求 12 | - 安卓下用vh设置高度,呼出键盘时内容会被压缩,同样的,可以设置页面的高度为document.getClientHeight来解决 13 | - 在vue-cli的webpack模板中使用postcss,无论如何配置,display:flex的前缀都添加不对,导致在ios8下显示异常,但单独使用postcss处理工程中的css或者在postcss的网页工具中处理,却能正确添加前缀。猜测是因为vue 2.3之后会自动为display:flex这样的属性添加前缀,但根据文档,vue只是针对v-bind:style添加到元素上的样式做处理,详见多[重值](https://cn.vuejs.org/v2/guide/class-and-style.html#多重值)
14 | 这里暂时强行解决,在webpack打包完成后,再用postcss为打包后的css添加前缀。在vue-cli工程中的处理为,在build/build.js中,在webpack打包完成的回调函数中添加如下代码: 15 | ```javascript 16 | var fs = require("fs"); 17 | var postcss = require('postcss'); 18 | var autoprefixer = require('autoprefixer'); 19 | var zlib = require('zlib'); 20 | // dist中的css路径 21 | var path = './dist/static/css'; 22 | // 读取css文件夹中的文件 23 | fs.readdir(path, function (err, files) { 24 | // 第一个文件即为输出的css 25 | var result_file = path + '/' + files[0]; 26 | // 读取css 27 | fs.readFile(result_file, function (err, data) { 28 | // 添加前缀 29 | postcss([autoprefixer({ browsers: ['Last 20 versions'] })]) 30 | .process(data.toString()).then(result => { 31 | // 输出添加前缀后的css 32 | fs.writeFile(result_file, result.css); 33 | // gzip压缩css 34 | zlib.gzip(result.css, (error, gzip)=>{ 35 | fs.writeFile(result_file + '.gz', gzip, ()=>{ 36 | console.log(chalk.cyan(' autoprefix css complete.\n')) 37 | console.log(chalk.cyan(' gzip css complete.\n')) 38 | console.log(chalk.cyan(' Build complete.\n')) 39 | console.log(chalk.yellow( 40 | ' Tip: built files are meant to be served over an HTTP server.\n' + 41 | ' Opening index.html over file:// won\'t work.\n' 42 | )) 43 | }); 44 | }); 45 | }); 46 | }); 47 | }); 48 | ``` 49 | - ios下上传图片,本地预览是正确显示的,上传到服务器后,页面再把图片请求回来显示,可能因为图片orientation值的不同导致图片是倒置的。解决代码[将图片旋转到正确的角度](https://github.com/Youjingyu/Code-Collection/blob/master/image-processing/resetImgOrientation.js) 50 | - outline不能单独设置某一边,可以用css3的box-shadow模拟,也可以用下面代码hack: 51 | ```css 52 | .element:before { 53 | content: "\a0"; 54 | display: block; 55 | padding: 2px 0; 56 | line-height: 1px; 57 | border-top: 1px dashed #000; 58 | } 59 | ``` 60 | - 只有a、button、input类标签才有blur、focus时间,希望其他元素有这两个事件,需要在元素上添加tabindex(有兼容性问题),其值在0到32767之间,数字代表被tab键遍历到的顺序,0代表不会被表遍历到。 61 | - 输入框opacity为0时,ie仍然可以可见光标,设置输入框的color: transparent,无效,设置text-indent: -999em,能够达到效果。 62 | - chrome和firefox支持outline: #ccc auto 1px 的写法,但在ie和微信浏览器下无效,必须制定外边框线类型,如outline: #ccc solid 1px 63 | - for in 会遍历原型链,Object.keys()与for in的区别是不会遍历原型链,两者都不可遍历不可枚举属性;Object.getOwnPropertyNames可以获取对象自身的可枚举和不可枚举属性。 64 | - video不能播放以链接返回的视频,如https://v.qq.com/iframe/player.html?vid=p0553hnmh8g&tiny=0&auto=0,可以使用flash播放,或者嵌入iframe: 65 | ```html 66 | > 67 | ``` 68 | - 可以使用如下方式实现垂直居中: 69 | ```css 70 | .parent{ 71 | position: relative; 72 | } 73 | .child{ 74 | position: absolute; 75 | top: 0; 76 | bottom: 0; 77 | margin: auto 0; 78 | } 79 | ``` 80 | - element ui的Select组件,手动设置值时,数组类型必须和option的value的数据类型一致,否则会显示为设置的值而不是值对应的label 81 | - 使用display: table; display: table-cell;vertical-align: bottom;可以实现某个子元素高度增加,但所有子元素仍然基线对齐。 82 | - 使用rem和border-radius: 50%实现圆形,在小分辨率下会变成椭圆。可以在小分辨率下使用固定像素解决。 83 | - 移动端webview很多都有导航栏(如微信和qq),设计整屏页面时,需要扣除导航高度(如qq内置浏览器,顶部占用150px,底部占用260px,750\*1334的设计稿最终为750*1074);一种设计是,页面主体内容不要太高,剩余部分用纯色填充,或者用绝对定位元素填充,实现自适应。 84 | - 二维码要用img引入,背景图长按不能识别为二维码。 85 | - qq中设置分享链接预览信息,参考文档[手机QQ接口文档:setShareInfo](http://open.mobile.qq.com/api/mqq/index#api:setShareInfo): 86 | ```html 87 | QQ中链接的标题由此处获取 88 | 89 | 90 | 91 | ``` 92 | 如果预览中没有正确显示预览图片,尝试将页面链接从somedomain/ 或者 somedomain/index,修改为 somedomain/index.html。 93 | - 微信中需要保证视区内只有一个二维码,否则只能识别出第一个二维码。 94 | - 微信中,使用meta缩放页面后,不能识别二维码: 95 | ```html 96 | 97 | 98 | < img style="right:0; top:0; height: auto;width: 100%;opacity: 0;position: absolute;" src="二维码图片地址"> 99 | 100 | < img src="二维码图片地址" title="qrcode" alt="qrcode"> 101 | 102 | ``` 103 | - 使用meta标签缩放页面 104 | ```javascript 105 | 106 | 107 | 122 | ``` 123 | - vue+vux的项目中,如果使用了未定义的事件方法会报错 124 | ```TypeError: Cannot read property '_withTask' of undefined``` 125 | 让人非常迷茫,代码如下: 126 | ```vue 127 | 132 | 143 | ``` 144 | - es6中,如果用如下方式使用class,可能会报错```Class constructor Config$1 cannot be invoked without 'new'``` 145 | ```javascript 146 | import superReport from './superReport' 147 | let Report = ( supperclass ) => class extends supperclass { 148 | constructor( options ) { 149 | super(options) 150 | } 151 | } 152 | new (Report(superReport))(); 153 | ``` 154 | 如果superReport来自node_modules,会报错,否则不会报错。 155 | 这是由es6 class的运行机制导致,可以修改babel的配置为```"presets": [ "es2015-node5" ]```解决 156 | 参考https://github.com/babel/babel/issues/4269 157 | - vmware安装centos,选择配置系统iso后,开机依然system not found:在设置 -> CD/DVD -> 开启启动时连接。 158 | - vmware安装centos过程中,f12为确定并到下一屏,相当于next 159 | - 为了在虚拟机与主机间复制粘贴,需要安装vmware tools,安装方法http://blog.csdn.net/programmer_sir/article/details/46626409 160 | - 虚拟机中centos不能联网 161 | ```bash 162 | vi /etc/sysconfig/network-scripts/ifcfg-eth0 163 | ONBOOT=yes // 开机启动网卡 164 | MM_CONTROLLED=no 165 | ``` 166 | - centos安装最新版git 167 | ```bash 168 | yum install curl-devel expat-devel gettext-devel openssl-devel zlib-devel // git依赖库 169 | yum install gcc perl-ExtUtils-MakeMaker // 编译gcc 170 | 171 | yum remove git 172 | 173 | cd /usr/local/webserver 174 | wget https://www.kernel.org/pub/software/scm/git/git-2.7.2.tar.gz 175 | tar xzf git-2.7.2.tar.gz 176 | 177 | cd git-2.7.2 178 | make prefix=/usr/local/git all 179 | make prefix=/usr/local/git install 180 | // 添加环境变量 181 | echo "export PATH=$PATH:/usr/local/git/bin" >> /etc/profile 182 | source /etc/profile 183 | ``` 184 | - centos编译安装node-rdkafka报错 185 | ```bash 186 | unrecognized command line option -std=c++11 187 | # 升级gcc即可:https://www.quyu.net/info/628.html 188 | # 升级过程中继续报错 189 | // make[1]: *** [all-stage1-gcc] Error 2 190 | // make[1]: *** [stage1-bubble] Error 2 191 | # 首先保证有10G硬盘、1G内存、1G Swap分区,swap分许可以开启临时的http://smilejay.com/2012/09/new-or-add-swap/ 192 | # 如果需要swap分区持久生效,需要配置swap分区的开机启动/dev/sdb2 swap swap defaults 0 0 193 | # 保证上述要求仍然报相同的错,可以尝试安装yum install gcc-c++ 、libgcc.i686等 194 | # 如果报错 gmp.h can't be found, or is unusable 195 | yum install gmp-devel 196 | # mpc.h: No such file or directory 197 | yum install libmpc-dev 198 | # 升级成功后,可能依然会报错unrecognized command line option -std=c++11 199 | # 可能是g++没有升级或者编译时调用的是低版本的g++ 200 | # 我这里是/usr/bin/g++ -v 版本为4.4.7,而/usr/local/bin/g++ -v 版本为6.3.0 201 | # 显然是编译时调用了/usr/bin/g++ 202 | # 将老的g++版本移动到/usr/bin/g++4.4.7 203 | mv /usr/bin/g++ /usr/bin/g++4.4.7 204 | # 建立新版本g++在/usr/bin目录下的软链 205 | ln -s /usr/local/bin/g++ /usr/bin/g++ 206 | # 同时gcc、cc, c++可能也有这个情况,解决方法相同 207 | # 参考http://blog.csdn.net/u012973744/article/details/36197937 208 | ``` 209 | 调用node-rdkafka时继续报错 210 | ```bash 211 | libstdc++.so.6 version glibcxx_3.4.21' not found 212 | // 因为升级gcc时,生成的动态库没有替换老版本gcc的动态库。 213 | // 重建默认库的软连接即可:https://itbilu.com/linux/management/NymXRUieg.html 214 | // 注意修改libstdc++.so.6.0.21版本,该文章中的是libstdc++.so.6.0.21,所有用到的地方都要修改为find / -name "libstdc++.so*"命令输出的版本 215 | ``` 216 | - 针对上面的centos node-kafka报错,最简单的解决方式是一键升级GCC到gcc 7: 217 | ```bash 218 | sudo yum install centos-release-scl 219 | sudo yum install devtoolset-7-gcc* 220 | scl enable devtoolset-7 bash 221 | which gcc 222 | # 得到gcc路径:/opt/rh/devtoolset-7/root/usr/bin/gcc 223 | # source该路径上上上层目录中的/opt/rh/devtoolset-7/enable 224 | source /opt/rh/devtoolset-7/enable 225 | gcc --version 226 | ``` 227 | - mac编译安装node-kafka时报错 228 | ```bash 229 | ld: symbol(s) not found for architecture x86_64 230 | clang: error: linker command failed with exit code 1 (use -v to see invocation) 231 | ``` 232 | 安装时添加命令行参数解决 233 | ```bash 234 | CPPFLAGS=-I/usr/local/opt/openssl/include LDFLAGS=-L/usr/local/opt/openssl/lib npm install 235 | # 参考 https://github.com/Blizzard/node-rdkafka/issues/373 236 | ``` 237 | - nodejs使用exec执行系统命令,参数中的双引号会被去掉 238 | ```javascript 239 | exec('elasticdump data={"test": "www"}'); 240 | ``` 241 | 在命令行中执行的实际为 242 | ```bash 243 | elasticdump data={test: www} 244 | ``` 245 | 解决方式 246 | ```javascript 247 | // windows 248 | exec('elasticdump data={"test": "www"}'.replace(/"/g, '\\"')); 249 | // linux 250 | exec('elasticdump data=\'{"test": "www"}\''); 251 | 252 | // 如果在某环境中依然报错,使用shelljs执行shelljs.exec 253 | ``` 254 | - 页面onload事件触发时机较晚,甚至不触发 255 | 对于有延迟加载资源的页面、或者页面内容较多(如大量图片)、或者页面是长列表型,导致浏览器一直在加载资源,从而阻塞onload事件;如果,用户在页面加载过程中,将浏览器后台运行,这时浏览器可能会挂起,也不会触发onload事件。从而,放到onload回调里的数据收集函数不被触发。而且,经观察,onload在新浪网这种图片多、列表长的页面来说,onload不触发的概率比较大。 256 | 可以使用settimeout兜底来解决,比如3s后直接执行onload回调函数,不再等待onload。 257 | - npm adduser,用户名、密码正确,仍旧提示错误。可能是使用了非官方npm源(如淘宝的源),需要切换回官方源: 258 | ```bash 259 | // 查看当前源 260 | npm config ls 261 | // 切换回官方源 262 | npm config set registry https://registry.npmjs.org/ 263 | ``` 264 | - git clone报错 265 | ```bash 266 | fatal: unable to access 'https://github.com/houshanren/hangzhou_house_knowledge.git/': SSL connect error 267 | ``` 268 | 升级ssl版本解决 269 | ```bash 270 | yum update -y nss curl libcurl 271 | ``` 272 | - 用fiddler抓包,手机证书无法安装 273 | IOS:设置 —> 通用 —> 关于本机 —> 受信任证书存储区,找到需要安装的证书,安装即可。 274 | ANDROID:设置 —> 安全 —> 从手机存储安装,找到需要安装的证书,安装即可。 275 | - cnpm link只能link用cnpm install的工程,如果工程师yarn安装的,用cnpm link 报错: 276 | ```bash 277 | npm WARN checkPermissions Missing write access to 278 | ``` 279 | - cnpm link的时候回安装工程,不要先安装再link,安装后会导致link文件过多,报错Maximum call stack size exceeded 280 | - windows下修改问价权限 281 | http://blog.csdn.net/taochangchang/article/details/12856041 282 | - 使用service start kibana,报错chown: invalid user: `kibana:kibana' 283 | ```bash 284 | adduser kibana 285 | chown -R kibana:kibana /opt/kibana/optimize 286 | service kibana start 287 | ``` 288 | - gitignore修改后不生效 289 | ```bash 290 | git rm --cached filename 291 | # 或者 292 | git rm -r --cached directory 293 | ``` 294 | 删除后commit删除操作就行了 295 | - elasticsearch aggregation查询报错: 296 | Scripts of type [inline], operation [aggs] and lang [groovy] are disabled 297 | 因为没有在/etc/elasticsearch/elasticsearch.yml中配置 298 | script.inline: true 299 | script.indexed: true 300 | 如果配置了还报错,可能是节点集群中的子机器没有配置 301 | 配置子节点后,重启,需要注意,子节点重启后母节点也要重启才能连接到子节点 302 | - webpack编译软连接的文件报错:找不到模块 303 | ```javascript 304 | resolve: { 305 | symlinks: false 306 | } 307 | ``` 308 | ln -s执行软连接时,需要在源文件的目录执行该命令,不然在目标目录cd找不到文件夹。如果仍报错:Too many levels of symbolic links,源文件和目标文件都需要使用绝对路径。 309 | - vscode会优先使用本地安装的eslint,如果使用的本地eslint,eslint只会在本地node_modules里查找eslint-plugin,全局安装的eslint-plugin不起作用 310 | - 在linux下按照下面方式安装nodejs,安装的确是0.10版本,: 311 | ```bash 312 | curl --silent --location https://rpm.nodesource.com/setup_8.x | sudo bash - 313 | sudo yum -y install nodejs 314 | ``` 315 | 怀疑是由于网路问题,curl并没有下载成功,从而可以转为而二进制文件安装: 316 | 到https://nodejs.org/zh-cn/download/ 选择linux下的二进制文件下载链接,比如我选择的linux-x64: 317 | ```bash 318 | wget https://nodejs.org/dist/v8.11.2/node-v8.11.2-linux-x64.tar.xz 319 | ``` 320 | 解压: 321 | ```bash 322 | sudo mkdir /usr/local/lib/nodejs 323 | sudo tar -xJvf node-$VERSION-$DISTRO.tar.xz -C /usr/local/lib/nodejs 324 | sudo mv /usr/local/lib/nodejs/node-$VERSION-$DISTRO /usr/local/lib/nodejs/node-$VERSION 325 | ``` 326 | 配置环境变量```vi /etc/profile```: 327 | ```bash 328 | # Nodejs 329 | export NODEJS_HOME=/usr/local/lib/nodejs/node-$VERSION/bin 330 | export PATH=$NODEJS_HOME:$PATH 331 | ``` 332 | 刷新profile:```source /etc/profile``` 333 | 验证是否安装成功: 334 | ```bash 335 | node -v 336 | npm -v 337 | ``` 338 | - 小程序中文本标签中换行,会一并渲染到视图中,如下面的写法,会渲染出两个换行: 339 | ```html 340 | 341 | 测试文本 342 | 343 | ``` 344 | - JSON.stringify的问题 345 | ```JavaScript 346 | // js对象序列化存入数据库,再请求回来可能parse失败 347 | // 比如 348 | JSON.stringify({'id("_test")': 123}) // 将结果'{"id(\"_test\")":123}'存入数据库 349 | // parse失败 350 | JSON.parse('{"id(\"_test\")":123}') // error 351 | ``` 352 | 猜测原因是JSON编码与js编码并不完全一致 353 | 一种解决方式是,在stringify之前,将js对象key中的双引号替换为单引号:```{'id("_test")': 123}```→```{"id('_test')": 123}``` 354 | 另种是在parse之前替换双引号为单引号: 355 | ```JavaScript 356 | jsonString = jsonString.replace(/({|,)("(.*?)"\:)/ig,function($1,$2,$3,$4){return $2 + '"' + $4.replace(/"/g,"'") + '":'}) 357 | ``` 358 | - 在ajax、onload等回调中,document.execCommand('copy')方法不会生效。该方法只对受信任的用户操作有效,比如click、mouseup等操作。参考[stackoverflow](https://stackoverflow.com/questions/31925944/execcommandcopy-does-not-work-in-ajax-xhr-callback)、[allowed-to-show-a-popup](https://www.w3.org/TR/html5/browsers.html#allowed-to-show-a-popup) 359 | - 腾讯云主机,nginx配置了https后,https依然不能访问,实际没有监听443端口。貌似是,腾讯云主机貌似禁止了,以自建的ssl证书启动https服务器。从腾讯云申请免费的证书后,启动成功。 360 | - 微信小程序 361 | - 如果使用ts开发小程序,会有一些古怪问题。比如在页面json中出现```useComponents```字段,会导致页面的生命周期函数不执行 362 | - 使用自定义组件递归实现树结构时,会报错VM13421:2 Error: Expect FLOW_MINIPULATE_CHILD but get another,但在手机上预览却能正常显示 363 | - 由于小程序没有computed,父组件传递的数据,需要在子组件进一步处理时,需要借助Object.defineProperty定义setter监听父组件数据的变化,但是不能监听数据类型的数据,否则js代码会中断执行,并且没有报错提示。一种间接的处理方式是,新建一个整型数据,跟随数组一起变化,监听该数据间接监听数据变化。 364 | - 递归组件触发事件,只能一级一级往外传,并且要在该递归组件中监听该事件,才能往外传 365 | - 小程序长文本用view组件在ios上会卡顿,需要用scroll-view 366 | - service elasticsearch start报错: 367 | # Exception in thread "main" ElasticsearchException[Failed to load logging configuration] 368 | 多半是权限问题,解决: 369 | ```bash 370 | sudo chown -R elasticsearch:elasticsearch /var/lib/elasticsearch 371 | sudo chown -R elasticsearch:elasticsearch /var/run/elasticsearch 372 | sudo chown -R elasticsearch:elasticsearch /etc/elasticsearch 373 | sudo chown -R elasticsearch:elasticsearch /var/log/elasticsearch 374 | ``` 375 | - swiper和mint-ui的infinite scroll插件一起视使用,swiper的slide宽度非常大的问题: 376 | 在swiper上添加width: 100%解决,貌似如果不加,会导致宽度计算错误 377 | - swiper和mint-ui的infinite scroll插件一起视使用,无限刷新事件不触发问题: 378 | 在swiper的slide上添加overflow-y: auto,不要在infinite scroll的容器上加overflow-y: auto 379 | - 解决canvas getImageData 跨域问题时,如果为img加上crossOrigin属性的时机,在添加src属性的时机之后,在第二次访问页面时,依然会报跨域错误。猜测是,由于src属性比crossOrigin属性先添加,第二次访问时,有缓存,src生效比较快,从而导致crossOrigin还来不及生效。因此src属性必须在crossOrigin属性之后添加。 380 | - ios下webview的滚动容器滚动不跟手的问题,添加css属性:-webkit-overflow-scrolling: touch; 381 | - pm2管理的nodjs服务启动后,一段时间后就会重启,问题可能是配置了进程的最大可用内存: 382 | ```javascript 383 | // pm.config.json 384 | { 385 | "name" : "max_mem", 386 | "script" : "big-array.js", 387 | // 将该值修改大一点 388 | "max_memory_restart" : "20M" 389 | } 390 | ``` 391 | - linux root用户全局安装需要gyp编译的包,报错:gyp WARN EACCES user "root" does not have permission to access the dev dir,安装时添加--unsafe-perm参数解决: 392 | ```bash 393 | sudo npm i -g --unsafe-perm appmetrics 394 | ``` 395 | - ios下使用swiper导致系统级别的返回不能触发,配置下列选项解决: 396 | ```javascript 397 | swiperOption: { 398 | iOSEdgeSwipeDetection: true, // 设置为true开启IOS的UIWebView环境下的边缘探测。如果拖动是从屏幕边缘开始则不触发swiper 399 | iOSEdgeSwipeThreshold: 50 // IOS的UIWebView环境下的边缘探测距离。如果拖动小于边缘探测距离则不触发swiper 400 | } 401 | ``` 402 | - 使用node-rdkafka的new Kafka.KafkaConsumer消费数据时,js线程出错退出,但c++线程没有监听js的错误事件,不会退出,导致整个进程不会退出,从而pm2不会重启,看似pm2是online的,但其实js线程已经死了。解决方式是使用流来消费,即使用Kafka.KafkaConsumer.createReadStream,或者在js报错后,手动调用disconnect方法: 403 | ```javascript 404 | const comsumer = new kafka.KafkaConsumer({}) 405 | process.on('uncaughtException', () => { 406 | comsumer.disconnect() 407 | }) 408 | comsumer.connect() 409 | ``` 410 | - gitlab 401 unauthorized while accessing:升级 git 解决 411 | - cnpm login 时报错:cnpm 409 /user/org.couchdb.user: ... conflict,很可能是密码写错了。 --------------------------------------------------------------------------------