diff --git a/.vscode/launch.json b/.vscode/launch.json index c7cd9d0..5a0dfc4 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -20,8 +20,8 @@ { "type": "node", "request": "launch", - "name": "Net", - "program": "${workspaceFolder}/dist/Net/server.js" + "name": "leetcode", + "program": "${workspaceFolder}/dist/leetcode/0x03.js" }, { "type": "node", diff --git a/README.md b/README.md index 3d33f38..1cc36c2 100644 --- a/README.md +++ b/README.md @@ -25,16 +25,36 @@ * [vue驱动原理](./docs/vue驱动原理.md) * [了解VirtualDOM](./docs/了解VirtualDOM.md) * [优化javascript](./docs/优化javascript.md) +* [常用的JS概念](./docs/常用的JS概念.md) +* [函数防抖与节流](./src/debounce/index.ts) +* [React下ECharts的数据驱动探索](./docs/Echarts/React下数据驱动.md) +* [TS下使用react-loadable](./docs/react笔记/TS下使用react-loadable.md) +* [从Highlight浅谈webpack按需加载](./docs/react笔记从highlight浅谈webpack按需加载.md) +* [Vue知识点相关](./docs/vue笔记) + * [Vue原型的初始化](./docs/vue笔记/01.md) + * [面试可能提问关键的几个函数](./docs/vue笔记/02.md) + * [Vue对模板的解析](./docs/vue笔记/03.md) +* [前端File那些事](./docs/前端File那些事.md) +* [大厂的第一堂课:完整的git流程](./docs/大厂的第一堂课:完整的git流程.md) +* [Vue打包优化之路](https://zhuanlan.zhihu.com/p/48461259) +* [TypeScript在Vue的实践](https://zhuanlan.zhihu.com/p/50179498) ## 算法 * [消息队列](./src/RabbitMQ) * [栈结构](./src/Stack) -* [二次树](./src/Tree) +* [二叉树](./src/Tree) * [选择排序](./src/sort/select.ts) * [快速排序](./src/sort/quick.ts) * [冒泡排序](./src/sort/bubble.ts) * [插入排序](./src/sort/insert.ts) +## Leetcode +* [二叉树相关](./src/leetcode/0x02.ts) + +### 数据结构 +* [常见定义](./docs/basic/0x00.md) +* [常见数据结构ADT](./docs/basic/0x01.md) + ## 思维图 * [cluster](./visio/cluster.vsdx) * [RabbitMQ](./visio/RabbitMQ.vsdx) @@ -50,5 +70,6 @@ * [Vue i18n工具](./gist/lang.js) * [Vue 入口main文件配置](./gist/main.js) * [Vue 统一表格注入](./gist/table.js) +* [React下自己使用的TSlint](./gist/tslint.json) diff --git "a/docs/CSS\344\273\243\347\240\201\350\247\204\350\214\203.md" "b/docs/CSS\344\273\243\347\240\201\350\247\204\350\214\203.md" new file mode 100644 index 0000000..a69a521 --- /dev/null +++ "b/docs/CSS\344\273\243\347\240\201\350\247\204\350\214\203.md" @@ -0,0 +1,321 @@ +--- +title: css代码规范化 +date: 2018-06-11 11:47 +--- + + +## 意义? + +#### 促进团队的协作。 + +提高更高的工作效率,减少沟通成本。 + +#### 降低维护成本 + +更容易让新人了解项目,更快的加入到项目中,维护代码成本降低。 + +#### 有助于个人的成长 + +规范化,标准化,体现一个工程师的专业程度,对工程化的理解,也是一个程序员职业素养的体现。 + +#### 对项目的规划能力 + +规范的过程,也是你对项目的把控能力,如何把项目拆分成组件,把通用的样式抽离成方法。 + +## css特性 + ++ 选择器具有优先级 ++ 规则之间可层叠 + +## 命名体系 + ++ [oocss](http://oocss.org/) - Object-Oriented CSS + + 容器与外观分离 + + 内容与结构分离 + ++ [SMACSS](https://smacss.com/) Scalable and Modular Architecture for CSS + + 将结构分离 + + 命名规范 + + css与html分离 + ++ [BEM](http://getbem.com/) - Block Element Modifier +## 良好的架构 + ++ 预测 - 可预见性 ++ 复用 ++ 维护 ++ 延展 + +## 遇到的问题 + ++ 无意义的选择器与嵌套 (副作用:权重) +```css +ul.infoContRightList {} +p.Size {} +p.Name {} +``` ++ 不规范的命名 (副作用:混乱) +```css +.Dw-b {} +.Tp-b {} +.left { + width: 100px; + background: #eee; +} +``` ++ 缺少公共方法 ++ 复用与分离 ++ [Style Resources Loader](https://www.npmjs.com/package/style-resources-loader) + +### 引用: +[漫談 CSS 架構方法 - 以 OOCSS, SMACSS, BEM 為例](https://www.slideshare.net/kurotanshi/css-oocss-smacss-bem) + +--- +title: css命名参考 +date: 2018-06-20 14:53 +--- + +## 模块划分 + ++ container ++ module / mod ++ card ++ article ++ side ++ tab ++ field + +## 标签划分 + ++ btn ++ form ++ input ++ img + +## 布局划分 + ++ colume ++ lf left ++ rt right ++ hd header ++ bd body ++ ft footer + +## 状态划分 + ++ is-error ++ is-hidden ++ is-collapsed ++ is-active ++ is-disabed + +## 实践 + +可以灵活的继承通用样式,覆盖某些规则,无副作用的添加样式。 + +```html +
+
+
+``` + +## 学习 + +通过参考优秀的前端组件库来学习命名 + ++ https://getbootstrap.com/ ++ http://semantic-ui.com/ ++ https://foundation.zurb.com/ ++ http://bulma.io/ + +## 参考 + +[BEM naming](http://getbem.com/naming/) + +--- +title: 一份项目内使用的的readme参考 +date: 2018-06-19 15:33 +--- + +# {{$page.title}} +> 引用 + +## 安装以及启动 + +不需要修改hosts文件,浏览器中打开。如果使用域名,则需要把域名指向本地,端口设置为80,浏览器中打开.com>。 + +安装yarn + +```js +npm install +npm install -g nodemon & babel-cli +npm run dev +``` + +## 开发模式 + +执行命令结束后,浏览器中打开 + +注:js与less的编译文件为 `['js/*.js', 'js/**/index.js', 'less/*/*.less', 'less/**/index.less']` + +``` +npm run dev +---- or ---- +gulp dev +``` + +## 风格指南 + +css规范 + +js规范 + +注释规范 + +vscode注释支持 + +编辑器格式化配置 + +## CSS命名规范 + +使用BEM(block__element__modifier)命名规范,为避免样式覆盖,禁止.class选择器单独使用,可嵌套在符合BEM规范的选择器内,作子选择器使用。避免嵌套过多选择器,非重用模块除外。 + +``` +.banner +.home__banner ✅ + +.close +.modal__login .close ✅ +``` + +## js检查 + +使用prettier作为formatter,eslint作为lint。 + +vscode插件: + ++ Prettier ++ EditorConfig for VS Code ++ ESLint ++ Vetur ++ Pug snippets + +settings.json配置 + +``` +"eslint.validate": [ "javascript", "javascriptreact", "html", "vue" ], +"prettier.eslintIntegration": true, +"prettier.stylelintIntegration": true +``` + +``` +sudo gulp formatter +``` +vue使用eslint-plugin-vue格式化代码,js文件使用eslint --fix格式化。 +编辑器内可使用prettier插件。 + +``` + > format document +``` + +## 项目结构 + +``` +common/ -- 后端或前后端通用js +controllers/ -- 前后端控制器 +dist/ -- src编译后生成文件 +doc/ -- 文档 +middlewares/ --服务端中间件 +node_modules/ +routes/ -- 服务端路由 +service -- 服务端代码 +src/ -- 预编译文件 + js/ + util/ --引用工具包 + lib/ --单独引用库 + less/ + ui/ --通用ui + common --只做注入css + vue/ + components/ --组件 + controls/ --api + mixins/ --混合 + pages/ --页面级single file + routes/ --路由 + bus.js --事件中间 + domControl.js --vue中dom控制 + web.js --入口文件 + mobile.js --mobile入口 +static/ -- 静态文件 +target/ -- 压缩包文件 +tasks/ -- 工作流任务 + .eslintrc.json -- 工作流eslint所需的配置 +temp/ -- 打包临时目录 +views/ -- 视图 +tools/ -- 工具类 + pr.js -- 提交pr任务 +app.js +boot.js -- 启动文件 +gulpfile.babel.js +process.json -- PM2配置 +yarn.lock +``` + +## 路由 + +路由尽量与文件目录相符,路由文件与视图文件对应。 + +## 视图 + ++ h5 - m.pug mobile.pug ++ hybird - h.pug hybird.pug ++ web - index.pug + +## 全局变量 + +``` +ctx.state.user {Object} - 当前登录用户 +ctx.state.isLogin {Boolean} - 当前登录状态 +ctx.state.ua {Object} - ua信息 + +global.cookieOptions - cookie设置 +global._OAUTH - 认证信息 +``` + +``` +window._LOGIN {Boolean} - 是否登录 +window._USER {Object} - 当前登录用户信息 +``` + +## Cookie + +页面之间的传递临时变量尽量使用session,存储非敏感信息使用storage,频繁使用的http状态使用cookie。 + +``` +_a_token //access token +_r_token //refresh token +_is_login //login state +u //uid 以及 匿名生成的uid +remember // 记住密码 +``` + +## FBI WARNING + ++ vue的挂载点为dom最顶层,所有dom操作应在vue mount生命周期之后。提供一个'vue-ready'事件,观察者函数单次载入,多次会覆盖之前的绑定。 ++ gulp任务新增依赖后,需要手动清空部署机的cache文件。 ++ 某些XMLHttpRequest POST请求返回400错误,需使用$.csrf发送请求,包装了请求token的逻辑。 ++ 关于请求跨域。开发模式中xmlHttpRequest通过cors.js由本地转发至线上。线上设置cors头或nginx把二级域名代理到\.com完成。 + +## 线上日志 + +## 编译机,部署机 + +## 提交PR +``` +node tools/pr.js -a ${assign_id} -t ${target_branch} +``` + +## 查看PR相关 +``` +node tools/pr.js +``` \ No newline at end of file diff --git "a/docs/Echarts/React\344\270\213\346\225\260\346\215\256\351\251\261\345\212\250.md" "b/docs/Echarts/React\344\270\213\346\225\260\346\215\256\351\251\261\345\212\250.md" new file mode 100644 index 0000000..e1a80bb --- /dev/null +++ "b/docs/Echarts/React\344\270\213\346\225\260\346\215\256\351\251\261\345\212\250.md" @@ -0,0 +1,349 @@ +![最终效果](../../img/ECharts.gif) +## 什么是数据驱动? +使用过Vue React框架我们就知道,我们不再更改某个DOM的innertext和innerhtml属性就能完成视图的改变,两者都是通过对状态的改变,唤起 virtualDOM 的diff方法,最终生成patch反应到真实DOM上。区别是Vue通过依赖收集观测数据的变化,而React是通过调用setState方法,不要小看这个区别。在结合ECharts的过程中,有着极大的不同。 + +尽管两者都是数据驱动的框架,不过它们仅仅改变的是DOM,不能直接唤起ECharts的改变(ECharts本身也是数据驱动的,通过适配不同的option,就能自动进行变换并且找到合理的动画过渡)。因此需要做一些适配。本文将浅谈在React中,完成ECharts的数据驱动所遇到的坑点 + +## 期待的效果 +如最上面的gif动图展示的,最终我们的ECharts要实现两个效果 +1. 尺寸变化引起的重绘 resize,有两种需要考虑的情况,第一个是页面尺寸的变化,即 window的 resize事件;第二种是上面的toggle按钮,导致容器的宽度发生变化。两者都需要进行 **chart.resize** +2. 数据驱动,通过用户触发DOM事件,让chart 进行重绘 + +## resize +本身实现resize并不复杂,ECharts为我们提供了 ECharts.resize 这个API。关键是调用这个API的时机。我们发现导致画面产生变化的因素只有两个。一个是 window.onresize 事件,另一个是toggle的点击事件。关于前者很多人都是在创建ECharts实例后,在window上绑定了事件,监听到变化时调用API。而后者处理的人就比较少,因为即使是不处理也能看。这当然是追求完美的我不能满足的。 + +仅仅从实现上来看,为每一个实例都 addEventlistener 不太划算。先不说不少人在实例销毁后忘记释放导致内存的占用。每一次都绑定一次也不符合 DRY 的原则。针对这个问题我做了如下处理 + +```javascript +// 注册一个事件中心 +const eventCenter = new EventCenter() + +class Base extends React.Component { + // 略掉不相关代码 + public async componentDidMount () { + // ...... + EventCenter.on('resize', this.handleDOMChange) + } + + public componentWillUnmount () { + // ...... + this.chart && this.chart.dispose() + delete this.chart + EventCenter.off('resize', this.handleDOMChange) + } + + private handleDOMChange () { + this.chart && this.chart.resize() + } +} +``` +我注册了一个事件中心。在 ECharts 的基类Base中,每当ECharts初始化以后,我都在 EventCenter 中注册了 resize 事件, 在 Base 将要销毁的时候注销这个事件,并且释放 ECharts的相关资源。注意这个 handleDOMChange 是在 EventCenter 中执行的,this 指向会改变。因此在 constructor 中执行绑定 ```this.handleDOMChange = this.handleDOMChange.bind(this)``` + +```javascript +window.addEventListener('resize', () => { + EventCenter.emit('resize') +}) + +handleToggle() { + setTimeout(() => { + EventCenter.emit('resize') + }, 500) +} +``` +在window和切换toggle中分别触发resize事件,这样EventCenter就会执行注册了的ECharts的resize方法。因为 Base 基类中也包含了注销事件,因此不会担心同一个ECharts注册多次导致内存的占用。 + +值得注意的是,在handleToggle的时候我设置了一个延时。这是因为点击了toggle按钮,视图并没有立即更新,即使这个时候 ECharts进行 resize 仍然取到的是不正确的宽度。应该等到视图更新完以后再进行resize。更加准确的是监听 AppMain(右侧主体)的 'transiationEnd' 事件。因为 antd 设置的变化时 .5s(CSS中的设置),此处就偷懒直接写了500ms + +```javascript +// ANTD-PRO中的实现 + +// antd\src\components\GlobalHeader + @Debounce(600) + triggerResizeEvent() { + const event = document.createEvent('HTMLEvents'); + event.initEvent('resize', true, false); + window.dispatchEvent(event); + } + +// antd\src\components\Charts\Bar + @Bind() + @Debounce(400) + resize() { + if (!this.node) { + return; + } + const canvasWidth = this.node.parentNode.clientWidth; + const { data = [], autoLabel = true } = this.props; + if (!autoLabel) { + return; + } + const minWidth = data.length * 30; + const { autoHideXLabels } = this.state; + + if (canvasWidth <= minWidth) { + if (!autoHideXLabels) { + this.setState({ + autoHideXLabels: true, + }); + } + } else if (autoHideXLabels) { + this.setState({ + autoHideXLabels: false, + }); + } + } +``` +在antd-pro中,他们没有分开设置, toggle是模拟了一个resize事件。总的逻辑放在了 ```window.addEventListener('resize', this.resize)``` 这段代码在每一个定义的图表类中都有,有些重复。相比引入一个 EventCenter 就能解决,这一点上我觉得我的做好更好些。当然也可以像他们一样加入节流,避免频繁触发带来的重绘消耗 + +## 数据驱动 +### 技术选型 +在讨论数据驱动之前,我要先讲讲我的技术选型。在React上能选择的框架很多,既灵活又容易踩坑。不同的技术方案对数据的处理是不一样的。我的选型主要参考了一下几点 +1. 没有使用antd-pro,虽然这套模板在对中后台处理给的实例非常完善,基本上能做到开箱即用,改改参数就行。但是因为没有Typescript的模板,我要从JS改成TS成本太高 +2. 使用mobx而不是使用redux,因为是后台页面,每个页面的数据基本都是独立的。因此不需要把所有状态都集中到一起,我为每一个页面单独配置一个mobx驱动store,这样逻辑更加简洁,将来也能充分扩展 +![技术选型](../../img/数据流向.png) +这就是我最后的技术选项,通过mobx提供对数据的驱动,父组件直接引用mobx配置的store实例,store中的数据发生变化时父组件就能自动更新视图。同样也可以作为参数传给子组件,子组件就能像正常的组件一样响应props的变动 + +### 数据驱动的尝试 +在进行数据驱动尝试的时候,总共有以下4种方式 +1. state传递配置数据 state传递变化数据 setOption为的配置数据 +2. state传递配置数据 EventCenter驱动 setOption为初始变动的配置数据 +3. state传递配置数据 mobx传递变化的数据 setOption为 变化的数据 +4. state传递配置数据 mobx传递变化的数据 setOption为初始变动的配置数据 + +其中有两种凉凉了,接下来依次讲讲每种方式的实现 + +```javascript +// state传递配置数据 state传递变化数据 setOption为的配置数据 + interface IProps { + width?: string | number + height?: string | number + theme?: object + config?: InitConfig + opt: ECharts.EChartOption | any + series?: any[] + dynamic?: boolean, + diff?: any + debug?: boolean + } + class Base extends React.Component { + + public chartDOM: HTMLDivElement | HTMLCanvasElement + public chart: ECharts.ECharts + public option: ECharts.EChartOption + + public async componentDidMount () { + let { theme, config } = this.props + + theme = theme || {} + config = config || {} + + this.option = this.props.opt + + // 延迟 500ms 等待外层 DOM 正确初始化 + setTimeout(() => { + this.props.debug && console.log('mount') + this.chart = ECharts.init(this.chartDOM, theme, config) + this.chart.setOption(this.option) + }, 500) + + + EventCenter.on('resize', this.handleDOMChange) + if (this.props.dynamic) { + EventCenter.on('update', this.handleUpdate) + } + } + + public getSnapshotBeforeUpdate () { + this.chart.setOption(this.option) + return null + } + } + + class Parent extends React.Component { + public state = { + opt: { + // 省略无关 + xAxis: { + type: 'category', + data: store.xAxis + } + } + } + + public render() { + return ( + + ) + } + } +``` +这种方式通过保存初始传入的配置项, 之后每次改动在 getSnapshotBeforeUpdate 中监听然后重新设置option。结果是凉凉,因为传入的opt虽然内部数据发生了变化,但是子组件感知不到,因此没有执行getSnapshotBeforeUpdate周期。我发现经管this.option发生了变化,但是子组件没有执行生命周期,因此我希望数据变化了能执行,能够执行setOption,参考之前resize的方法,做了如下改动 + +```javascript +class Base extends React.Component { + public async componentDidMount () { + + EventCenter.on('resize', this.handleDOMChange) + if (this.props.dynamic) { + EventCenter.on('update', this.handleUpdate) + } + } +} + +class Store { + + @observable + public xAxis: any[] = [] + + + private week: string[] = ['2018/07/12', '2018/07/13', '2018/07/14', '2018/07/15', '2018/07/16', '2018/07/17', '2018/07/18', '2018/07/19'] + + constructor() { + this.today = GET_TIME() + this.xAxis = this.today + } + + @action + public handleWeek() { + this.xAxis = this.week + EventCenter.emit('update') + } + + @action + public handleDay() { + this.xAxis = this.today + EventCenter.emit('update') + } +} +``` +我为每一个图形组件注册了一个 update 事件(同样在unmount里注销)。然而并没有成功。尽管mobx传递给父组件的数据变化了,子组件接收的数据却没有发生变化。具体的原因可以简化为 +```javascript +// A是父组件,B是子组件,B组件初始化时获取了A.attr引用 +const A = { + attr: [1 ,2, 3] +} +const B.prop = A.attr +// 数据变化 +A.attr = [3, 4, 5] +this.chart.setOption(B.prop) // B.prop === [1, 2, 3] +``` +B.prop还保持着原来属性的引用,此时setOption并不能起作用。这和在react中直接修改state并不会导致子组件的更新一样,必须通过setState改变一样。所以如果想要setOption生效,我们就不能直接替换原数组的应用,而是保持引用修改内部的值。mobx为装饰过的数组提供了这样一个能力 +```javascript +class Store { + @action + public handleWeek() { + this.xAxis.clear() + for (let i of this.week) { + this.xAxis.push(i) + } + EventCenter.emit('update') + } + + @action + public handleDay() { + this.xAxis.clear() + for (let i of this.week) { + this.xAxis.push(i) + } + EventCenter.emit('update') + } +} +``` +我们通过清空原来的数组并保持组件中对数组的应用,重新填入值。再通过EventCenter触发ECharts的更新命令,这样就能使的ECharts能够正确修改。但是我们仍然不能正常通过子组件的生命周期来修改,因为对于子组件来说,它感知不到传入数据发生了变化(React通过判断浅引用来判断需要不需要更新,数据变更前后传入的 option都没有发生变化,尽管内部数据发生了改变,但是组件是不知道的)。 + +这样的一种 hack 实现的并不优雅,首先我们引入了 EventCenter 必须每次在变动数据的时候触发 update 事件,并且数据的修改还得时刻注意不能直接修改 子组件的引用,比如不能 ```this.arr = newArr``` + +**回到最初的目标,我们究竟需要什么样的数据驱动?** + +我们希望子组件尽可能的抽象,使得我们可以通过父组件传参数给子组件,子组件再绘制出相应的图表。而不是针对 Bar line map 每一个图表类型都单独生成类。并且我们还需要图表能根据父组件传递数据的变化而进行变化,并且是在子组件的生命周期执行。而不是额外指定。 + +上面两个情况是我们实际的需求,前者我们可以通过父组件传递一个 option 选项控制图表的类型。后者我们希望在子组件的生命周期里完成,因此必须要让子组件感知到数据的变化。 + +## 最佳实践 +```javascript +class Store { + // 省略无关代码 + @computed + public get diff() { + return { + xAxis: { + data: this.xAxis + } + } + } + + @action + public handleWeek() { + this.xAxis = this.week + } + + @action + public handleDay() { + this.xAxis = this.today + } +} + +class Parent extends React.Component { + public state = { + opt: { + // 省略无关代码 + xAxis: { + type: 'category', + data: store.xAxis + } + } + } + public render() { + return ( + + ) + } +} + +class Base extends React.Component{ + public getSnapshotBeforeUpdate () { + this.props.debug && console.log('snapshot', this.props.diff) + if (this.props.diff) { + this.chart && this.chart.setOption(this.props.diff) + } + return null + } +} +``` +我们仍然通过父组件传递给子组件用来渲染正确的图表,接着把需要变化的部分 diff 从 store 里单独抽出来传递给子组件。子组件通过 diff 属性接收,这样一旦 diff 发生了变化 store 便能传递给子组件,子组件也能监听到 props 的变化进而在生命周期里执行ECharts的更新操作。 + +需要注意的是 this.chart.setOption(option: ECharts.EChartsOption) 这个 option 要实现 ECharts.EChartsOption 接口,因此我们通过 mobx 提供的 computed 属性直接将 diff 变为一个符合该接口的实现。 + +为什么选择 ```getSnapshotBeforeUpdate``` 这个生命周期? + +因为在 React16 中, componentWillMount, componentWillReceiveProps, componentWillUpdate 都被标记为不安全的生命周期(和fiber算法有关), 而 getDerivedStateFromProps 是一个静态生命周期,找不到 this.chart 这个实例,因此这能选这个生命周期执行ECharts的更新 + +## 总结 +最后的最佳实践是经过前几次的失败以后尝试出来的,当时真的很气,ECharts各种不按照自己的预计进行更新,当然事后分析了行为,发现了子组件还保持着原来数据的引用导致失败的。并且一直发现子组件的生命周期没有更新,后来仔细发现,要想是的子组件数据发生变化执行变化相关的钩子,一定得父组件使用 setState 方法, 直接更改 state 是没有效果的,这一点又回到 React 数据驱动的本质。在尝试将 diff 部分也通过 state 传递, 通过 setState 更新以后再尝试的 mobx 的改造。mobx的本质就是将 setState 部分改为了 mobx 装饰过后的数据通过代理驱动。最后取得了成功 + +当然之所以一开始就采取直接传递 option 的方法,来自于 vue 的使用经验,具体参考[Vue下使用ECharts](https://www.jianshu.com/p/7994176fbcc4),直接通过父组件传递 option 选项,因为 vue 有依赖收集,因此直接在子组件的 updated 周期更新 ECharts 就行了。不得不说 Vue真香 + +## 源码 +[Echarts基类](https://github.com/MrTreasure/Algorithm/blob/master/gist/Base.tsx) +[父组件](https://github.com/MrTreasure/Algorithm/blob/master/gist/index.tsx) +[mobx装饰的Store](https://github.com/MrTreasure/Algorithm/blob/master/gist/store.ts) + + +以上都是我瞎编的,如果你喜欢我一本正经的胡说八道,欢迎star我的 [Github](https://github.com/MrTreasure/Algorithm) + + diff --git a/docs/Echarts/style.md b/docs/Echarts/style.md new file mode 100644 index 0000000..2a032ca --- /dev/null +++ b/docs/Echarts/style.md @@ -0,0 +1,28 @@ +1. 全局theme + +2. option设置 + + ```typescript + interface option { + color: string[] // 全局主题 + series: type[ + { + type: string // 系列的类型 + color: string[] // 配色方案 + itemStyle: { + color: string // 点的颜色 + } + label: { + show: boolean + formatter: Function | string // 格式化 + } + emphasis: { // 高亮 + itemStyle: any + label: any + } + } + ] + } + ``` + + \ No newline at end of file diff --git "a/docs/Linux\345\255\246\344\271\240.md" "b/docs/Linux\345\255\246\344\271\240.md" new file mode 100644 index 0000000..60232c3 --- /dev/null +++ "b/docs/Linux\345\255\246\344\271\240.md" @@ -0,0 +1,21 @@ +### Linux是如何启动的 +#### boot初始化 +1. 启动boot loader 它是操作系统内核运行之前的一小段程序,严重依赖硬件实现 +2. 选择内核镜像,加载到内存空间,为最终调用操作系统内核准备好正确的环境 +3. 初始化硬件设备及其驱动程序 +4. 挂载根目录 +5. 内核启动一个初始化程序,从这里,虚拟内存开始划分出使用者空间 +6. 初始化剩余的操作系统进程 +7. 有些时候,初始化启动一个进程的时候需要登录,这通常发生在boot运行结束左右的时期 +#### linux内核初始化(对应上面的步骤2) +1. 检查CPU +2. 检查内存 +3. 发现设备总线 +4. 发现设备 +5. 辅助内核子系统启动,如网络等 +6. 挂载根目录 +7. 用户空间启动 + +### 什么是负载 +负载可以分为CPU负载、IO负载 +![负载](../img/linux/load.png) diff --git "a/docs/NodeJS\346\212\200\345\267\247.md" "b/docs/NodeJS\346\212\200\345\267\247.md" index a1dc891..3cc4533 100644 --- "a/docs/NodeJS\346\212\200\345\267\247.md" +++ "b/docs/NodeJS\346\212\200\345\267\247.md" @@ -138,16 +138,4 @@ const dailyCleanup = setInterval(() => { }, 1000 * 60 * 60 * 24); dailyCleanup.unref(); -``` - -```javascript -// 这两个都会遍历原型链上的属性(不包含方法) -Object.keys() -for (let key in obj) { - console.log(key) -} -// 只会获得对象本身的key,不包含原型链 -Object.getOwnPropertyNames() -// 箭头函数不能用作 构造函数是因为缺少[[construct]]属性 -() => {} ``` \ No newline at end of file diff --git a/docs/WebRTC/00.md b/docs/WebRTC/00.md new file mode 100644 index 0000000..c077fcf --- /dev/null +++ b/docs/WebRTC/00.md @@ -0,0 +1,69 @@ +### MediaDevices +```typescript +interface MediaDevices extends EventTarget { + ondevicechange: ((this: MediaDevices, ev: Event) => any) | null; + enumerateDevices(): Promise; + getSupportedConstraints(): MediaTrackSupportedConstraints; + getUserMedia(constraints: MediaStreamConstraints): Promise; + addEventListener(type: K, listener: (this: MediaDevices, ev: MediaDevicesEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; + removeEventListener(type: K, listener: (this: MediaDevices, ev: MediaDevicesEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; +} +``` + +### MediaStreamConstraints +```typescript +interface MediaStreamConstraints { + audio?: boolean | MediaTrackConstraints; + peerIdentity?: string; + video?: boolean | MediaTrackConstraints; +} +``` + +### MediaStream +```typescript +interface MediaStreamEventMap { + "active": Event; + "addtrack": MediaStreamTrackEvent; + "inactive": Event; + "removetrack": MediaStreamTrackEvent; +} + +interface MediaStream extends EventTarget { + readonly active: boolean; + readonly id: string; + onactive: ((this: MediaStream, ev: Event) => any) | null; + onaddtrack: ((this: MediaStream, ev: MediaStreamTrackEvent) => any) | null; + oninactive: ((this: MediaStream, ev: Event) => any) | null; + onremovetrack: ((this: MediaStream, ev: MediaStreamTrackEvent) => any) | null; + addTrack(track: MediaStreamTrack): void; + clone(): MediaStream; + getAudioTracks(): MediaStreamTrack[]; + getTrackById(trackId: string): MediaStreamTrack | null; + getTracks(): MediaStreamTrack[]; + getVideoTracks(): MediaStreamTrack[]; + removeTrack(track: MediaStreamTrack): void; + stop(): void; + addEventListener(type: K, listener: (this: MediaStream, ev: MediaStreamEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; + removeEventListener(type: K, listener: (this: MediaStream, ev: MediaStreamEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; +} + +interface MediaSource extends EventTarget { + readonly activeSourceBuffers: SourceBufferList; + duration: number; + readonly readyState: ReadyState; + readonly sourceBuffers: SourceBufferList; + addSourceBuffer(type: string): SourceBuffer; + endOfStream(error?: EndOfStreamError): void; + removeSourceBuffer(sourceBuffer: SourceBuffer): void; +} + +declare var MediaSource: { + prototype: MediaSource; + new(): MediaSource; + isTypeSupported(type: string): boolean; +} +``` \ No newline at end of file diff --git a/docs/WebRTC/01.md b/docs/WebRTC/01.md new file mode 100644 index 0000000..6006114 --- /dev/null +++ b/docs/WebRTC/01.md @@ -0,0 +1,26 @@ +## WebRTC +1. MediaStrem +2. RTCPeerConnection +3. RTCDataChannel + +## 开启一个基本的视频捕获 +```typescript +export default class UserMedia extends React.Component { + private video: HTMLVideoElement | null + + @Bind() + private async handleGetVideo() { + const config = {audio: false, video: true} + const stream = await navigator.mediaDevices.getUserMedia(config) + this.video && (this.video.srcObject = stream) + } + + public render() { + return Get Video}> +
+
+
+ } +} +``` \ No newline at end of file diff --git a/docs/basic/0x00.md b/docs/basic/0x00.md new file mode 100644 index 0000000..4cf5e84 --- /dev/null +++ b/docs/basic/0x00.md @@ -0,0 +1,43 @@ +## ADT +抽象数据类型(ADT)是一个实现包括储存数据元素的存储结构以及实现基本操作的算法。 +``` +ADT { + 数据对象: (数据元素集合) + 数据关系: (数据关系二元组结合) + 基本操作: (操作函数的罗列) +} +``` +## 算法 +算法是指解决问题的一种方法或者一个过程 +1. 正确性。它必须完成所期望的功能 +2. 具体步骤。一种算法应该由一系列具体步骤组成。 +3. 确定性。下一步应执行的步骤必须明确 +4. 有限性。一种算法必须有有限步骤组成 +5. 可终止性。算法必须可以终止 + +## 数学归纳法 +1. 初始情况: n = c时 Thrm 成立 +2. 归纳步骤:如果 n - 1 时 Thrm 成立,则 Thrm 对于 n 也成立 +3. 如果对于所有满足条件 c <= k < n 的 k,Thrm 都成立,则 Thrm 对于 n 也成立 + +## 程序运行时间的计算 +1. while循环与for循环类似 +2. if语句最差的情况时间代价是then和else语句中时间代价较大的那一个 +3. switch语句的最差情况时间代价是所有分支中开销最大的那一个 + +## 事前分析估算方法 +在计算机程序编写前,依据统计方法对算法进行估算 +1. 算法采用的策略和方案 +2. 编译产生的代码质量 +3. 问题的输入规模 +4. 机器执行指令的速度 + +## 评估 +1. 确定影响问题的主要参数 +2. 推导出一个与问题的参数有关的公式 +3. 选择参数值,由该公式得出一个评估解 + +## 推导大O阶方法 +1. 用常数1取代运行时间中所有的加法常数 +2. 在修改后的运行次数函数中,只保留最高阶项 +3. 如果最高阶项存在且不是1,则去除渔哥这个项相乘的常数 diff --git a/docs/basic/0x01.md b/docs/basic/0x01.md new file mode 100644 index 0000000..122799f --- /dev/null +++ b/docs/basic/0x01.md @@ -0,0 +1,118 @@ + ## 常见数据结构的ADT类型 + ### 线性表 + ``` + ADT 线性表 List + Data + 线性表数据对象的集合为{a1,a2,...,aN},每个元素的类型均为DataType。其中除第一个元素a1外,每一个元素有且只有一个直接前驱元素,除了最后一个元素aN外,每一个元素有且仅有一个直接后继元素。 + Operation + InitList(*L) 初始化操作 + ListEmpty(L) 若线性表为空,返回true,否则返回false + ClearList(*L) 将线性表清空 + GetElem(L, i, *e) 将线性表L中的第i个元素值返回给e + LocateElem(L, e) 在线性表L中查找与给定值e相等的元素,如果查找成功,返回该元素在表中序号表示成功;否则返回0表示失败 + ListInsert(*L, i, e) 在线性表L中的第i个位置插入新元素e + ListLength(L) 返回线性表L的元素个数 + ``` + +### 栈 +``` +ADT 栈 stack +Data + 同线性表。元素具有相同的类型,相邻元素有前驱后后继关系 +Operation + InitStack(*S) 初始化操作,建立一个空栈S + DestroyStack(*S) 若栈存在,则销毁它 + ClearStack(*S) 将栈清空 + StackEmpty(S) 若栈为空,返回true,否则返回false + GetTop(*S, *e) 若栈存在且非空,用e返回S的栈顶元素 + Push(S*S,e) 若栈S存在,插入新元素e到栈S中并成为栈顶元素 + Pop(*S, *e) 删除栈S中栈顶元素,并用e返回其值 + StackLength(S) 返回栈S的元素个数 +``` + +### 队列 +``` +ADT 队列 Queue +Data + 同线性表。元素具有相同的类型,相邻元素具有前驱和后继关系 +Operation + InitQueue(*Q) 初始化操作,简历一个空队列Q + DestroyQueue(*Q) 若队列Q存在,则销毁它 + ClearQueue(*Q) 将队列Q清空 + QueueEmpty(*Q) 若队列为空,返回 true, 否则返回 false + GetHead(Q, *e) 若队列存在且非空, 用e返回队列Q的队头元素 + EnQueue(*Q, e) 若队列Q存在,插入新元素e到队列Q中并成为队尾元素 + DeQueue(*Q, *e) 删除队列Q中的队头元素,并用e返回其值 + QueueLength(Q) 返回队列Q的元素个数 +``` + +### 串 +``` +ADT 串 string +Data + 串中元素仅由一个字符组成,相邻元素具有前驱和后继关系 +Operation + StrAssign(T, *chars) 生成一个其值等于字符串常量的chars的串T + StrCopy(T, S) 传S存在,由串S复制得到串T + ClearString(S) 串S存在,将串清空 + StringEmpty(S) 若串S存在,将串清空 + StrLength(S) 返回串S的元素个数,即串的长度 + StrCompare(S, T) 若S>T, 返回值>0, 若S=T, 返回0, 若S 若G是无向图,还需要增添对称弧 + DeleteArc(*G, v, w) 在图G中删除弧 若G是无向图,还需要删除对称弧 + DFSTraverse(G) 对图G中进行深度优先遍历,在遍历过程对每个顶点调用 + HFSTraverse(G) 对图G中进行广度优先遍历,在遍历过程对每个顶点调用 +``` + +### 习题 +1. 已知:先序 ABCDEF 中序 CBAEDF 求后序 +2. 已知:中序 ABCDEF 后序 BDCAFE 求先序 + +思路 + +先序和后序确定根结点,找到根结点后在中序找到左右子树,然后重新查找根结点 \ No newline at end of file diff --git a/docs/basic/0x02.md b/docs/basic/0x02.md new file mode 100644 index 0000000..52546b7 --- /dev/null +++ b/docs/basic/0x02.md @@ -0,0 +1,90 @@ +## 逆波兰表达式 +```go +// 中缀转后缀 +func pre2Post() *[]string { + slice := strings.Split("") + stack := Stack.NewStack(20) + var slice2 []string + for _, val := range slice { + if isOperation(val) { + if (val != ")") { + now := stack.GetValue() + if now == "+" || now == "-" { + stack.Push(val) + } else { + var popEle string + stack.Pop(&popEle) + slice2 = append(slice2, popEle) + } + + } else { + for ele := stack.Pop(&ele); ele != "(" { + slice2 = append(slice2, ele) + } + } + } else { + slice2 = append(slice2, val) + } + } +} +// 后缀表达式 +func postFun() { + nums := []string{"9", "3", "1", "-", "3", "*", "+", "10", "2", "/", "+"} + stack := Stack.NewStack(20) + + for _, val := range nums { + if isOperation(val) { + var prev, next int + stack.Pop(&next) + stack.Pop(&prev) + result := operation(prev, next, val) + stack.Push(result) + } else { + num, _ := strconv.Atoi(val) + stack.Push(num) + } + } + fmt.Println(stack.GetValue()) +} +// 辅助方法 +func operation(a, b int, opt string) int { + switch opt { + case "-": + return a - b + case "+": + return a + b + case "*": + return a * b + case "/": + return a / b + default: + panic("wrong operation") + + } +} + +func isOperation(str string) bool { + slice := []string{"+", "-", "*", "/"} + for _, val := range slice { + if val == str { + return true + } + } + return false +} +``` +没有泛型是真的不方便 + +中缀表达式 (栈用来进出运算的符号) +* 定义当前元素 ele +* 数字类型直接输出 +* 遇到 **(** 进栈 遇到 **)** 开始出栈输出,直到 **(** 出栈为止 +* 如果当前栈顶元素 比 ele 优先级低 ("* /" < "+ -"),入栈 +* 如果当前栈顶元素 比 ele 优先级高 (如果栈顶), 则出栈所有栈中元素,直到栈空, 然后将当前元素入栈 +* 如果当前栈顶元素 和 ele 优先级相等, 输出当前元素 +* 当前元素为 nil 后,出栈输出到队尾 + +后缀表达式 (栈用来进出运算的数字) +* 定义当前元素 ele +* 如果 ele 是数字类型入栈 +* 如果 ele 是运算符类型 出栈两个元素(一定是数字类型), 第一个出栈的元素为b, 第二个出栈的元素为a, (a opt b)将得到结果入栈 \ No newline at end of file diff --git a/docs/basic/0x03.md b/docs/basic/0x03.md new file mode 100644 index 0000000..237ac1a --- /dev/null +++ b/docs/basic/0x03.md @@ -0,0 +1 @@ +## KMP模式匹配算法 diff --git "a/docs/basic/\347\256\227\346\263\225\346\200\235\350\267\257\346\212\245\345\221\212.md" "b/docs/basic/\347\256\227\346\263\225\346\200\235\350\267\257\346\212\245\345\221\212.md" new file mode 100644 index 0000000..52b33e2 --- /dev/null +++ "b/docs/basic/\347\256\227\346\263\225\346\200\235\350\267\257\346\212\245\345\221\212.md" @@ -0,0 +1,21 @@ +

