├── 2016 ├── 06 │ └── 25.md └── 09 │ └── 11.md ├── 2017 ├── 11 │ └── 18.md └── 06 │ ├── 05.md │ └── 11.md ├── 2018 ├── 02 │ └── 20.md └── 06 │ └── 04.md ├── 2019 ├── 11 │ ├── 04.md │ └── image │ │ ├── cmd-01.gif │ │ ├── cmd-02.gif │ │ ├── cmd-03.gif │ │ ├── cmd-04.gif │ │ ├── cmd-05.gif │ │ ├── cmd-06.gif │ │ └── cmd-07.gif ├── 06 │ ├── 09.md │ ├── 10.md │ ├── 24.md │ └── image │ │ ├── mp-01.jpg │ │ ├── mp-02.jpg │ │ ├── mp-03.png │ │ └── mp-04.jpg ├── 07 │ ├── 28.md │ └── image │ │ ├── pictool-01.gif │ │ ├── pictool-02.png │ │ ├── pictool-03.jpg │ │ ├── pictool-04.gif │ │ ├── pictool-05.gif │ │ ├── pictool-06.gif │ │ ├── pictool-07.gif │ │ ├── pictool-08.jpg │ │ ├── pictool-09.jpg │ │ ├── pictool-10.jpg │ │ └── pictool-11.jpg └── 08 │ ├── 26.md │ └── image │ ├── wasmscript-01.jpg │ ├── wasmscript-02.jpg │ └── wasmscript-03.jpg ├── 2020 ├── 01 │ ├── 07.md │ └── image │ │ ├── pictool-01.gif │ │ ├── pictool-02.gif │ │ └── pictool-03.gif └── 03 │ ├── 09.md │ └── image │ ├── vscode-debug-rust-01.jpg │ ├── vscode-debug-rust-02.jpg │ ├── vscode-debug-rust-03.jpg │ └── vscode-debug-rust-04.jpg ├── 2021 ├── 09 │ └── 09.md └── images │ ├── .DS_Store │ ├── element-circle.gif │ ├── element-html.gif │ ├── element-image.gif │ ├── element-rect.gif │ ├── element-svg.gif │ ├── element-text.gif │ ├── snapshot-001.png │ ├── snapshot-002.png │ ├── snapshot-003.png │ ├── snapshot-004.png │ └── snapshot-005.png ├── 2024 └── 01 │ ├── 01.md │ └── images │ ├── idraw-playground.png │ ├── idraw-studio-002.png │ ├── idraw-studio.png │ ├── idrawjs-banner-zh.png │ ├── idrawjs-banner.png │ ├── idrawjs-doc.png │ ├── idrawjs-features.png │ ├── issue.jpg │ └── sea.jpg ├── .gitignore ├── README.md ├── SUMMARY.md └── assets ├── blog-avatar.jpg └── qrcode.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /2016/06/25.md: -------------------------------------------------------------------------------- 1 | # ubuntu 环境下开发配置 2 | ## 安装git 3 | 4 | ``` sh 5 | sudo apt-get install git 6 | ``` 7 | ### 初始化SSH key 8 | 9 | ``` sh 10 | ssh-keygen -t rsa -b 4096 -C "your_email@example.com" 11 | ``` 12 | ### 初始化git配置 13 | 14 | ``` sh 15 | git config --global user.name "xxx" 16 | git config --global user.email "xxx" 17 | ``` 18 | ## sublime中文编辑支持配置 19 | 20 | ``` sh 21 | git clone https://github.com/lyfeyaj/sublime-text-imfix.git 22 | 23 | cd ~/sublime-text-imfix 24 | 25 | sudo cp ./lib/libsublime-imfix.so /opt/sublime_text/ 26 | 27 | sudo cp ./src/subl /usr/bin/ 28 | 29 | # 启动方法1 30 | LD_PRELOAD=./libsublime-imfix.so subl 31 | 32 | # 启动方法2 33 | ## 修改/usr/share/applications/sublime_text.desktop文件 34 | ### 将Exec=/opt/sublime_text/sublime_text %F修改为 35 | Exec=bash -c 'LD_PRELOAD=/usr/lib/libsublime-imfix.so /opt/sublime_text/sublime_text' %F 36 | 37 | ### 将Exec=/opt/sublime_text/sublime_text -n修改为 38 | Exec=bash -c 'LD_PRELOAD=/usr/lib/libsublime-imfix.so /opt/sublime_text/sublime_text' -n 39 | 40 | ``` 41 | ## 安装atom编辑器 42 | 43 | ``` sh 44 | sudo apt-get install atom 45 | 46 | ``` 47 | ## node.js开发环境设置 48 | - 去官网下载node.js linux下的tar安装包 (下载编译好的安装包) [https://nodejs.org/en/](https://nodejs.org/en/) 49 | - 安装node.js环境 50 | 51 | ``` shell 52 | 53 | //解压安xz装包成tar 54 | xz -d node-v6.3.1-linux-x64.tar.xz 55 | 56 | //解压tar包 57 | tar -zxvf node-v6.3.1-linux-x64.tar 58 | 59 | //将解压后的文件重命名为nodejs,再复制到 /opt/目录下 60 | 61 | sudo cp -a nodejs /opt/ 62 | 63 | 64 | //配置全局环境变量 65 | sudo gedit /etc/profile 66 | 67 | //配置个人账户环境变量 68 | gedit ~/.bashrc 69 | 70 | 71 | //在文件的最末尾加上环境变量配置 72 | export NODE_HOME=/opt/nodejs/bin 73 | export NODE_PATH=/opt/nodejs/lib/node_modules 74 | export PATH=$NODE_HOME:$PATH 75 | 76 | 77 | //保存配置全局变量 78 | sudo source /etc/profile 79 | 80 | //保存个人账户环境变量 81 | gedit ~/.bashrc 82 | 83 | //配置国内镜像源 84 | npm config set registry https://registry.npm.taobao.org 85 | 86 | ``` 87 | ## 安装chrome浏览器 88 | 89 | ``` sh 90 | #!/bin/sh 91 | 92 | sudo wget https://repo.fdzh.org/chrome/google-chrome.list -P /etc/apt/sources.list.d/ 93 | 94 | wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add - 95 | 96 | sudo apt-get update 97 | 98 | sudo apt-get install google-chrome-stable 99 | 100 | /usr/bin/google-chrome-stable 101 | ``` 102 | -------------------------------------------------------------------------------- /2016/09/11.md: -------------------------------------------------------------------------------- 1 | # ubuntu下apache2+php+mysql环境配置 2 | 3 | ## 安装apache2 4 | 5 | ``` sh 6 | # 安装 7 | sudo apt-get install apache2 8 | 9 | # 执行文件地址 10 | /etc/init.d/apache2 11 | 12 | # 配置文件目录 13 | /etc/apache2 14 | 15 | # 启动apache2 16 | sudo apache2ctl -k start 17 | 18 | # 停止apache2 19 | sudo apache2ctl -k stop 20 | 21 | # 重新启动apache2 22 | sudo apache2ctl -k restart 23 | 24 | # 如果重启遇到一下问题 25 | # AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 127.0.1.1. Set the 'ServerName' directive globally to suppress this message 26 | #需要在apache2.conf 文件中加上 ServerName 127.0.0.1 27 | 28 | ``` 29 | ## 安装php 30 | 31 | ``` sh 32 | # 目前会自动安装到php7版本 33 | 34 | sudo apt install php 35 | 36 | sudo apt-get install libapache2-mod-php 37 | 38 | ``` 39 | ## 安装mysql 40 | 41 | ``` sh 42 | # 目前会安装到mysql5.7版本 43 | 44 | sudo apt-get update 45 | 46 | sudo apt-get upgrade 47 | 48 | sudo apt-get install mysql-server mysql-client 49 | 50 | ``` 51 | ## 彻底卸载以上的安装 52 | 53 | ``` sh 54 | # 彻底卸载apache2 55 | sudo apt-get --purge remove apache-common 56 | sudo apt-get --purge remove apache 57 | sudo find /etc -name "*apache*" |xargs rm -rf 58 | sudo rm -rf /var/www 59 | sudo rm -rf /etc/libapache2-mod-jk 60 | sudo rm -rf /etc/init.d/apache2 61 | sudo rm -rf /etc/apache2 62 | dpkg -l |grep apache2|awk '{print $2}'|xargs dpkg -P 63 | 64 | 65 | # 彻底卸载php 66 | sudo apt-get –purge remove libapache2-mod-php php php-gd php-mysql 67 | sudo apt-get autoremove php 68 | sudo find /etc -name "*php*" |xargs rm -rf 69 | dpkg -l |grep ^rc|awk '{print $2}' |sudo xargs dpkg -P 70 | 71 | 72 | # 彻底卸载mysql 73 | sudo apt-get autoremove --purge mysql-server 74 | sudo apt-get remove mysql-server 75 | sudo apt-get autoremove mysql-server 76 | sudo apt-get remove mysql-common 77 | dpkg -l |grep ^rc|awk '{print $2}' |sudo xargs dpkg -P 78 | sudo find /etc -name "*mysql*" |xargs rm -rf 79 | 80 | 81 | # 检查是否完整删除 82 | dpkg -l | grep apache 83 | dpkg -l | grep apache2 84 | dpkg -l | grep php 85 | dpkg -l | grep mysql 86 | 87 | 88 | ``` 89 | -------------------------------------------------------------------------------- /2017/06/05.md: -------------------------------------------------------------------------------- 1 | # 几种js的URL参数操作 2 | 3 | ## URL信息 4 | 5 | 获取 http://www.example.com/page/index.html?name=hello&key=world#view-show-name 6 | 7 | ```js 8 | // 获取URL信息 9 | location 10 | 11 | /* 12 | { 13 | "href": "http://www.example.com/page/index.html?name=hello&key=world#view-show-name", 14 | "ancestorOrigins": {}, 15 | "origin": "http://www.example.com", 16 | "protocol": "http:", 17 | "host": "www.example.com", 18 | "hostname": "www.example.com", 19 | "port": "", 20 | "pathname": "/page/index.html", 21 | "search": "?name=hello&key=world", 22 | "hash": "#view-show-name" 23 | } 24 | */ 25 | ``` 26 | 27 | ## 获取URL信息 28 | 29 | ### 获取URL参数 30 | 31 | #### 一般方法 32 | 33 | 兼容大部分浏览器 34 | 35 | ```js 36 | function getURLParam ( name ) { 37 | if (typeof name === 'undefined') { 38 | return null; 39 | } 40 | 41 | let paramReg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i"); 42 | let value = window.location.search.substr(1).match(paramReg); 43 | if (value != null) { 44 | return unescape(value[2]); 45 | } 46 | return false; 47 | } 48 | 49 | getURLParam('name') 50 | // 返回 'hello' 51 | 52 | getURLParam('key') 53 | // 返回 'world' 54 | 55 | ``` 56 | 57 | #### 高阶方法 58 | 59 | 只兼容新版浏览器 60 | ![js-urlsearchparam-caniuse"](https://cloud.githubusercontent.com/assets/8216630/26758623/61d2d27e-4916-11e7-9bba-0d33d8aedfbb.png) 61 | 62 | 63 | ```js 64 | let params = new URLSearchParams(location.search); 65 | 66 | params.has('name'); 67 | // 返回 true 68 | 69 | params.get('name'); 70 | // 返回 'hello' 71 | 72 | params.getAll("name"); 73 | // 返回 ['hello'] 74 | 75 | ``` 76 | -------------------------------------------------------------------------------- /2017/06/11.md: -------------------------------------------------------------------------------- 1 | # JS简单的加密解密方法 2 | 3 | ## 前言 4 | 前段时间看了阮一峰老师的 [《XOR 加密简介》](http://www.ruanyifeng.com/blog/2017/05/xor.html), 突发奇想写了一个基于XOR(异或) 加密解密的JavaScript程序 5 | 6 | ## 具体步骤 7 | 8 | ### 加密 9 | ``` 10 | +-------------------------------------+ 11 | | 待 加 密 字 符 串 | 12 | +-------------+----+------------------+ 13 | | | 14 | +-----------------------v----v----------------------------+ 15 | | | 16 | | 将 字 符 串 每 个 字 符 转 成 ascll 码 | 17 | | | 18 | +-----------------------+----+----------------------------+ 19 | | | 20 | +-----------------------v----v----------------------------+ 21 | | | 22 | | 将 ascll 码 进 行 xor ( 异 或 ) 加 密 | 23 | | | 24 | +-----------------------+----+----------------------------+ 25 | | | 26 | +-----------------------v----v----------------------------+ 27 | | | 28 | | 将 加 密 的 ascll 码 转 成 自 定 义 进 制 的 字 符 | 29 | | | 30 | +-----------------------+----+----------------------------+ 31 | | | 32 | +-------------v----v------------------+ 33 | | 加 密 后 的 字 符 串 | 34 | +-------------------------------------+ 35 | 36 | ``` 37 | 38 | ### 解密 39 | 40 | ``` 41 | +-------------------------------------+ 42 | | 待 解 密 字 符 串 | 43 | +-------------+----+------------------+ 44 | | | 45 | +-----------------------v----v----------------------------+ 46 | | | 47 | | 将 字 符 串 每 个 字 符 转 成 ascll 码 | 48 | | | 49 | +-----------------------+----+----------------------------+ 50 | | | 51 | +-----------------------v----v----------------------------+ 52 | | | 53 | | 将 ascll 码 进 行 xor ( 异 或 ) | 54 | | | 55 | +-----------------------+----+----------------------------+ 56 | | | 57 | +-----------------------v----v----------------------------+ 58 | | | 59 | | 将 解 密 的 ascll 码 转 成 对 应 进 制 的 字 符 | 60 | | | 61 | +-----------------------+----+----------------------------+ 62 | | | 63 | +-------------v----v------------------+ 64 | | 解 密 后 的 字 符 串 | 65 | +-------------------------------------+ 66 | 67 | Close 68 | 69 | ``` 70 | 71 | ## 字符串加密 72 | 73 | ```js 74 | /** 75 | * encrypto 加密程序 76 | * @param {Strng} str 待加密字符串 77 | * @param {Number} xor 异或值 78 | * @param {Number} hex 加密后的进制数 79 | * @return {Strng} 加密后的字符串 80 | */ 81 | function encrypto( str, xor, hex ) { 82 | if ( typeof str !== 'string' || typeof xor !== 'number' || typeof hex !== 'number') { 83 | return; 84 | } 85 | 86 | let resultList = []; 87 | hex = hex <= 25 ? hex : hex % 25; 88 | 89 | for ( let i=0; i 由于历史原因,在ES6的Modules还没确定之前,JavaScript的模块化处理方案都是八仙过海,各显神通,例如前端的AMD、CMD模块方案,Node的CommonJS方案也在这个“乱世”诞生。 86 | > 当到了ES6规范确定后,Node的CommonJS方案已经是JavaScript中比较成熟的模块化方案,但ES6怎么说都是正统的规范,“法理”上是需要兼容的,所以`*.mjs`这个针对`ECMAScript Modules`规范的Node文件方案在一片讨论声中应运而生。 87 | 88 | > 当然如果`import/export`只能对`*.mjs`文件起作用,意味着Node原生模块和npm所有第三方模块都不能。所以这时候Node 9就提供了 `Loader Hooks`,开发者可自定义配置`Resolve Hook`规则去利用`import/export`加载使用Node原生模块,`*.js`文件,npm模块,C/C++的Node编译模块等Node生态圈的模块。 89 | 90 | ### Loader Hooks 使用步骤 91 | - 自定义loader规则 92 | - 启动的flag要加载loader规则文件 93 | - 例如:`node --experimental-modules --loader ./custom-loader.mjs ./index.js` 94 | 95 | 如果觉得以下文字太长,可以先去玩玩对应的demo3 [https://github.com/chenshenhai/node-modules-demo/tree/master/demo3](https://github.com/chenshenhai/node-modules-demo/tree/master/demo3) 96 | 97 | ### 自定义规则快速上手 98 | 99 | - 文件目录 100 | ``` 101 | ├── demo3 102 | │   ├── es 103 | │   │   ├── custom-loader.mjs 104 | │   │   ├── index.js 105 | │   │   ├── mod-1.js 106 | │   │   └── mod-2.js 107 | │   └── package.json 108 | ``` 109 | 110 | 111 | - 加载自定义loader,执行`import/export`的`*.js`文件 112 | 113 | ```sh 114 | node --experimental-modules --loader ./es/custom-loader.mjs ./es/index.js 115 | ``` 116 | 117 | 118 | 119 | ### 自定义loader规则解析 120 | 以下是Node 9.2官方文档提供的一个自定义loader文件 121 | ```js 122 | import url from 'url'; 123 | import path from 'path'; 124 | import process from 'process'; 125 | 126 | // 获取所有Node原生模块名称 127 | const builtins = new Set( 128 | Object.keys(process.binding('natives')).filter((str) => 129 | /^(?!(?:internal|node|v8)\/)/.test(str)) 130 | ); 131 | 132 | // 配置import/export兼容的文件后缀名 133 | const JS_EXTENSIONS = new Set(['.js', '.mjs']); 134 | 135 | // flag执行的resolve规则 136 | export function resolve(specifier, parentModuleURL /*, defaultResolve */) { 137 | 138 | // 判断是否为Node原生模块 139 | if (builtins.has(specifier)) { 140 | return { 141 | url: specifier, 142 | format: 'builtin' 143 | }; 144 | } 145 | 146 | // 判断是否为*.js, *.mjs文件 147 | // 如果不是则,抛出错误 148 | if (/^\.{0,2}[/]/.test(specifier) !== true && !specifier.startsWith('file:')) { 149 | // For node_modules support: 150 | // return defaultResolve(specifier, parentModuleURL); 151 | throw new Error( 152 | `imports must begin with '/', './', or '../'; '${specifier}' does not`); 153 | } 154 | const resolved = new url.URL(specifier, parentModuleURL); 155 | const ext = path.extname(resolved.pathname); 156 | if (!JS_EXTENSIONS.has(ext)) { 157 | throw new Error( 158 | `Cannot load file with non-JavaScript file extension ${ext}.`); 159 | } 160 | 161 | // 如果是*.js, *.mjs文件,封装成ES6 Modules格式 162 | return { 163 | url: resolved.href, 164 | format: 'esm' 165 | }; 166 | } 167 | ``` 168 | 169 | ### 规则总结 170 | 在自定义loader中,export的resolve规则最核心的代码是 171 | ```js 172 | return { 173 | url: '', 174 | format: '' 175 | } 176 | ``` 177 | 178 | - url 是模块名称或者文件URL格式路径 179 | - format 是模块格式有`esm`, `cjs`, `json`, `builtin`, `addon`这四种模块/文件格式. 180 | 181 | 182 | ## Koa2 直接使用import/export 183 | 184 | 看看demo4,[https://github.com/chenshenhai/node-modules-demo/tree/master/demo4](https://github.com/chenshenhai/node-modules-demo/tree/master/demo4) 185 | 186 | - 文件目录 187 | 188 | ```js 189 | ├── demo4 190 | │   ├── README.md 191 | │   ├── custom-loader.mjs 192 | │   ├── index.js 193 | │   ├── lib 194 | │   │   ├── data.json 195 | │   │   ├── path.js 196 | │   │   └── render.js 197 | │   ├── package-lock.json 198 | │   ├── package.json 199 | │   └── view 200 | │   ├── index.html 201 | │   └── todo.html 202 | ``` 203 | 204 | 代码片段太多,不一一贴出来,只显示主文件 205 | 206 | ```js 207 | import Koa from 'koa'; 208 | import { render } from './lib/render.js'; 209 | import data from './lib/data.json'; 210 | 211 | let app = new Koa(); 212 | app.use((ctx, next) => { 213 | let view = ctx.url.substr(1); 214 | let content; 215 | if ( view === 'data' ) { 216 | content = data; 217 | } else { 218 | content = render(view); 219 | } 220 | ctx.body = content; 221 | }) 222 | app.listen(3000, ()=>{ 223 | console.log('the modules test server is starting'); 224 | }); 225 | ``` 226 | 227 | - 执行代码 228 | ``` 229 | node --experimental-modules --loader ./custom-loader.mjs ./index.js 230 | ``` 231 | 232 | - 访问 233 | - 访问 [http://127.0.0.1:3000/index](http://127.0.0.1:3000/index) 234 | - 访问 [http://127.0.0.1:3000/data](http://127.0.0.1:3000/data) 235 | - 访问 [http://127.0.0.1:3000/todo](http://127.0.0.1:3000/todo) 236 | 237 | 238 | ### 自定义loader规则优化 239 | 240 | 从上面官方提供的自定义loader例子看出,只是对`*.js`文件做`import/export`做loader兼容,然而我们在实际开发中需要对npm模块,`*.json`文件也使用`import/export` 241 | 242 | ### loader规则优化解析 243 | 244 | ```js 245 | import url from 'url'; 246 | import path from 'path'; 247 | import process from 'process'; 248 | import fs from 'fs'; 249 | 250 | // 从package.json中 251 | // 的dependencies、devDependencies获取项目所需npm模块信息 252 | const ROOT_PATH = process.cwd(); 253 | const PKG_JSON_PATH = path.join( ROOT_PATH, 'package.json' ); 254 | const PKG_JSON_STR = fs.readFileSync(PKG_JSON_PATH, 'binary'); 255 | const PKG_JSON = JSON.parse(PKG_JSON_STR); 256 | 257 | // 项目所需npm模块信息 258 | const allDependencies = { 259 | ...PKG_JSON.dependencies || {}, 260 | ...PKG_JSON.devDependencies || {} 261 | } 262 | 263 | //Node原生模信息 264 | const builtins = new Set( 265 | Object.keys(process.binding('natives')).filter((str) => 266 | /^(?!(?:internal|node|v8)\/)/.test(str)) 267 | ); 268 | 269 | // 文件引用兼容后缀名 270 | const JS_EXTENSIONS = new Set(['.js', '.mjs']); 271 | const JSON_EXTENSIONS = new Set(['.json']); 272 | 273 | export function resolve(specifier, parentModuleURL, defaultResolve) { 274 | // 判断是否为Node原生模块 275 | if (builtins.has(specifier)) { 276 | return { 277 | url: specifier, 278 | format: 'builtin' 279 | }; 280 | } 281 | 282 | // 判断是否为npm模块 283 | if ( allDependencies && typeof allDependencies[specifier] === 'string' ) { 284 | return defaultResolve(specifier, parentModuleURL); 285 | } 286 | 287 | // 如果是文件引用,判断是否路径格式正确 288 | if (/^\.{0,2}[/]/.test(specifier) !== true && !specifier.startsWith('file:')) { 289 | throw new Error( 290 | `imports must begin with '/', './', or '../'; '${specifier}' does not`); 291 | } 292 | 293 | // 判断是否为*.js、*.mjs、*.json文件 294 | const resolved = new url.URL(specifier, parentModuleURL); 295 | const ext = path.extname(resolved.pathname); 296 | if (!JS_EXTENSIONS.has(ext) && !JSON_EXTENSIONS.has(ext)) { 297 | throw new Error( 298 | `Cannot load file with non-JavaScript file extension ${ext}.`); 299 | } 300 | 301 | // 如果是*.js、*.mjs文件 302 | if (JS_EXTENSIONS.has(ext)) { 303 | return { 304 | url: resolved.href, 305 | format: 'esm' 306 | }; 307 | } 308 | 309 | // 如果是*.json文件 310 | if (JSON_EXTENSIONS.has(ext)) { 311 | return { 312 | url: resolved.href, 313 | format: 'json' 314 | }; 315 | } 316 | 317 | } 318 | ``` 319 | 320 | ## 后记 321 | 目前Node对`import/export`的支持现在还是`Stability: 1 - Experimental`阶段,后续的发展还有很多不确定因素,自己练手玩玩还可以,但是在还没去flag使用之前,尽量不要在生产环境中使用。 322 | 323 | -------------------------------------------------------------------------------- /2018/02/20.md: -------------------------------------------------------------------------------- 1 | # Node.js 多进程自动守护管理 2 | 3 | > 前言:放假在家阅读了`cfork`模块的源码,发现其中的进程重启管理很有意思,值得学习,模仿该功能自己写了简单的进程fork和refork的核心代码片段 4 | 5 | ### 主要功能 6 | 7 | - 启动多进程 8 | - 子进程退出自动fork新进程 9 | 10 | ### 多进程启动和自动守护代码 11 | 12 | ./fork.js 13 | 14 | ```js 15 | const cluster = require('cluster'); 16 | const os = require('os'); 17 | const cupCount = os.cpus().length; 18 | 19 | module.exports = fork; 20 | 21 | /** 22 | * @name {Function} frok 23 | * @param {Object} options 24 | * - {String} exec [必填]进程文件 25 | * - {Array} args [必填]进程命令参数 26 | * - {Boolean} silent 是否要发送输 默认false 27 | * - {Number} count, 进程数量 默认为CPU核数 28 | * - {Boolean} refork, 当work进程退出或者断开,是否需要重启, 默认是true 29 | */ 30 | function fork (options = {}) { 31 | if (cluster.isWorker) { 32 | return; 33 | } 34 | 35 | if( !options.exec ) { 36 | return; 37 | } 38 | 39 | const totalWorkerCount = options.count > 0 ? options.count : cupCount; 40 | 41 | let opts = { 42 | exec: options.exec 43 | }; 44 | let newWorker; 45 | let workerCount = 0; 46 | 47 | // 启动主进程 48 | cluster.setupMaster(opts); 49 | 50 | for( let i=0; i= cupCount ) { 62 | return; 63 | } 64 | // 如果不超过CPU数量的就fork worker进程 65 | workerCount ++; 66 | return cluster.fork(); 67 | } 68 | 69 | /** 70 | * @name reforkWorker worker进程重启方法 71 | * @return {Object} 72 | */ 73 | function reforkWorker() { 74 | return forkWorker() 75 | } 76 | 77 | // 监听进程是否退出 78 | cluster.on('exit', (worker, code, signal) => { 79 | console.log( `the worker pid ${worker.process.pid} has exited` ) 80 | // 重新fork进程 81 | reforkWorker(); 82 | }); 83 | 84 | // 监听进程是否断开连接 85 | cluster.on('disconnect', (worker) => { 86 | console.log( `the worker pid ${worker.process.pid} has disconnected` ) 87 | let isDead = worker.isDead && worker.isDead(); 88 | if ( isDead ) { 89 | console.log( `the worker pid ${worker.process.pid} is dead` ) 90 | return; 91 | } 92 | workerCount --; 93 | reforkWorker(); 94 | }); 95 | 96 | return cluster; 97 | } 98 | ``` 99 | 100 | ### 多进程守护使用 101 | index.js 102 | ```js 103 | const fork = require('./fork'); 104 | 105 | fork({ 106 | exec: './worker' 107 | }) 108 | 109 | ``` 110 | 111 | worker.js 112 | ```js 113 | const http = require('http'); 114 | const process = require('process'); 115 | 116 | const PORT = 3001; 117 | 118 | const server = http.createServer((req, res) => { 119 | res.write('hello fork'); 120 | res.end(); 121 | }); 122 | 123 | server.listen(PORT, () => { 124 | console.log(`the server is start at port ${PORT}, PID=${process.pid}`) 125 | }) 126 | ``` 127 | 128 | ### 测试效果 129 | 130 | #### 启动多进程守护 131 | 132 | 2018-02-20 3 47 53 133 | 134 | #### 手动删除其中的一个子进程 135 | 2018-02-20 3 48 27 136 | 137 | 可以看出当监听到子进程退出后,就会自动重启一个新的进程,保证指定数量多进程的执行 138 | 139 | ### 参考代码 140 | [https://github.com/node-modules/cfork/blob/master/index.js](https://github.com/node-modules/cfork/blob/master/index.js) 141 | -------------------------------------------------------------------------------- /2018/06/04.md: -------------------------------------------------------------------------------- 1 | # 基于JSON Schema的HTML解析器 2 | 3 | ## 前言 4 | 5 | > 写这篇文章主要是近年来前端服务端渲染框架层出不穷,开发的选择眼花缭乱。最近有个想法,把HTML的DOM树抽象成用`JSON schema` 语言描述,可以用于HTML渲染描述中间层抹平框架的差异。 6 | 7 | > **注意:只是作为中间层抹平差异,不是代替框架** 。更好地抽象处理HTML的DOM节点在前端渲染,或者服务端渲染。 8 | 9 | - 如果觉得文章啰嗦,我已经实现了该Node模块 `html-schema-parser`,可以直接安装使用。 10 | - 或点击 github 查看源码[https://github.com/chenshenhai/html-schema-parser/](https://github.com/chenshenhai/html-schema-parser/) 11 | 12 | 具体的渲染设想如下: 13 | 14 | - 输入 JSON Schema 15 | ```js 16 | { 17 | tag: 'div', 18 | attribute: { 19 | style: 'background:#f0f0f0;' 20 | }, 21 | content: [ 22 | { 23 | tag: 'ul', 24 | content: [ 25 | { 26 | tag: 'li', 27 | content: ['item 001'] 28 | }, { 29 | tag: 'li', 30 | content: ['item 002'] 31 | } 32 | ] 33 | } 34 | ], 35 | } 36 | ``` 37 | - 输出HTML 38 | ```html 39 |
40 |
    41 |
  • item 001
  • 42 |
  • item 002
  • 43 |
  • item 003
  • 44 |