算法思路报告

+ +### 题目 + +### 例子 + +### 题目假设 + +### 重要问题 + +### 直觉 +### 解决方案可行性 +### 数据结构 + +### 复杂度 + +### 其他解法 + +### 可能的follow-up + +### 代码 \ No newline at end of file diff --git "a/docs/react\347\254\224\350\256\260/TS\344\270\213\344\275\277\347\224\250react-loadable.md" "b/docs/react\347\254\224\350\256\260/TS\344\270\213\344\275\277\347\224\250react-loadable.md" new file mode 100644 index 0000000..258937c --- /dev/null +++ "b/docs/react\347\254\224\350\256\260/TS\344\270\213\344\275\277\347\224\250react-loadable.md" @@ -0,0 +1,38 @@ +```typescript +// /src/Treasure +export default class Treasure extends React.Component { + public render() { + return ( +
Treasure
+ ) + } +} +``` + +```typescript +// /src/App +import * as Loadable from 'react-loadabel' +import ReactLoading from 'react-loading' + +const Treasure = Loadable({loader: () => import('/src/Treasure'), loading: ({ type, color }) => }) +class App extends React.Component{ + public render() { + return ( + + ) + } +} +ReactDOM.render( + , + document.getElementById('root') as HTMLElement +) +``` + + +```bash +npm install babel-plugin-import --dev +``` + +1. 安装 baebl-plugun-imort 插件,否则会报不识别 import() 错误 +2. 路由级组件一定要通过 export default 默认导出,否则TS下import会报找不到组件的错误 +3. Loadable配置项必须有 loading 参数,推荐 react-loading \ No newline at end of file diff --git "a/docs/react\347\254\224\350\256\260/\344\273\216highlight\346\265\205\350\260\210webpack\346\214\211\351\234\200\345\212\240\350\275\275.md" "b/docs/react\347\254\224\350\256\260/\344\273\216highlight\346\265\205\350\260\210webpack\346\214\211\351\234\200\345\212\240\350\275\275.md" new file mode 100644 index 0000000..e5b4270 --- /dev/null +++ "b/docs/react\347\254\224\350\256\260/\344\273\216highlight\346\265\205\350\260\210webpack\346\214\211\351\234\200\345\212\240\350\275\275.md" @@ -0,0 +1,109 @@ +![最后效果](../../img/highlight/动态加载CSS.gif) +## 前言 +最近有在使用 highlight.js 做代码的高亮展示,主要是展示对 SQL 语言的处理。看了看 highlight.js 的提供的相关代码 + +![语言部分](../../img/highlight/1.png) +![样式部分](../../img/highlight/2.png) + +因为只需要加载对应语言的种类,以及一种样式,所以我们希望 webpack 能够按需加载 + +## 按需加载的实践 + +### 完全加载 +为了对比出按需加载究竟能帮助我们节约多少资源,我们先贴出没有按需加载的代码 +```typescript +// 忽略一些无关的代码 +import * as hljs from 'highlight.js/lib/highlight' +import 'highlight.js/styles/atom-one-light.css' + +export class Highlight extends React.Component { + + public componentDidMount() { + hljs.highlightBlock((this.code as any)) + } + + public render() { + return ( +
 this.code = ref} style={{marginTop: 20}}>
+        {this.props.content}
+      
+ ) + } +} +``` +这是一份完整的加载,我们看看最后的数据有多大(包含完整引用的 antd 文件,我在项目中使用了 antd ) + +![完全加载](../../img/highlight/highlight-1.png) + +### 按需加载 +接着我们按照官方的 demo 实现按需加载 +```typescript +import * as hljs from 'highlight.js/lib/highlight' +import * as javascript from 'highlight.js/lib/languages/javascript' +hljs.registerLanguage('javascript', javascript) +``` +其他的部分和上文相同,区别在于,没有从整个 highlight 中加载,而是引用了部分文件以及需要注册的 javascript 语言部分,默认是加载包含所有语言版本的 hljs ,看看这下的打包大小 + +![按需加载](../../img/highlight/highlight-2.png) + +我们可以看到,使用按需加载将近节省了600KB的空间,而使用按需加载的引入方式是 +```import * as XXX from 'module/lib/xxx'```。并且使用 ```import { xx } from 'moduls'``` 并不能触发 webpack 的 treeshake,webpack仍然会打包完整库,哪怕引用的仅仅是从库里导出的接口(在ECharts下是如此表现的)。我们看看按需引用 antd 里的组件会是什么情况 + +### 部分按需引用 +上面1.78MB的打包体积是 ```import { Card } from 'antd'```(如gif效果图,我用Card包裹了高亮组件),接着我们看看 +```typescript +import Card from 'antd/lib/card' +``` + +这种方式最后的打包体积 + +![部分按需加载](../../img/highlight/highlight-4.png) + +妈耶,居然这么小。 + +### 小结 +1. 如果要实现按需加载得使用[babel-plugin-import](https://github.com/ant-design/babel-plugin-import),这个在TS下的情况还没有检查过 +2. 使用TS时,因为某些库的 d.ts 文件 指向的路径是模块,因此要导入该库的接口只能完整的导入该模块,比如ECharts,这个问题目前暂时还未解决 + +## 动态加载的实践 +上面只是按需加载部分的JS,并且通过字符串写死的方式指定了路径,还有一部分,如同CSS的部分需要在组件生成时动态加载,或者通过变量的形式加载。如下所示 +```typescript + constructor(props) { + super(props) + require('highlight.js/styles/' + this.props.css) + } + + static async getDerivedStateFromProps(nextProps) { + // const css = await import('highlight.js/styles/' + nextProps.css) + const css = require('highlight.js/styles/' + nextProps.css) + console.log(css) + return null + } +``` +我们在构造阶段通过props传过来的变量加载对应的CSS文件,之前是使用```import 'highlight.js/styles/atom-one-light.css'```的方式,我们看看两者打包体积的区别 + +![指定加载](../../img/highlight/highlight-css-1.png) +![动态加载](../../img/highlight/highlight-css-2.png) + +通过指定加载的CSS体积大小是427KB,而动态加载的体积大小是484KB。动态加载的体积要比静态加载的体积大很多。分析一下webpack打包的行为 + +webpack始终结合关键字并按照静态地址信息进行打包。比如```require('highlight.js/styles/' + nextProps.css)``` +require是关键字,接下来 webpack 会对 require 这个函数中的入参进行分析,它会发现入参有两个部分构成, 一部分是硬编码的 'highlight.js/styles/' 另一部分是不可知的变量。webpack将会以硬编码部分为打包入口,将'highlight.js/styles/*'下所有文件打包,在运行时根据完整的路径记载资源。 + +所以我们没办法使用完全的变量 require(variable),因为这样webpack找不到打包的路径。 + +### 缺陷 +效果图虽然能看到我们通过 Select 的选择按需加载 CSS 样式,但其实是有缺陷的,表现为右侧可以看到,动态加载的CSS是通过一个个style标签加载上去的,这样后面的样式效果会覆盖前面的。表现为 当 Select 又选到已经加载的样式时, 浏览器并不会重新加载那段代码,导致样式无效。这个问题在另一个组件中得到了解决 +[react-syntax-highlighter](https://github.com/conorhastings/react-syntax-highlighter) + +还没来得及看具体的实现,不过我估计应该是使用了 CSS-MODULES,明天再看看 + +### 没来得及验证的部分 +有注意到 我在使用 ```const css = await import('xxx')```,```const css = require('xxx')```,这两者的表现上是有区别的,前者是一个Promise对象,后者直接返回了值,这就涉及到了一个同步和异步的问题,虽然最后打印出来都是 {}, 不过这是因为没有使用CSS modules的原因。以后再研究研究 import require 动态加载时的区别 + +## 总结 +1. ```import { Card } from 'antd'```并不会触发按需加载,仍然会加载全部antd文件,应该使用```import Card from 'antd/lib/Card'``` +2. 使用变量加载```require('highlight.js/styles/' + this.props.style)``` webpack会打包 ```'highlight.js/styles/*'```下所有文件 +3. 猜想 在TS下即使只从某个库里引用接口, ```import { IXxx } from 'xxx'```,webpack仍然会打包所有的 'xxx' 文件(在ECharts的表现下如此) + +以上都是我瞎编的 \ No newline at end of file diff --git "a/docs/react\347\254\224\350\256\260/\346\265\205\350\260\210React16\347\224\237\345\221\275\345\221\250\346\234\237.md" "b/docs/react\347\254\224\350\256\260/\346\265\205\350\260\210React16\347\224\237\345\221\275\345\221\250\346\234\237.md" new file mode 100644 index 0000000..a36f71b --- /dev/null +++ "b/docs/react\347\254\224\350\256\260/\346\265\205\350\260\210React16\347\224\237\345\221\275\345\221\250\346\234\237.md" @@ -0,0 +1,35 @@ + +## 代码 +```typescript +class React { + static getDerivedStateFromProps(nextProps, nextState) { + console.log('%c getDerivedStateFromProps', "color:#97de95") + console.log(nextProps, nextState) + return null + } + + public shouldComponentUpdate(nextProps, nextState): boolean { + console.log('%c shouldComponentUpdate', "color:#08c299") + console.log(this.props, this.state) + console.log(nextProps, nextState) + // 具有缓存效应 + return true + } + + public getSnapshotBeforeUpdate(preProps, preState): any { + console.log('%c getSnapshotBeforeUpdate', "color:#0960bd") + console.log(preProps, preState) + return preProps.name + } + + public render() { + + } + + public componentDidUpdate(preProps, preState, val) { + console.log('%c componentDidUpdate', "color:#571179") + console.log(preProps, preState, val) + } +} +``` +## 结论 \ No newline at end of file diff --git a/docs/resume.md b/docs/resume.md index 665ee29..f0ad239 100644 --- a/docs/resume.md +++ b/docs/resume.md @@ -1,54 +1,80 @@ ## 简历 ### 个人信息 -* **姓名** Treasure -* **微信号** TreasureTesla(备注招人) +* **姓名** 吴明骏 +* **微信号** TreasureTesla +* **电话** 13550038728 +* **邮箱** treasurewmj@foxmail.com +* **Github** https://github.com/MrTreasure * **毕业院校** 成都大学 数字媒体技术 本科 -* **期望工作地点** 成都 上海 +* **毕业时间** 2017-07 +* **个人荣誉** 2019 中国 JSConf 受邀讲师;知乎前端话题下活跃回答者 +* **意向** 中台开发 serverless + ### 技能简介 -1. 熟练HTML+CSS+JS开发(前端本职) -2. 熟练Vue全家桶开发,根据业务需求自定义组件;使用过React mobx开发 -3. 熟练使用Typescript开发,并在vscode下调试(后端项目均使用TS编写) -4. 熟悉node的基本模块,熟练使用Koa搭建web服务器 -5. 掌握mysql,mongodb,redis在node环境下的开发使用 -6. 了解密码学常用概念,并使用crypto块完成了各自的实现 -7. 掌握基本的linux环境命令,有过linux部署经验 -8. 知乎社区前端话题活跃回答者,github社区活跃用户,简书博主 +1. 熟练 HTML+CSS+JS开发(前端本职) + +2. 熟练 Vue React 开发,掌握其实现原理,了解其设计思想;目前主要使用 typescript + hooks; + 结合部门业务特点,封装了常用的业务组件并在团队内部进行推广; + + 平时也负责 review 代码,给出性能优化等相关建议; + +3. 熟悉 HTTP 协议,掌握缓存控制、CORS、HTTPS 等网络编程基础知识; + + 深入了解 cookie 等前后端权限校验机制,解决不同项目中授权认证等问题; + +4. 熟悉 nodejs go 开发,能够完成基本后端开发,掌握后端开发具备的理论知识及相关工程知识 ### 工作经历 -• 2017年10月——至今 深圳竹云科技有限公司 前端工师 -公司的项目为大型身份认证管理系统,我在其中负责后台管理页面开发。入职以前,公司还是传JSP页面,由后台负责前端业务逻辑,UI老旧。入职后进行vue框架的推广,投入公司新开的项目通过vue的组件复用,数据驱动,前后端分离等特性,快速完成了开发。前后端解耦也使得逻辑更清晰,有利用后期模块的扩展。并且将相关开发经验及使用到的库整理成文档,在全公司推广 +• 2018年9月——至今 阿里本地生活 资深前端工程师 -• 2017年6月——2017年10月 成都畅联九洲科技有限公司 前端工程师 - -公司主要负责大数据收集,前端负责可视化展现。通过vue的钩子函数进一步封装了echarts组件,使得每个图形化模块可以根据数据驱动并且多处复用。同时处理了PC端和移动端兼容性问题。完成了两个月在PC和微信公众号的上线 +​ FI前端基础设施部门下的大数据组,负责大数据部门的业务开发工作,参与项目的评审与设计。有丰富的大数据产品落地经验,掌握了使用各种技术赋能用户; +​ 搭建可视化报表系统(中台类型,前端配置生成网站)供一线用户使用。 + +​ 对接过 ECharts、Antv 等各种图表制作;深入了解过 Antv-G6,熟悉流程图,脑图开发,参与过 GGEditor 贡献;完成高德地图等相关开发。 +​ 同时也参与部门建设,不限于团队招聘、指导新人、review 等相关工作 + + +• 2017年10月——2018年9月 深圳竹云科技有限公司 前端工程师 + +​ 公司的项目为大型身份认证管理系统,我在其中负责后台管理页面开发; + +​ 入职以前,公司还是传JSP页面,由后台负责前端业务逻辑,UI老旧; -### 项目经历 -• 2018年3月——2018年4月 互联网用户管理系统 +​ 入职后进行vue框架的推广,投入公司新开的项目通过vue的组件复用,数据驱动,前后端分离等特性,快速完成了开发。前后端解耦也使得逻辑更清晰,有利用后期模块的扩展。并且将相关开发经验及使用到的库整理成文档,在全公司推广 -该项目是对企业的互联网用户进行统一的管理,权限授取。根据业务的需求, -二次开发及重写了树型组件、穿梭框组件、表格组件,参考elementUI的设计方式,结合我司后台供的数据结构,针对性的编写了符合要求的组件 -该项目对用户数据的修改管理也较为复杂,因此在前端的表单编辑有着较为严格的限制。项目中用了动态表单的生成(根据后台的数据要求,生成表单数据,使用了适配器转换数据结构),并涉及到了动态的表单校验与提交 -• 2017年12月——2018年2月 安全通讯录 +### 主要项目经历 +•2019年5月至今 策略中台 - 1. 参与项目需求设计,协商讨论restful风格接口在该项目的实现 - 2. 设计并开发项目中的可复用组件,如名片和多个弹窗组件 -该项目是企业级的通讯录展示系统,涉及到的痛点有用户数据庞大,用户可访问的权限限制,用信息的动态渲染 -针对以上特点,在进行前端页面设计时,充分考虑了数据节流以及懒加载,避免不必要的DOM渲染通过axios的拦截器等多种手段统一拦截判断用户有无可访问的权限避免不必要的网络请求; +​ BU 内各团队需要借助大数据的能力,精确的找出符合条件的各种商户、BD、物流等对象。 -• 2017年10月——2017年 11月 Epass认证管理后台系统 +​ 在此背景下,我主导了策略中台前端部分的建设,并且联合产品、后端定义中台组件不同项目之间交互、通信的方案。前端封装了核心的筛选逻辑,并通过 npm 包的形式提供给接入方使用,节省了接入方前端的开发时间;和后端的交互统一封装成为了 service 层,提高了项目的维护性,接入方也能通过查询参数之间获取相关的数据信息。 - 1. 参与项目需求设计,与后台协商定义接口约定 - 2. 根据需求制定前端的整体模块架构,以及开发规范 - 3. 打包完成后通过nginx部署,不再需要后台支持 -这是新公司的第一个项目,因此领导比较重视。该项目主要作用是定义终端设备登录时的动态认规则,涉及指纹、手势、声纹、二维码、短信、OTP、帐号多种登录方式。管理台本身支持所有的证方式。我在其中实现了动态设置登录选项,并且能够根据后台判定的风险级别,动态增加额外证措施。 -不同的用户具有不同的操作权限,根据后台返回的权限列表,在前端实现了按钮级的操作权限限制 -管理台还实现了用户行为记录,风险提醒模板编辑,表格数据的导入导出等 +​ 目前该策略中台在 BU 内部多端都有使用,累计接入方达 10+,如商户补贴、高风险商户预警等。项目完成后,我也进行了相关复盘,分享了[《从 0 到 1 搭建业务中台》](https://zhuanlan.zhihu.com/p/181641511)的经验 + +•2020年1月至今 新雷达门户 + +​ 该项目前身是一个老的门户网站,主要提供各种图表看板给一线城市经理、BD 用于业务分析。老项目中数据结构不合理,后端返回的数据中包含了前端样式相关信息,导致后端开发时间成本高;组件复用率低,同一个组件项目中多次出现不同的代码 copy,往往修改一处别处还会出现故障;前端开发效率低,明明相差不大的页面却需要重复开发。针对以上痛点,联合产品、后端等对项目进行重构。 + +​ 整理出了通用的数据结构, 封装了常用的业务组件。数仓直接对数据结构进行出数,减少了后端接入的过程; + +​ 提出了配置式开发理念。前端不再关心具体的值直接由组件根据业务属性进行适配。相关的组件也推广到了其他项目中使用; + +​ 提效以后,原本项目的迭代需要 3 个前端同学,现在只需要 1 到 2 个同学完成。目前新雷达门户已经作为大数据平台拳头产品,在 BU 内部推广 + +### 阿里师兄 + +​ 在工作中担任过阿里师兄,负责带实习生。针对实习生制定了一系列的学习内容,包括了 gitflow 流程、框架背后原理分析、后端交互场景、项目评审和产品需求分析等。实习生目前已顺利转正。 ### 个人评价 -1. 除了前端开发以外,个人时间大部分投入到node、后台服务的学习 -2. 活跃于开源社区,在知乎的前端话题下积极回答问题,帮助新人解决前端相关的问题及疑惑,能巩固自身的知识基础;将个人在学习中的感悟,一些实践整理成文档发布在简书以及知乎专栏热衷于github的开源分享,仿照eggjs模块,在项目ts-koa中实现了部分模块功能,并且加入eggjs开发者组织。也通过github向日常使用的库提出issue,帮助开发者完善库的功能和bug -3. 学习typescript加强JS语言开发的严谨性,避免一些低级错误,同时也极大的拓展了JS在OO方向,元编程方向的学习经验,在这方面收获颇丰 -在node方面主要研究node在微服务框架中的应用,自身所处的位置。作为一个API网关的应用,并且对微服务,云计算等有着浓厚的兴趣 \ No newline at end of file + + 项目经历主要集中在可视化(前端搭建、复杂图表、脑图、流程图)以及组件化赋能(拆分、整合业务单元供多个业务线使用)方向,个人也保持着对前沿技术学习。主要在于微前端、serverless 方向。并且部门推崇 "工程师" 理念,学习的方向也不仅仅局限于前端方向,从 HTTP 入手,学习并掌握 nodejs 和 go 语言的 web 开发。 + +​ 在平常的业务开发中也比较注重相关经验积累,内网编写了相关实践 10 余篇帮助团队内同学快速填坑,提升团队同学的开发效率; + +​ 深入了解过部门后端内部服务间通信,也经常协助测试、后端同学定位、解决线上问题; + +​ 平常也关注前端社区、帮助社区新人进步,学习前沿的前端理论,并思考如何作用于公司的业务。之前也有维护部门的知乎专栏、微博号等 + diff --git "a/docs/vue\347\254\224\350\256\260/01.md" "b/docs/vue\347\254\224\350\256\260/01.md" new file mode 100644 index 0000000..2d09c51 --- /dev/null +++ "b/docs/vue\347\254\224\350\256\260/01.md" @@ -0,0 +1,74 @@ +![原理图](../../img/reactive.png) +## 构造Vue +```javascript +function Vue (opts) { + // 一些检查this指向代码 + this._init(opts) +} + +initMixin(Vue) +stateMixin(Vue) +eventsMixin(Vue) +lifecycleMixin(Vue) +renderMixin(Vue) +``` +```javascript +export function initState (vm: Component) { + vm._watchers = [] + const opts = vm.$options + if (opts.props) initProps(vm, opts.props) + if (opts.methods) initMethods(vm, opts.methods) + if (opts.data) { + initData(vm) + } else { + observe(vm._data = {}, true /* asRootData */) + } + if (opts.computed) initComputed(vm, opts.computed) + if (opts.watch && opts.watch !== nativeWatch) { + initWatch(vm, opts.watch) + } +} +``` +初始化顺序 props -> methods -> data -> computed -> watch + +## stateMixin +```javascript +// 做 data 属性和 props 属性的代理 +const dataDef = {} +const propsDef = {} + +// 定义三个方法 +Vue.prototype.$set = set +Vue.prototype.$delete = del +Vue.prototype.$watch = function() {} +``` + +## eventsMixin +```javascript +// 定义事件相关 +Vue.prototype.$on = function() {} +Vue.prototype.$once = function() {} +Vue.prototype.$off = function() {} +Vue.prototype.$emit = function() {} +``` + +## lifecycleMixin +```javascript +// 注册和生命周期相关的API +Vue.prototype._update = function() {} +Vue.prototype.$foreceUpdate = function() {} +Vue.prototype.$destroy = function() {} +``` + +## renderMixin +```javascript +// 一些列 render 相关方法 + +Vue.prototype.$nextTick = function() {} +Vue.prototype._render = function() {} +``` + +## 小结 +1. 利用类似装饰者模式修改 Vue 的原型,优点在于使得逻辑清晰,每一个 Mixin 只负责自己的部分 +2. 主要给 Vue 原型添加 API 方法,以及添加 Vue 的静态属性 +3. 目前为止都是纯 JS 层面操作,没有涉及浏览器相关的 API \ No newline at end of file diff --git "a/docs/vue\347\254\224\350\256\260/02.md" "b/docs/vue\347\254\224\350\256\260/02.md" new file mode 100644 index 0000000..391d714 --- /dev/null +++ "b/docs/vue\347\254\224\350\256\260/02.md" @@ -0,0 +1,282 @@ +## 规范化 +1. 检查组件名称是否符合要求(避免和HTML标签名冲突, 避免和内置组件冲突) +2. 规范化 props inject directive,将数组或者对象规范化为统一的结构 +3. props 的初始化优先于 data 的初始化 + +```javascript +vm.$options = mergeOptions( + // resolveConstructorOptions(vm.constructor) + { + components: { + KeepAlive + Transition, + TransitionGroup + }, + directives:{ + model, + show + }, + filters: Object.create(null), + _base: Vue + }, + // options || {} + { + el: '#app', + data: { + test: 1 + } + }, + vm +) +``` + +## 属性代理(不会代理以 _ 或 $ 开头的属性) +```javascript +export function proxy (target: Object, sourceKey: string, key: string) { + sharedPropertyDefinition.get = function proxyGetter () { + return this[sourceKey][key] + } + sharedPropertyDefinition.set = function proxySetter (val) { + this[sourceKey][key] = val + } + Object.defineProperty(target, key, sharedPropertyDefinition) +} +``` + +## 关键函数 +```javascript +/** + * Define a property. + */ +export function def (obj: Object, key: string, val: any, enumerable?: boolean) { + Object.defineProperty(obj, key, { + value: val, + enumerable: !!enumerable, + writable: true, + configurable: true + }) +} + +/** + * Define a reactive property on an Object. + */ +export function defineReactive ( + obj: Object, + key: string, + val: any, + customSetter?: ?Function, + shallow?: boolean +) { + const dep = new Dep() + + const property = Object.getOwnPropertyDescriptor(obj, key) + if (property && property.configurable === false) { + return + } + + // cater for pre-defined getter/setters + const getter = property && property.get + const setter = property && property.set + if ((!getter || setter) && arguments.length === 2) { + val = obj[key] + } + + let childOb = !shallow && observe(val) + Object.defineProperty(obj, key, { + enumerable: true, + configurable: true, + get: function reactiveGetter () { + const value = getter ? getter.call(obj) : val + if (Dep.target) { + dep.depend() + if (childOb) { + childOb.dep.depend() + if (Array.isArray(value)) { + dependArray(value) + } + } + } + return value + }, + set: function reactiveSetter (newVal) { + const value = getter ? getter.call(obj) : val + /* eslint-disable no-self-compare */ + if (newVal === value || (newVal !== newVal && value !== value)) { + return + } + /* eslint-enable no-self-compare */ + if (process.env.NODE_ENV !== 'production' && customSetter) { + customSetter() + } + if (setter) { + setter.call(obj, newVal) + } else { + val = newVal + } + childOb = !shallow && observe(newVal) + dep.notify() + } + }) +} +``` +```javascript +const queue: Array = [] +let has: { [key: number]: ?true } = {} +let waiting = false +let flushing = false +/** + * Push a watcher into the watcher queue. + * Jobs with duplicate IDs will be skipped unless it's + * pushed when the queue is being flushed. + */ +export function queueWatcher (watcher: Watcher) { + const id = watcher.id + if (has[id] == null) { + has[id] = true + if (!flushing) { + queue.push(watcher) + } else { + // if already flushing, splice the watcher based on its id + // if already past its id, it will be run next immediately. + let i = queue.length - 1 + while (i > index && queue[i].id > watcher.id) { + i-- + } + queue.splice(i + 1, 0, watcher) + } + // queue the flush + if (!waiting) { + waiting = true + nextTick(flushSchedulerQueue) + } + } +} +``` +每次添加watcher时检查是否存在,只加入不存在的watcher + +```javascript +import { noop } from 'shared/util' +import { handleError } from './error' +import { isIOS, isNative } from './env' + +const callbacks = [] +let pending = false + +function flushCallbacks () { + pending = false + const copies = callbacks.slice(0) + callbacks.length = 0 + for (let i = 0; i < copies.length; i++) { + copies[i]() + } +} + +// Here we have async deferring wrappers using both microtasks and (macro) tasks. +// In < 2.4 we used microtasks everywhere, but there are some scenarios where +// microtasks have too high a priority and fire in between supposedly +// sequential events (e.g. #4521, #6690) or even between bubbling of the same +// event (#6566). However, using (macro) tasks everywhere also has subtle problems +// when state is changed right before repaint (e.g. #6813, out-in transitions). +// Here we use microtask by default, but expose a way to force (macro) task when +// needed (e.g. in event handlers attached by v-on). +let microTimerFunc +let macroTimerFunc +let useMacroTask = false + +// Determine (macro) task defer implementation. +// Technically setImmediate should be the ideal choice, but it's only available +// in IE. The only polyfill that consistently queues the callback after all DOM +// events triggered in the same loop is by using MessageChannel. +/* istanbul ignore if */ +if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { + macroTimerFunc = () => { + setImmediate(flushCallbacks) + } +} else if (typeof MessageChannel !== 'undefined' && ( + isNative(MessageChannel) || + // PhantomJS + MessageChannel.toString() === '[object MessageChannelConstructor]' +)) { + const channel = new MessageChannel() + const port = channel.port2 + channel.port1.onmessage = flushCallbacks + macroTimerFunc = () => { + port.postMessage(1) + } +} else { + /* istanbul ignore next */ + macroTimerFunc = () => { + setTimeout(flushCallbacks, 0) + } +} + +// Determine microtask defer implementation. +/* istanbul ignore next, $flow-disable-line */ +if (typeof Promise !== 'undefined' && isNative(Promise)) { + const p = Promise.resolve() + microTimerFunc = () => { + p.then(flushCallbacks) + // in problematic UIWebViews, Promise.then doesn't completely break, but + // it can get stuck in a weird state where callbacks are pushed into the + // microtask queue but the queue isn't being flushed, until the browser + // needs to do some other work, e.g. handle a timer. Therefore we can + // "force" the microtask queue to be flushed by adding an empty timer. + if (isIOS) setTimeout(noop) + } +} else { + // fallback to macro + microTimerFunc = macroTimerFunc +} + +/** + * Wrap a function so that if any code inside triggers state change, + * the changes are queued using a (macro) task instead of a microtask. + */ +export function withMacroTask (fn: Function): Function { + return fn._withTask || (fn._withTask = function () { + useMacroTask = true + const res = fn.apply(null, arguments) + useMacroTask = false + return res + }) +} + +export function nextTick (cb?: Function, ctx?: Object) { + let _resolve + callbacks.push(() => { + if (cb) { + try { + cb.call(ctx) + } catch (e) { + handleError(e, ctx, 'nextTick') + } + } else if (_resolve) { + _resolve(ctx) + } + }) + if (!pending) { + pending = true + if (useMacroTask) { + macroTimerFunc() + } else { + microTimerFunc() + } + } + // $flow-disable-line + if (!cb && typeof Promise !== 'undefined') { + return new Promise(resolve => { + _resolve = resolve + }) + } +} +``` +IOS下有一个bug,如果当前没有执行的 task 那么不会执行microtask + + +## 数据的初始化 +* 根据 vm.$options.data 选项获取真正想要的数据(注意:此时 vm.$options.data 是函数) +* 校验得到的数据是否是一个纯对象 +* 检查数据对象 data 上的键是否与 props 对象上的键冲突 +* 检查 methods 对象上的键是否与 data 对象上的键冲突 +* 在 Vue 实例对象上添加代理访问数据对象的同名属性 +* 最后调用 observe 函数开启响应式之路 \ No newline at end of file diff --git "a/docs/vue\347\254\224\350\256\260/03.md" "b/docs/vue\347\254\224\350\256\260/03.md" new file mode 100644 index 0000000..6c00328 --- /dev/null +++ "b/docs/vue\347\254\224\350\256\260/03.md" @@ -0,0 +1,48 @@ +## 解析模板 +1. start 钩子函数是当解析 html 字符串遇到开始标签时被调用的。 +2. 模板中禁止使用