45 |
46 | ``` 47 | 48 | 49 | ## HTML的基本要素 50 | 51 | 如果要实现以上解析功能,就先抽象出前端、后端的HTML渲染的元素。 52 | 53 | - tag类型 `tag` 54 | - tag属性 `attribute` 55 | - tag内容(文本 和 子节点)`content` 56 | 57 | ## Schema解析实现 58 | 59 | ### 设定tag 60 | 61 | - 合法的tag 62 | ```js 63 | const legalTags = [ 64 | 'html', 'head', 'title', 'base', 'link', 'meta', 'style', 'script', 65 | 'noscript', 'body', 'section', 'nav', 'article', 'aside', 'h1', 'h2', 66 | 'h3', 'h4', 'h5', 'h6', 'hgroup', 'header', 'footer', 'address', 'main', 67 | 'p', 'hr', 'pre', 'blockquote', 'ol', 'ul', 'li', 'dl', 'dt', 'dd', 68 | 'figure', 'figcaption', 'div', 'a', 'em', 'strong', 'small', 's', 'cite', 69 | 'q', 'dfn', 'abbr', 'data', 'time', 'code', 'var', 'samp', 'kbd', 'sub', 70 | 'sup', 'i', 'b', 'u', 'mark', 'ruby', 'rt', 'rp', 'bdi', 'bdo', 'span', 'br', 71 | 'wbr', 'ins', 'del', 'img', 'iframe', 'embed', 'object', 'param', 'video', 72 | 'audio', 'source', 'track', 'canvas', 'map', 'area', 'svg', 'math', 73 | 'table', 'caption', 'colgroup', 'col', 'tbody', 'thead', 'tfoot', 'tr', 74 | 'td', 'th', 'form', 'fieldset', 'legend', 'label', 'input', 'button', 75 | 'select', 'datalist', 'optgroup', 'option', 'textarea', 'keygen', 76 | 'output', 'progress', 'meter', 'details', 'summary', 'command', 'menu' 77 | ] 78 | 79 | ``` 80 | 81 | - 不闭合的tag 82 | ```js 83 | const notClosingTags = { 84 | 'area': true, 85 | 'base': true, 86 | 'br': true, 87 | 'col': true, 88 | 'hr': true, 89 | 'img': true, 90 | 'input': true, 91 | 'link': true, 92 | 'meta': true, 93 | 'param': true, 94 | 'embed': true 95 | } 96 | ``` 97 | 98 | ### 解析tag的schema 99 | 100 | - 解析schema入口 101 | ```js 102 | function parseSchema (schema) { 103 | let html = '' 104 | 105 | if (isJSON(schema)) { 106 | // 如果是JSON类型,就直接用tag类型解析 107 | html = parseTag(schema) 108 | } else if (isArray(schema)) { 109 | // 如果是JSON类型,就直接用tag上下文类型解析 110 | html = parseContent(schema) 111 | } 112 | return html 113 | } 114 | ``` 115 | 116 | - 解析tag类型 117 | ```js 118 | function parseTag (schema) { 119 | let html = '' 120 | if (isJSON(schema) !== true) { 121 | return html 122 | } 123 | let tag = schema.tag || 'div' 124 | const content = schema.content 125 | // 检查是否为合法的tag, 如果不是,默认为div 126 | if (tags.legalTags.indexOf(tag) < 0) { 127 | tag = 'div' 128 | } 129 | const attrStr = parseAttribute(schema.attribute) 130 | // 判断是否为闭合tag 131 | if (tags.notClosingTags[tag] === true) { 132 | // 如果是闭合的tag 133 | html = `<${tag} ${attrStr} />` 134 | } else { 135 | // 如果是非闭合tag 136 | html = `<${tag} ${attrStr} >${parseContent(content)}` 137 | } 138 | return html 139 | } 140 | ``` 141 | 142 | - 解析tag内容 143 | ```js 144 | // 内容的类型为 Array 145 | // Array的内容为字符串,或者tag类型的JSON 146 | function parseContent (content) { 147 | let html = '' 148 | if (isArray(content) !== true) { 149 | return html 150 | } 151 | for (let i = 0; i < content.length; i++) { 152 | const item = content[i] 153 | if (isJSON(item) === true) { 154 | html += parseTag(item) 155 | } else if (isString(item)) { 156 | html += item 157 | } 158 | } 159 | return html 160 | } 161 | ``` 162 | 163 | 164 | 165 | ## 参考 166 | [https://github.com/caolan/pithy](https://github.com/caolan/pithy) 167 | -------------------------------------------------------------------------------- /2019/06/09.md: -------------------------------------------------------------------------------- 1 | # 从寻呼机到jQuery,一枚jQuery钉子户的独白 2 | 3 | ## 前言 4 | 5 | 今年难得的4天五一假期,放假当天就去下馆子吃饭,发现餐馆的叫餐系统很奇特,在前台点餐后,每桌领取块塑料牌子。餐馆前台在饭菜做好的时候,就做操作让食客的塑料牌子响动和发亮,引导食客交回塑料牌子然后去取餐。 6 | 7 | 当时点餐的我觉得这套系统很高大上,最后回家上网一查,发现这种系统叫“无线取号寻呼系统”,换句通俗的话来讲,就是局域网的“寻呼机”或者“BB机”。这就涨知识了,我的惯性常识认为二十多年前主流通信工具的“BB机”,应该早就退出历史的舞台,尘封在日新月异智能手机的浪潮底下,没想到此类技术还能在餐饮行业的信息化发展中继续发挥着余热。 8 | 9 | 当我抱着好奇心在网上继续搜索时候,发现当年的“BB机”无线寻呼技术除了应用在餐饮行业,还应用在医院的局域通信中。说到这里,也许读者很奇怪我会这么大的反应,因为我是信息工程专业出身的,虽然毕业后从事了互联网前端开发工作,但是大学4年耳濡墨染的通信技术还是有一定的技术敏感度的。 10 | 11 | 这次假期吃饭发现的“BB机”技术的新时代应用,在处于互联网前端技术的频繁更替的大环境下,给了我很大的思考,因为我的开发工作时间中,基本每周有那么一两次和同事对所谓前端“新旧技术”的友好(si)交流(bi)。因为我负责的是基础功能的前端应用,接受的锅很多都是近十年的陈年代码,基础应用99%都是基于jQuery的技术去实现的。日常重构和升级,最大的交流是对jQuery是否已经落伍淘汰的讨论和分析。 12 | 13 | ## 寻呼机的前世今生 14 | 15 | 作为一个电子信息相关专业出身的开发者,先从卖弄一下自己了解到的“寻呼机”技术历史 16 | 17 | ![mp-002](./image/mp-01.jpg) 18 | 19 | > 注: 图片来自于网络 20 | 21 | 22 | - 大概在1970年代,寻呼机在香港和台湾开始流行,香港称之为“Call机”,也有称之为“传呼机”、“BB机”或者“BP机”。 23 | - 到了1980年代,寻呼机开始在内地普及。在1990年代,内地的寻呼机业务达到了空前的繁荣。 24 | - 到了1990年代,此时的香港由于手提电话的开始普及,寻呼机开始没落了。但是直到2017年,香港仍然有一间无线电寻呼运营商继续运营“寻呼机”的服务。 25 | - 1998年,中国的寻呼机数量达到六千多万,已经是世界第一的数量。 26 | - 2000年后,功能手机开始普及,寻呼机在内地开始式微。 27 | - 直到2007年,内地大部分身份的运营商开始申请停止寻呼机的无线传呼服务,这也标志着“寻呼机”在内地正式推出通信界的历史舞台。 28 | - “寻呼机”虽然在推出了主流民用通信方式,但在一些特殊场合,如餐厅和医院,利用其在局域网内无线寻呼通信能力,继续发光发热实现着自己的技术价值。 29 | 30 | 31 | ![mp-001](./image/mp-02.jpg) 32 | 33 | > 注: 图片来自于网络 34 | 35 | ## jQuery真的过时了么? 36 | 37 | ### 先聊聊jQuery的历史 38 | 39 | mp-003 40 | 41 | > 注: 图片来自于网络 42 | 43 | 44 | 上述说了一大通“寻呼机”的前世今生,以及在特定场景仍旧发挥着余热,那我们来说说jQuery的前世今生以及是否已经过时了。以下我们来先聊聊jQuery的主要发展历程。 45 | 46 | - 2006年,jQuery发布了第一个正式版本,1.0 47 | - 2009年,jQuery 1.3.2 引用了Sizzle选择器引擎 48 | - 2011年,jQuery 1.5.2 重写了Ajax模块 49 | - 2012年,jQuery 1.8.3 重写Sizzle引擎 50 | - 2013年,jQuery 2.0.3 不再支持 IE 6-8,降低体积大小,提高性能 51 | - 2016年,jQuery 3.0 发布,Ajax支持Promise 52 | - 2018年,jQuery 3.3.1 发布,使用了更多ES和HTML5新特性 53 | 54 | 从上述的jQuery的发展历程看,jQuery的从诞生开始,发展没有停止过。jQuery在1.x阶段是处理PC端操作的兼容,在2.x阶段是为了摈弃低版本IE的兼容和提高性能,在3.x阶段则是与时俱进,吸收了很多ES和HTML5新特性。 55 | 56 | ### 再聊聊主流前端框架的实际应用对比 57 | 58 | 近几年来,前端的框架和工具库层出不穷,大浪淘沙后,目前呼声较高的框架有 React.js、Vue.js和Angular。同时,在2018年GitHub宣布放弃使用jQuery的时候,前端社区对摈弃jQuery的声音层出不穷。因为我工作中可以说50%的开发是跟jQuery有关的,在这种背景下,我就在想jQuery真的有那么落后么? 59 | 60 | 我换了一种思考角度,我为啥还使用jQuery?是在什么情况下我选择使用了jQuery? 61 | 62 | 我开发过程中,遇到的场景是服务端是Java,主要提供服务端渲染,前端用jQuery绑定对应的操作事件和DOM变化。 63 | 我的业务需求,遇到的是SEO强需求,需要在服务端渲染模板上动态渲染好数据,等待爬虫来抓取数据。 64 | 我的维护中。同时还要最低要兼容到IE8,保证在IE8上基本能正常使用。 65 | 66 | - 现在我们假设选则了React 67 | 68 | 那么第一个就面临着IE8的兼容性问题,这个是无解的,即使引用一堆 polyfill 库也不能彻底解决问题。 69 | 再次就是服务端模板渲染的问题,如果是以服务端Java的模板为主呢,那么React本身的JSX模板就需要重写一套,前端页面渲染时候,覆盖在服务端渲染的DOM上面。如果是以React模板为主呢,就得需要人力和机器投入去整一套SSR系统专门做服务端渲染。 70 | 71 | - 现在我们假设选择Vue或者Angular 72 | 73 | 其实会发现遇到的困难和 React类似的。 74 | 75 | 从上述很浅显的应用选择层面就可以知道,其实jQuery还是有一定的应用场景的,React/Vue/Angular虽然流行且提高开发效率但是也有一定的场景短板的。就像刚开始的“BB机”的历史和如今的应用场景一样,总结一句话,没有落后的技术,只有不适应场景的技术。 76 | 77 | 78 | ## 我们使用jQuery要注意什么? 79 | 80 | 其实如果只是考虑Chrome和IE11+的浏览器,普通的DOM操作和事件直接用原生浏览器WebAPI就可以满足要求,但是如果考虑到兼容性和API统一性,用jQuery其实也是个不错的选择。React/Vue/Angular的方便性就是抹平了操作DOM的方式,让开发者可以更关注于组件的组合和业务的实现,而jQuery核心就是操作DOM,所以很多DOM操作的注意事项要关注的。 81 | 82 | jQuery的使用注意点其实就只有一点,就是操作DOM事件和数据生命周期控制,保证DOM在销毁时候,对应有事件和数据也一并清除,避免事件注册太多和数据残留导致的内存溢出。 83 | 84 | - 事件的生命周期 85 | - jQuery给DOM注册了`$('div').on('click')`事件,如果DOM有销毁的步骤,那么在销毁前就要一并把事件给删除掉`$('div').off('click')` 86 | - DOM绑定数据声明周期 87 | - jQuery给DOM绑定了数据`$('div').data({a:123})`,如果DOM有销毁的步骤,那么在销毁前就要一并把数据给清除掉 88 | 89 | ## 技术选型的STAR法则 90 | 91 | 我们从“寻呼机的前世今生”讲到“jQuery的是否过时”,其实核心的一点就是在讨论一个技术是否有过时落伍一说。 92 | 93 | 一门技术的诞生肯定是有其应用场景,所谓的“过时”,是应该相关的应用场景有更加高效低成本的技术代替了。 94 | 95 | 一门技术其实就是一种工具,工具的诞生是为了解决实际问题,脱离了实际问题的本身,技术的价值很难说清楚。所以这里面对技术选型,我的建议是参考STAR法则。 96 | 97 | - Situation 场景 98 | - Task 任务 99 | - Action 行动 100 | - Result 结果 101 | 102 | 套用上述的内容我们可以这么说 103 | 104 | - S: 餐馆点菜和送菜需要提高效率 105 | - T: 顾客点菜、厨师做好菜、通知顾客菜好了,让顾客取餐,来降低运营成本 106 | - A: 要定制一个系统,来让顾客点菜,厨师做好菜后,通知顾客去领取,所以需要一个局域网的通信低成本系统,对比很多寻呼技术,发现“BB机”的成本最低,就去落地开发。 107 | - R:最后结果是,无线餐馆寻呼系统实现了,顾客不用去找服务员问进度等待被寻呼,服务员也不用频繁被顾客询问,厨师也不用频繁被服务员催。 108 | 109 | 问题圆满解决 110 | 111 | 我为啥选择jQuery技术呢 112 | - S: 在现有Java系统上,低成本实现SEO友好的动态数据展示型页面 113 | - T: Java服务端动态数据渲染,前端展示型页面,轻量的事件交互,要低成本。 114 | - A: Java服务端直接根据不同数据动态渲染HTML,前端用jQuery直接操作DOM的事件和效果 115 | - R: 快速让页面上线,无需新增SSR开发量。 116 | 117 | ## 前端技术的信息熵 118 | 119 | > 熵,通常作为一个物理名词,用来描述一个系统的混乱程度,失序程度的指标。在我大学信息工程专业学习的信息导论中,信息熵是接受的每条消息里包含信息的数量和不确定性的综合度量。也就是消息里包含的信息数量、不确定性和随机性越大,就代表着信源的熵就越大。 120 | 121 | ![mp-005](./image/mp-04.jpg) 122 | > 注: 图片来自于网络 123 | 124 | 近几年互联网的技术发展飞快,各种技术框架爆炸性增长,带来的是眼花缭乱的技术选型困难症。对比原来比较中规中矩的前端技术体系,在2012年前,jQuery + jQuery衍生框架是主流前端技术体系,主要的理念是如何更加兼容地操作DOM和渲染页面。 125 | 126 | 但是大概2012年以后,各种前端技术理念开始普及,例如MVVM, Virtual-Dom等,对原有有序的前端技术体系带来冲击,极大提高了前端技术体系的信息熵,这带来利的一面是技术选择面广了,弊的一面是需要更多时间去衡量和选择技术。 127 | 128 | 到现在,一提到前端技术体系,充斥着眼花缭乱的技术框架和衍生框架。信息熵的增大,有序变无序并不可怕,最重要的是,要怎么学会在庞杂的前端信息中学会选择技术,学会应用到实际的场景中。 129 | 130 | ## 后记 131 | 132 | 对于技术优劣的讨论,一切脱离了实际问题场景讨论技术优劣的都是耍流氓。没有的绝对的银弹,只有合适场景的解决方案。 133 | 134 | -------------------------------------------------------------------------------- /2019/06/10.md: -------------------------------------------------------------------------------- 1 | ## 前言 2 | 3 | 上个月写了一篇[《从寻呼机到jQuery,一枚jQuery钉子户的独白》](https://github.com/chenshenhai/blog/issues/33)后,引起和技术小伙伴们的对HTML操作的讨论。 4 | 5 | A君: `jQuery`直接操作`HTML`,让项目代码很难维护。 6 | 7 | B君: `React`/`Vue`来管理 `DOM`和抹平`DOM`的操作,让开发者可以专注前端功能的实现。 8 | 9 | C君: 用`jQuery`不能让页面的节点`Node`变化方便可控。 10 | 11 | D君: 元素`Element`操作还是交给有模板能力的框架来操作。 12 | 13 | 讨论过后我回想对话,感觉有哪些不对,`HTML`,`DOM`, `Node`和`Element`在交谈中各种混用,到底讨论是否是同一个问题呢,抱着这个心态我查了`MDN`文档,算是初步理清楚了以上几个名词的含义。 14 | 15 | ## 什么是HTML 16 | 17 | 说起这个,应该很多人都很熟悉,就是`HyperText Markup Language`的缩写,翻译过来就是`超文本标记语言`。 18 | 19 | `HTML`是用来描述网页的结构,如果把网页比喻成一个摩天大楼,那么`HTML`就是构成摩天大楼的`钢筋混泥土结构`。 20 | 21 | 同时一个`HTML`网页,可以描述成一个文档`Document` 22 | 23 | 24 | 25 | ## 什么是DOM 26 | 27 | `DOM`,是`Document Object Model`的缩写,也就是文档对象模型,是对`HTML`构成网页文档的一种对象描述。换句话说,`DOM`是用于脚本程序(例如`JavaScript`)操作HTML网页的对象模型。 28 | 29 | `DOM` 已经实现了对 `HTML`的节点操作、属性操作、事件操作和内容操作等接口和方法。可以这么说,所有对`HTML`进行动态脚本(例如`JavaScript`)的操作,都是对`DOM`的操作 30 | 31 | 32 | 如果把网页比喻成一个摩天大楼 33 | 34 | - `HTML`就构成摩天大楼的`钢筋混泥土结构` 35 | - `DOM`就是构建摩天大楼的`包工头`,做着管理操作`HTML`的事情。 36 | 37 | `DOM`最常见的接口例如 `document.getElementsByName('body')`,查找整个`DOM tree`元素里的`body`元素 38 | 39 | 40 | ## 什么是Element 41 | 42 | `Element`,通常称为“元素”,是对接口`Node`实现,是所有文档对象(`DOM`)的基类。 43 | - 实现了节点接口`Node`的接口操作,例如节点的增删改查。 44 | - 扩展了对节点的属性操作,例如`className`和`attribute`操作。 45 | 46 | 如果把网页比喻成一个摩天大楼 47 | 48 | - `HTML`就构成摩天大楼的`钢筋混泥土结构` 49 | - `DOM`就是构建摩天大楼的`包工头` 50 | - 那么`Element`是摩天大楼的`装修工人`,主要实现`DOM`中样式和内容的操作 51 | 52 | 例如操作`DOM`的样式 53 | 54 | ```js 55 | // 获取DOM中的div元素 56 | var divElems = document.getElementsByTagName('div'); 57 | // 元素操作 58 | // 给第一个div元素加上 bg-red 的样式名称 59 | divElems[0].classList.add('bg-red') 60 | ``` 61 | 62 | ## 什么是Node 63 | 64 | `Node`是一个接口`interface`,同时也是继承父接口`EventTarget`。`Node`主要描述了节点操作的方法和属性,例如描述了操作父节点`parentNode`、子节点`childNode`和兄弟节点`previousSibling/nextSibling`的操作。 65 | 66 | 如果把网页比喻成一个摩天大楼 67 | 68 | - `HTML`就构成摩天大楼的`钢筋混泥土结构` 69 | - `DOM`就是构建摩天大楼的`包工头` 70 | - `Element`是摩天大楼的`装修工人`。 71 | - 那么`Node`就是构建摩天大楼的`建筑工人`,主要实现结构的操作 72 | 73 | 74 | ```js 75 | // 获取DOM中的div元素 76 | var divElems = document.getElementsByTagName('div'); 77 | 78 | // 节点操作 79 | // 给第一个div元素里追加一个 span的子节点 80 | var span = document.createElement('span'); 81 | divElems[0].appendChild(span); 82 | ``` 83 | 84 | ## 什么是 EventTarget 85 | 86 | `EventTarget` 是一个事件接口,用于注册和触发事件描述的接口,也是最基本的事件监听器的接口。 87 | 88 | 89 | 如果把网页比喻成一个摩天大楼 90 | 91 | - `HTML`就构成摩天大楼的`钢筋混泥土结构` 92 | - `DOM`就是构建摩天大楼的`包工头` 93 | - `Element`是摩天大楼的`装修工人`。 94 | - `Node`就是构建摩天大楼的`建筑工人`。 95 | - 那么 `EventTarget` 就是构建摩天大楼的`电力工人`,主要是事件的注册和触发。 96 | 97 | 98 | ```js 99 | // 获取DOM中的div元素 100 | var divElems = document.getElementsByTagName('div'); 101 | 102 | // 事件注册 103 | var event = new Event('myclick'); 104 | divElems[0].addEventListener('myclick', function() { 105 | alert('hello myclick event') 106 | }); 107 | 108 | // 广播触发事件 109 | divElems[0].dispatchEvent(event) 110 | ``` 111 | 112 | ## 总结一下 113 | 114 | 我们回到`前言`中的语境里,讨论所谓的`jQuery`操作`HTML`,其实本质就是`JavaScript`对`DOM`的操作。 115 | 116 | 其中`DOM`是`Element`的扩展实现,`Element`是`Node`接口的一种实现,而最基本的`Node`接口是继承于底层的`EventTarget` 事件接口。 117 | 118 | - `DOM`里相关事件事件监听和操作是,继承于`EventTarget`实现的。 119 | - `DOM`里相关属性和内容操作是,继承于`Element`实现的。 120 | - `DOM`里相节点操作是,继承于`Node`实现的。 121 | 122 | 123 | 124 | 参考资料 125 | 126 | - [1] [MDN web docs: Web/HTML](https://developer.mozilla.org/en-US/docs/Web/HTML) 127 | - [2] [MDN web docs: Web/API/Document_Object_Model](https://developer.mozilla.org/en-US/docs/) 128 | - [3] [MDN web docs: Web/API/Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) 129 | - [4] [MDN web docs: Web/API/Node](https://developer.mozilla.org/en-US/docs/Web/API/Node) 130 | - [5] [MDN web docs: Web/API/EventTarget](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget) 131 | 132 | -------------------------------------------------------------------------------- /2019/06/24.md: -------------------------------------------------------------------------------- 1 | ## 前言 2 | 3 | 对于js的`Worker`的使用,看了很多网上资料,都是基于`worker.js`新开一个文件来执行后台线程。 4 | 5 | 但是如果不想太麻烦新开一个`work.js`文件,想在同一个js文件里执行后台线程的资料就很少,最后翻遍了[MDN](https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Workers_API/Using_web_workers) 文档,找到了一种在同js文件下执行后台线程Worker的方法,具体实现如下 6 | 7 | ## 实现 8 | 9 | ### 实现源码 10 | 11 | ```js 12 | /** 13 | * 执行函数的后台线程 14 | * @param func 待后台线程执行的函数 15 | * @param params 待后台线程执行的函数参数 16 | * @param feedback 执行后台线程函数后反馈回调函数 17 | */ 18 | const asyncWorker = function (func, params, feedback) { 19 | // 设置后台执行函数的UUID 20 | const uuid = Math.random().toString(26).substr(2); 21 | 22 | // 封装成自执行函数字符串 23 | const scriptCode = `(${func.toString()})(event.data.params)`; 24 | const feedbackMap = new Map(); 25 | // 后台线程代码字符串 26 | const workerCode = ` 27 | onmessage = function (event) { 28 | let result = null; 29 | let err = null; 30 | result = eval(event.data.code); 31 | postMessage({ 32 | id: event.data.id, 33 | result: result, 34 | error: err, 35 | }); 36 | } 37 | `; 38 | const workerCodeStr = encodeURIComponent(workerCode); 39 | // 初始化后台线程 40 | const worker = new Worker('data:text/javascript;charset=US-ASCII,' + workerCodeStr); 41 | 42 | // 监听正常后台线程通信 43 | worker.onmessage = function (event) { 44 | const callback = feedbackMap.get(event.data.id); 45 | if (typeof callback === 'function') { 46 | callback(event.data.result, event.data.error); 47 | } 48 | feedbackMap.delete(event.data.id); 49 | }; 50 | 51 | // 捕获后台线程错误 52 | worker.onerror = function (err) { 53 | const callback = feedbackMap.get(uuid); 54 | if (typeof callback === 'function') { 55 | callback(null, err.message); 56 | } 57 | feedbackMap.delete(uuid); 58 | }; 59 | 60 | // 将函数存入map中 61 | feedbackMap.set(uuid, feedback); 62 | 63 | // 发起通信,执行ID为UUID的函数 64 | worker.postMessage({ 65 | id: uuid, 66 | params: params, 67 | code: scriptCode 68 | }); 69 | }; 70 | 71 | ``` 72 | 73 | ### 执行正常后台线程 74 | 75 | ```js 76 | // 待执行在后台线程函数 77 | // 斐波那契数列函数 78 | const fibonacciFunc = function(params = {}) { 79 | const { count = 1 } = params; 80 | let result = 1; 81 | for (let i = 0; i < count; i ++) { 82 | result += result; 83 | } 84 | return result; 85 | } 86 | // 斐波那契数列的参数为 50 87 | const params = { count: 50 }; 88 | // 开始执行后台线程 计算数列 89 | asyncWorker(fibonacciFunc, params, function (result, err) { 90 | console.log('result = ', result); 91 | console.log('error = ', err); 92 | }); 93 | ``` 94 | 95 | ##### 执行结果 96 | 97 | ```sh 98 | # 斐波那契数列 执行结果 99 | result = 1125899906842624 100 | error = null 101 | ``` 102 | 103 | ### 执行异常后台线程 104 | 105 | ```js 106 | // 待执行在后台线程 异常函数 107 | const errorFunc = function(params = {}) { 108 | throw new Error('i am an error') 109 | } 110 | // 开始执行后台线程 异常函数 111 | asyncWorker(errorFunc, {}, function (result, err) { 112 | console.log('result = ', result); 113 | console.log('error = ', err); 114 | }); 115 | ``` 116 | 117 | ##### 执行结果 118 | 119 | ```sh 120 | result = null 121 | error = Uncaught Error: i am an error 122 | ``` 123 | 124 | ## 参考 125 | 126 | [MDN:Web/API/Web_Workers_API/Using_web_workers](https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Workers_API/Using_web_workers) -------------------------------------------------------------------------------- /2019/06/image/mp-01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2019/06/image/mp-01.jpg -------------------------------------------------------------------------------- /2019/06/image/mp-02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2019/06/image/mp-02.jpg -------------------------------------------------------------------------------- /2019/06/image/mp-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2019/06/image/mp-03.png -------------------------------------------------------------------------------- /2019/06/image/mp-04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2019/06/image/mp-04.jpg -------------------------------------------------------------------------------- /2019/07/28.md: -------------------------------------------------------------------------------- 1 | # 用TypeScript写了个低配版H5美图工具 2 | 3 | ## 前言 4 | 5 | 最近两月在学习`canvas`时候,发现很多有意思的技术能力,特别是在图像处理这一领域。让我想起大学课堂教学的《数字图像处理》(冈萨雷斯 版本)。但是很遗憾的是,大学上完课应付考试后全部还给老师了,毕业后一直做WEB相关开发,再也没怎么去接触图像处理这一领域技术。 6 | 7 | 利用每天下班回家后的零星时间,用`TypeScript`基于`canvas`的能力,写了一个H5图像处理小工具,勉强算是低配版的“美图秀秀”。这个图像处理的小工具我命名为 `Pictool`。 8 | 9 | ![pictool-ui-adjust](./image/pictool-01.gif) 10 | 11 | 具体源码地址 12 | 13 | [https://github.com/chenshenhai/pictool](https://github.com/chenshenhai/pictool) 14 | 15 | 16 | 具体文档地址 17 | 18 | [https://chenshenhai.github.io/pictool-doc/](https://chenshenhai.github.io/pictool-doc/) 19 | 20 | 在线例子 21 | [https://chenshenhai.github.io/pictool/example/index.html](https://chenshenhai.github.io/pictool/example/index.html) 22 | 23 | 24 | 25 | ![pictool-logo](./image/pictool-02.png) 26 | 27 | 28 | 29 | ## CDN 快速使用 30 | 31 | ```html 32 | 33 | ``` 34 | 35 | ```js 36 | (function(Pictool) { 37 | const util = Pictool.browser.util; 38 | const PictoolUI = Pictool.UI; 39 | 40 | // 获取测试图片,实际使用请输入实际的图片URL 41 | // 注意如果图片是跨域的,请保证图片源站允许跨域 42 | util.getImageDataBySrc('./xxx.jpg').then(function(imgData) { 43 | const pictoolUI = new PictoolUI(imgData, { 44 | uiConfig: { 45 | language: 'zh-cn', 46 | }, 47 | }); 48 | pictoolUI.show(); 49 | }).catch(function(err) { 50 | alert(JSON.stringify(err)); 51 | }); 52 | })(window.Pictool); 53 | ``` 54 | 55 | ![example-ui](./image/pictool-03.jpg) 56 | 57 | ### 具体动态效果 58 | 59 | ![pictool-ui-adjust](./image/pictool-04.gif) 60 | 61 | ![pictool-ui-effect](./image/pictool-05.gif) 62 | 63 | ![pictool-ui-process](./image/pictool-06.gif) 64 | 65 | 66 | 67 | ## NPM使用 68 | 69 | 快速安装 70 | 71 | ```sh 72 | npm i --save pictool 73 | ``` 74 | 75 | 快速使用 76 | 77 | ```js 78 | 79 | import Pictool from 'pictool'; 80 | 81 | (async function() { 82 | const imgData = await Pictool.browser.util.getImageDataBySrc('./xxx.jpg'); 83 | const tool = new Pictool.UI(imgData, { 84 | uiConfig: { 85 | language: 'zh-cn', 86 | }, 87 | }); 88 | tool.show(); 89 | })() 90 | 91 | ``` 92 | 93 | 把编译后的代码放在`HTML页面上`,就可以实现上述`CDN`的使用效果 94 | 95 | ![pictool-ui-process](./image/pictool-07.gif) 96 | 97 | ## Pictool 功能 98 | 99 | `Pictool` 图像处理小工具目前支持了常用的图像处理能力,分别都可以独立抽出使用。 100 | 101 | ### 图像处理能力 102 | 103 | - `Brightness(Lightness)` 亮度 104 | - `Hue` 色相 105 | - `Saturation` 饱和度 106 | - `Alpha` 透明度 107 | - `Invert` 反色 108 | - `Grayscale` 灰度 109 | - `Sobel` Sobel边缘计算 110 | - `Sepia` 褐色化(怀旧) 111 | - `Posterize` 色阶 112 | - `Gamma` 伽马处理 113 | 114 | ### 图像滤镜效果 115 | 116 | 可以通过图像处理的基础能力,组合成滤镜效果。 117 | 例如 `Sobel边缘计算` + `反色` 组合就可以产生 `素描画` 的效果 118 | 119 | ![example-digit-browser-sanbox](./image/pictool-08.jpg) 120 | 121 | 122 | 123 | ```js 124 | var sandbox = new Pictool.browser.Sandbox('./xxx.jpg'); 125 | sandbox.queueProcess([ 126 | { process: 'sobel', options: {}, }, 127 | { process: 'invert', options: {}, } 128 | ]).then(function(base64) { 129 | document.querySelector('body').innerHTML = ``; 130 | }).catch(function(err) { 131 | console.log(err); 132 | }); 133 | ``` 134 | 135 | ### 浏览器能力 136 | 137 | - 图片数据转换 138 | - 图片 `URL`转图片`HTMLImage` 139 | - 图片 `URL`转图片`ImageData` 140 | - 图片 `ImageData`转图片`base64` 141 | - 图片压缩: 将图片压缩在 400百万像素内 142 | - 其他能力,详见文档 143 | - [https://chenshenhai.github.io/pictool-doc/](https://chenshenhai.github.io/pictool-doc/) 144 | 145 | 146 | ## Pictool 文档 147 | 148 | 在写了这个 `Pictool` 图像处理小工具后,顺便把所有的功能点的使用方式都整理成文档,方便使用时候查阅。 149 | 150 | [https://chenshenhai.github.io/pictool-doc/](https://chenshenhai.github.io/pictool-doc/) 151 | 152 | 153 | ![pictool-doc](./image/pictool-09.jpg) 154 | 155 | ![pictool-doc-quick](./image/pictool-10.jpg) 156 | 157 | 158 | ## TypeScript 使用感想 159 | 160 | 这次开发这个小工具,其实也是为了深入熟悉 `TypeScript` 在项目开发使用,主要有一下感想的总结。 161 | 162 | - 1 如果是开始接触 `TypeScript`,建议使用时候,开启`strict: true`最严格模式。 163 | - 2 所有模块、函数、变量等都要严格声明类型。 164 | - 3 开启 `eslint` 的 `TypeScript` 最严格校验和修复 165 | - 4 `webpack`和`rollup`两种编译体系下建议都尝试一遍。 166 | - 5 多折腾多写代码,学习新东西没有捷径可言 167 | 168 | 169 | 170 | ## 后记 171 | 172 | 经过两个月的开发 `Pictool` 的沉淀,后续已经开始整理下一本关于`canvas`和`图像处理`的学习笔记。目前已经沉淀了部分笔记,后续会持续整理更新上去,同时也会在公众号分享其中比较有意思的技术能力。 173 | 174 | ![canvas-note](./image/pictool-11.jpg) 175 | 176 | 177 | -------------------------------------------------------------------------------- /2019/07/image/pictool-01.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2019/07/image/pictool-01.gif -------------------------------------------------------------------------------- /2019/07/image/pictool-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2019/07/image/pictool-02.png -------------------------------------------------------------------------------- /2019/07/image/pictool-03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2019/07/image/pictool-03.jpg -------------------------------------------------------------------------------- /2019/07/image/pictool-04.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2019/07/image/pictool-04.gif -------------------------------------------------------------------------------- /2019/07/image/pictool-05.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2019/07/image/pictool-05.gif -------------------------------------------------------------------------------- /2019/07/image/pictool-06.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2019/07/image/pictool-06.gif -------------------------------------------------------------------------------- /2019/07/image/pictool-07.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2019/07/image/pictool-07.gif -------------------------------------------------------------------------------- /2019/07/image/pictool-08.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2019/07/image/pictool-08.jpg -------------------------------------------------------------------------------- /2019/07/image/pictool-09.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2019/07/image/pictool-09.jpg -------------------------------------------------------------------------------- /2019/07/image/pictool-10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2019/07/image/pictool-10.jpg -------------------------------------------------------------------------------- /2019/07/image/pictool-11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2019/07/image/pictool-11.jpg -------------------------------------------------------------------------------- /2019/08/26.md: -------------------------------------------------------------------------------- 1 | ## 前言 2 | 3 | 前段时间开发图像处理工具[Pictool](https://github.com/chenshenhai/pictool)后,遇到高频的计算瓶颈。在寻找高频计算的前端能力解决方案过程中,入门学习了一下 WebAssembly 在前端中的应用。入门的过程中踩了不少坑,例如使用`AssemblyScript` 开发`wasm`时候,发现 `npm` 包 `assemblyscript` 已经不维护了,需要自己人工添加成从`Github` 仓库引用`assemblyscript` 的`npm`模块。 4 | 5 | 同时网上很多教程已经有点不同步,很多按照教程步骤后实现的代码跑不起来。最后参考原有网上的教程,一步步踩坑,实现了demo,同时也写下这篇文章作为笔记! 6 | 7 | ## WebAssembly 8 | 9 | ### 什么是 WebAssembly 10 | 11 | - 计算机是不能直接识别运行高级语言(C/C++, Java, JavaScript等)。 12 | - 计算机能读懂是0和1的电子元件信号,对应到运行的机器码。 13 | - 在前端浏览器领域里,JS是解释执行,也就是运行到哪就解释成机器码让计算机读懂并执行,在高频计算性能上有一定的瓶颈。 14 | - WebAssembly 字节码是接近计算机能识别的机器码,只要运行环境有对应的虚拟机,能快速加载运行。 15 | 16 | ### WebAssembly 优势 17 | 18 | 在前端主要的优势有 19 | 20 | - 体积小 21 | - 加载快 22 | - 兼容强 23 | 24 | ### WebAssembly 前端能力现状 25 | 26 | - Node.js 目前已经支持了 WebAssembly 27 | - 大部分主流浏览器厂商也支持了 WebAssembly 28 | 29 | ### 什么是 AssemblyScript 30 | 31 | - `AssemblyScript` 是 `TypeScript` 的一个子集 32 | - 可以用 `TypeScript` 语法编写功能编译成 `wasm`,对前端来说比较友好。 33 | 34 | 35 | ## 快速开始 36 | 37 | ### demo源码地址 38 | 39 | 如果想更快速尝试,可以直接去该 demo 仓库获取源码使用。 40 | 41 | [https://github.com/chenshenhai/assemblyscript-demo](https://github.com/chenshenhai/assemblyscript-demo) 42 | 43 | ### 安装 AssemblyScript 44 | 45 | 由于 `AssemblyScript` 的 `npm` 官方模块已经停止维护,所以`AssemblyScript`的模块需要从`Github` 来源安装。 46 | 47 | ![wasm-003](./image/wasmscript-01.jpg) 48 | 49 | 50 | 51 | 在`package.json`的依赖加入 `AssemblyScript` 模块的 `Github` 来源 52 | 53 | ./package.json 54 | 55 | ```js 56 | { 57 | // ... 58 | "devDependencies": { 59 | "assemblyscript": "github:assemblyscript/assemblyscript" 60 | // ... 61 | } 62 | } 63 | ``` 64 | 65 | 再执行 `npm install` 从 `Github` 下载该模块到本地 `node_module`中 66 | 67 | ```sh 68 | npm install 69 | ``` 70 | 71 | ### 编写功能代码 72 | 73 | 编写一个 `斐波那契数列` 函数 74 | 75 | 在 demo 的目录 `./src/index.ts` 中 76 | 77 | ```js 78 | export function fib(num: i32): i32 { 79 | if (num === 1 || num === 2) { 80 | return 1; 81 | } else { 82 | return fib(num - 1) + fib(num - 2) 83 | } 84 | } 85 | ``` 86 | 87 | ### 编译 88 | 89 | 在 `package.json` 编写编译脚本 90 | 91 | 92 | ./package.json 93 | 94 | ```js 95 | { 96 | // ... 97 | "scripts": { 98 | "build": "npm run build:untouched && npm run build:optimized", 99 | "build:untouched": "./node_modules/assemblyscript/bin/asc src/index.ts -t dist/module.untouched.wat -b dist/module.untouched.wasm --validate --sourceMap --measure", 100 | "build:optimized": "./node_modules/assemblyscript/bin/asc src/index.ts -t dist/module.optimized.wat -b dist/module.optimized.wasm --validate --sourceMap --measure --optimize" 101 | 102 | // ... 103 | }, 104 | } 105 | ``` 106 | 107 | 在项目根目录开始执行编译 108 | 109 | ```sh 110 | npm run build 111 | ``` 112 | 113 | 后面会在 `./dist/` 目录下产生编译后的几种 `wasm` 文件格式 114 | 115 | ```sh 116 | ├── dist 117 | │   ├── module.optimized.wasm 118 | │   ├── module.optimized.wasm.map 119 | │   ├── module.optimized.wat 120 | │   ├── module.untouched.wasm 121 | │   ├── module.untouched.wasm.map 122 | │   └── module.untouched.wat 123 | ``` 124 | 125 | ### Node.js 使用 126 | 127 | 在 `./example/node/module.js` 文件中,封装`wasm`的`CommonJS`使用模块 128 | 129 | ```js 130 | const fs = require('fs'); 131 | const path = require('path'); 132 | 133 | const wasmFile = fs.readFileSync(path.join(__dirname, '..', '..', './dist/module.optimized.wasm')) 134 | 135 | const wasm = new WebAssembly.Module(wasmFile, {}); 136 | 137 | module.exports = new WebAssembly.Instance(wasm, { 138 | env: { 139 | memoryBase: 0, 140 | tableBase: 0, 141 | memory: new WebAssembly.Memory({ 142 | initial: 256, 143 | maximum: 512, 144 | }), 145 | table: new WebAssembly.Table({ 146 | initial: 0, 147 | maximum: 0, 148 | element: 'anyfunc', 149 | }), 150 | abort: console.log, 151 | }, 152 | }).exports; 153 | ``` 154 | 155 | Node.js 使用 156 | 157 | ```js 158 | const mod = require('./module'); 159 | 160 | const result = mod.fib(40); 161 | console.log(result); 162 | 163 | ``` 164 | 165 | 执行 Node.js 的 `wasm` 引用 166 | 167 | 输出结果会是 168 | 169 | ```sh 170 | 102334155 171 | ``` 172 | 173 | ### 浏览器使用 174 | 175 | 在 `./example/browser/` 目录下部署浏览器访问的服务 176 | 177 | 178 | ```js 179 | ├── dist 180 | │   ├── module.optimized.wasm 181 | │   └── module.untouched.wasm 182 | ├── example 183 | │   ├── browser 184 | │   │   ├── demo.js 185 | │   │   ├── index.html 186 | │   │   └── server.js 187 | ``` 188 | 189 | 临时浏览器可访问的服务,这里用 `koa` 来搭建服务 190 | 191 | 具体实现在 `./example/browser/server.js` 文件中 192 | 193 | ```js 194 | const Koa = require('koa') 195 | const path = require('path') 196 | const static = require('koa-static') 197 | 198 | const app = new Koa() 199 | 200 | const staticPath = './../../' 201 | 202 | app.use(static( 203 | path.join( __dirname, staticPath) 204 | )) 205 | 206 | app.listen(3000, () => { 207 | console.log('[INFO]: server starting at port 3000'); 208 | console.log('open: http://127.0.0.1:3000/example/browser/index.html') 209 | }) 210 | ``` 211 | 212 | 浏览器使用 `wasm` 模块 213 | 214 | 具体实现在 `./example/browser/demo.js` 文件中实现 215 | 216 | ```js 217 | const $body = document.querySelector('body'); 218 | 219 | fetch('/dist/module.optimized.wasm') 220 | .then(res => res.arrayBuffer()) 221 | .then((wasm) => { 222 | return new WebAssembly.instantiate(wasm, { 223 | env: { 224 | memoryBase: 0, 225 | tableBase: 0, 226 | memory: new WebAssembly.Memory({ 227 | initial: 256, 228 | maximum: 512, 229 | }), 230 | table: new WebAssembly.Table({ 231 | initial: 0, 232 | maximum: 0, 233 | element: 'anyfunc', 234 | }), 235 | abort: console.log, 236 | }, 237 | }) 238 | }).then(mod => { 239 | const result = mod.instance.exports.fib(40); 240 | console.log(result) 241 | }); 242 | ``` 243 | 244 | 访问页面在 `./example/browser/index.html` 中 245 | 246 | ```html 247 | 248 | 249 | 250 | 251 | demo 252 | 253 | 254 | 255 | 256 | 257 | ``` 258 | 259 | 启动服务 260 | 261 | ```sh 262 | node ./example/browser/server.js 263 | ``` 264 | 265 | 浏览器访问页面 266 | 267 | 268 | [http://127.0.0.1:3000/example/browser/index.html](http://127.0.0.1:3000/example/browser/index.html) 269 | 270 | 浏览器会出现结果 271 | 272 | 273 | 274 | ```sh 275 | 102334155 276 | ``` 277 | 278 | 279 | ## 性能测试 280 | 281 | ### Node.js 对比测试 282 | 283 | 284 | ```js 285 | const mod = require('./module'); 286 | 287 | const start = Date.now(); 288 | mod.fib(40) 289 | // 打印 Node.js 环境下 wasm 计算 斐波那契数列 参数为40 的耗时结果 290 | console.log(`nodejs-wasm time consume: ${Date.now() - start} ms`) 291 | 292 | // 原生Node.js实现的 斐波那契数列 函数 293 | function pureFib(num) { 294 | if (num === 1 || num === 2) { 295 | return 1; 296 | } else { 297 | return pureFib(num - 1) + pureFib(num - 2) 298 | } 299 | } 300 | 301 | 302 | const startPure = Date.now() 303 | pureFib(40); 304 | // 打印 Nodejs环境下 原生js 计算 斐波那契数列 参数为40 的耗时结果 305 | console.log(`nodejs-js time consume: ${Date.now() - startPure} ms`) 306 | ``` 307 | 308 | #### 测试结果 309 | 310 | ![wasm-001](./image/wasmscript-02.jpg) 311 | 312 | 313 | - Node.js环境下,原生js 执行耗时 `833 ms` 314 | - Node.js环境下,wasm 执行耗时 `597 ms` 315 | - 对比下来,wasm 计算`斐波那契数列` 比 js 执行快了接近 `30%` 316 | 317 | ### 浏览器对比测试 318 | 319 | ```js 320 | const $body = document.querySelector('body'); 321 | 322 | fetch('/dist/module.optimized.wasm') 323 | .then(res => res.arrayBuffer()) 324 | .then((wasm) => { 325 | return new WebAssembly.instantiate(wasm, { 326 | env: { 327 | memoryBase: 0, 328 | tableBase: 0, 329 | memory: new WebAssembly.Memory({ 330 | initial: 256, 331 | maximum: 512, 332 | }), 333 | table: new WebAssembly.Table({ 334 | initial: 0, 335 | maximum: 0, 336 | element: 'anyfunc', 337 | }), 338 | abort: console.log, 339 | }, 340 | }) 341 | }).then(mod => { 342 | 343 | const start = Date.now(); 344 | mod.instance.exports.fib(40); 345 | const logWasm = `browser-wasm time consume: ${Date.now() - start} ms`; 346 | $body.innerHTML = $body.innerHTML + `

${logWasm}

` 347 | // 打印 浏览器环境下 wasm 计算 斐波那契数列 参数为40 的耗时结果 348 | console.log(logWasm) 349 | }); 350 | 351 | 352 | // 打印 浏览器环境下 原生js 计算 斐波那契数列 参数为40 的耗时结果 353 | function pureFib(num) { 354 | if (num === 1 || num === 2) { 355 | return 1; 356 | } else { 357 | return pureFib(num - 1) + pureFib(num - 2) 358 | } 359 | } 360 | const startPure = Date.now() 361 | pureFib(40); 362 | const logPure = `browser-js time consume: ${Date.now() - startPure} ms`; 363 | $body.innerHTML = $body.innerHTML + `

${logPure}

` 364 | console.log(logPure); 365 | ``` 366 | 367 | 368 | #### 测试结果 369 | 370 | ![wasm-002](./image/wasmscript-03.jpg) 371 | 372 | 373 | - Chrome浏览器环境下,原生js 执行耗时 `884 ms` 374 | - Chrome浏览器环境下,wasm 执行耗时 `612 ms` 375 | - 对比下来,wasm 计算`斐波那契数列` 比 js 执行快了也是接近 `30%` 376 | 377 | 378 | 从上述 Node.js 和 Chrome 环境下运行 `wasm` 和 原生`js` 的对比中,`wasm`的在高频计算的场景下,耗时的确是比原生`js`低,同时都是接近 `30%` 的计算性能提升。 379 | 380 | 381 | ## 参考资料 382 | 383 | - [1] [IBM开发者文档: WebAssembly 现状与实战](https://www.ibm.com/developerworks/cn/web/wa-lo-webassembly-status-and-reality/index.html) 384 | - [2] [奇舞周刊: 20分钟上手 webAssembly](https://juejin.im/post/5b7a3f3cf265da43606e9109) 385 | - [3] [cunzaizhuyi: WebAssembly.LinkError()报错](https://cunzaizhuyi.github.io/WebAssembly-LinkError/) -------------------------------------------------------------------------------- /2019/08/image/wasmscript-01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2019/08/image/wasmscript-01.jpg -------------------------------------------------------------------------------- /2019/08/image/wasmscript-02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2019/08/image/wasmscript-02.jpg -------------------------------------------------------------------------------- /2019/08/image/wasmscript-03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2019/08/image/wasmscript-03.jpg -------------------------------------------------------------------------------- /2019/11/04.md: -------------------------------------------------------------------------------- 1 | ## 前言 2 | 经常用系统终端进行技术开发的过程会看到一些终端的动画效果,例如这个 3 | ![002-progress](./image/cmd-01.gif) 4 | 5 | 最近刚好好奇的了解了一下,才知道实现这个能力是利用了终端支持的 `ANSI转义序列`,任何语言只要能调用终端的标准输入/输出(`stdin/stdout`),都可以直接使用`ANSI转义序列`的规范制作对应的终端动画。 6 | 7 | ## 什么是ANSI 8 | 9 | ANSI是一种字符代码,为使计算机支持更多语言,通常使用 `0x00~0x7f` 范围的1个字节来表示 1个英文字符。超出此范围的使用`0x80~0xFFFF`来编码,即扩展的ASCII编码 [1] 10 | 11 | ## 什么是ANSI转义序列 12 | 13 | ANSI转义序列是一种带内信号的转义序列标准, 相关视频终端控制光标屏幕位置、文本显示、显示样式等操作。[2] 14 | 15 | ## 历史 [2] 16 | 17 | ### 规范历史 18 | 19 | - 每个计算机显示终端制造商都有自己的转义序列规范 20 | - 为了解决转义序列的不统一带来的问题,1976年通过的第一版ANSI标准——ECMA-48 21 | - “ANSI转义序列”这个名词可以追溯到 1979年ANSI采用ANSI X3.64 22 | - ANSI X3L2委员会与ECMA委员会TC1制定的标准合并为ISO 6429的国际标准 23 | - 经历过多年改版,从1991年开始的第5版被ISO和IEC用作标准规范 24 | - 1994年,ANSI取消了其标准,后续均以ISO标准为主 25 | 26 | ### 系统支持历史 27 | 28 | - 微软的DOS 1.x不支持ANSI或任何其他转义序列 29 | - DOS 2.0引入了添加设备驱动程序来支持ANSI转义序列的功能(基于ANSI.SYS标准),由于性能问题,使用率还是很低 30 | - Win32控制台不支持ANSI转义序列,只能借助其他软件辅助使用ANSI能力 31 | - Window9x/Window XP/Window7/Window8 均不直接支持ANSI转义序列 32 | - 2016年,Windows 10 开始在控制台应用程序中支持ANSI转义序列 33 | 34 | 35 | ## 基本使用 36 | > 注意,需要在Linux/MacOS 的环境下学习和使用 37 | 38 | ### 输出带前景色的文本 39 | ![000_red_front_color](./image/cmd-02.gif) 40 | 41 | 42 | 43 | ```sh 44 | echo -e "\x1b[31m helloworld\x1b[0m" 45 | ``` 46 | 47 | - `\x1b[31m` 后面的文本字体色为红色,其中`31m`就是代表`ANSI`中的红色 48 | - `\x1b[0m` 关闭所有属性 49 | 50 | 51 | - 前景色范围在 `40 - 49` 52 | 53 | |ANSI|后景色| 54 | |----|----| 55 | |40|黑色| 56 | |41|红色| 57 | |42|绿色| 58 | |43|黄色| 59 | |44|蓝色| 60 | |45|紫色| 61 | |46|深绿色| 62 | |47|白色| 63 | 64 | 65 | ### 输出带后景色的文本 66 | ![000_red_back_color](./image/cmd-03.gif) 67 | 68 | 69 | 70 | ```sh 71 | echo -e "\x1b[41m helloworld\x1b[0m" 72 | ``` 73 | - `\x1b[41m` 后面的后景色为红色,其中`41m`就是代表`ANSI`中的红色 74 | - `\x1b[0m` 关闭所有属性 75 | 76 | 77 | - 文本颜色范围在 `40 - 49` 78 | 79 | |ANSI|后景色| 80 | |----|----| 81 | |30|黑色| 82 | |31|红色| 83 | |32|绿色| 84 | |33|黄色| 85 | |34|蓝色| 86 | |35|紫色| 87 | |36|深绿色| 88 | |37|白色| 89 | 90 | 91 | ### 输出闪烁的文本 92 | 93 | ![000_flash](./image/cmd-04.gif) 94 | 95 | ```sh 96 | echo -e "\x1b[5m helloworld\x1b[0m" 97 | ``` 98 | - `\x1b[5m` 后面的文本闪烁 99 | - `\x1b[0m` 关闭所有属性 100 | 101 | 102 | ## 广泛支持的 ANSI 转义序列 103 | 104 | |ANSI|功能|备注| 105 | |----|----|----| 106 | |\x1b[0m|重置|关闭所有属性| 107 | |\x1b[1m |粗体或强度|-| 108 | |\x1b[4m|下划线|-| 109 | |\x1b[5m|闪烁|低于每分钟150次| 110 | |\x1b[7m|反显|前景色与背景色交换| 111 | |\x1b[8m |隐藏|-| 112 | |\x1b[10m |默认字体|-| 113 | |\x1b[11m |替代字体|范围(11-19)| 114 | |\x1b[22m |正常颜色或强度|-| 115 | |\x1b[23m |非斜体、非尖角体|-| 116 | |\x1b[24m |关闭下划线|去掉单双下划线| 117 | |\x1b[25m |关闭闪烁|-| 118 | |\x1b[27m |关闭反显|-| 119 | |\x1b[28m |关闭隐藏|-| 120 | |\x1b[29m |关闭划除|-| 121 | |\x1b[30m|前景色(黑色)|颜色范围(30-37: 黑-红-绿-黄-蓝-紫-深绿-白)| 122 | |\x1b[38m|RGB前景色|参数为5;n或2;r;g;b| 123 | |\x1b[39m|默认前景色|| 124 | |\x1b[40m|后景色(黑色)|颜色范围(40-47: 黑-红-绿-黄-蓝-紫-深绿-白)| 125 | |\x1b[48m|RGB后景色|参数为5;n或2;r;g;b| 126 | |\x1b[49m|默认后景色|| 127 | |\x1b[53m|上划线|| 128 | |\x1b[1A|光标上移1行|上移动n行,就是\x1b[nA| 129 | |\x1b[1B|光标下移1行|下移动n行,就是\x1b[nB| 130 | |\x1b[1C|光标右移1行|右移动n行,就是\x1b[nC| 131 | |\x1b[1D|光标左移1行|左移动n行,就是\x1b[nD| 132 | |\x1b[2J|清屏|-| 133 | |\x1b[K|清除光标至行尾内容|-| 134 | |\x1b[?25l |隐藏光标|-| 135 | |\x1b[?25h|显示光标|-| 136 | |\x1b[s|保存光标位置|-| 137 | |\x1b[u|恢复光标位置|-| 138 | 139 | ## Node.js例子 140 | 141 | ### 例子一: 实现一个简单的控制台进度条 142 | 143 | #### 实现效果 144 | 145 | ![001_progress_simple](./image/cmd-05.gif) 146 | 147 | #### 实现源码 148 | 149 | ```js 150 | const process = require("process"); 151 | 152 | const frame = "▊"; 153 | 154 | /** 155 | * 等待操作 156 | * @param {number} time 157 | */ 158 | async function sleep(time = 10) { 159 | return new Promise((resolve) => { 160 | setTimeout(() => { resolve(); }, time) 161 | }) 162 | } 163 | 164 | /** 165 | * 换行操作 166 | */ 167 | function printNewLine() { 168 | process.stdout.write(`\x1b[0C \x1b[K\r\n`); 169 | } 170 | 171 | 172 | class Progress { 173 | 174 | async run(time = 1000, percent = 100, modulo = 2) { 175 | const count = Math.floor(percent / modulo); 176 | for (let i = 0; i < count; i ++) { 177 | await sleep(time / count); 178 | this._print(); 179 | } 180 | printNewLine(); 181 | } 182 | 183 | _print() { 184 | // 控制打印进度条每帧的内容 185 | // 重复使用的时候打印开始会从上次结束的位置开始 186 | process.stdout.write(`\x1b[K${frame}`); 187 | } 188 | 189 | } 190 | 191 | const progress = new Progress(); 192 | progress.run(1000, 100); 193 | ``` 194 | 195 | ### 例子二: 实现一个带背景和数字变化的进度条 196 | 197 | #### 实现效果 198 | 199 | ![002-progress](./image/cmd-06.gif) 200 | 201 | 202 | #### 实现源码 203 | 204 | ```js 205 | const process = require("process"); 206 | 207 | /** 208 | * 等待操作 209 | * @param {number} time 210 | */ 211 | async function sleep(time = 10) { 212 | return new Promise((resolve) => { 213 | setTimeout(() => { 214 | resolve(); 215 | }, time) 216 | }) 217 | } 218 | 219 | /** 220 | * 换行操作 221 | */ 222 | function printNewLine() { 223 | process.stdout.write(`\x1b[0C \x1b[K\r\n`); 224 | } 225 | 226 | /** 227 | * 清行操作 228 | * @param {number} len 原有文本长度 229 | */ 230 | function clearLine(len) { 231 | process.stdout.write(`\x1b[${len}D`); 232 | } 233 | 234 | const frame = "▓"; 235 | const backgroundFrame = "░"; 236 | 237 | class Progress { 238 | 239 | async run(time = 1000, percent = 100, modulo = 2) { 240 | const count = Math.floor(percent / modulo); 241 | 242 | for (let i = 0; i < count; i ++) { 243 | await sleep(time / count); 244 | const progressLength = this._printProcess(i, count, modulo); 245 | if (i < count - 1) { 246 | clearLine(progressLength); 247 | } 248 | } 249 | printNewLine(); 250 | } 251 | 252 | /** 253 | * 打印进度条 254 | * @param {number} index 进度条当前帧索引位置 255 | * @param {number} count 进度条帧数 256 | * @param {number} modulo 进度条帧率变化模数 257 | */ 258 | _printProcess(index, count, modulo) { 259 | let progressLength = 0; 260 | for (let i = 0; i < count; i ++) { 261 | if (i <= index) { 262 | progressLength += this._print(frame); 263 | } else { 264 | progressLength += this._print(backgroundFrame); 265 | } 266 | } 267 | 268 | let percentNum = (index + 1) * modulo; 269 | percentNum = Math.min(100, percentNum); 270 | percentNum = Math.max(0, percentNum); 271 | progressLength += this._print(` ${percentNum}%`); 272 | return progressLength; 273 | } 274 | 275 | /** 276 | * 打印终端文本 277 | * @param {string} text 文本 278 | * @param {number} leftMoveCols 光标左移动位数 279 | */ 280 | _print(text, leftMoveCols) { 281 | let code = `\x1b[K${text}`; 282 | if (leftMoveCols >= 0) { 283 | code = `\x1b[${leftMoveCols}D\x1b[K${text}`; 284 | } 285 | process.stdout.write(code); 286 | return code.length; 287 | } 288 | 289 | } 290 | 291 | const progress = new Progress(); 292 | progress.run(1000, 100); 293 | ``` 294 | 295 | 296 | ### 例子三: 实现一个等待Loading效果 297 | 298 | #### 实现效果 299 | 300 | ![003-loading](./image/cmd-07.gif) 301 | 302 | 303 | #### 实现源码 304 | 305 | ```js 306 | const process = require("process"); 307 | 308 | 309 | /** 310 | * 等待操作 311 | * @param {number} time 312 | */ 313 | async function sleep(time = 10) { 314 | return new Promise((resolve) => { 315 | setTimeout(() => { 316 | resolve(); 317 | }, time) 318 | }) 319 | } 320 | 321 | /** 322 | * 换行操作 323 | */ 324 | function printNewLine() { 325 | process.stdout.write(`\x1b[0C \x1b[K\r\n`); 326 | } 327 | 328 | 329 | class Loading { 330 | 331 | constructor() { 332 | this._intervalId = -1; 333 | this._beforeLength = 0; 334 | this._isDuringLoading = false; 335 | this._loadingIndex = 0; 336 | } 337 | 338 | /** 339 | * Loading动画开始操作 340 | * @param {number} speed Loading每帧速度 341 | */ 342 | start(speed = 100) { 343 | if (this._isDuringLoading === true) { 344 | return; 345 | } 346 | this._intervalId = setInterval(() => { 347 | this._printLoadingText(); 348 | }, speed); 349 | this._isDuringLoading = true; 350 | } 351 | 352 | /** 353 | * Loading 停止操作 354 | */ 355 | stop() { 356 | clearInterval(this._intervalId); 357 | printNewLine(); 358 | this._isDuringLoading = false; 359 | this._loadingIndex = 0; 360 | } 361 | 362 | /** 363 | * 打印 Loading 操作 364 | */ 365 | _printLoadingText() { 366 | const max = 10; 367 | if (this._loadingIndex >= max) { 368 | this._loadingIndex = 0; 369 | } 370 | const charList = []; 371 | for (let i = 0; i < max; i++) { 372 | if (i === this._loadingIndex) { 373 | charList.push('==>'); 374 | } else { 375 | charList.push(' '); 376 | } 377 | } 378 | 379 | const loadingText = [...['['], ...charList, ...[']']].join(''); 380 | this._print(loadingText); 381 | this._loadingIndex += 1; 382 | } 383 | 384 | _print(text) { 385 | const code = `\x1b[${this._beforeLength}D \x1b[K ${text}` 386 | process.stdout.write(code); 387 | this._beforeLength = code.length; 388 | } 389 | } 390 | 391 | async function main() { 392 | const loading = new Loading(); 393 | loading.start(); 394 | await sleep(2000); 395 | loading.stop(); 396 | } 397 | 398 | main(); 399 | ``` 400 | 401 | 402 | 403 | ## 参考资料 404 | [1] 百度百科: ANSI 405 | [2] 维基百科: ANSI转义序列 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | -------------------------------------------------------------------------------- /2019/11/image/cmd-01.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2019/11/image/cmd-01.gif -------------------------------------------------------------------------------- /2019/11/image/cmd-02.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2019/11/image/cmd-02.gif -------------------------------------------------------------------------------- /2019/11/image/cmd-03.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2019/11/image/cmd-03.gif -------------------------------------------------------------------------------- /2019/11/image/cmd-04.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2019/11/image/cmd-04.gif -------------------------------------------------------------------------------- /2019/11/image/cmd-05.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2019/11/image/cmd-05.gif -------------------------------------------------------------------------------- /2019/11/image/cmd-06.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2019/11/image/cmd-06.gif -------------------------------------------------------------------------------- /2019/11/image/cmd-07.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2019/11/image/cmd-07.gif -------------------------------------------------------------------------------- /2020/01/07.md: -------------------------------------------------------------------------------- 1 | 2 | # 枸杞水背后的2019年业余技术总结 3 | 4 | 人到了中年,经常忙里偷闲多喝了两口枸杞水就容易上头,伤春悲秋地胡思乱想。想着2019年已经结束了,不能免俗地总结一下2019年的不羁的业余学习总结。 5 | 6 | 为啥是只做业余时间的总结?是因为工作是生活所迫,业余学习才是兴趣驱动。在很多时候,业余时间才能静下来看书写字,丝毫没有工作需求那种滚滚烟火的纷扰。用句鸡汤点的表达,业余学习才是超越别人成长的秘诀。 7 | 8 | ## 重新变“废”为宝 9 | 10 | 2019这一年,花了最多心思是在思考怎样把以前学到过的但是没用过知识做个落地使用,例如大学教过一堆图像处理的知识,但是受限于工作的场景,很少有用武之地。因为对于大学学到的知识基本都在吃灰感到可惜,所以在花了三个多月的构思和编程,将`TypeScript`和“荒废”已久的图像处理知识结合,写了一个H5美图工具 `pictool`[1]。 11 | 12 | ![pictool-ui-adjust](./image/pictool-01.gif) 13 | 14 | ![pictool-ui-effect](./image/pictool-02.gif) 15 | 16 | ![pictool-ui-process](./image/pictool-03.gif) 17 | 18 | 19 | 写了这个H5美图工具,也顺便熟悉了 `TypeScript`。同时感慨,工作了三四年,才第一次用`TypeScript`写了一个完整的项目。 20 | 21 | 很多时候都在想大学或者平时学到的东西有什么用?其实换个思路来看待这个问题,应该是我目前想实现的事物,在我学到的知识哪些可以用来给达到目的铺路。 22 | 23 | ## 重新理解“一万小时定律” 24 | 25 | 26 | > “人们眼中的天才之所以卓越非凡,并非天资超人一等,而是付出了持续不断的努力。1万小时的锤炼是任何人从平凡变成世界级大师的必要条件。” ——出自 马尔科姆·格拉德韦尔《异类》[2] 27 | 28 | > 美国两位畅销书作家,丹尼尔·科伊尔的《一万小时天才理论》与马尔科姆·格拉德韦尔的一本类似“成功学”的书《异类》,其核心都是“一万小时定律”,就是不管你做什么事情,只要坚持一万小时,基本上都可以成为该领域的专家。[2] 29 | 30 | 31 | “一万小时定律” 这个词汇我是在刚毕业不久合租的室友跟我提到的一个定律,意思是任何人把一件事件做一万小时,就能成为这个领域的大师。当时“年轻气盛”的我是对于这种“成功学鸡汤”是嗤之以鼻的,后来在个人的技术成长遇到瓶颈的时候,又“真香”地喝起这碗鸡汤。 32 | 33 | 在前端快餐式的技术潮流中,很容易给人一种错觉,就是不管任何技术,只要找到官方文档和多写demo踩多几次坑就能掌握,然后就不知道下一步该怎么学习技术陷入迷茫。 34 | 35 | 这个问题在我工作了两三年后就变得更加明显。一开始是在技术快餐的错觉中写了《Koa2进阶学习笔记》、《Koa.js设计模式学习笔记》等开源书籍后续就陷入技术快餐式学习带来的迷茫期。 36 | 37 | 在2019年为了跳出这种快餐式的迷茫状态,刚好遇上 Deno这个技术的起步,就选择在无文档啃源码去学习。换个方式去学习一门技术,从2019年初的时候,就开始写《Deno进阶开发笔记》,跟着Deno的一年多的官方推翻重写等迭代,去维护这本开源书。 38 | 39 | 一开始的每天一小时读源码维护《Deno进阶开发笔记》,到后续工作太忙了变成每周至少4小时的学习去迭代,在这2019年里一共提交了370多个commit迭代。重新捧起“一万小时定律” 这碗鸡汤,放下技术快餐,静下心去沉淀。 40 | 41 | ## 具体业余产出 42 | 43 | - 《Deno进阶开发笔记》 44 | 45 | - 《Canvas开发笔记》 46 | 47 | - `Pictool` H5美图库 48 | 49 | 其中最开心的是边学习`Deno`时候边入门`TypeScript`,然后基于`TypeScript`写了`Pictool`这个H5美图库,也顺便写了 《Canvas开发笔记》 这本开源书。今年的业余学习技术点都是相辅相成,一气呵成。 50 | 51 | 感触最深的是,写文档文章比写代码还累,还要费神。经常遇到提笔忘词的窘境,怎样组织语言表达出技术的能力的确有待提高。 52 | 53 | 54 | ## 技术焦虑的反思 55 | 56 | 人总会走弯路 57 | 58 | - 人总会被眼前看到的,做出当时认为正确的选择 59 | 60 | 但是遇到弯路最好要当断则断 61 | 62 | - 中途验证遇到阻力,就会开始怀疑当初的选择 63 | - 要么一条道走到黑,要么及时止损 64 | 65 | 66 | ## 技术外的故事 67 | 68 | ### 程序员的思维陷阱 69 | 70 | - 习惯了机器输入输出的确定性 71 | - 容易忽略了生活的不确定性 72 | 73 | ### 学会跳出“代码民工陷阱”[3] 74 | - 表达和沟通更重要 75 | - 把想到的东西表达出来 76 | - 把能表达出来的东西用文字简练记录下来 77 | 78 | ## 认识了更多有趣的人 79 | 80 | 认识了一个10年的Java+Android开发的小伙伴,离职去卖保险。在一杯咖啡的交谈中,也发现了代码外的其他世界,虽然答应了这位小伙伴会写一篇交谈后的心得,苦于工作繁忙,放了好几次鸽子。 81 | 82 | 认识了一个工作4年多的iOS开发程序员,离职去“体验世界”,去记录互联网浪潮下的背后故事。他的朋友圈记录着驾车从杭州一路南下,走访互联网浪潮下的各行各业。有国企电视台的工作人员、有电子厂里的厂哥厂妹,也有传统电子厂的硬件工程师,看他记录着各个一二三线城市在互联网影响下的人生百态。 83 | 84 | ## 后记 85 | 86 | 当你看到这段文字时,十分感谢你读完这篇罗里吧嗦的总结,也难为你能看完这篇毫无条理的文章。在2020年,我也懒得去展望给自己立flag,在最后祝各位读者工作顺利,天天开心! 87 | 88 | ## 参考资料 89 | 90 | - [1][大深海|用TypeScript写了个低配版H5美图工具](https://github.com/chenshenhai/blog/issues/37) 91 | - [2][百度百科: 一万小时定律](https://baike.baidu.com/item/%E4%B8%80%E4%B8%87%E5%B0%8F%E6%97%B6%E5%AE%9A%E5%BE%8B/8255848) 92 | - [3][不要自称为程序员|阮一峰的网络日志](https://www.ruanyifeng.com/blog/2011/10/dont_call_yourself_a_programmer.html) -------------------------------------------------------------------------------- /2020/01/image/pictool-01.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2020/01/image/pictool-01.gif -------------------------------------------------------------------------------- /2020/01/image/pictool-02.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2020/01/image/pictool-02.gif -------------------------------------------------------------------------------- /2020/01/image/pictool-03.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2020/01/image/pictool-03.gif -------------------------------------------------------------------------------- /2020/03/09.md: -------------------------------------------------------------------------------- 1 | # VSCode调试Rust代码 2 | 3 | ## 安装环境 4 | 5 | - 安装 Rust 环境 [https://www.rust-lang.org/tools/install](https://www.rust-lang.org/tools/install) 6 | - 安装 VSCode [https://code.visualstudio.com/](https://code.visualstudio.com/) 7 | - 安装 VSCode 的 Rust 扩展 [https://marketplace.visualstudio.com/items?itemName=rust-lang.rust](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust) 8 | - 安装 VSCode 的 Rust 调试环境 9 | - Windows 系统 [https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) 10 | - Linux/Mac 系统 [https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb) 11 | 12 | ## 开始调试 13 | 14 | ### 初始化调试项目 15 | 16 | ```sh 17 | mkdir debug_rs 18 | 19 | cd debug_rs 20 | 21 | cargo init 22 | ``` 23 | 24 | ### 编辑调试代码 25 | 26 | 在 `./debug_rs/src/main.rs` 文件上写入待调试代码 27 | 28 | ```rust 29 | fn add(x: i32, y: i32) -> i32 { 30 | return x + y; 31 | } 32 | 33 | fn main() { 34 | let x = 123; 35 | let y = 456; 36 | let result = add(x, y); 37 | 38 | let result = add(result, result); 39 | println!("result = {}", result); 40 | } 41 | ``` 42 | 43 | ### 调试配置 44 | 45 | - 选择 `调试 -> 添加配置` 46 | 47 | ![debug_rs_001](./image/vscode-debug-rust-01.jpg) 48 | 49 | - 会初始化配置文件 50 | 51 | ![debug_rs_002](./image/vscode-debug-rust-02.jpg) 52 | 53 | - 打断点 54 | 55 | ![debug_rs_003](./image/vscode-debug-rust-03.jpg) 56 | 57 | - 调试结果 58 | 59 | ![debug_rs_debug](./image/vscode-debug-rust-04.jpg) 60 | -------------------------------------------------------------------------------- /2020/03/image/vscode-debug-rust-01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2020/03/image/vscode-debug-rust-01.jpg -------------------------------------------------------------------------------- /2020/03/image/vscode-debug-rust-02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2020/03/image/vscode-debug-rust-02.jpg -------------------------------------------------------------------------------- /2020/03/image/vscode-debug-rust-03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2020/03/image/vscode-debug-rust-03.jpg -------------------------------------------------------------------------------- /2020/03/image/vscode-debug-rust-04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2020/03/image/vscode-debug-rust-04.jpg -------------------------------------------------------------------------------- /2021/09/09.md: -------------------------------------------------------------------------------- 1 | ## 休息3个月,基于Canvas写了个简单的前端绘图JavaScript库 2 | 3 | ## 前言 4 | 不知不觉,有一年没发布公众号文章了,这几天刚好有时间,就抽空写了这篇文章。本文主要讲解我裸辞3个月以来,利用在家休息的空隙时间,开发了一个前端绘图的JavaScript库 iDraw.js。 5 | ## 为啥要开发这个东西 6 | 7 | - 为了实现用纯Canvas能力结合图片、HTML和SVG作为素材 来做绘图工作。 8 | - 为了试试看单独用 Canvas的2D(二维)API 能作出怎样的多种素材绘图工作。 9 | - 最后是为了试试看尽量用 Canvas做素材的操控操作。 10 | - 目前还没发现类似的可以操控素材绘图的Canvas开源框架,所以就想自己开发一个。 11 | ## iDraw.js简介 12 | 13 | - 一个基于纯Canvas来实现绘图和操控素材能力的JavaScript库。 14 | - 可以基于 iDraw.js 进行扩展自定义开发各种可视化操控应用,这里可以参考 idraw.js.org/studio/ 案例 15 | ## iDraw.js有哪些功能 16 | 17 | - 支持绘制文字、矩形、圆形、图片、HTML片段和SVG片段 绘图元素 18 | - 绘制文字 19 | ![element-text](./../images/element-text.gif) 20 | - 绘制矩形 21 | ![element-rect](./../images/element-rect.gif) 22 | - 绘制圆形 23 | ![element-circle](./../images/element-circle.gif) 24 | - 绘制图片 25 | ![element-image](./../images/element-image.gif) 26 | - 绘制HTML片段 27 | ![element-html](./../images/element-html.gif) 28 | - 绘制SVG片段 29 | ![element-svg](./../images/element-svg.gif) 30 | 31 | - 支持基于Canvas的可视化操作 32 | 33 | ## iDraw.js有哪些特点 34 | 35 | 36 | - 可以绘制文字、矩形、圆形、图片、HTML片段和SVG文件,并且作为绘图元素。 37 | - 可以直接在Canvas操控以上绘图元素,不用担心CSS和DOM变化的污染问题。 38 | - Canvas操控绘制,并且是所见即所得可以直接导出绘制的图片结果。 39 | - 由于可视化操控和图片生成都是基于Canvas,可以尽量减少绘图的浏览器兼容问题。 40 | 41 | ## 原理介绍 42 | 43 | - 基于数据驱动来绘制Canvas的图画 44 | - 基于 requestAnimationFrame 来控制数据变化时候,Canvas的重绘处理 45 | - 内置一个前端并发队列来处理 图片、HTML和SVG的图片化转换渲染 46 | 47 | 48 | ## 实际使用案例 49 | 50 | - 一个基于 iDraw.js 实现的UI可视化绘图 51 | - @idraw/studio 的实现 52 | 53 | ![element-svg](./../images/snapshot-001.png) 54 | 55 | ![element-svg](./../images/snapshot-002.png) 56 | 57 | ![element-svg](./../images/snapshot-003.png) 58 | 59 | ![element-svg](./../images/snapshot-004.png) 60 | 61 | ![element-svg](./../images/snapshot-005.png) 62 | 63 | ## 其他 64 | 65 | - GitHub地址: [github.com/idrawjs/](https://github.com/idrawjs/) 66 | - 官方网站: [idraw.js.org/](https://idraw.js.org/) 67 | - Playground API示例: [idraw.js.org/playground](https://idraw.js.org/playground/) 68 | - 基于iDraw.js的Studio实际案例: [idraw.js.org/studio](https://idraw.js.org/studio/) 69 | -------------------------------------------------------------------------------- /2021/images/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2021/images/.DS_Store -------------------------------------------------------------------------------- /2021/images/element-circle.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2021/images/element-circle.gif -------------------------------------------------------------------------------- /2021/images/element-html.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2021/images/element-html.gif -------------------------------------------------------------------------------- /2021/images/element-image.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2021/images/element-image.gif -------------------------------------------------------------------------------- /2021/images/element-rect.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2021/images/element-rect.gif -------------------------------------------------------------------------------- /2021/images/element-svg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2021/images/element-svg.gif -------------------------------------------------------------------------------- /2021/images/element-text.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2021/images/element-text.gif -------------------------------------------------------------------------------- /2021/images/snapshot-001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2021/images/snapshot-001.png -------------------------------------------------------------------------------- /2021/images/snapshot-002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2021/images/snapshot-002.png -------------------------------------------------------------------------------- /2021/images/snapshot-003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2021/images/snapshot-003.png -------------------------------------------------------------------------------- /2021/images/snapshot-004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2021/images/snapshot-004.png -------------------------------------------------------------------------------- /2021/images/snapshot-005.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2021/images/snapshot-005.png -------------------------------------------------------------------------------- /2024/01/01.md: -------------------------------------------------------------------------------- 1 | # `iDraw.js`的v0.4重构,大深海的2023年度总结 2 | 3 | ## 前言 4 | 5 | 2023年已经结束,是时候对过去一年做个总结。 6 | 7 | 话说熟悉我公众号的朋友们,应该有印象,就是我已经有两年没写任何文章了。在此,简单唠嗑一下这两年历程,两年前(2021年)裸辞休息,在写完 `iDraw.js` 第一版并开源后,我就回归职场了。在回归职场后,由于业余时间有限,加上对 `iDraw.js` 定位理解飘忽不定,经历过怀疑、弃坑、不舍和重构这四个过程,最终在2023年上半年开始了对 的重构。 8 | 9 | ## 重构`iDraw.js`是好玩的事情 10 | 11 | 12 | 在过去2023年里,利用业余零碎的时间,对`iDraw.js`重写、推翻、再重写,让 `iDraw.js` 的功能更上一个层次。至于回归职场后的工作内容,其实和普通程序员工作内容大同小异,乏善可陈,还不如自己业余时间沉淀的 `iDraw.js` 更好玩,更有成就感。 13 | 14 | 15 | 为啥`iDraw.js`对于我来说更好玩,这是因为`iDraw.js`是在我人生第一次裸辞长假中创作的开源产品,全凭技术兴趣驱动,没任何KPI压力,没掺杂任何职场气息。更重要是没有职场哪种“指指点点PUA”、“技术价值怀疑”和“方案选型撕逼”。 16 | 17 | 在开发和开源`iDraw.js`过程中,主打的就是“我开心,我就乐意这么写代码”。因此,我的2023总结,就是总结我开心的事情,有成就感的事情,也就是2023年的业余时间下,对`iDraw.js`进行v0.4版本的重构。 18 | 19 | 20 | 21 | 这是2023年五月份错峰出游,面朝大海写代码拍摄的海景,在假期里,在好玩的地方,面对好玩的景色,写好玩的代码,就是最好玩的事情。 22 | 23 | 也就是在这次出游后,加速了`iDraw.js`的重构步骤,到了年底,`iDraw.js`基本重构完毕,v0.4版本也初步完成,目前进入beta阶段。 24 | 25 | ## `iDraw.js` v0.4 来了,让Web绘图更简单 26 | 27 | 按照惯例,宣传产品需要带个口号,`iDraw.js`就不免俗,也来个口号,就是“让Web绘图更简单”,可能有些新朋友不知道`iDraw.js`是什么,那我也来简单介绍一下。 28 | 29 | 30 | 31 | 32 | 一个普通的Web绘图JavaScript框架,可以用于图片设计,Web网页设计等二维平面设计。 33 | 34 | 35 | 36 | 37 | 也只是一个正在并长期处于发展中的开源工具,从2021年5月提交第一次代码开始至今2年半里,持续迭代和优化。 38 | 39 | ## 为什么要重构`iDraw.js` 40 | 41 | 为什么要重构,主要是v0.3的设计缺陷,导致GitHub和邮箱收到以下几类 42 | - 放大模糊,比如一像素内容放大后会出现模糊 43 | - 画布受限,比如无法类似Figma进行无限画布操作 44 | - 没有标尺,没有标尺可以知道元素的目测位置 45 | - 缺少群组,导致所有元素平铺,管理元素不方便 46 | - 没动画效果,支持绘制动画效果 47 | - 元素匮乏,比如二维码元素、gif动图元素支持等 48 | 49 | 50 | 51 | 52 | 在此反馈的基础上,我也不是全量接受,毕竟开源只是“用爱发电”,况且时间精力有限,我还都是周末等业余空闲时间在维护。因此,我只挑选了符合`iDraw.js`定位“二维平面设计”的内容进行重构,也就是后续`iDraw.js`新增的内容。 53 | 54 | ## `iDraw.js` v0.4带来什么? 55 | 56 | - 无限画布,数据层面上的“无限”,只要在JS的数值范围内,都可以作为尺寸数值。 57 | - 矢量绘制,只要是非图片元素,绘制的内容都可以无损放大,也是`iDraw.js`前几个版本中,社区反馈的方法模糊问题,在v0.4版本中已经得到解决。 58 | - 新增元素类型,新增了路径元素,作为兜底画图操作,基于SVG路径规范,可以绘制出任何形状和内容。 59 | - 新增群组类型,更加方案绘图的元素组织操作。 60 | - 功能中间件化,底层的功能采用中间件的思路,做到可以组装功能。比如,可以组装标尺、缩放和滚动条等功能。 61 | 62 | 63 | 64 | 65 | 以上就是`iDraw.js`的重构后特性功能,这些功能我将集成到 `@idraw/studio` 中,也就是`iDraw.js`的能力展示场所。 66 | 67 | 68 | 69 | ## @idraw/studio 向Figma学习 70 | 71 | 上述内容,提到一个点,就是`iDraw.js`从2021年第一次提交代码到2023年上半年,定位非常不清楚,全靠一腔热血“无脑”写代码。 72 | 73 | 这里就存在一个问题,缺少一个发展方向,容易会让兴趣和热情做无用功。因此,在开发`@idraw/studio`来集成演示`iDraw.js`功能时候,我就把Figma作为学习对象,尽可能地让`@idraw/studio`来复刻实现`Figma`的部分功能。 74 | 75 | 76 | 77 | 78 | 现在`@idraw/studio`已经能初步实现类似`Figma`的网页设计功能,虽然`@idraw/studio`目前处于很原始阶段,但在接下来一年会推动`iDraw.js`持续迭代优化,朝着Figma的特性来作为`iDraw.js`深度开发的方向。 79 | 80 | 81 | 82 | 83 | ## 踩过的坑 84 | 85 | 在开发和开源`iDraw.js`这两年多的过程并不是一帆风顺的。其中遇到各种磕磕碰碰,好在到了2023年底,轻舟已过万重山,现在也能总结一下踩坑的心得。 86 | 87 | ### 时间在精不在多 88 | 89 | 想清楚要实现的逻辑,每周末或节假日抽出2小时实现。每次写代码,就把精神集中控制在两小时内,休闲时间写多代码会产生疲劳感。 90 | 91 | 要时刻保持“兴趣驱动”来做技术开源,不要“为了做而做”,要为了“我高兴”和“有成就感”来做。 92 | 93 | ### 功能先跑起来,优化以后再说 94 | 95 | “技术兴趣”驱动的开源项目,那么天然会自带很多“技术思维”,比如要尽量追求极致的技术性能。但如果过于注重性能优化,将会陷入牛角尖中浪费宝贵的时间和精力。 96 | 97 | 性能优化是个持续的工程,不是一蹴而就,持续的优化失败会带来连续的挫败感,会大大削弱技术动力。 98 | 99 | ### 不要怀疑,兴趣第一 100 | 101 | 在开源维护`iDraw.js`这两年里,被人吐槽是玩具,导致自我怀疑花费业余时间维护有意义吗? 102 | 103 | 发现有更完善的绘图框架,导致怀疑自己重新造轮子有意义吗? 104 | 105 | 在投入大量业余时间维护时候,怀疑是一个很大的阻力,消除阻力的方式就是强调“兴趣”的重要性,因为是兴趣驱动,自己开心就好,别管哪些和兴趣无关的因素。 106 | 107 | 108 | ## 未来计划 109 | 110 | 目前`iDraw.js`还处于v0.4的beta阶段,还有很多待优化的地方,打算在2024年花一年的业余时间,打磨出稳定的 v0.4 版本。 111 | 112 | ## 其它 113 | 114 | - iDraw.js官网 https://idrawjs.github.io/ 115 | - iDraw.js仓库 https://github.com/idrawjs/idraw 116 | - @idraw/studio仓库 https://github.com/idrawjs/studio 117 | - @idraw/studio演示 https://idrawjs.github.io/studio/ 118 | 119 | 120 | -------------------------------------------------------------------------------- /2024/01/images/idraw-playground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2024/01/images/idraw-playground.png -------------------------------------------------------------------------------- /2024/01/images/idraw-studio-002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2024/01/images/idraw-studio-002.png -------------------------------------------------------------------------------- /2024/01/images/idraw-studio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2024/01/images/idraw-studio.png -------------------------------------------------------------------------------- /2024/01/images/idrawjs-banner-zh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2024/01/images/idrawjs-banner-zh.png -------------------------------------------------------------------------------- /2024/01/images/idrawjs-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2024/01/images/idrawjs-banner.png -------------------------------------------------------------------------------- /2024/01/images/idrawjs-doc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2024/01/images/idrawjs-doc.png -------------------------------------------------------------------------------- /2024/01/images/idrawjs-features.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2024/01/images/idrawjs-features.png -------------------------------------------------------------------------------- /2024/01/images/issue.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2024/01/images/issue.jpg -------------------------------------------------------------------------------- /2024/01/images/sea.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/2024/01/images/sea.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 大深海的技术博客 2 | 3 | - 个人博客 [chenshenhai.com](https://chenshenhai.com) 4 | - 个人Github [github.com/chenshenhai](https://github.com/chenshenhai) 5 | - 博客文章备份 [https://github.com/chenshenhai/blog/issues](https://github.com/chenshenhai/blog/issues) 6 | - 只是偶尔记录一下,欢迎在issue中交流开发经验 7 | - 目前在忙着开发 [idraw.js](https://github.com/idrawjs/idraw/), 详情可预览 [@idraw/studio](https://idraw.js.org/studio) 8 | - 主玩前端开发和`Node.js`开发,在学习 `Rust`和 玩 `Deno`,偶尔涉猎Java Web开发、PHP开发 (好久没玩,已经忘光了 ~(~ ̄▽ ̄)~ ) 9 | 10 | ## 我的开源书 11 | 12 | - [《Deno进阶开发笔记》](https://github.com/chenshenhai/deno_note)(不定时更新)🌶🌶🌶 13 | - [《Koa2进阶学习笔记》](https://github.com/chenshenhai/koa2-note) 14 | - [《Koa.js 设计模式-学习笔记》](https://github.com/chenshenhai/koajs-design-note) 15 | - [《Rollup.js 实战学习笔记》](https://github.com/chenshenhai/rollupjs-note) 16 | - [《Canvas开发笔记》](https://github.com/chenshenhai/canvas-note) (不定时更新) 17 | - [《Egg.js 深入浅出学习笔记》](https://github.com/chenshenhai/eggjs-note)(暂时停更) 18 | 19 | ## 博客日志精选 20 | - 2024 21 | - [iDraw.js的v0.4重构,大深海的2023年度总结](./2024/01/01.md) 22 | - 2021 23 | - [休息3个月,基于Canvas写了个简单的前端绘图JavaScript库](./2021/09/09.md) 24 | - 2020 25 | - [VSCode调试Rust代码 ](./2020/03/09.md) 26 | - [枸杞水背后的2019年业余技术总结 ](./2020/01/07.md) 27 | - 2019 28 | - [终端控制台的画笔: ANSI转义序列](./2019/11/04.md) 29 | - [浅尝 WebAssembly 在Node.js和浏览器的性能对比](./2019/08/26.md) 30 | - [用TypeScript写了个低配版H5美图工具](./2019/07/28.md) 31 | - [浏览器在同js文件下执行后台线程Worker](./2019/06/24.md) 32 | - [回炉重学HTML/DOM/Element/Node之间的关系](./2019/06/10.md) 33 | - [从寻呼机到jQuery,一枚jQuery钉子户的独白](./2019/06/09.md) 34 | - 2018 35 | - [基于JSON Schema的HTML解析器](./2018/06/04.md) 36 | - [Node.js 多进程自动守护管理](./2018/02/20.md) 37 | - [Node 9下import/export的丝般顺滑使用](./2017/11/18.md) 38 | - [JS简单的加密解密方法](./2017/06/11.md) 39 | - [几种js的URL参数操作](./2017/06/05.md) 40 | - [ubuntu下apache2+php+mysql环境配置 ](./2016/09/11.md) 41 | - [ubuntu环境下开发配置](./2016/06/25.md) 42 | - [其他文章备份](https://www.github.com/chenshenhai/blog/issues/) 43 | 44 | ## 其他 45 | 46 | 47 | 48 | 49 | - 本博客内容首发会在公众号显示,如果想第一时间知道博客内容更新 50 | - 可以 `watch` 本项目 51 | - 关注我公众号 `大海码` 或 `DeepSeaCode` 52 | 53 | ![qrcode_for_gh_959d1c4d729a_258](./assets/qrcode.jpg) 54 | 55 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | 2 | * 我的开源书 3 | * [《Deno进阶开发笔记》](https://github.com/chenshenhai/deno_note) 4 | * [《Koa2进阶学习笔记》](https://github.com/chenshenhai/koa2-note) 5 | * [《Koa.js 设计模式-学习笔记》](https://github.com/chenshenhai/koajs-design-note) 6 | * [《Rollup.js 实战学习笔记》](https://github.com/chenshenhai/rollupjs-note) 7 | * [《Canvas开发笔记》](https://github.com/chenshenhai/canvas-note) 8 | * [《Egg.js 深入浅出学习笔记》](https://github.com/chenshenhai/eggjs-note) 9 | -------------------------------------------------------------------------------- /assets/blog-avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/assets/blog-avatar.jpg -------------------------------------------------------------------------------- /assets/qrcode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenshenhai/blog/65b78eb9a4f0e31269290858148d20f4ca517dfc/assets/qrcode.jpg --------------------------------------------------------------------------------