├── .gitattributes ├── .gitignore ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── CNAME ├── LICENSE ├── README.md ├── _config.yml ├── assets └── img │ └── logo.png ├── astro.config.ts ├── interview ├── css │ ├── BFC.md │ ├── CSS3特性.md │ ├── WechatIMG851.png │ ├── WechatIMG852.png │ ├── WechatIMG853.png │ ├── 权重优先级.md │ └── 盒模型.md ├── http │ ├── eventLoop.md │ ├── http 缓存.md │ ├── http1.0、http1.1、http2.0区别.md │ ├── session 和 cookie 的区别.md │ ├── 一个网页输入 url 发生了什么.md │ └── 实现一个ajax.md ├── js │ ├── GC.md │ ├── closure.md │ ├── event-loop.js │ ├── prototype.png │ ├── prototypeChain.md │ └── scopChain.md ├── react │ ├── diff机制.md │ ├── setState 更新机制.md │ └── 事件机制.md ├── 工程化 │ ├── Mobx 和 Redux 的区别.md │ ├── tree-shaking.md │ ├── webpack4热更新原理.md │ └── 模块化.md ├── 手写代码 │ ├── debounce.md │ ├── deepClone.md │ ├── new 的实现.md │ ├── unique.md │ ├── useState 的实现.md │ ├── 实现一个 call.md │ ├── 实现一个 promise.all.md │ ├── 实现函数curry.md │ ├── 手写 Promise.md │ └── 数组拍平.md ├── 算法题 │ ├── 两数之和.md │ ├── 单向链表反转.md │ ├── 反转二叉树.md │ ├── 斐波那契.md │ └── 环形链表.md └── 设计模式 │ └── 观察者模式.md ├── main.py ├── package.json ├── public ├── favicon.svg ├── placeholder.png ├── pretty-feed-v3.xsl └── typograph-og.jpg ├── scripts ├── new-post.sh └── update-theme.sh ├── src ├── components │ ├── Footer.astro │ ├── Header.astro │ ├── ListItem.astro │ ├── ListSection.astro │ ├── Pagination.astro │ └── Post.astro ├── content │ ├── config.ts │ └── posts │ │ ├── Error-write.md │ │ ├── 16进制和字符串互转.md │ │ ├── 2023年中前端面试真题之CSS篇.md │ │ ├── 2023年中前端面试真题之HTML篇.md │ │ ├── 2023年中前端面试真题之JS篇.md │ │ ├── 2023年中前端面试真题之React篇.md │ │ ├── 2023年中前端面试真题之Vue篇.md │ │ ├── 2023年中前端面试真题之编码篇.md │ │ ├── 4 年前端如何选择方向.md │ │ ├── Ant Design表格列拖拽,部分源码.md │ │ ├── Babel6 如何升级 7.md │ │ ├── CSS 盒模型.md │ │ ├── Charles 如何扑获 curl 和 Postman 请求.md │ │ ├── CircleCI 自动化部署.md │ │ ├── Could not execute GraphicsMagick:ImageMagick.md │ │ ├── Decorating class property failed.md │ │ ├── ERR_SSL_PROTOCOL_ERROR.md │ │ ├── Eslint for React.md │ │ ├── EventSource 引发的一系列事件.md │ │ ├── Git 分支管理策略.md │ │ ├── GitHub 本地配置和全局配置.md │ │ ├── JS 垃圾回收机制.md │ │ ├── JS 实现 deepCopy.md │ │ ├── JS 实现出入栈操作.md │ │ ├── JS 实现列表操作.md │ │ ├── JS 实现各种排序.md │ │ ├── JS 实现斐波那契数列.md │ │ ├── JS 实现队列.md │ │ ├── JS 数组扁平化之简单方法实现.md │ │ ├── JS 语法规范.md │ │ ├── JS 运行机制类文章.md │ │ ├── MacBook Pro 外接显示屏开启 HiDPI.md │ │ ├── MobX or Redux.md │ │ ├── MobX 源码解析-observable.md │ │ ├── Nginx & Node.js & Express 配置 HTTPS.md │ │ ├── Node.js 爬虫获取网页内容乱码.md │ │ ├── PhantomJS not found on PATH.md │ │ ├── React Router v6 探索.md │ │ ├── React 如何进行上传图片.md │ │ ├── React全家桶建站教程-Express.md │ │ ├── React全家桶建站教程-Nginx.md │ │ ├── React全家桶建站教程-React&Ant.md │ │ ├── React全家桶建站教程-Redux&Saga.md │ │ ├── React全家桶建站教程-Router.md │ │ ├── React全家桶建站教程-Webpack.md │ │ ├── React全家桶建站教程-发布.md │ │ ├── SaaS架构图.md │ │ ├── Taro init遇到权限问题(mac环境).md │ │ ├── Taro实现列表下拉刷新无限滚动.md │ │ ├── Tauri 与 Electron.md │ │ ├── Ubuntu 下安装 WeChat.md │ │ ├── VS Code 提高前端开发效率插件.md │ │ ├── VS Code 用户自定义配置推荐.md │ │ ├── Webpack 如何配置代理.md │ │ ├── Webpack 如何配置热更新.md │ │ ├── Webpack4 性能优化实践.md │ │ ├── Weex如何实现dialog.md │ │ ├── Win CMD 常用命令.md │ │ ├── pm2 The "data" argument must be of type string or an instance of Buffer 问题排查.md │ │ ├── toB开发范式.md │ │ ├── win node-sass 安装失败.md │ │ ├── 【从前端入门到全栈】Node.js 之核心概念.md │ │ ├── 【从前端入门到全栈】Node.js之大文件分片上传.md │ │ ├── 【从前端入门到全栈】全栈是什么- 系列必读.md │ │ ├── 【从前端入门到全栈】前端框架之核心概念.md │ │ ├── 【从前端入门到全栈】登录 Token 鉴权设计.md │ │ ├── 【从前端入门到全栈】系列介绍.md │ │ ├── 【从前端入门到全栈】网站 SEO 提升.md │ │ ├── 【从前端入门到全栈】网站版本更新通知.md │ │ ├── 【技术需求】Element 升级2.md │ │ ├── 【软件工程管理】技术负债.md │ │ ├── 专科如何进入互联网大厂.md │ │ ├── 串串房维权.md │ │ ├── 人生思考.md │ │ ├── 什么是Serverless.md │ │ ├── 从零搭建 Vite + React 开发环境.md │ │ ├── 企业微信自建应用.md │ │ ├── 使用 Sourcetree 提示需要输入密码.md │ │ ├── 使用 react.lazy 打包之后得文件如何不显示数字.md │ │ ├── 修改滚动条样式.md │ │ ├── 关于 TRTC (实时音视频通话模式)的实践.md │ │ ├── 关于 tristana.md │ │ ├── 创业艰难,道阻且长.md │ │ ├── 前端如何支持PDF、Excel、Word在线预览.md │ │ ├── 前端技术架构选型.md │ │ ├── 前端模块化.md │ │ ├── 告前端同学的小记.md │ │ ├── 命令行 MySQL 基本操作(CentOS).md │ │ ├── 图片和文件预览组件(部分源码),可拖动,缩小,放大.md │ │ ├── 垂直居中.md │ │ ├── 基于 Taro 实现签字,轨迹回放.md │ │ ├── 基于MobX 封装 Action 接口 Loading.md │ │ ├── 基于React、Mobx、Webpack 和 React-Router的项目模板.md │ │ ├── 大厂面试题.md │ │ ├── 如何在 MobX 中组织 Stores.md │ │ ├── 如何基于tristana项目模板,打造一个基本列表展示.md │ │ ├── 如何搭建前端异常监控系统.md │ │ ├── 如何搭建前端架构和团队体系.md │ │ ├── 对数字孪生的理解.md │ │ ├── 微信如何打开 VConsole.md │ │ ├── 微信小程序 Canvas 签字.md │ │ ├── 微信小程序 web-view 下渲染 cover-view,文字消失.md │ │ ├── 微信小程序 web-view 问题讨论.md │ │ ├── 搭建自托管密码管理器.md │ │ ├── 桌面、生产力和泛娱乐 .md │ │ ├── 检测浏览器刷新还是退出代码.md │ │ ├── 水平居中.md │ │ ├── 精通JS (持续更新).md │ │ ├── 缓存.md │ │ ├── 网站性能优化几个点.md │ │ ├── 聊聊我对前端已死的看法.md │ │ ├── 解决PDF.js转Canvas图片,toDataURL空白问题.md │ │ ├── 解决canvas,toDataURL跨域问题.md │ │ ├── 解决前端如何通过游览器下载视频地址.md │ │ ├── 解决如何知道iframe下载完成.md │ │ ├── 跨域.md │ │ ├── 配置多入口 Webpack 热更新失效.md │ │ ├── 阿里云 OSS 如何通过 Node.js 上传图片.md │ │ └── 项目0-1.md ├── env.d.ts ├── i18n.ts ├── layouts │ └── LayoutDefault.astro ├── middleware.ts ├── pages │ ├── about.astro │ ├── archive.astro │ ├── atom.xml.ts │ ├── categories │ │ ├── [...category].astro │ │ └── index.astro │ ├── index.astro │ └── posts │ │ ├── [...slug].astro │ │ └── page │ │ └── [page].astro ├── styles │ ├── heti.min.css │ └── index.css ├── theme.config.ts ├── types │ └── index.d.ts └── utils │ └── index.ts ├── tsconfig.json └── uno.config.ts /.gitattributes: -------------------------------------------------------------------------------- 1 | *.xsl linguist-detectable=false 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | # generated types 4 | .astro/ 5 | 6 | # dependencies 7 | node_modules/ 8 | 9 | # logs 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | pnpm-debug.log* 14 | 15 | 16 | # environment variables 17 | .env 18 | .env.production 19 | 20 | # macOS-specific files 21 | .DS_Store 22 | 23 | Markdown style guide.md 24 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["astro-build.astro-vscode"], 3 | "unwantedRecommendations": [] 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "command": "./node_modules/.bin/astro dev", 6 | "name": "Development server", 7 | "request": "launch", 8 | "type": "node-terminal" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[markdown]": { 3 | "editor": { 4 | "quickSuggestions": true 5 | }, 6 | "editor.quickSuggestions": { 7 | "comments": "on", 8 | "other": "on", 9 | "strings": "on" 10 | } 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | blog.downfuture.com -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Moeyua 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | remote_theme: pages-themes/minimal@v0.2.0 2 | plugins: 3 | - jekyll-remote-theme 4 | 5 | title: [江辰博客] 6 | logo: /assets/img/logo.png 7 | description: [分享技术,日常,思考,人生] 8 | 9 | google_analytics: [UA-144561642-1] 10 | -------------------------------------------------------------------------------- /assets/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuya227939/blog/894802b5cfe2f3ce298fccfcbd49e5300806101e/assets/img/logo.png -------------------------------------------------------------------------------- /astro.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "astro/config"; 2 | import UnoCSS from "unocss/astro"; 3 | import { THEME_CONFIG } from "./src/theme.config"; 4 | import robotsTxt from "astro-robots-txt"; 5 | import sitemap from "@astrojs/sitemap"; 6 | 7 | // https://astro.build/config 8 | export default defineConfig({ 9 | site: THEME_CONFIG.website, 10 | prefetch: true, 11 | markdown: { 12 | shikiConfig: { 13 | // Use build-in Shiki theme 14 | // https://github.com/shikijs/shiki/blob/main/docs/themes.md 15 | theme: "one-dark-pro", 16 | // Or visit here for more themes 17 | // https://shikiji.netlify.app/guide/dual-themes#light-dark-dual-themes 18 | // experimentalThemes: { 19 | // light: 'github-light', 20 | // dark: 'github-dark', 21 | // }, 22 | // Add your customized languages 23 | // Note that shiki has many build-in langs including .astro! 24 | // https://github.com/shikijs/shiki/blob/main/docs/languages.md 25 | langs: [], 26 | // auto wrap for better display 27 | wrap: true, 28 | }, 29 | }, 30 | integrations: [ 31 | UnoCSS({ 32 | injectReset: true, 33 | }), 34 | robotsTxt(), 35 | sitemap(), 36 | ], 37 | }); 38 | -------------------------------------------------------------------------------- /interview/css/BFC.md: -------------------------------------------------------------------------------- 1 | ## BFC 概念 2 | 3 | > Formatting context(格式化上下文) 是 W3C CSS2.1 规范中的一个概念。它是页面中的一块渲染区域,并且有一套渲染规则,它决定了其子元素将如何定位,以及和其他元素的关系和相互作用。 4 | 5 | ## BFC 是什么? 6 | 7 | `BFC` 即 `Block Formatting Contexts` (块级格式化上下文),它属于上述定位方案的普通流。 8 | 9 | 具有 `BFC` 特性的元素可以看作是隔离了的独立容器,容器里面的元素不会在布局上影响到外面的元素,并且 `BFC` 具有普通容器所没有的一些特性。 10 | 11 | 通俗一点来讲,可以把 `BFC` 理解为一个封闭的大箱子,箱子内部的元素无论如何翻江倒海,都不会影响到外部。 12 | 13 | ## 触发 BFC 14 | 15 | - html 根元素 16 | - 浮动元素:float 除 none 以外的值 17 | - 绝对定位元素:position (absolute、fixed) 18 | - display 为 inline-block、table-cells、flex 19 | - overflow 除了 visible 以外的值 (hidden、auto、scroll) 20 | 21 | ## BFC 特性及应用 22 | 23 | ### 同一个 BFC 下外边距会发生折叠 24 | 25 | 理解外边距的一个关键是外边距折叠的概念。如果你有两个外边距相接的元素,这些外边距将合并为一个外边距,即最大的单个外边距的大小。 26 | 27 | 在下面的例子中,我们有两个段落。顶部段落的页 margin-bottom 为 50px。第二段的 margin-top 为 30px。因为外边距折叠的概念,所以框之间的实际外边距是 50px,而不是两个外边距的总和。 28 | 29 | 您可以通过将第 2 段的 margin-top 设置为 0 来测试它。两个段落之间的可见边距不会改变——它保留了第一个段落 margin-bottom 设置的 50 像素。 30 | 31 | 32 |
33 |
34 | 35 | 36 | 从效果上看,因为两个 `div` 元素都处于同一个 `BFC` 容器下 (这里指 body 元素) 所以第一个 `div` 的下边距和第二个 `div` 的上边距发生了重叠,所以两个盒子之间距离只有 `100px`,而不是 `200px`。 37 | 38 | 首先这不是 `CSS` 的 bug,我们可以理解为一种规范,如果想要避免外边距的重叠,可以将其放在不同的 `BFC` 容器中。 39 | 40 |
41 |

42 |
43 |
44 |

45 |
46 | 47 | ### BFC 可以包含浮动的元素(清除浮动) 48 | 49 | 我们都知道,浮动的元素会脱离普通文档流,来看下下面一个例子 50 | 51 | ``` 52 |
53 |
54 |
55 | ``` 56 | 57 | 由于容器内元素浮动,脱离了文档流,所以容器只剩下 2px 的边距高度。如果使触发容器的 BFC,那么容器将会包裹着浮动元素。 58 | 59 |
60 |
61 |
62 | 63 | ### BFC 可以阻止元素被浮动元素覆盖 64 | 65 |
我是一个左浮动的元素
66 |
我是一个没有设置浮动, 67 | 也没有触发 BFC 元素, width: 200px; height:200px; background: #eee;
68 | 69 | 这时候其实第二个元素有部分被浮动元素所覆盖,(但是文本信息不会被浮动元素所覆盖) 如果想避免元素被覆盖,可触第二个元素的 BFC 特性,在第二个元素中加入 `overflow: hidden`,就会变成: 70 | 71 |
我是一个左浮动的元素
72 |
我是一个没有设置浮动, 73 | 也没有触发 BFC 元素, width: 200px; height:200px; background: #eee;
74 | -------------------------------------------------------------------------------- /interview/css/CSS3特性.md: -------------------------------------------------------------------------------- 1 | ## box-sizing 2 | 3 | ### border-box 4 | 5 | context = width + padding + border; 6 | 7 | ### context-box 8 | 9 | context = width 10 | 11 | ## flex 布局 12 | 13 | ### flex: 0 1 auto 的含义 14 | 15 | flex-grow:0,表示当子元素的空间小于父元素的空间时,如何处理剩余空间,0 表示不占有剩余空间 16 | 17 | flex-shrink: 1 表示当子元素的空间大于父元素的空间时,如何缩小子元素,1 等比缩小 18 | 19 | flex-basis:auto 用于设置项目占据的主轴空间, auto 等同于盒子的初始宽度 20 | 21 | ## Filter 滤镜 22 | 23 | 一键变灰 24 | 25 | ## 换行 26 | 27 | word-break 28 | 29 | ## box-shadow 30 | 31 | 阴影部分 32 | 33 | ## 形状转换 34 | 35 | transform:适用于 2D 或 3D 转换的元素 36 | 37 | ## transition 38 | 39 | 过渡 40 | -------------------------------------------------------------------------------- /interview/css/WechatIMG851.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuya227939/blog/894802b5cfe2f3ce298fccfcbd49e5300806101e/interview/css/WechatIMG851.png -------------------------------------------------------------------------------- /interview/css/WechatIMG852.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuya227939/blog/894802b5cfe2f3ce298fccfcbd49e5300806101e/interview/css/WechatIMG852.png -------------------------------------------------------------------------------- /interview/css/WechatIMG853.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuya227939/blog/894802b5cfe2f3ce298fccfcbd49e5300806101e/interview/css/WechatIMG853.png -------------------------------------------------------------------------------- /interview/css/权重优先级.md: -------------------------------------------------------------------------------- 1 | ## 什么是权重 2 | 3 | - 权重决定了你 `css` 规则怎样被浏览器解析直到生效。 4 | - 当很多的样式被应用到某一个元素上时,权重是一个决定哪种样式生效,或者是优先级的过程。 5 | - 每个选择器都有自己的权重。你的每条 `css` 规则,都包含一个权重级别。 这个级别是由不同的选择器加权计算的,通过权重,不同的样式最终会作用到你的网页中 。 6 | - 如果两个选择器同时作用到一个元素上,权重高者生效。 7 | 8 | ## 权重记忆口诀 9 | 10 | - !important 优先级最高 11 | - 一个行内样式 +1000 12 | - 一个 id 选择器 +100 13 | - 一个属性选择器,class 或伪类 +10 14 | - 一个元素选择器或伪元素 +1 15 | - 通配符 +0 16 | 17 | ## 总结 18 | 19 | - 常用选择器权重优先级:!important > id > class > tag 20 | - 如果两条样式都使用 !important,则权重值高的优先级更高 21 | -------------------------------------------------------------------------------- /interview/css/盒模型.md: -------------------------------------------------------------------------------- 1 | ## 什么是盒模型 2 | 3 | > 完整的 CSS 盒模型应用于块级盒子,内联盒子只使用盒模型中定义的部分内容。模型定义了盒的每个部分 —— margin, border, padding, and content —— 合在一起就可以创建我们在页面上看到的内容。为了增加一些额外的复杂性,有一个标准的和替代(IE)的盒模型。 4 | 5 | ## 组成部分 6 | 7 | - Content box: 这个区域是用来显示内容,大小可以通过设置 width 和 height. 8 | - Padding box: 包围在内容区域外部的空白区域; 大小通过 padding 相关属性设置。 9 | - Border box: 边框盒包裹内容和内边距。大小通过 border 相关属性设置。 10 | - Margin box: 这是最外面的区域,是盒子和其他元素之间的空白区域。大小通过 margin 相关属性设置。 11 | 12 | ![WechatIMG851](./WechatIMG851.png) 13 | 14 | ## 标准盒模型 15 | 16 | 在标准模型中,如果你给盒设置 `width` 和 `height`,实际设置的是 `content box`。 `padding` 和 `border` 再加上设置的宽高一起决定整个盒子的大小。 见下图。 17 | 18 | 假设定义了 width, height, margin, border, and padding 19 | 20 | > ``` 21 | > .box { 22 | > width: 350px; 23 | > height: 150px; 24 | > margin: 25px; 25 | > padding: 25px; 26 | > border: 5px solid black; 27 | > } 28 | > ``` 29 | 30 | 如果使用标准模型宽度 = 410px (350 + 25 + 25 + 5 + 5),高度 = 210px (150 + 25 + 25 + 5 + 5),padding 加 border 再加 content box。 31 | 32 | ![WechatIMG852](./WechatIMG852.png) 33 | 34 | > 注: margin 不计入实际大小 —— 当然,它会影响盒子在页面所占空间,但是影响的是盒子外部空间。盒子的范围到边框为止 —— 不会延伸到 margin。 35 | 36 | ## 怪异盒模型(IE) 37 | 38 | 你可能会认为盒子的大小还要加上边框和内边距,这样很麻烦,而且你的想法是对的! 因为这个原因,css 还有一个替代盒模型。使用这个模型,所有宽度都是可见宽度,所以内容宽度是该宽度减去边框和填充部分。使用上面相同的样式得到 (width = 350px, height = 150px). 39 | 40 | ![WechatIMG853](./WechatIMG853.png) 41 | 42 | 默认浏览器会使用标准模型。如果需要使用替代模型,您可以通过为其设置 `box-sizing: border-box` 来实现。 这样就可以告诉浏览器使用 `border-box` 来定义区域,从而设定您想要的大小。 43 | 44 | > 注: 一个有趣的历史记录 ——Internet Explorer 默认使用替代盒模型,没有可用的机制来切换。(译者注:IE8+ 支持使用 box-sizing 进行切换 ) 45 | -------------------------------------------------------------------------------- /interview/http/eventLoop.md: -------------------------------------------------------------------------------- 1 | ## 什么是 Event Loop 2 | 3 | 我的理解是浏览器事件循环,说到 Event Loop,首先我们同步下几个概念: 4 | 5 | - JS 分同步任务和异步任务 6 | - 同步任务都在 JS 引擎线程上执行,形成一个执行栈 7 | - 事件触发线程管理一个任务队列 8 | - 执行栈中所有同步任务执行完毕,此时 JS 引擎线程空闲,系统会读取任务队列,将可执行的异步任务回调事件添加到执行栈中,开始执行 9 | 10 | ## 宏任务 11 | 12 | 我们可以将每次执行栈执行的代码当做是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行), 每一个宏任务会从头到尾执行完毕,不会执行其他。 13 | 14 | 我们前文提到过 JS 引擎线程和 GUI 渲染线程是互斥的关系,浏览器为了能够使宏任务和 DOM 任务有序的进行,会在一个宏任务执行结果后,在下一个宏任务执行前,GUI 渲染线程开始工作,对页面进行渲染。 15 | 16 | ### 哪些是宏观任务 17 | 18 | - setTimeout 的回调 19 | - setInterval 的回调 20 | - setImmediate 的回调 21 | - requestAnimationFrame (浏览器独有) 22 | - I/O 23 | - UI rendering (浏览器独有) 24 | - promise 的回调(new Promise()) 25 | 26 | ## 微任务 27 | 28 | 我们已经知道宏任务结束后,会执行渲染,然后执行下一个宏任务, 29 | 而微任务可以理解成在当前宏任务执行后立即执行的任务。 30 | 也就是说,当宏任务执行完,会在渲染前,将执行期间所产生的所有微任务都执行完。 31 | Promise,process.nextTick 等,属于微任务 32 | 33 | ### 哪些是微观任务 34 | 35 | - process.nextTick (Node 独有) 36 | - Promise 的 then 的回调(注意,new Promise()的函数参数是同步代码,then 的回调才是异步) 37 | - await 下方的代码和赋值代码(注意,await 后方的函数是同步代码) 38 | - MutationObserver 39 | 40 | ## 总结 41 | 42 | - 执行一个宏任务(栈中没有就从事件队列中获取) 43 | - 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中 44 | - 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行) 45 | - 当前宏任务执行完毕,开始检查渲染,然后 GUI 线程接管渲染 46 | - 渲染完毕后,JS 线程继续接管,开始下一个宏任务(从事件队列中获取) 47 | 48 | eventLoop 执行的生命周期是,首先执行同步任务队列栈,同步任务执行完之后,执行过程中碰到微观任务,则放到微任务队列栈中,执行宏观任务,所有的宏观任务执行完之后,如果有微观任务,立即执行当前微任务队列中的所有微任务,当前宏任务执行完毕,开始检查渲染,再执行 GUI 渲染线程,然后回到执行栈,一直重复这个这个循环过程。 49 | 50 | ## 例题 51 | 52 | ### 例题一 53 | 54 | ``` 55 | console.log(1); 56 | 57 | setTimeout(function () { 58 | console.log(2); 59 | }, 0); 60 | 61 | const p = new Promise((resolve, reject) => { 62 | resolve(1000); 63 | }); 64 | 65 | p.then(data => { 66 | console.log(data); 67 | }); 68 | 69 | console.log(3); 70 | 71 | // 代码运行结果为 1 3 1000 2 72 | ``` 73 | 74 | ### 例题二 75 | 76 | ``` 77 | console.log(1); 78 | 79 | setTimeout(function () { 80 | console.log(2); 81 | }, 0); 82 | 83 | const p = new Promise((resolve, reject) => { 84 | resolve(1000); 85 | }); 86 | 87 | const p2 = new Promise((resolve, reject) => { 88 | resolve(2000); 89 | }); 90 | 91 | const p3 = new Promise((resolve, reject) => { 92 | reject(3000); 93 | }); 94 | 95 | p.then(data => { 96 | console.log(data); 97 | setTimeout(() => { 98 | console.log('t3'); 99 | }, 0); 100 | }); 101 | 102 | p2.then(data => { 103 | console.log(data); 104 | setTimeout(() => { 105 | console.log('t2'); 106 | }, 0); 107 | }); 108 | 109 | p3.catch(err => { 110 | console.log(err); 111 | setTimeout(() => { 112 | console.log('t1'); 113 | }, 1000); 114 | }); 115 | 116 | console.log(3); 117 | 118 | // 代码运行结果为 1 3 1000 2000 3000,2,t3,t2, t1 119 | ``` 120 | 121 | ### 例题三 122 | 123 | ``` 124 | new Promise((resolve, reject) => { 125 | console.log(1) 126 | resolve(2) 127 | }).then((data) => { 128 | // 1号回调 129 | console.log(data); 130 | return 3 131 | }).then((data) => { 132 | // 2号回调 133 | console.log(data); 134 | return 4; 135 | }).then((data) => { 136 | console.log(data); 137 | }); 138 | 139 | new Promise((resolve, reject) => { 140 | console.log(5) 141 | resolve(6) 142 | }).then((data) => { 143 | // 3号回调 144 | console.log(data); 145 | return 7; 146 | }).then((data) => { 147 | // 4号回调 148 | console.log(data); 149 | return 8; 150 | }).then((data) => { 151 | console.log(data); 152 | }); 153 | ``` 154 | 155 | // 代码运行结果为 1 5 2 6 3 7 4 8 156 | 157 | ### 例题四 158 | 159 | ``` 160 | console.log('script start'); 161 | 162 | async function async1() { 163 | await async2(); 164 | console.log('async1 end'); 165 | }; 166 | 167 | async function async2() { 168 | console.log('async2 end'); 169 | }; 170 | 171 | async1() 172 | 173 | setTimeout(() => { 174 | console.log('setTimeout') 175 | }, 0) 176 | 177 | new Promise((resolve, reject) => { 178 | console.log('promise start'); 179 | resolve() 180 | }) 181 | .then(() => console.log('promise end')) 182 | 183 | console.log('script end') 184 | 185 | // 执行结果 186 | 187 | script start 188 | 189 | async2 end 190 | 191 | promise start 192 | 193 | script end 194 | 195 | async1 end 196 | 197 | promise end 198 | 199 | setTimeout 200 | ``` 201 | -------------------------------------------------------------------------------- /interview/http/http1.0、http1.1、http2.0区别.md: -------------------------------------------------------------------------------- 1 | ## HTTP1.1 2 | 3 | ### 缺陷 4 | 5 | 线程阻塞,在同一时间,同一域名的请求有一定数量限制,超过限制数目的请求会被阻塞(Chrome 最多 6 个并发请求) 6 | 7 | 浏览器与服务器只保持短暂的连接,浏览器的每次请求都需要与服务器建立一个 TCP 连接(TCP 连接的新建成本很高,因为需要客户端和服务器三次握手),服务器完成请求处理后立即断开 TCP 连接,服务器不跟踪每个客户也不记录过去的请求 8 | 解决方案: 9 | 10 | 添加头信息——非标准的 Connection 字段 Connection: keep-alive 11 | http1.1: 12 | 13 | ### 改进点 14 | 15 | 1. 持久化连接 16 | 17 | 引入了持久连接,即 TCP 连接默认不关闭,可以被多个请求复用,不用声明 Connection: keep-alive(对于同一个域名,大多数浏览器允许同时建立 6 个持久连接) 18 | 19 | 2. 管道机制 20 | 21 | 即在同一个 TCP 连接里面,客户端可以同时发送多个请求 22 | 分块传输编码 23 | 24 | 即服务端没产生一块数据,就发送一块,采用”流模式”而取代”缓存模式” 25 | 26 | 3. 新增请求方式 27 | 28 | PUT: 请求服务器存储一个资源; 29 | DELETE:请求服务器删除标识的资源; 30 | OPTIONS:请求查询服务器的性能,或者查询与资源相关的选项和需求; 31 | TRACE:请求服务器回送收到的请求信息,主要用于测试或诊断; 32 | CONNECT:保留将来使用 33 | 34 | 缺点: 35 | 36 | 虽然允许复用 TCP 连接,但是同一个 TCP 连接里面,所有的数据通信是按次序进行的。服务器只有处理完一个请求,才会接着处理下一个请求。如果前面的处理特别慢,后面就会有许多请求排队等着。这将导致“队头堵塞” 37 | 避免方式: 38 | 39 | 1. 是减少请求数 40 | 2. 同时多开持久连接 41 | 42 | ## HTTP/2.0 43 | 44 | ### 特点 45 | 46 | - 采用二进制格式而非文本格式 47 | - 完全多路复用,而非有序并阻塞的、只需一个连接即可实现并行; 48 | - 使用报头压缩,降低开销 49 | - 服务器推送 50 | 51 | #### 二进制协议 52 | 53 | HTTP/1.1 版的头信息肯定是文本(ASCII 编码),数据体可以是文本,也可以是二进制。HTTP/2 则是一个彻底的二进制协议,头信息和数据体都是二进制,并且统称为”帧”:头信息帧和数据帧。 54 | 二进制协议解析起来更高效、“线上”更紧凑,更重要的是错误更少。 55 | 56 | #### 完全多路复用 57 | 58 | HTTP/2 复用 TCP 连接,在一个连接里,客户端和浏览器都可以同时发送多个请求或回应,而且不用按照顺序一一对应,这样就避免了”队头堵塞”。 59 | 60 | #### 报头压缩 61 | 62 | HTTP 协议是没有状态,导致每次请求都必须附上所有信息。所以,请求的很多头字段都是重复的,比如 Cookie,一样的内容每次请求都必须附带,这会浪费很多带宽,也影响速度。 63 | 对于相同的头部,不必再通过请求发送,只需发送一次 64 | HTTP/2 对这一点做了优化,引入了头信息压缩机制 65 | 一方面,头信息使用 gzip 或 compress 压缩后再发送 66 | 另一方面,客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,产生一个索引号,之后就不发送同样字段了,只需发送索引号。 4. 服务器推送 67 | 68 | HTTP/2 允许服务器未经请求,主动向客户端发送资源; 69 | 通过推送那些服务器任务客户端将会需要的内容到客户端的缓存中,避免往返的延迟 70 | 71 | ## HTTPS 72 | 73 | HTTP 协议通常承载于 TCP 协议之上,在 HTTP 和 TCP 之间添加一个安全协议层(SSL 或 TSL),这个时候,就成了我们常说的 HTTPS 74 | 75 | ### HTTPS 主要作用 76 | 77 | - 对数据进行加密,并建立一个信息安全通道,来保证传输过程中的数据安全 78 | - 对网站服务器进行真实身份认证 79 | - HTTPS 和 HTTP 的区别 80 | 81 | - HTTPS 是加密传输协议,HTTP 是名文传输协议 82 | - HTTPS 需要用到 SSL 证书,而 HTTP 不用 83 | - HTTPS 比 HTTP 更加安全,对搜索引擎更友好,利于 SEO 84 | - HTTPS 标准端口 443,HTTP 标准端口 80; - 5、 HTTPS 基于传输层,HTTP 基于应用层 85 | 3、HTTPS 和 HTTP 的工作过程区别 86 | 87 | ### HTTP 包含动作 88 | 89 | 浏览器打开一个 TCP 连接 90 | 浏览器发送 HTTP 请求到服务器端 91 | 服务器发送 HTTP 回应信息到浏览器 92 | TCP 连接关闭 93 | 94 | #### SSL 包含动作: 95 | 96 | 验证服务器端 97 | 客户端和服务器端选择加密算法和密码,确保双方都支持 98 | 验证客户端(可选) 99 | 使用公钥加密技术来生成共享加密数据 100 | 创建一个加密的 SSL 连接 101 | 基于该 SSL 连接传递 HTTP 请求 102 | 103 | ### HTTPS 加密方式 104 | 105 | #### 对称加密 106 | 107 | 加密和解密都是使用的同一个密钥; 108 | 109 | #### 非对称加密: 110 | 111 | 加密使用的密钥和解密使用的密钥是不相同的,分别称为:公钥、私钥 112 | 公钥和算法都是公开的,私钥是保密的 113 | 114 | #### 非对称加密过程: 115 | 116 | 服务端生成配对的公钥和私钥 117 | 私钥保存在服务端,公钥发送给客户端 118 | 客户端使用公钥加密明文传输给服务端 119 | 服务端使用私钥解密密文得到明文 120 | 数字签名:签名就是在信息的后面再加上一段内容,可以证明信息没有被修改过 121 | -------------------------------------------------------------------------------- /interview/http/session 和 cookie 的区别.md: -------------------------------------------------------------------------------- 1 | ## 差异 2 | 3 | 1. 储存位置 4 | 5 | session 储存在服务器端 6 | 7 | cookie 储存在客户端 8 | 9 | 2. 安全性 10 | 11 | cookie 不是很安全,储存在客户端,有 cookie 欺骗的风险 12 | 13 | session 则储存在服务器端,相对安全 14 | 15 | 3. 储存大小 16 | 17 | 单个 cookie 储存大小不能超过 4k 18 | 19 | 4. 存储类型 20 | 21 | cookie 只能存储 string 对象 22 | 23 | session 则支持 java 对象 24 | 25 | ## session 和 cookies 不同点 26 | 27 | session 和 cookies 同样都是针对单独用户的变量(或者说是对象好像更合适点),不同的用户在访问网站的时候 都会拥有各自的 session 或者 cookies,不同用户之间互不干扰。 28 | 他们的不同点是: 29 | 30 | 1. 存储位置不同 31 | session 在服务器端产生,比较安全,但是如果 session 较多则会影响性能 32 | cookies 在客户端产生,安全性稍弱 33 | 2. 生命周期不同 34 | session 生命周期 在指定的时间(如 20 分钟)到了之后会结束,不到指定的时间,也会随着浏览器进程的结束而结束。 35 | cookies 默认情况下也随着浏览器进程结束而结束,但如果手动指定时间,则不受浏览器进程结束的影响。 36 | -------------------------------------------------------------------------------- /interview/http/一个网页输入 url 发生了什么.md: -------------------------------------------------------------------------------- 1 | 用户输入 url 并回车 2 | 浏览器进程检查 url,组装协议,构成完整的 url 3 | 浏览器进程通过进程间通信(IPC)把 url 请求发送给网络进程 4 | 网络进程接收到 url 请求后检查本地缓存是否缓存了该请求资源,如果有则将该资源返回给浏览器进程 5 | 6 | - 如果没有,网络进程向 web 服务器发起 http 请求(网络请求),请求流程如下: 7 | 进行 DNS 解析,获取服务器 ip 地址,端口(端口是通过 dns 解析获取的吗?这里有个疑问) 8 | 利用 ip 地址和服务器建立 tcp 连接 9 | 构建请求头信息 10 | 发送请求头信息 11 | 服务器响应后,网络进程接收响应头和响应信息,并解析响应内容 12 | - 网络进程解析响应流程; 13 | 检查状态码,如果是 301/302,则需要重定向,从 Location 自动中读取地址,重新进行第 4 步 (301/302 跳转也会读取本地缓存吗?这里有个疑问),如果是 200,则继续处理请求。 14 | 200 响应处理:检查响应类型 Content-Type,如果是字节流类型,则将该请求提交给下载管理器,该导航流程结束,不再进行后续的渲染,如果是 html 则通知浏览器进程准备渲染进程准备进行渲染。 15 | - 准备渲染进程 16 | 浏览器进程检查当前 url 是否和之前打开的渲染进程根域名是否相同,如果相同,则复用原来的进程,如果不同,则开启新的渲染进程 17 | - 传输数据、更新状态 18 | 渲染进程准备好后,浏览器向渲染进程发起“提交文档”的消息,渲染进程接收到消息和网络进程建立传输数据的“管道” 19 | 渲染进程接收完数据后,向浏览器发送“确认提交” 20 | 浏览器进程接收到确认消息后更新浏览器界面状态:安全、地址栏 url、前进后退的历史状态、更新 web 页面 21 | -------------------------------------------------------------------------------- /interview/http/实现一个ajax.md: -------------------------------------------------------------------------------- 1 | ## 例子 2 | 3 | ``` 4 | function ajax(options = {}) { 5 | const xhr = new XmlHttpRequest(); 6 | options.type = (options.type || 'GET').toUpperCase(); 7 | options.dataType = options.dataType || 'json'; 8 | const params = options.data; 9 | if(options.type == 'GET') { 10 | xhr.open('GET', options.url + '?' + params, true); 11 | xhr.send(null); 12 | } 13 | if(options.type == 'POST') { 14 | xhr.open('POST', options.url, true); 15 | xhr.send(params); 16 | } 17 | 18 | xhr.onreadystatechange = function() { 19 | if(xhr.readyState == 4) { 20 | if(xhr.status >= 200 && xhr.status < 300) { 21 | options.success && options.success(xhr.responseText, xhr.responseXml); 22 | } else { 23 | options.fail && options.fail(xhr.status); 24 | } 25 | } 26 | } 27 | } 28 | ``` 29 | 30 | ## 进阶 1,封装一个 all 函数,在实现的 ajax 基础上实现调用所有请求,等到所有请求结果成功之后,再返回结果 31 | 32 | ``` 33 | function all(arr, callback) { 34 | let values = [], count = 0; 35 | for(let i = 0; i < arr.length; i++) { 36 | ajax({ url: arr[i], success: (data) => { 37 | count++; 38 | if(count == arr.length) { 39 | values.push(data); 40 | callback(values); 41 | } 42 | }, fail: (error) => { 43 | count++; 44 | if(count == arr.length) { 45 | values.push(error); 46 | callback(values); 47 | } 48 | } }); 49 | } 50 | } 51 | ``` 52 | 53 | ## 进阶 2, 在实现的 ajax 基础上实现请求顺序调用,第一个调用成功之后,再调用第二个,以此类推 54 | 55 | ``` 56 | function all(arr, callback) { 57 | let values = [], count = 0; 58 | for(let i = 0; i < arr.length; i++) { 59 | ajax({ url: arr[i], success: (data) => { 60 | count++; 61 | if(count == arr.length) { 62 | // 这里是区别,增加了索引,来保存这个值 63 | values[i] = data; 64 | callback(values); 65 | } 66 | }, fail: (error) => { 67 | count++; 68 | if(count == arr.length) { 69 | // 这里是区别,增加了索引,来保存这个值 70 | values[i] = error; 71 | callback(values); 72 | } 73 | } }); 74 | } 75 | } 76 | ``` 77 | -------------------------------------------------------------------------------- /interview/js/GC.md: -------------------------------------------------------------------------------- 1 | # 垃圾回收机制(GC) 2 | 3 | ## 可达性(Reachability) 4 | 5 | JavaScript 中主要的内存管理概念是 可达性。 6 | 7 | 简而言之,“可达”值是那些以某种方式可访问或可用的值。它们一定是存储在内存中的。 8 | 9 | 这里列出固有的可达值的基本集合,这些值明显不能被释放。 10 | 11 | 比方说: 12 | 13 | - 当前函数的局部变量和参数。 14 | - 嵌套调用时,当前调用链上所有函数的变量与参数。 15 | - 全局变量。 16 | - (还有一些内部的) 17 | 18 | 这些值被称作 根(roots)。 19 | 20 | 如果一个值可以通过引用或引用链从根访问任何其他值,则认为该值是可达的。 21 | 22 | 比方说,如果全局变量中有一个对象,并且该对象有一个属性引用了另一个对象,则 该 对象被认为是可达的。而且它引用的内容也是可达的。下面是详细的例子。 23 | 24 | 在 JavaScript 引擎中有一个被称作 垃圾回收器 的东西在后台执行。它监控着所有对象的状态,并删除掉那些已经不可达的。 25 | 26 | ## 内存生命周期 27 | 28 | 内存也是有生命周期的,不管什么程序语言,一般可以按顺序分为三个周期: 29 | 30 | - 分配期 31 | 分配所需要的内存 32 | 33 | - 使用期 34 | 使用分配到的内存(读、写) 35 | 36 | - 释放期 37 | 不需要时将其释放和归还 38 | 39 | 内存分配 -> 内存使用 -> 内存释放。 40 | 41 | ## 什么是内存泄漏? 42 | 43 | > 在计算机科学中,内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。 44 | 45 | ## 内存分配 46 | 47 | ``` 48 | // 给数值变量分配内存 49 | const number = 123; 50 | // 给字符串分配内存 51 | const string = "xianshannan"; 52 | // 给对象及其包含的值分配内存 53 | const object = { 54 | a: 1, 55 | b: null 56 | }; 57 | // 给数组及其包含的值分配内存(就像对象一样) 58 | const array = [1, null, "abra"]; 59 | // 给函数(可调用的对象)分配内存 60 | function func(a) { 61 | return a; 62 | } 63 | ``` 64 | 65 | ## 内存使用 66 | 67 | > 使用值的过程实际上是对分配内存进行读取与写入的操作。读取与写入可能是写入一个变量或者一个对象的属性值,甚至传递函数的参数。 68 | 69 | ## 内存回收 70 | 71 | > 前端界一般称垃圾内存回收为 GC(Garbage Collection,即垃圾回收)。 72 | 73 | 内存泄漏一般都是发生在这一步,JavaScript 的内存回收机制虽然能回收绝大部分的垃圾内存,但是,还是存在回收不了的情况,如果存在这些情况,需要我们手动清理内存。 74 | 75 | ### 引用计数垃圾收集 76 | 77 | > 这是最初级的垃圾收集算法。此算法把“对象是否不再需要”简化定义为“对象有没有其他对象引用到它”。如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收。 78 | 79 | #### 优点 80 | 81 | - 可即刻回收垃圾 82 | - 最大暂停时间短 83 | - 没有必要沿指针查找, 不要和标记-清除算法一样沿着根集合开始查找 84 | 85 | #### 缺点 86 | 87 | - 计数器的增减处理繁重 88 | - 计数器需要占用很多位 89 | - 实现繁琐复杂, 每个赋值操作都得替换成引用更新操作 90 | - 循环引用无法回收 91 | 92 | ### 标记清除法 93 | 94 | > 当变量进入执行环境时标记为“进入环境”,当变量离开执行环境时则标记为“离开环境”,被标记为“进入环境”的变量是不能被回收的,因为它们正在被使用,而标记为“离开环境”的变量则可以被回收 95 | 96 | ``` 97 | // 假设这里是全局变量 98 | // b 被标记进入环境 99 | var b = 2; 100 | function test() { 101 | var a = 1; 102 | // 函数执行时,a 被标记进入环境 103 | return a + b; 104 | } 105 | // 函数执行结束,a 被标记离开环境,被回收 106 | // 但是 b 就没有被标记离开环境 107 | test(); 108 | ``` 109 | 110 | #### 内部算法 111 | 112 | 垃圾回收的基本算法被称为 “mark-and-sweep”。 113 | 114 | 定期执行以下“垃圾回收”步骤: 115 | 116 | - 垃圾收集器找到所有的根,并“标记”(记住)它们。 117 | - 然后它遍历并“标记”来自它们的所有引用。 118 | - 然后它遍历标记的对象并标记 它们的 引用。所有被遍历到的对象都会被记住,以免将来再次遍历到同一个对象。 119 | - ……如此操作,直到所有可达的(从根部)引用都被访问到。 120 | - 没有被标记的对象都会被删除。 121 | 122 | #### 优点 123 | 124 | 实现简单, 容易和其他算法组合 125 | 126 | #### 缺点 127 | 128 | - 碎片化, 会导致无数小分块散落在堆的各处 129 | - 分配速度不理想,每次分配都需要遍历空闲列表找到足够大的分块 130 | - 与写时复制技术不兼容,因为每次都会在活动对象上打上标记 131 | 132 | ### 代际假说 133 | 134 | 代际假说(The Generational Hypothesis)是垃圾回收领域中的一个重要术语, V8 的垃圾回收的策略也是建立在该假说的基础之上。 135 | 代际假说也很简单,主要有两个特点: 136 | 137 | - 大部分对象在内存中存在的时间很短,简单来说,就是很多对象一经分配内存,很快就变得不可访问。 138 | 139 | - 不死的对象,会活的更久。 140 | 141 | 基于这个这个假说 V8 才会把堆分为新生代和老生代两个区域,同时设计了两个垃圾回收器: 142 | 143 | 副垃圾回收器 负责新生代区域的垃圾回收 144 | 主垃圾回收器 负责老生代区域的垃圾回收 145 | 146 | ## v8 的垃圾回收机制 147 | 148 | 采用了分代式垃圾回收机制。V8 将内存(堆)分为新生代和老生代两部分。 149 | 150 | ### 新生代算法 151 | 152 | > 新生代中的对象一般存活时间较短,使用 Scavenge GC 算法。大多数的对象被分配在这里,这个区域很小但是垃圾回特别频繁。在新生代分配内存非常容易,我们只需要保存一个指向内存区的指针,不断根据新对象的大小进行递增即可。当该指针到达了新生代内存区的末尾,就会有一次清理(仅仅是清理新生代)在新生代空间中,内存空间分为两部分,分别为 From 空间和 To 空间。在这两个空间中,必定有一个空间是使用的,另一个空间是空闲的。新分配的对象会被放入 From 空间中,当 From 空间被占满时,新生代 GC 就会启动了。算法会检查 From 空间中存活的对象并复制到 To 空间中,如果有失活的对象就会销毁。当复制完成后将 From 空间和 To 空间互换,这样 GC 就结束了 153 | 154 | ### 老生代算法 155 | 156 | > 老生代所保存的对象大多数是生存周期很长的甚至是常驻内存的对象,而且老生代占用的内存较多,老生代中的垃圾回收策略是 Mark-Sweep(标记清除)和 Mark-Compact(标记整理)相结合。 157 | -------------------------------------------------------------------------------- /interview/js/closure.md: -------------------------------------------------------------------------------- 1 | ## 闭包 2 | 3 | 开发者通常应该都知道“闭包”这个通用的编程术语。 4 | 5 | 闭包 是指内部函数总是可以访问其所在的外部函数中声明的变量和参数,即使在其外部函数被返回(寿命终结)了之后。在某些编程语言中,这是不可能的,或者应该以特殊的方式编写函数来实现。但是如上所述,在 JavaScript 中,所有函数都是天生闭包的(只有一个例外,将在 "new Function" 语法 中讲到)。 6 | 7 | 也就是说:JavaScript 中的函数会自动通过隐藏的 [[Environment]] 属性记住创建它们的位置,所以它们都可以访问外部变量。 8 | 9 | 在面试时,前端开发者通常会被问到“什么是闭包?”,正确的回答应该是闭包的定义,并解释清楚为什么 JavaScript 中的所有函数都是闭包的,以及可能的关于 [[Environment]] 属性和词法环境原理的技术细节。 10 | -------------------------------------------------------------------------------- /interview/js/event-loop.js: -------------------------------------------------------------------------------- 1 | Promise.resolve() 2 | .then(() => { 3 | console.log(0); 4 | return Promise.resolve(4); 5 | }) 6 | .then(res => { 7 | console.log(res); 8 | }) 9 | .catch(() => {}); 10 | 11 | Promise.resolve() 12 | .then(() => { 13 | console.log(1); 14 | }) 15 | .then(() => { 16 | console.log(2); 17 | }) 18 | .then(() => { 19 | console.log(3); 20 | }) 21 | .then(() => { 22 | console.log(5); 23 | }) 24 | .then(() => { 25 | console.log(6); 26 | }); 27 | -------------------------------------------------------------------------------- /interview/js/prototype.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuya227939/blog/894802b5cfe2f3ce298fccfcbd49e5300806101e/interview/js/prototype.png -------------------------------------------------------------------------------- /interview/js/prototypeChain.md: -------------------------------------------------------------------------------- 1 | ## prototype 2 | 3 | 每个函数都有一个 prototype 属性,就是我们经常在各种例子中看到的那个 prototype ,比如: 4 | 5 | ``` 6 | function Person() { 7 | 8 | } 9 | // 虽然写在注释里,但是你要注意: 10 | // prototype是函数才会有的属性 11 | Person.prototype.name = 'Kevin'; 12 | var person1 = new Person(); 13 | var person2 = new Person(); 14 | console.log(person1.name) // Kevin 15 | console.log(person2.name) // Kevin 16 | ``` 17 | 18 | 那这个函数的 prototype 属性到底指向的是什么呢?是这个函数的原型吗? 19 | 20 | 其实,函数的 prototype 属性指向了一个对象,这个对象正是调用该构造函数而创建的实例的原型,也就是这个例子中的 person1 和 person2 的原型。 21 | 22 | 那什么是原型呢?你可以这样理解:每一个 JavaScript 对象(null 除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型"继承"属性。 23 | 24 | 让我们用一张图表示构造函数和实例原型之间的关系: 25 | 26 | ![prototype](./prototype.png) 27 | 28 | 在这张图中我们用 Object.prototype 表示实例原型。 29 | 30 | 那么我们该怎么表示实例与实例原型,也就是 person 和 Person.prototype 之间的关系呢,这时候我们就要讲到第二个属性 31 | 32 | ## _proto_ 33 | 34 | 这是每一个 JavaScript 对象(除了 null )都具有的一个属性,叫\_\_proto\_\_,这个属性会指向该对象的原型。 35 | 36 | ## 原型链 37 | 38 | 理解原型链的基础,首先要理解,原型、实例、构造函数名词的意思 39 | 40 | 当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层(当查找到 Object.prototype)为止。 41 | 42 | 原型链的最顶层则是 `Obejct.prototype`,`Object.prototype` 的原形则是 `null` 43 | 44 | ## 原型继承 45 | 46 | ``` 47 | function Parent() { 48 | this.name = '123' 49 | } 50 | 51 | Parent.prototype.getName = function() { 52 | console.log(this.name); 53 | } 54 | 55 | function Child() {} 56 | 57 | Child.prototype = new Parent(); 58 | 59 | var child = new Child(); 60 | 61 | child.getName(); 62 | ``` 63 | 64 | 优点 65 | 66 | - 简单 67 | 68 | 缺点 69 | 70 | - 引用类型的属性被所有示例共享 71 | - 在创建实例时,不能向父实例传参 72 | 73 | ## 寄生组合继承 74 | 75 | ``` 76 | function Parent(name) { 77 | this.name = name; 78 | } 79 | 80 | Parent.prototype.getName = function() { 81 | console.log(this.name); 82 | } 83 | 84 | function Child(name, age) { 85 | Parent.call(this, name); 86 | this.age = age; 87 | } 88 | 89 | Child.prototype = new Parent(); 90 | 91 | var child1 = new Child('faker', 18); 92 | console.log(child1); 93 | ``` 94 | 95 | 优点 96 | 97 | - 这种方式的高效率体现它只调用了一次 Parent 构造函数,并且因此避免了在 Parent.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用 instanceof 和 isPrototypeOf。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。 98 | 99 | 缺点 100 | 101 | - 寄生组合继承最大的缺点是会调用两次父构造函数。 102 | -------------------------------------------------------------------------------- /interview/js/scopChain.md: -------------------------------------------------------------------------------- 1 | ## scopChian 2 | 3 | > 当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。 4 | 5 | 以下面的例子为例,结合着之前讲的变量对象和执行上下文栈,我们来总结一下函数执行上下文中作用域链和变量对象的创建过程: 6 | 7 | ``` 8 | var scope = "global scope"; 9 | function checkscope() { 10 | var scope2 = 'local scope'; 11 | return scope2; 12 | } 13 | checkscope(); 14 | ``` 15 | 16 | 执行过程如下: 17 | 18 | 1.checkscope 函数被创建,保存作用域链到 内部属性[[scope]] 19 | 20 | ``` 21 | checkscope.[[scope]] = [ 22 | globalContext.VO 23 | ]; 24 | ``` 25 | 26 | 2.执行 checkscope 函数,创建 checkscope 函数执行上下文,checkscope 函数执行上下文被压入执行上下文栈 27 | 28 | ``` 29 | ECStack = [ 30 | checkscopeContext, 31 | globalContext 32 | ]; 33 | ``` 34 | 35 | 3.checkscope 函数并不立刻执行,开始做准备工作,第一步:复制函数[[scope]]属性创建作用域链 36 | 37 | ``` 38 | checkscopeContext = { 39 | Scope: checkscope.[[scope]], 40 | } 41 | ``` 42 | 43 | 4.第二步:用 arguments 创建活动对象,随后初始化活动对象,加入形参、函数声明、变量声明 44 | 45 | ``` 46 | checkscopeContext = { 47 | AO: { 48 | arguments: { 49 | length: 0 50 | }, 51 | scope2: undefined 52 | }, 53 | Scope: checkscope.[[scope]], 54 | } 55 | ``` 56 | 57 | 5.第三步:将活动对象压入 checkscope 作用域链顶端 58 | 59 | ``` 60 | checkscopeContext = { 61 | AO: { 62 | arguments: { 63 | length: 0 64 | }, 65 | scope2: undefined 66 | }, 67 | Scope: [AO, [[Scope]]] 68 | } 69 | ``` 70 | 71 | 6.准备工作做完,开始执行函数,随着函数的执行,修改 AO 的属性值 72 | 73 | ``` 74 | checkscopeContext = { 75 | AO: { 76 | arguments: { 77 | length: 0 78 | }, 79 | scope2: 'local scope' 80 | }, 81 | Scope: [AO, [[Scope]]] 82 | } 83 | ``` 84 | 85 | 7.查找到 scope2 的值,返回后函数执行完毕,函数上下文从执行上下文栈中弹出 86 | 87 | ``` 88 | ECStack = [ 89 | globalContext 90 | ]; 91 | ``` 92 | 93 | [原文](https://github.com/mqyqingfeng/Blog/issues/6) 94 | -------------------------------------------------------------------------------- /interview/react/diff机制.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuya227939/blog/894802b5cfe2f3ce298fccfcbd49e5300806101e/interview/react/diff机制.md -------------------------------------------------------------------------------- /interview/react/setState 更新机制.md: -------------------------------------------------------------------------------- 1 | - 首先,setState 会产生当前更新的优先级(老版本用 expirationTime ,新版本用 lane )。 2 | - 接下来 React 会从 fiber Root 根部 fiber 向下调和子节点,调和阶段将对比发生更新的地方,更新对比 expirationTime ,找到发生更新的组件,合并 state,然后触发 render 函数,得到新的 UI 视图层,完成 render 阶段。 3 | - 接下来到 commit 阶段,commit 阶段,替换真实 DOM ,完成此次更新流程。 4 | - 此时仍然在 commit 阶段,会执行 setState 中 callback 函数,如上的()=>{ console.log(this.state.number) },到此为止完成了一次 setState 全过程。 5 | 6 | `enqueueSetState` 作用实际很简单,就是创建一个 `update` ,然后放入当前 `fiber` 对象的待更新队列中,最后开启调度更新,进入上述讲到的更新流程。 7 | 8 | ``` 9 | enqueueSetState() { 10 | /* 每一次调用`setState`,react 都会创建一个 update 里面保存了 */ 11 | const update = createUpdate(expirationTime, suspenseConfig); 12 | /* callback 可以理解为 setState 回调函数,第二个参数 */ 13 | callback && (update.callback = callback) 14 | /* enqueueUpdate 把当前的update 传入当前fiber,待更新队列中 */ 15 | enqueueUpdate(fiber, update); 16 | /* 开始调度更新 */ 17 | scheduleUpdateOnFiber(fiber, expirationTime); 18 | } 19 | ``` 20 | 21 | 开启批量更新 22 | 23 | ``` 24 | function batchedEventUpdates(fn,a){ 25 | /* 开启批量更新 */ 26 | isBatchingEventUpdates = true; 27 | try { 28 | /* 这里执行了的事件处理函数, 比如在一次点击事件中触发setState,那么它将在这个函数内执行 */ 29 | return batchedEventUpdatesImpl(fn, a, b); 30 | } finally { 31 | /* try 里面 return 不会影响 finally 执行 */ 32 | /* 完成一次事件,批量更新 */ 33 | isBatchingEventUpdates = false; 34 | } 35 | } 36 | ``` 37 | 38 | 原生方法比如 setTimeout、setInterval 会绕过批量更新机制,进行单个更新 39 | -------------------------------------------------------------------------------- /interview/react/事件机制.md: -------------------------------------------------------------------------------- 1 | ## 背景 2 | 3 | 首先,对于不同的浏览器,对事件存在不同的兼容性,React 想实现一个兼容全浏览器的框架, 为了实现这个目标就需要创建一个兼容全浏览器的事件系统,以此抹平不同浏览器的差异。 4 | 5 | 其次,v17 之前 React 事件都是绑定在 document 上,v17 之后 React 把事件绑定在应用对应的容器 container 上,将事件绑定在同一容器统一管理,防止很多事件直接绑定在原生的 DOM 元素上。造成一些`不可控`的情况。由于不是绑定在真实的 DOM 上,所以 React 需要模拟一套事件流:事件捕获-> 事件源 -> 事件冒泡,也包括重写一下事件源对象 event 。 6 | 7 | 最后,这种事件系统,大部分处理逻辑都在底层处理了,这对后期的 ssr 和跨端支持度很高。 8 | 9 | ## 事件合成 10 | 11 | - 第一个部分是事件合成系统,初始化会注册不同的事件插件。 12 | - 第二个就是在一次渲染过程中,对事件标签中事件的收集,向 container 注册事件。 13 | - 第三个就是一次用户交互,事件触发,到事件执行一系列过程。 14 | 15 | ## 插件机制 16 | 17 | registrationNameDependencies 18 | 19 | ## 事件绑定 20 | 21 | 所谓事件绑定,就是在 React 处理 props 时候,如果遇到事件比如 onClick ,就会通过 addEventListener 注册原生事件 22 | 23 | ``` 24 | function diffProperties() { 25 | /* 判断当前的 propKey 是不是 React合成事件 */ 26 | if(registrationNameModules.hasOwnProperty(propKey)){ 27 | /* 这里多个函数简化了,如果是合成事件, 传入成事件名称 onClick ,向document注册事件 */ 28 | legacyListenToEvent(registrationName, document); 29 | } 30 | } 31 | ``` 32 | 33 | ``` 34 | function legacyListenToEvent(registrationName,mountAt) { 35 | const dependencies = registrationNameDependencies[registrationName]; // 根据 onClick 获取 onClick 依赖的事件数组 [ 'click' ]。 36 | for (let i = 0; i < dependencies.length; i++) { 37 | const dependency = dependencies[i]; 38 | // addEventListener 绑定事件监听器 39 | ... 40 | } 41 | } 42 | ``` 43 | -------------------------------------------------------------------------------- /interview/工程化/Mobx 和 Redux 的区别.md: -------------------------------------------------------------------------------- 1 | ## 共同点 2 | 3 | - 两者都是为了解决状态不好管理,无法有效同步的问题而产生的工具。 4 | - 都是用来统一管理应用状态的工具 5 | - 某一个状态只有一个可靠的数据来源 6 | - 操作更新的方式是统一的,并且是可控的 7 | - 都支持 store 与 react 组件,如 react-redux,mobx-react; 8 | 9 | ## 区别 10 | 11 | 1. 设计思想不同 12 | Redux 函数式编程思想 13 | Mobx 对象编程和响应式编程 14 | 2. 对 store 管理不同 15 | Redux 将所有共享的数据集中在一个大的 store 中,统一管理 16 | Mobx 按模块将状态划出多个独立的 store 进行管理 17 | 3. 数据可变性的不同 18 | Redux 强调的是对象的不可变性,不能直接操作状态对象。而是在原来状态对象的基础上返回一个新的状态对象,最后返回应用的上一个状态 19 | Mobx 可以直接使用新值更新状态对象 20 | 4. 状态更新方式不同 21 | 得益于 Mobx 的 observable,使用 mobx 可以做到精准更新 22 | 对应的 Redux 是用 dispath 进行广播,通过 Provider 和 connect 来比对前后差别控制更新粒度 23 | -------------------------------------------------------------------------------- /interview/工程化/tree-shaking.md: -------------------------------------------------------------------------------- 1 | ## 什么是 tree-shaking 2 | 3 | tree-Shaking 是一种基于 ES Module 规范的 Dead Code Elimination 技术,它会在运行过程中静态分析模块之间的导入导出,确定 ESM 模块中哪些导出值未曾其它模块使用,并将其删除,以此实现打包产物的优化。 4 | 5 | ## 使用前提条件 6 | 7 | tree-shakinng 基于 ES6 模块实现 8 | 9 | - 只能作为模块顶层的语句出现 10 | - import 的模块名只能是字符串常量 11 | - import binding 是 immutable 的 12 | 13 | ## Webpack 14 | 15 | Webpack 官网提到,要开启模块的 Tree-shaking,需要满足以下四个条件: 16 | 17 | - 使用 ES6 的 import export 语句 18 | - 确保 ES6 模块没有被 babel 等编译器转换成 ES5 CommonJS 的形式 19 | - 项目 package.json 文件中,要有 "sideEffects" 属性的定义(false 表示所有文件无副作用,可启用 Tree Shaking) 20 | - 使用 Webpack 的 production mode 21 | 22 | ## 实现原理 23 | 24 | Webpack 中,tree-shaking 的实现一是先「标记」出模块导出值中哪些没有被用过,二是使用 Terser 删掉这些没被用到的导出语句。标记过程大致可划分为三个步骤: 25 | 26 | Make 阶段,收集模块导出变量并记录到模块依赖关系图 ModuleGraph 变量中 27 | 28 | Seal 阶段,遍历 ModuleGraph 标记模块导出变量有没有被使用 29 | 30 | 生成产物时,若变量没有被其它模块使用则删除对应的导出语句 31 | 32 | ## 最佳实践 33 | 34 | 虽然 Webpack 自 2.x 开始就原生支持 tree Shaking 功能,但受限于 JS 的动态特性与模块的复杂性,直至最新的 5.0 版本依然没有解决许多代码副作用带来的问题,使得优化效果并不如 Tree Shaking 原本设想的那么完美,所以需要使用者有意识地优化代码结构,或使用一些补丁技术帮助 Webpack 更精确地检测无效代码,完成 Tree Shaking 操作。 35 | -------------------------------------------------------------------------------- /interview/工程化/webpack4热更新原理.md: -------------------------------------------------------------------------------- 1 | ## 核心流程 2 | 3 | - npm run start 启动静态编译服务 4 | - node 启动监听文件更新服务,生成 webpack 编译主引擎 `compiler`,`setupApp` 生成 `express` 服务 5 | - `setupApp` 监听文件改动以及编译结束,当监听到 `webpack` 编译结束的时候,就会调用 `sendStats` 方法通过通过 `webSocet` 传输到前端,根据传入的 `hash` 值和变更的文件,与上一次的 `hash` 值进行对比,进行更新, 6 | - 客户端进行热模块更新、替换,`module.hot.check` 7 | - 真正实现更新的是 `hotApply` 方法进行遍历所有模块,删除之前的模块,替换新的模块,`moduleId`,如果在热更新的过程中,遇到代码报错,则会调用 `window.location.reload()` 进行页面刷新 8 | 9 | ## 回答字节的问题 10 | 11 | 前端如何实现在某个文件更新之后,打印 console.log 12 | 13 | 当用新的模块代码替换老的模块后,但是我们的业务代码并不能知道代码已经发生变化,也就是说,当 hello.js 文件修改后,我们需要在 index.js 文件中调用 HMR 的 accept 方法,添加模块更新后的处理函数,及时将 hello 方法的返回值插入到页面中。代码如下 14 | 15 | ``` 16 | // index.js 17 | if(module.hot) { 18 | module.hot.accept('./hello.js', function() { 19 | div.innerHTML = hello() 20 | }) 21 | } 22 | ``` 23 | -------------------------------------------------------------------------------- /interview/手写代码/debounce.md: -------------------------------------------------------------------------------- 1 | ## 防抖和节流都要解决的核心问题 2 | 3 | - this 指向 4 | let context = this; 5 | - arguments 传参 6 | let args = arguments; 7 | - 形成一个闭包 8 | return function() { 9 | 10 | }; 11 | 12 | - 增加锁的条件,比如通过时间判断,通过当前是否有正在执行的定时器等 13 | let timeout = setTimeout(() => {}, wait); 14 | if(previous - now > wait) {}; 15 | 16 | ## 防抖 17 | 18 | > 所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。 19 | 20 | ### 非立即执行版 21 | 22 | ``` 23 | function debounce(func, wait) { 24 | var timeout; 25 | return function () { 26 | let context = this; 27 | let args = arguments; 28 | if(timeout) clearTimeout(timeout); 29 | 30 | timeout = setTimeout(() => { 31 | func.apply(context, args); 32 | }, wait); 33 | } 34 | } 35 | ``` 36 | 37 | ### 立即执行版 38 | 39 | ``` 40 | function debounce(func, wait) { 41 | let timeout; 42 | return function() { 43 | let context = this; 44 | let args = arguments; 45 | 46 | if(timeout) clearTimeout(timeout); 47 | 48 | timeout = setTimeout(() => { 49 | timeout = null; 50 | }, wait); 51 | 52 | if(!timeout) func.apply(context, args); 53 | } 54 | } 55 | ``` 56 | 57 | ## 节流 58 | 59 | > 所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。节流会稀释函数的执行频率。 60 | 61 | ``` 62 | function throttle(func, wait) { 63 | let last = 0, deferTimer; 64 | return function() { 65 | let now = Date.now(); 66 | let context = this; 67 | let args = arguments; 68 | if(now - last > wait) { 69 | clearTimeout(deferTimer) 70 | deferTimer = setTimeout(function () { 71 | last = now; 72 | fun.apply(context, args); 73 | }, wait) 74 | } else { 75 | last = now; 76 | fun.apply(context, args); 77 | } 78 | } 79 | } 80 | ``` 81 | -------------------------------------------------------------------------------- /interview/手写代码/deepClone.md: -------------------------------------------------------------------------------- 1 | ## 考察知识点 2 | 3 | - 基本实现 4 | 5 | 递归能力 6 | 7 | 循环引用 8 | 9 | - 考虑问题的全面性 10 | 理解 `weakmap` 的真正意义 11 | 12 | 多种类型 13 | 14 | - 考虑问题的严谨性 15 | 创建各种引用类型的方法,`JS API` 的熟练程度 16 | 17 | 准确的判断数据类型,对数据类型的理解程度 18 | 19 | - 通用遍历 20 | 写代码可以考虑性能优化 21 | 22 | 了解集中遍历的效率 23 | 24 | 代码抽象能力 25 | 26 | - 拷贝函数 27 | 箭头函数和普通函数的区别 28 | 29 | 正则表达式熟练程度 30 | 31 | ## 核心问题 32 | 33 | - 如何遍历对象的每一个字段; 34 | 35 | for in,while 36 | 37 | - 如何解决引用问题; 38 | 39 | 递归 40 | 41 | - 如何判断对象还是数组 42 | 43 | Array.isArray || Object.prototype.toString.call 判断 44 | 45 | - 如何解决循环引用个问题 46 | 47 | weakMap 48 | 49 | ## deepClone 50 | 51 | ``` 52 | var target = { 53 | field1: 1, 54 | field2: undefined, 55 | field3: { 56 | child: 'child' 57 | }, 58 | field4: [2, 4, 8] 59 | }; 60 | target.target = target; 61 | 62 | function getType(target) { 63 | return Object.prototype.toString.call(target); 64 | } 65 | 66 | function deepClone(target, map = new WeakMap()) { 67 | if(typeof target == 'object') { 68 | let cloneTarget = getType(target) == '[object Array]' ? [] : {}; 69 | if (map.get(target)) { 70 | return target; 71 | } 72 | 73 | map.set(target, cloneTarget); 74 | 75 | if (getType(target) === '[object Map]') { 76 | target.forEach((value, key) => { 77 | cloneTarget.set(key, deepClone(value)); 78 | }); 79 | return cloneTarget; 80 | } 81 | 82 | for(const key in target) { 83 | cloneTarget[key] = deepClone(target[key], map); 84 | } 85 | return cloneTarget; 86 | } 87 | return target; 88 | } 89 | 90 | 91 | function iterationClone(target) { 92 | let cloneTarget = {}; 93 | for(const key in target) { 94 | if(typeof target[key] == 'object') { 95 | cloneTarget[key] = target[key]; 96 | } else { 97 | let test = target[key]; 98 | cloneTarget[key] = test; 99 | } 100 | } 101 | 102 | return cloneTarget; 103 | } 104 | ``` 105 | 106 | ## full deepClone 107 | 108 | ``` 109 | 110 | // 判断引用类型 111 | function isObject(target) { 112 | const type = typeof target; 113 | return target !== null && (type === 'object' || type === 'function'); 114 | } 115 | 116 | // 获取类型 117 | function getType(target) { 118 | return Object.prototype.toString.call(target); 119 | } 120 | 121 | // 获取可继续遍历类型 122 | function getInit(target) { 123 | const Ctor = target.constructor; 124 | return new Ctor(); 125 | } 126 | 127 | function clone(target, map = new WeakMap()) { 128 | 129 | // 防止循环引用 130 | if (map.get(target)) { 131 | return target; 132 | } 133 | map.set(target, cloneTarget); 134 | 135 | // 克隆set 136 | if (type === setTag) { 137 | target.forEach(value => { 138 | cloneTarget.add(clone(value)); 139 | }); 140 | return cloneTarget; 141 | } 142 | 143 | // 克隆map 144 | if (type === mapTag) { 145 | target.forEach((value, key) => { 146 | cloneTarget.set(key, clone(value)); 147 | }); 148 | return cloneTarget; 149 | } 150 | 151 | // 克隆对象和数组 152 | const keys = type === arrayTag ? undefined : Object.keys(target); 153 | forEach(keys || target, (value, key) => { 154 | if (keys) { 155 | key = value; 156 | } 157 | cloneTarget[key] = clone(target[key], map); 158 | }); 159 | 160 | return cloneTarget; 161 | } 162 | ``` 163 | 164 | ## JSON.stringify 缺陷 165 | 166 | 无法处理循环引用,比如这段代码,objA 引用 objB,objB 引用 objA 167 | 168 | JSON.stringify 报错 169 | 170 | > VM915:9 Uncaught TypeError: Converting circular structure to JSON--> starting at object with constructor 'Object'| property 'child' -> object with constructor 'Object'--- property 'parent' closes the circle 171 | 172 | ``` 173 | var objA = { 174 | name: 'this is A' 175 | } 176 | var objB = { 177 | name: 'this is B' 178 | } 179 | objA.child = objB 180 | objB.parent = objA 181 | console.log(JSON.stringify(objA)) 182 | ``` 183 | -------------------------------------------------------------------------------- /interview/手写代码/new 的实现.md: -------------------------------------------------------------------------------- 1 | ## new 运算符做了那些事 2 | 3 | - 创建一个空的简单 JavaScript 对象(即{}); 4 | - 为步骤 1 新创建的对象添加属性**proto**,将该属性链接至构造函数的原型对象 ; 5 | - 将步骤 1 新创建的对象作为 this 的上下文 ; 6 | - 如果该函数没有返回对象,则返回 this 7 | 8 | ## 代码实现 9 | 10 | ``` 11 | function objectFactory() { 12 | var obj = new Object(); 13 | Constructor = [].shift.call(arugments); 14 | obj._proto_ = Constructor.prototype; 15 | var res = Constructor.apply(obj, arguments); 16 | return typeof res == 'object' ? res : obj; 17 | } 18 | ``` 19 | -------------------------------------------------------------------------------- /interview/手写代码/unique.md: -------------------------------------------------------------------------------- 1 | ## 考察知识点 2 | 3 | - 基本实现 4 | 5 | 迭代遍历 6 | 7 | 循环引用 8 | 9 | 如何去重 10 | 11 | ## 核心问题 12 | 13 | - 如何遍历数组的每一个元素; 14 | forEach、for 循环 15 | - 如何去重 16 | 通过新数组 || filter 或 es6 17 | 18 | ## 代码实现 19 | 20 | ### 产生一个新的数组,indexOf 21 | 22 | ``` 23 | function unique(arr) { 24 | if(arr && !Array.isArray(arr)) return; 25 | 26 | var newArr = []; 27 | arr.forEach((item) => { 28 | if(newArr.toString().indexOf(item) < 0) { 29 | newArr.push(item); 30 | } 31 | }); 32 | return newArr; 33 | } 34 | ``` 35 | 36 | ### filter,不产生新的数组 37 | 38 | ``` 39 | var array = [1, 2, 1, 1, '1', '2']; 40 | 41 | function unique(array) { 42 | var res = array.filter(function(item, index, array){ 43 | return array.indexOf(item) === index; 44 | }) 45 | return res; 46 | } 47 | ``` 48 | 49 | ### es6 50 | 51 | ``` 52 | var array = [1, 2, 1, 1, '1']; 53 | 54 | function unique(array) { 55 | return Array.from(new Set(array)); 56 | } 57 | 58 | console.log(unique(array)); 59 | ``` 60 | -------------------------------------------------------------------------------- /interview/手写代码/useState 的实现.md: -------------------------------------------------------------------------------- 1 | ``` 2 | let values = []; 3 | let curIndex = 0; 4 | function useState(initialState) { 5 | values[curIndex] = values[curIndex] ? values[curIndex] : initialState; 6 | const setState = function(newState) { 7 | values[curIndex] = newState; 8 | curIndex = 0; 9 | render(); 10 | } 11 | curIndex++; 12 | return [values[curIndex], setState]; 13 | } 14 | ``` 15 | -------------------------------------------------------------------------------- /interview/手写代码/实现一个 call.md: -------------------------------------------------------------------------------- 1 | ## 核心问题 2 | 3 | - call 支持传入默认值,如何解决 this 指向问题 4 | context.fn = obj || window; 5 | - 如何处理多个传参问题 6 | arguments,从 1 开始,0 默认是数组当前的函数 7 | - 如何执行函数 8 | eval 9 | 10 | ## 实现代码如下 11 | 12 | ``` 13 | Function.prototype.call2 = function(obj) { 14 | let context = obj || window; 15 | let arr = []; 16 | context.fn = this; 17 | for(let i = 1; i < arguments.length; i++) { 18 | arr.push(`arguments[${i}]`); 19 | } 20 | 21 | const res = eval('context.fn(' + arr + ')'); 22 | 23 | delete context.fn; 24 | return res; 25 | } 26 | 27 | function test(name) { 28 | console.log(111, name); 29 | } 30 | 31 | test.call2(this, 'Faker'); 32 | ``` 33 | 34 | eval 和 new Function 都可以动态解析和执行字符串。但是它们对解析内容的运行环境判定不同。 35 | 36 | eval 中的代码执行时的作用域为当前作用域。它可以访问到函数中的局部变量。 37 | 38 | new Function 中的代码执行时的作用域为全局作用域 39 | 40 | ## apply 的实现 41 | 42 | ``` 43 | Function.prototype.apply = function (context, arr) { 44 | let context = Object(context) || window; 45 | context.fn = this; 46 | 47 | let result; 48 | if (!arr) { 49 | result = context.fn(); 50 | } else { 51 | let args = []; 52 | for (var i = 0, i < arr.length; i++) { 53 | args.push('arr[' + i + ']'); 54 | } 55 | result = eval('context.fn(' + args + ')') 56 | } 57 | 58 | delete context.fn 59 | return result; 60 | } 61 | ``` 62 | -------------------------------------------------------------------------------- /interview/手写代码/实现一个 promise.all.md: -------------------------------------------------------------------------------- 1 | ## 如何回答 promise 原理 2 | 3 | 从 es5 的 callback 调用 -> es6 的 promise -> es7 的 aync 和 await 4 | 5 | es5 回调地域, es6 同步写法, es7 的真正同步 6 | 7 | ## promise.all 8 | 9 | ``` 10 | Promise.all = function(arr) { 11 | let res = []; 12 | let count = 0; 13 | return new Promise((resolve, reject) => { 14 | for(let i = 0; i < arr.length; i++) { 15 | Promise.resove(arr[i]).then(res => { 16 | count++; 17 | res[index] = res; 18 | if(count == arr.length) { 19 | return resolve(res); 20 | } 21 | }).catch(error => { 22 | return reject(error); 23 | }); 24 | } 25 | }); 26 | } 27 | ``` 28 | 29 | ## promise.race 30 | 31 | ``` 32 | Promise.race = function(arr) { 33 | return new Promise((resolve, reject) => { 34 | for(let i = 0; i < arr.length; i++) { 35 | Promise.resolve(arr[i]).then((res) => { 36 | return resolve(res); 37 | }, (error) => { 38 | return reject(error); 39 | }); 40 | } 41 | }) 42 | } 43 | ``` 44 | 45 | ## promise 的链式调用 46 | 47 | then 方法中,创建并返回了新的 Promise 实例,这是串行 Promise 的基础,是实现真正链式调用的根本 48 | -------------------------------------------------------------------------------- /interview/手写代码/实现函数curry.md: -------------------------------------------------------------------------------- 1 | ## 核心思路 2 | 3 | 要判断当前传入函数的参数个数 (args.length) 是否大于等于原函数所需参数个数 (fn.length) ,如果是,则执行当前函数;如果是小于,则返回一个函数。 4 | 5 | ``` 6 | function curry(fn) { 7 | return function curriedFn() { 8 | var args = Array.prototype.slice.call(arguments); 9 | if(args.length < fn.length) { 10 | return function() { 11 | var args2 = Array.prototype.slice.call(arguments); 12 | return curriedFn.apply(null, args.concat(args2)); 13 | } 14 | } 15 | 16 | return fn.apply(null, args); 17 | } 18 | } 19 | 20 | function sum(x, y, z) { 21 | return x + y + z; 22 | } 23 | 24 | var add = curry(sum); 25 | console.log(add(1, 2, 3)); 26 | console.log(add(1)(2)(3)); 27 | ``` 28 | -------------------------------------------------------------------------------- /interview/手写代码/手写 Promise.md: -------------------------------------------------------------------------------- 1 | ## Promise.all 方法的实现 2 | 3 | ``` 4 | function all(list) { 5 | return new MyPromise((resolve, reject) => { 6 | let values = []; 7 | let count = 0; 8 | for(let [i, v] of list.entreis()) { 9 | values[i] = v; 10 | count++; 11 | if(count == list.length) resolve(values); 12 | } 13 | }, err => { 14 | reject(err); 15 | }); 16 | } 17 | ``` 18 | 19 | ## Promise.race 方法的实现 20 | 21 | ``` 22 | function race (list) { 23 | return new MyPromise((resolve, reject) => { 24 | for (let p of list) { 25 | // 只要有一个实例率先改变状态,新的MyPromise的状态就跟着改变 26 | this.resolve(p).then(res => { 27 | resolve(res) 28 | }, err => { 29 | reject(err) 30 | }) 31 | } 32 | }) 33 | } 34 | ``` 35 | -------------------------------------------------------------------------------- /interview/手写代码/数组拍平.md: -------------------------------------------------------------------------------- 1 | ## 核心问题 2 | 3 | - 第一个要解决的就是遍历数组的每一个元素; 4 | forEach、for、map 循环 5 | - 第二个要解决的就是判断元素是否是数组; 6 | Array.isArray || Object.prototype.toString.call 判断 7 | - 第三个要解决的就是将数组的元素展开一层; 8 | 递归解决 或 reduce 解决 9 | - 如何控制层级 10 | 通过 num 标识符 11 | - 如何过滤空属性 12 | 通过 forEach 13 | 14 | ### 进阶 15 | 16 | 如何以正序排列 17 | 18 | ## 递归遍历 19 | 20 | ``` 21 | 22 | let arr = [1, 2, 3, , [4, , [5, , [6]]], 7, "string", { name: "弹铁蛋同学" }]; 23 | 24 | function flat(arr, num) { 25 | let arrRes = []; 26 | if(num > 0) { 27 | arr.forEach(item => { 28 | if(Array.isArray(item)) { 29 | arrRes.push(...arguments.callee(item, num - 1)); 30 | } else { 31 | arrRes.push(item); 32 | } 33 | }); 34 | } else { 35 | arrRes.push(arr); 36 | } 37 | return arrRes; 38 | } 39 | ``` 40 | 41 | ## 递归遍历 + 解决以正序显示 42 | 43 | ``` 44 | function flat(arr, num) { 45 | let res = []; 46 | if(num > 0) { 47 | arr.forEach(item => { 48 | if(Array.isArray) { 49 | res.push(...arguments.callee(item). num - 1); 50 | } else { 51 | res.push(arr[item]); 52 | } 53 | }); 54 | } else { 55 | res.push(arr); 56 | } 57 | return res.sort(compare); 58 | } 59 | 60 | function compare(val1, val2) { 61 | if(val1 < val2) return -1; 62 | if(val1 > val2) return 1; 63 | return 0; 64 | } 65 | ``` 66 | 67 | ## reduce 遍历 68 | 69 | ``` 70 | 71 | let arr = [1, 2, 3, [4, [5, [6]]], 7, "string", { name: "弹铁蛋同学" }]; 72 | 73 | function flat(arr, num) { 74 | return num > 0 75 | ? arr.reduce( 76 | (pre, cur) => 77 | pre.concat(Array.isArray(cur) ? flat(cur, num - 1) : cur) 78 | , []) 79 | : arr.slice(); 80 | } 81 | ``` 82 | 83 | ## 空位情况 84 | 85 | ``` 86 | 87 | let arr = [1, 2, 3, , [4, , [5, , [6]]], 7, "string", { name: "弹铁蛋同学" }]; 88 | 89 | function flat(arr, num) { 90 | return num > 0 91 | ? arr.reduce( 92 | (pre, cur) => { 93 | if(cur) { 94 | return pre.concat(Array.isArray(cur) ? flat(cur, num - 1) : cur) 95 | } 96 | } 97 | ,[]) 98 | : arr.reduce((pre, cur) => cur ? pre.concat(cur) : '', []); 99 | } 100 | ``` 101 | -------------------------------------------------------------------------------- /interview/算法题/两数之和.md: -------------------------------------------------------------------------------- 1 | ## 第一种解法 2 | 3 | 时间复杂度在 o(n) 的平方 4 | 5 | ``` 6 | var twoSum = function(nums, target) { 7 | let result = [], bool = true, num = nums[nums.length - 1], index = nums.length - 1; 8 | while(bool) { 9 | for(let i = nums.length - 2; i >= 0; i--) { 10 | if(num + nums[i] == target && i != index) { 11 | result.push(i); 12 | result.push(index); 13 | bool = false; 14 | } 15 | } 16 | 17 | if(bool) { 18 | index--; 19 | num = nums[index]; 20 | } 21 | } 22 | return result; 23 | }; 24 | ``` 25 | 26 | ## 最优解 27 | 28 | 利用 map 做一个唯一值的存储,通过消耗空间来换回时间 29 | 30 | ``` 31 | var twoSum = function (nums, target) { 32 | let map = new Map(); 33 | for (let i = 0; i < nums.length; i++) { 34 | if (map.get(target - nums[i]) !== undefined) { 35 | return [i, map.get(target - nums[i])]; 36 | } 37 | map.set(nums[i], i); 38 | } 39 | return []; 40 | }; 41 | ``` 42 | -------------------------------------------------------------------------------- /interview/算法题/单向链表反转.md: -------------------------------------------------------------------------------- 1 | ``` 2 | var reverseList = function(head) { 3 | if(head == null || head.next == null) return head; 4 | let temp = null, pre = null, cur = head; 5 | while(cur) { 6 | temp = cur.next; 7 | cur.next = pre; 8 | pre = cur; 9 | cur = temp; 10 | } 11 | return pre; 12 | }; 13 | ``` 14 | -------------------------------------------------------------------------------- /interview/算法题/反转二叉树.md: -------------------------------------------------------------------------------- 1 | ``` 2 | var invertTree = function(root) { 3 | if(root == null) return null; 4 | 5 | // 交换左右子节点 6 | let tmp = root.left; 7 | root.left = root.right; 8 | root.right = tmp; 9 | 10 | // 左右子节点继续翻转它们的子节点 11 | invertTree(root.left); 12 | invertTree(root.right); 13 | 14 | return root; 15 | } 16 | ``` 17 | -------------------------------------------------------------------------------- /interview/算法题/斐波那契.md: -------------------------------------------------------------------------------- 1 | ## 最优解 2 | 3 | 递归调用自己,注意结束条件 4 | 5 | ``` 6 | var fib = function(n) { 7 | if(n == 1) return 1; 8 | if(n == 0) return 0; 9 | return fib(n - 2) + fib(n - 1); 10 | }; 11 | ``` 12 | -------------------------------------------------------------------------------- /interview/算法题/环形链表.md: -------------------------------------------------------------------------------- 1 | ## 讨巧解 2 | 3 | 超过最大范围数值,则不存在环形链表 4 | 5 | ``` 6 | var hasCycle = function(head) { 7 | for(let i = 0; i < 10001; i++) { 8 | if(head == null) return false; 9 | head = head.next; 10 | } 11 | return true; 12 | }; 13 | ``` 14 | 15 | ## 最优解 16 | 17 | 通过快慢指针来快速遍历,如果快指针走到下个节点跟慢指针相通,则存在环形链表 18 | 19 | ``` 20 | var hasCycle = function(head) { 21 | let low = head, fast = head; 22 | 23 | if(head == null || head.next == null) return false; 24 | 25 | while(fast != null && fast.next != null) { 26 | low = low.next; 27 | fast = fast.next.next; 28 | if(low == fast) return true; 29 | } 30 | return false; 31 | }; 32 | ``` 33 | -------------------------------------------------------------------------------- /interview/设计模式/观察者模式.md: -------------------------------------------------------------------------------- 1 | ``` 2 | class PubSub { 3 | constructor() { 4 | this.map = {}; 5 | } 6 | 7 | on(type, fn) { 8 | if(!this.map[type]) { 9 | this.map[type] = []; 10 | } 11 | this.map[type].push(fn); 12 | } 13 | 14 | emit(type, ...args) { 15 | let listeners = this.map[type]; 16 | if(!listeners || !listeners.length) return; 17 | listeners.forEach(fn => fn(...args)); 18 | } 19 | 20 | off(type, fn) { 21 | let listeners = this.map[type]; 22 | if(!listeners || !listeners.length) return; 23 | this.map[type] = listeners.filter(v => v != fn ); 24 | } 25 | } 26 | 27 | let ob = new PubSub(); 28 | ob.on('add', (val) => console.log(val)); 29 | ob.emit('add', 1); 30 | ``` 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "astro-theme-typography", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "scripts": { 6 | "dev": "astro check && astro dev", 7 | "start": "astro dev", 8 | "build": "astro check && astro build", 9 | "preview": "astro preview", 10 | "astro": "astro", 11 | "new-post": "./scripts/new-post.sh", 12 | "update-theme": "./scripts/update-theme.sh" 13 | }, 14 | "dependencies": { 15 | "@astrojs/check": "^0.3.4", 16 | "@astrojs/rss": "^4.0.4", 17 | "@astrojs/sitemap": "^3.0.5", 18 | "astro": "^4.5.9", 19 | "astro-robots-txt": "^1.0.0", 20 | "astro-seo": "^0.8.0", 21 | "typescript": "^5.3.3", 22 | "vite": "^5.0.12" 23 | }, 24 | "devDependencies": { 25 | "@iconify-json/mdi": "^1.1.64", 26 | "@types/markdown-it": "^13.0.7", 27 | "@types/node": "^20.11.22", 28 | "@types/sanitize-html": "^2.9.5", 29 | "@unocss/reset": "^0.58.5", 30 | "@unocss/transformer-directives": "^0.58.5", 31 | "markdown-it": "^14.0.0", 32 | "sanitize-html": "^2.11.0", 33 | "unocss": "^0.58.5" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /public/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuya227939/blog/894802b5cfe2f3ce298fccfcbd49e5300806101e/public/placeholder.png -------------------------------------------------------------------------------- /public/typograph-og.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuya227939/blog/894802b5cfe2f3ce298fccfcbd49e5300806101e/public/typograph-og.jpg -------------------------------------------------------------------------------- /scripts/new-post.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Get the current date and time 4 | timestamp=$(date +"%Y-%m-%d %H:%M:%S") 5 | 6 | # Prompt the user to enter the title of the article 7 | read -p "Please enter the title of the article: " title 8 | 9 | # Generate the filename based on the title 10 | filename=$(echo "$title" | tr '[:upper:]' '[:lower:]' | tr ' ' '-') 11 | 12 | # Concatenate the file path 13 | filepath="./src/content/posts/$filename.md" 14 | 15 | # Check if the file already exists 16 | if [ -f "$filepath" ]; then 17 | echo "Error: File already exists!" 18 | exit 1 19 | fi 20 | 21 | # Create a new article file 22 | touch "$filepath" 23 | 24 | # Write the header information of the article 25 | echo "---" >> "$filepath" 26 | echo "title: $title" >> "$filepath" 27 | echo "pubDate: $timestamp" >> "$filepath" 28 | echo "categories: []" >> "$filepath" 29 | echo "description: ''" >> "$filepath" 30 | echo "---" >> "$filepath" 31 | echo "" >> "$filepath" 32 | 33 | echo "Successfully created a new article: $filepath" 34 | -------------------------------------------------------------------------------- /scripts/update-theme.sh: -------------------------------------------------------------------------------- 1 | # Check if the template repository is already added 2 | 3 | git remote | grep template > /dev/null 4 | 5 | # If not, add the template repository 6 | 7 | if [ $? -ne 0 ]; then 8 | git remote add template https://github.com/moeyua/astro-theme-typography.git 9 | fi 10 | 11 | # Fetch the latest changes from the template repository 12 | 13 | git fetch template 14 | 15 | # Merge the latest changes from the template repository into the current branch 16 | git merge template/main --allow-unrelated-histories 17 | -------------------------------------------------------------------------------- /src/components/Footer.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { HTMLAttributes } from 'astro/types' 3 | 4 | type Props = HTMLAttributes<'footer'> 5 | 6 | const { ...attrs } = Astro.props 7 | 8 | const year = new Date().getFullYear() 9 | 10 | const { website, author } = Astro.locals.config 11 | --- 12 | 13 | 19 | -------------------------------------------------------------------------------- /src/components/Header.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { HTMLAttributes } from 'astro/types' 3 | 4 | type Props = HTMLAttributes<'header'> 5 | 6 | const { ...attrs } = Astro.props 7 | 8 | const { title, author, socials, navs } = Astro.locals.config 9 | 10 | const { translate: t } = Astro.locals 11 | --- 12 | 13 |
14 |
20 | 21 |

{author}

22 |

{title}

23 |
24 |
25 | 26 | 46 |
47 | -------------------------------------------------------------------------------- /src/components/ListItem.astro: -------------------------------------------------------------------------------- 1 | --- 2 | interface Props { 3 | href: string 4 | title?: string 5 | description?: string 6 | } 7 | const { href, title, description } = Astro.props 8 | --- 9 | 10 |
  • 11 |

    12 | {title} 13 |

    14 |

    {description}

    15 |
  • 16 | -------------------------------------------------------------------------------- /src/components/ListSection.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const { title } = Astro.props 3 | --- 4 | 5 |
    6 |

    {title}

    7 | 10 |
    11 | -------------------------------------------------------------------------------- /src/components/Pagination.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const { translate: t } = Astro.locals 3 | 4 | interface Props { 5 | showLeft?: boolean 6 | leftTitle?: string 7 | leftUrl?: string 8 | 9 | showRight?: boolean 10 | rightTitle?: string 11 | rightUrl?: string 12 | 13 | showPageCount?: boolean 14 | currentPage?: number 15 | totalPage?: number 16 | } 17 | 18 | const { showLeft = true, showRight = true, leftTitle, rightTitle, leftUrl, rightUrl, showPageCount = true, currentPage, totalPage } = Astro.props 19 | --- 20 | 21 | 48 | 49 | 63 | -------------------------------------------------------------------------------- /src/components/Post.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { formatDate } from '~/utils' 3 | interface Props { 4 | post: Post 5 | } 6 | 7 | const { post } = Astro.props 8 | const { translate: t } = Astro.locals 9 | --- 10 | 11 |
    12 |
    13 |

    14 | {post.data.title} 15 |

    16 |
    17 | {t('posted_at')} 18 | 19 | { 20 | post.data.categories && 21 | post.data.categories.map((category) => ( 22 | 23 | # {category} 24 | 25 | )) 26 | } 27 |
    28 |
    29 | 30 |
    31 | -------------------------------------------------------------------------------- /src/content/config.ts: -------------------------------------------------------------------------------- 1 | import { defineCollection, z } from "astro:content"; 2 | 3 | const posts = defineCollection({ 4 | type: "content", 5 | schema: ({ image }) => 6 | z.object({ 7 | title: z.string(), 8 | description: z.string().optional(), 9 | pubDate: z.coerce.date(), 10 | customData: z.string().optional(), 11 | banner: image() 12 | .refine((img) => Math.max(img.width, img.height) <= 4096, { 13 | message: "Width and height of the banner must less than 4096 pixels", 14 | }) 15 | .optional(), 16 | categories: z.array(z.string()), 17 | author: z.string().optional(), 18 | commentsUrl: z.string().optional(), 19 | source: z.optional(z.object({ url: z.string(), title: z.string() })), 20 | enclosure: z.optional( 21 | z.object({ url: z.string(), length: z.number(), type: z.string() }) 22 | ), 23 | }), 24 | }); 25 | 26 | export const collections = { 27 | posts, 28 | }; 29 | -------------------------------------------------------------------------------- /src/content/posts/ Error-write.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: EPROTO 3928:error:1408F10B:SSL routines:ssl3 3 | pubDate: 2019-07-26 13:26:49 4 | categories: ["Node.js"] 5 | description: "" 6 | --- 7 | 8 | ## How fix 9 | 10 | node.js use request package and options add a secureProtocol is 'TLSv1_method' 11 | 12 | ## Example 13 | 14 | ``` 15 | const request = require('request'); 16 | const options = { 17 | url: 'https://10.134.136.112:8888/casserver/login?service=http%3A%2F%2F10.134.137.120%3A8000%2Fpiccclaim%2Fj_acegi_security_check%3BPICC_CLAIM_Cookie%3DWTXhd5pQY4SwQJpdKGxMQhXvl0L4Qp7pJhPrprm0ptmCqlW7JHkS%21566150954%21-472537668', 18 | // proxy: 'http://127.0.0.1:8888' 19 | secureProtocol: 'TLSv1_method' 20 | }; 21 | request.get(options, function (err, response, data) { 22 | console.log(data, err, response); 23 | }); 24 | ``` 25 | -------------------------------------------------------------------------------- /src/content/posts/16进制和字符串互转.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 16进制和字符串互转 3 | pubDate: 2019-06-24 16:21:20 4 | categories: ["React"] 5 | description: "" 6 | --- 7 | 8 | ### 字符串转 16 进制 9 | 10 | ``` 11 | function strToHexCharCode(str) { 12 | if(str === '') return ''; 13 | let hexCharCode = []; 14 | hexCharCode.push('0x'); 15 | for(var i = 0; i < str.length; i++) { 16 | hexCharCode.push((str.charCodeAt(i)).toString(16)); 17 | } 18 | return hexCharCode.join(''); 19 | } 20 | ``` 21 | 22 | ### 16 进制转字符串 23 | 24 | ``` 25 | function hexCharCodeToStr(hexCharCodeStr) { 26 | const trimedStr = hexCharCodeStr.trim(); 27 | const rawStr = trimedStr.substr(0, 2).toLowerCase() === '0x' ? trimedStr.substr(2) : trimedStr; 28 | const len = rawStr.length; 29 | if (len % 2 !== 0) { 30 | throw("Illegal Format ASCII Code!"); 31 | } 32 | let curCharCode; 33 | let resultStr = []; 34 | for (let i = 0; i < len; i = i + 2) { 35 | curCharCode = parseInt(rawStr.substr(i, 2), 16); 36 | resultStr.push(String.fromCharCode(curCharCode)); 37 | } 38 | return resultStr.join(""); 39 | } 40 | ``` 41 | -------------------------------------------------------------------------------- /src/content/posts/4 年前端如何选择方向.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 4 年前端如何选择方向 3 | pubDate: 2021-04-28 13:45:28-4 4 | categories: ["职业规划"] 5 | description: "" 6 | --- 7 | 8 | 背景:在第一家公司呆了两年(含实习),由于技术无法得到成长,拿着存款 1 万,裸辞。迫于不会面试,换过三家公司,在第二家呆了六个月,身体不适,离开。当前这家,已呆满两年,想尝试进入大厂,也面临职业选择问题。 9 | 10 | ## 最有收获的三个问题与回复 11 | 12 | #### 问题一:我如今四年工作经验,目前担任的是项目负责人/前端组长,关于未来的方向是选择前端技术专家还是项目管理? 13 | 14 | 我的想法:偏项目管理去走,因为我目前在做项目管理的一些事,负责把控项目进度,人力资源规划等。 15 | 16 | Scott 的分析与建议:Scott 分析了我的技术深度和广度,项目管理上是否有一套自己的方法论。 17 | 18 | 建议我往前端技术专家这条路走,理由是年轻,技术深度也不够,如果盲目转型项目管理,遇到一些技术比较厉害的人,很难让人服众,比如我现在的老大,完全不懂技术,对项目开发排期,基本是由开发说了算,因为你心理没底,这个技术到底要多少天?如果你技术吃透了,别人就忽悠不了你。 19 | 20 | #### 问题二:项目负责人如何去管理项目,如何建立自己的威信? 21 | 22 | 我的想法:无。 23 | 24 | Scott 的分析与建议:Scott 分析了我的团队规模,是否有实权。 25 | 26 | 建议我少管理,现在的我还年轻,如果太早介入项目管理,反而会导致我的侧重点在项目上,而非技术,这样会带来很严重的问题,技术深度不够。会让我在技术上荒废掉,哪样就没啥意义,何况现在前端的技术迭代非常快(前端娱乐圈)。 27 | 28 | 担任一个中间人,传递消息,知道每个人手头上的事,排期计划,遇到有何困难,汇报上去即可。 29 | 30 | 因为没有实权,没有 KPI 考核权,底下的人,压根不听你的,无法树立威严。 31 | 32 | #### 问题三:我最近想跳槽进大厂,请问有何建议? 33 | 34 | 我的想法:想今年面一波所有中小公司,然后尝试大厂,如果能拿到合适的 offer 就进去。 35 | 36 | Scott 的分析与建议:Scott 分析了我的工作履历和技术能力,建议是不跳槽,因为我之前的跳槽过于频繁,工作履历来不太好看,让别人会觉得你是个不稳定的人,而大厂对于这方面看得很重,另一方面,我今年也没太多时间去准备面试。 37 | 38 | ## 聊完的整体感受 39 | 40 | 让人有种豁然开朗的感觉,之前我太侧重项目管理,以致我的技术成长反而慢下来了,一直在想项目管理应该如何去管。但我又没有实权,底下的人,压根就不听我的。现在我知道应该怎么去做了。非常感谢 Scott 的指点。 41 | 42 | ## 接下来做的事 43 | 44 | - 不再去想项目管理上的事,只做一个中间人。 45 | - 技术继续深入,把现有技术栈原理吃透,了解新的技术。 46 | - 把前端技术栈体系整理出来,打造前端基建和规范. 47 | -------------------------------------------------------------------------------- /src/content/posts/Babel6 如何升级 7.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Babel6 如何升级 7 3 | pubDate: 2019-06-05 13:25:00 4 | categories: ["Babel"] 5 | description: "" 6 | --- 7 | 8 | ## Babel 9 | 10 | ``` 11 | $ npm install babel-upgrade -g 12 | $ babel-upgrade --write 13 | ``` 14 | 15 | 然后会发现 package.json 依赖包,自动给转换到了最新版。 16 | 17 | ![image](https://user-images.githubusercontent.com/16217324/58931894-117fab80-8794-11e9-8dee-67a3abb6d498.png) 18 | 19 | Babel7 新增了`babel.config.js`,这里我没有用到,所以还是选择使用`.babelrc`文件。 20 | 最终配置如下 21 | 22 | ``` 23 | { 24 | "presets": [ 25 | [ 26 | "@babel/env", 27 | { 28 | "targets": { 29 | "edge": "17", 30 | "firefox": "60", 31 | "chrome": "67", 32 | "safari": "11.1" 33 | }, 34 | "useBuiltIns": "usage" 35 | } 36 | ], 37 | [ 38 | "@babel/preset-react", 39 | ], 40 | ], 41 | "plugins": [ 42 | ["@babel/plugin-proposal-class-properties"], 43 | ["@babel/plugin-transform-runtime"], 44 | ["@babel/plugin-syntax-import-meta"], 45 | ["@babel/plugin-syntax-dynamic-import"], 46 | ["@babel/plugin-proposal-json-strings"], 47 | [ 48 | "import", { 49 | "libraryName": "antd", 50 | "libraryDirectory": "es", 51 | "style": "css", 52 | } 53 | ], 54 | ] 55 | } 56 | 57 | ``` 58 | 59 | 这里引入了`Ant`,解决`Ant`按需加载 60 | 61 | ``` 62 | [ 63 | "import", { 64 | "libraryName": "antd", 65 | "libraryDirectory": "es", 66 | "style": "css", 67 | } 68 | ], 69 | ``` 70 | 71 | Webpack 配置如下 72 | 73 | ``` 74 | module: { 75 | rules: [ 76 | { 77 | test: /\.js|jsx$/, 78 | exclude: /(node_modules)/, 79 | loader: 'babel-loader', 80 | }, 81 | ], 82 | }, 83 | ``` 84 | 85 | 遇到的坑 86 | 87 | ![image](https://user-images.githubusercontent.com/16217324/58932163-2741a080-8795-11e9-85ef-984d2db54afb.png) 88 | 89 | 无法识别`JSX`语法,因为在`.babelrc`文件中没有引入`@babel/preset-react` 90 | 91 | 完美升级 babel7 92 | -------------------------------------------------------------------------------- /src/content/posts/CSS 盒模型.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: CSS 盒模型 3 | pubDate: 2018-07-23 15:20:49 4 | categories: ["CSS"] 5 | description: "" 6 | --- 7 | 8 | ## 盒模型 9 | 10 | 盒模型分 IE 盒模型(怪异盒模型)和 W3C 盒模型,属性分别是 margin、border、padding、content,两者的区别在于,IE 盒模型:`width=content + padding + border`,而 w3c 盒模型:`width=content`,这个和现实当中的盒子比较类似,所以称之为盒模型。w3c 在 CSS3 中新增了一个属性,box-sizing。包含两个属性:`content-box`和`border-box`。`content-box:width=width`,`border-box:width=content+padding+border` 11 | -------------------------------------------------------------------------------- /src/content/posts/Charles 如何扑获 curl 和 Postman 请求.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Charles 如何扑获 curl 和 Postman 请求 3 | pubDate: 2019-06-24 22:44:56 4 | categories: ["React"] 5 | description: "" 6 | --- 7 | 8 | ## Curl 请求如何设置 9 | 10 | ### 终端设置代理 11 | 12 | ``` 13 | $ export https_proxy=http://127.0.0.1:8888 14 | $ export http_proxy=http://127.0.0.1:8888 15 | ``` 16 | 17 | ### 取消终端代理 18 | 19 | ``` 20 | $ unset http_proxy 21 | $ unset https_proxy 22 | ``` 23 | 24 | ### Charles Proxy 设置 25 | 26 | 输入端口 27 | 28 | ![image](https://user-images.githubusercontent.com/16217324/60028211-85e1a680-96d1-11e9-96c0-fa3315b78f49.png) 29 | 30 | ### curl 发起请求 31 | 32 | ``` 33 | curl -X POST 127.0.0.1:8888 -H "Content-Type: application/x-www-form-urlencoded" -i https://ip:8118/mcpinterf/user/login -d "userId=45729907&imeiNo=99001123927671&simNo=89860315540101069256&pwd=eb4UecxU3vg9fgszuQqk7w..&loginFlag=0×tamp=1560829091595" -k 34 | ``` 35 | 36 | ### End 37 | 38 | 然后在 Charles 界面即可看到 39 | 40 | ## Postman 如何设置 41 | 42 | 打开 Postman 设置界面,然后进行代理设置,如下图。 43 | 44 | ![image](https://user-images.githubusercontent.com/16217324/60029480-ce9a5f00-96d3-11e9-8de8-7d23a1f5ff78.png) 45 | -------------------------------------------------------------------------------- /src/content/posts/CircleCI 自动化部署.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: CircleCI 自动化部署 3 | pubDate: 2019-07-08 21:13:56 4 | categories: ["CircleCI"] 5 | description: "" 6 | --- 7 | 8 | ## Curl 请求如何设置 9 | 10 | ### 终端设置代理 11 | 12 | ``` 13 | $ export https_proxy=http://127.0.0.1:8888 14 | $ export http_proxy=http://127.0.0.1:8888 15 | ``` 16 | 17 | ### 取消终端代理 18 | 19 | ``` 20 | $ unset http_proxy 21 | $ unset https_proxy 22 | ``` 23 | 24 | ### Charles Proxy 设置 25 | 26 | 输入端口 27 | 28 | ![image](https://user-images.githubusercontent.com/16217324/60028211-85e1a680-96d1-11e9-96c0-fa3315b78f49.png) 29 | 30 | ### curl 发起请求 31 | 32 | ``` 33 | curl -X POST 127.0.0.1:8888 -H "Content-Type: application/x-www-form-urlencoded" -i https://ip:8118/mcpinterf/user/login -d "userId=45729907&imeiNo=99001123927671&simNo=89860315540101069256&pwd=eb4UecxU3vg9fgszuQqk7w..&loginFlag=0×tamp=1560829091595" -k 34 | ``` 35 | 36 | ### End 37 | 38 | 然后在 Charles 界面即可看到 39 | 40 | ## Postman 如何设置 41 | 42 | 打开 Postman 设置界面,然后进行代理设置,如下图。 43 | 44 | ![image](https://user-images.githubusercontent.com/16217324/60029480-ce9a5f00-96d3-11e9-8de8-7d23a1f5ff78.png) 45 | -------------------------------------------------------------------------------- /src/content/posts/Could not execute GraphicsMagick:ImageMagick.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Could not execute GraphicsMagick:ImageMagick 3 | pubDate: 2018-06-27 09:35:12 4 | categories: ["ImageMagick"] 5 | description: "" 6 | --- 7 | 8 | ## How to fix 9 | 10 | - Linux-CentOS:`$ yum install GraphicsMagick && yum install ImageMagick` 11 | - Mac:`$ brew install imagemagick && brew install graphicsmagick` 12 | -------------------------------------------------------------------------------- /src/content/posts/Decorating class property failed.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Decorating class property failed 3 | pubDate: 2019-10-01 20:21:33 4 | categories: ["mobx"] 5 | description: "" 6 | --- 7 | 8 | ## How to fix 9 | 10 | ``` 11 | export default new DashBoardStore(); 12 | ``` 13 | 14 | update is 15 | 16 | ``` 17 | export default DashBoardStore(); 18 | ``` 19 | -------------------------------------------------------------------------------- /src/content/posts/ERR_SSL_PROTOCOL_ERROR.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ERR_SSL_PROTOCOL_ERROR 3 | pubDate: 2019-05-12 20:15:52 4 | categories: ["ERR_SSL"] 5 | description: "" 6 | --- 7 | 8 | 起因是因为阿里云机器快要到期了,然后重新买了台低配的机器,在上面跑我的服务。根据快照直接进行创建,发现新机器和老机器的数据一摸一样,这功能真舒服,根据自定义镜像创建机器。购买完毕,启动服务。然后就遇到了下面这个问题 9 | 10 | ![image](https://user-images.githubusercontent.com/16217324/57581972-d6ba8880-74f1-11e9-8cf3-c27d0fa62827.png) 11 | 12 | - 尝试 Google、百度解决方法,发现没有跟我一样的。 13 | - 尝试重新申请 SSL 证书和重新配置 Nginx SSL 也是一样。 14 | - 实在没辙,下午先放着了,晚上再解决。 15 | 16 | 到了晚上,查看了下 9000 端口占用情况,发现正常。 17 | 18 | ![image](https://user-images.githubusercontent.com/16217324/57582015-5c3e3880-74f2-11e9-87e7-7df938115799.png) 19 | 20 | 把 PM2 进程管理给停止,然后重新 npm start,发现可以正常访问了,刺激! 21 | 22 | 然后怀疑跟 PM2 有关系,一直使用的是`pm2 start all`以为开启了,才发现 all 其实是管理。 23 | 24 | 需要重新`pm2 start ./bin/www --watch` 一下才行。 25 | 26 | ![image](https://user-images.githubusercontent.com/16217324/57582054-bb03b200-74f2-11e9-96c7-555ffdf86e15.png) 27 | 28 | 还有可能就是证书无效了,重新配置下证书。 29 | -------------------------------------------------------------------------------- /src/content/posts/Eslint for React.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Eslint for React 3 | pubDate: 2019-06-13 18:22:14 4 | categories: ["Eslint"] 5 | description: "" 6 | --- 7 | 8 | # React 脚手架配置 eslint 9 | 10 | ## Install 11 | 12 | ``` 13 | npm install -g eslint 14 | ``` 15 | 16 | ## .eslintrc.json 17 | 18 | ``` 19 | { 20 | "extends": "eslint:recommended", 21 | "env": { 22 | "browser": true, 23 | "commonjs": true, 24 | "es6": true 25 | }, 26 | "globals": { 27 | "$": true, 28 | "process": true, 29 | "__dirname": true 30 | }, 31 | "parser": "babel-eslint", 32 | "parserOptions": { 33 | "ecmaFeatures": { 34 | "jsx": true, 35 | "modules": true 36 | }, 37 | "sourceType": "module", 38 | "ecmaVersion": 6 39 | }, 40 | "plugins": [ 41 | "react", 42 | "standard", 43 | "promise" 44 | ], 45 | "rules": { 46 | "quotes": [ 47 | 2, 48 | "single" 49 | ], 50 | "no-console": 0, 51 | "no-debugger": 1, 52 | "no-var": 2, 53 | "semi": 2, 54 | "no-irregular-whitespace": 0, 55 | "no-trailing-spaces": 0, 56 | "eol-last": 0, 57 | "no-unused-vars": [ 58 | 2, 59 | { 60 | "vars": "all", 61 | "args": "after-used" 62 | } 63 | ], 64 | "no-case-declarations": 0, 65 | "no-underscore-dangle": 0, 66 | "no-alert": 2, 67 | "no-lone-blocks": 0, 68 | "no-class-assign": 2, 69 | "no-cond-assign": 2, 70 | "no-const-assign": 2, 71 | "no-delete-var": 2, 72 | "no-dupe-keys": 0, 73 | "no-duplicate-case": 2, 74 | "no-dupe-args": 2, 75 | "no-empty": 2, 76 | "no-func-assign": 2, 77 | "no-invalid-this": 0, 78 | "no-redeclare": 2, 79 | "no-spaced-func": 2, 80 | "no-this-before-super": 0, 81 | "no-undef": 2, 82 | "no-return-assign": 0, 83 | "no-script-url": 2, 84 | "no-use-before-define": 2, 85 | "jsx-quotes": [ 86 | 2, 87 | "prefer-double" 88 | ], 89 | "react/display-name": 0, 90 | "react/forbid-prop-types": [ 91 | 2, 92 | { 93 | "forbid": [ 94 | "any" 95 | ] 96 | } 97 | ], 98 | "react/jsx-boolean-value": 0, 99 | "react/jsx-closing-bracket-location": 1, 100 | "react/jsx-curly-spacing": [ 101 | 2, 102 | { 103 | "when": "never", 104 | "children": true 105 | } 106 | ], 107 | "indent": ["error", 4, {"SwitchCase": 1}], 108 | "react/jsx-indent": ["error", 4], 109 | "react/jsx-key": 2, 110 | "react/jsx-no-bind": 0, 111 | "react/jsx-no-duplicate-props": 2, 112 | "react/jsx-no-literals": 0, 113 | "react/jsx-no-undef": 1, 114 | "react/jsx-pascal-case": 0, 115 | "react/jsx-sort-props": 0, 116 | "react/jsx-uses-react": 1, 117 | "react/jsx-uses-vars": 2, 118 | "react/no-danger": 0, 119 | "react/no-did-mount-set-state": 0, 120 | "react/no-did-update-set-state": 0, 121 | "react/no-direct-mutation-state": 2, 122 | "react/no-multi-comp": 0, 123 | "react/no-set-state": 0, 124 | "react/no-unknown-property": 2, 125 | "react/prefer-es6-class": 2, 126 | "react/prop-types": 0, 127 | "react/react-in-jsx-scope": 2, 128 | "react/self-closing-comp": 0, 129 | "react/sort-comp": 0, 130 | "no-extra-boolean-cast": 0, 131 | "react/no-array-index-key": 0, 132 | "react/no-deprecated": 1, 133 | "react/jsx-equals-spacing": 2, 134 | "no-unreachable": 1, 135 | "comma-dangle": 2, 136 | "no-mixed-spaces-and-tabs": 2, 137 | "prefer-arrow-callback": 0, 138 | "arrow-parens": 0, 139 | "arrow-spacing": 0, 140 | "camelcase": 0 141 | }, 142 | "settings": { 143 | "import/ignore": [ 144 | "node_modules" 145 | ], 146 | "react": { 147 | "version": "latest" 148 | } 149 | } 150 | } 151 | ``` 152 | -------------------------------------------------------------------------------- /src/content/posts/GitHub 本地配置和全局配置.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: GitHub 本地配置和全局配置 3 | pubDate: 2018-10-25 16:43:53 4 | categories: ["React"] 5 | description: "" 6 | --- 7 | 8 | 原文地址:https://segmentfault.com/a/1190000002994742 9 | 10 | 还需要设置项目本地的配置和全局配置 11 | 12 | ``` 13 | $ git config --local user.name "" 14 | $ git config --local user.email "" 15 | $ git config --local user.password "" 16 | 17 | $ git config --global user.name "" 18 | $ git config --global user.email "" 19 | $ git config --global user.password "" 20 | ``` 21 | -------------------------------------------------------------------------------- /src/content/posts/JS 垃圾回收机制.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: JS 垃圾回收机制 3 | pubDate: 2018-07-23 15:30:03 4 | categories: ["GC"] 5 | description: "" 6 | --- 7 | 8 | ## 标记清除 9 | 10 | 标记清除是指对变量进行标记,当变量进入作用域中的时候,标记"进入环境",从逻辑上来讲,永远不能是释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就会用到它们。比如闭包。当变量离开环境的时候,则标记为"离开环境"。 11 | 垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记。然后,它会去掉环境中的变量以及被环境中的变量引用的标记。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后。垃圾收集器完成内存清除工作,销毁那些带标记的值,并回收他们所占用的内存空间。 12 | 13 | ## 引用计数 14 | 15 | 另一种不太常见的垃圾回收策略是引用计数。引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型赋值给该变量时,则这个值的引用次数就是 1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数就减 1。当这个引用次数变成 0 时,则说明没有办法再访问这个值了,因而就可以将其所占的内存空间给收回来。这样,垃圾收集器下次再运行时,它就会释放那些引用次数为 0 的值所占的内存。 16 | -------------------------------------------------------------------------------- /src/content/posts/JS 实现 deepCopy.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: JS 实现 deepCopy 3 | pubDate: 2019-03-12 10:18:02 4 | categories: ["JS"] 5 | description: "" 6 | --- 7 | 8 | ``` 9 | function getType(obj) { 10 | // 为啥不用typeof? typeof无法区分数组和对象 11 | if(Object.prototype.toString.call(obj) == '[object Object]') { 12 | return 'Object'; 13 | } 14 | 15 | if(Object.prototype.toString.call(obj) == '[object Array]') { 16 | return 'Array'; 17 | } 18 | return 'nomal'; 19 | }; 20 | 21 | function deepCopy(obj) { 22 | if (getType(obj) == 'nomal') { 23 | return obj; 24 | } else { 25 | var newObj = getType(obj) == 'Object' ? {} : []; 26 | for(var key in obj) { 27 | // 为啥要用hasOwnProperty?不需要从对象的原型链上进行复制 28 | if(obj.hasOwnProperty(key)) { 29 | newObj[key] = deepCopy(obj[key]); 30 | } 31 | } 32 | } 33 | return newObj; 34 | } 35 | 36 | 37 | var object = [ 38 | { 39 | title: 'test', 40 | checked: false 41 | } 42 | ]; 43 | 44 | deepCopy(object); 45 | ``` 46 | -------------------------------------------------------------------------------- /src/content/posts/JS 实现出入栈操作.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: JS 实现出入栈操作 3 | pubDate: 2018-07-18 09:22:58 4 | categories: ["JS"] 5 | description: "" 6 | --- 7 | 8 | ## 栈 9 | 10 | 栈是一种特殊的列表,栈内的元素只能通过列表的一端访问,这一端称之为栈顶。栈被称为一种后入先出(LIFO,last-in-first-out)的数据结构。盘子就是最好的例子,最后叠入的盘子,总是最先出去。 11 | 12 | ## 实现 13 | 14 | ``` 15 | function stack() { 16 | this.dataStore = []; //初始化数组 17 | this.topa = 0; //栈位 18 | this.pop = pop; //出栈 19 | this.push = push; //入栈 20 | this.clear = clear; //清楚栈 21 | this.length = length; //返回栈的长度 22 | } 23 | 24 | function pop() { 25 | return this.dataStore[--this.topa]; 26 | } 27 | 28 | function push(element) { 29 | return this.dataStore[this.topa++] = element; 30 | } 31 | 32 | function length() { 33 | return this.topa; 34 | } 35 | 36 | function clear() { 37 | this.topa = 0; 38 | } 39 | var s = new stack(); 40 | s.push(1); 41 | s.topa; 42 | ``` 43 | 44 | ## 利用栈的特点很容易实现回文 45 | 46 | 回文是指,正过来和反过来都是一样,比如`dad,racecar`,比如`dad`,在栈中就是 47 | `[d] [a] [d]` 48 | 通过循环读取,判断与传入的字符串是否相等,即判断是否是回文。 49 | 50 | ``` 51 | function isPalindremo(word) { 52 | var s = new stack(); 53 | for (var i = 0, len = word.length; i < len; i++) { 54 | s.push(word[i]); 55 | } 56 | var rword = ''; 57 | while (s.length() > 0) { 58 | rword += s.pop(); 59 | } 60 | if (rword === word) { 61 | return true; 62 | } else { 63 | return false; 64 | } 65 | } 66 | isPalindremo('dad'); 67 | ``` 68 | 69 | ## 判断括号是否匹配 70 | 71 | ``` 72 | function isBrackets(expresseion) { 73 | var s = new stack(); 74 | var b = false; 75 | var c = false; 76 | for (var i = 0, len = expresseion.length; i < len; i++) { 77 | if (expresseion[i] == '(') { 78 | b = true; 79 | } 80 | if (expresseion[i] == ')') { 81 | c = true; 82 | } 83 | s.push(expresseion[i]); 84 | } 85 | if (!(b && c)) { 86 | s.push(')'); 87 | return s.length(); 88 | } else { 89 | return false; 90 | } 91 | } 92 | console.log(isBrackets('123 + (a+x+x5+a')); 93 | ``` 94 | -------------------------------------------------------------------------------- /src/content/posts/JS 实现列表操作.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: JS 实现列表操作 3 | pubDate: 2018-07-17 16:54:16 4 | categories: ["JS"] 5 | description: "" 6 | --- 7 | 8 | ## 列表 9 | 10 | 人们经常使用列表,比如待办事项列表、购物车等,如果数据不太多的话,列表就显得尤为有用。 11 | 12 | ## JS 实现 13 | 14 | ``` 15 | function list() { 16 | this.dataStore = []; //初始化数组 17 | this.clear = clear; //清除列表 18 | this.remove = remove; //移除列表中的元素 19 | this.find = find; //寻找列表中的元素 20 | this.length = length; //返回列表的长度 21 | } 22 | 23 | function find(element) { 24 | for (var i = 0, len = this.dataStore.length; i < len; i++) { 25 | if (this.dataStore[i] === element) { 26 | return i; 27 | } 28 | } 29 | return -1; 30 | } 31 | 32 | function remove(element) { 33 | for (var i = 0, len = this.dataStore.length; i < len; i++) { 34 | if (this.dataStore[i] === element) { 35 | this.dataStore.splice(i, 1); 36 | } 37 | } 38 | return this.dataStore; 39 | } 40 | 41 | function length() { 42 | return this.dataStore.length; 43 | } 44 | 45 | function clear() { 46 | this.dataStore = []; 47 | } 48 | ``` 49 | -------------------------------------------------------------------------------- /src/content/posts/JS 实现各种排序.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: JS 实现各种排序 3 | pubDate: 2018-07-16 11:23:52 4 | categories: ["JS"] 5 | description: "" 6 | --- 7 | 8 | ## 冒泡排序 9 | 10 | 冒泡排序是一种把数字两两交换的排序,时间复杂度为 O(n2)。 11 | 12 | ``` 13 | function bubbleSort(array) { 14 | if (array.length < 1) { 15 | return; 16 | } 17 | var temp; 18 | var len = array.length; 19 | for (var i = 0; i < len; i++) { 20 | for (var j = 0; j < len; j++) { 21 | if (array[i] < array[j]) { 22 | temp = array[i]; 23 | array[i] = array[j]; 24 | array[j] = temp; 25 | } 26 | } 27 | } 28 | return array; 29 | } 30 | console.log(bubbleSort([3, 8, 5, 2, 1, 4])); 31 | ``` 32 | 33 | ## 快速排序 34 | 35 | 快速排序其实是二分排序和冒泡排序的变种,基本思想就是左边放最小数据,右边放最大数据,分别对左边和右边进行递归。然后再组成最小数据集。时间复杂度为 O(n2) 36 | 37 | ``` 38 | function quickSort(array) { 39 | if (array.length < 1) { 40 | return array; 41 | } 42 | var len = array.length; 43 | var ban = Math.floor(len / 2); 44 | var num = array[ban]; 45 | var left = [], 46 | right = [], 47 | mid = []; 48 | for (var i = 0; i < len; i++) { 49 | if (array[i] < num) { 50 | left.push(array[i]); 51 | } else if (array[i] > num) { 52 | right.push(array[i]); 53 | } else { 54 | mid.push(array[i]); 55 | } 56 | } 57 | return [].concat(quickSort(left), mid, quickSort(right)); 58 | } 59 | console.log(quickSort([3, 8, 5, 2, 1, 4])); 60 | ``` 61 | -------------------------------------------------------------------------------- /src/content/posts/JS 实现斐波那契数列.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: JS 实现斐波那契数列 3 | pubDate: 2018-07-16 11:05:07 4 | categories: ["JS"] 5 | description: "" 6 | --- 7 | 8 | ## 斐波那契数列 9 | 10 | 0,1,1,2,3,5,8 像这样的数列就是斐波那契数列,特点是第 n 项等于前两项的和。 11 | 12 | ## 递归实现 13 | 14 | ``` 15 | function fib(n) { 16 | if (n == 0) { 17 | return 0; 18 | } 19 | if (n == 1) { 20 | return 1; 21 | } 22 | return fib(n - 1) + fib(n - 2); 23 | } 24 | console.log(fib(5)); 25 | ``` 26 | 27 | ## 迭代实现 28 | 29 | ``` 30 | function fib(n) { 31 | var a = 0, 32 | b = 1, 33 | total = 0; 34 | if (n < 2) { 35 | return n; 36 | } 37 | for (var i = 2; i <= n; i++) { 38 | total = a + b; 39 | a = b; 40 | b = total; 41 | } 42 | return total; 43 | } 44 | console.log(fib(5)); 45 | ``` 46 | 47 | ## 总结 48 | 49 | 斐波那契数列最好不要用递归去实现,因为它在重复计算,而迭代计算的效率则要高很多并且时间复杂度为 O(n),不信?输入个 100 看看?所以,面试的时候用迭代去写会让面试官更称心如意。 50 | -------------------------------------------------------------------------------- /src/content/posts/JS 实现队列.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: JS 实现队列 3 | pubDate: 2018-07-19 10:08:43 4 | categories: ["JS"] 5 | description: "" 6 | --- 7 | 8 | ## 队列 9 | 10 | 队列是一种列表,与栈相反,特点表现为先入先出(First-in-First-out,FIFO)结构。常见的例子就是银行排队,先到人的先办理业务。 11 | 12 | ## 实现 13 | 14 | 使用数组实现,js 中的数组相对于其他语言,有它自己的优势,比如 push()方法,向数组末尾追加元素并更新数组长度,shift()方法,取出数据第一项元素。所以,利用数组就很容易实现队列。 15 | 16 | ``` 17 | function queue() { 18 | this.dataStore = []; 19 | this.length = length; 20 | this.iqueue = iqueue; 21 | this.oqueue = oqueue; 22 | this.front = front; 23 | this.back = back; 24 | this.clear = clear; 25 | } 26 | 27 | function length() { 28 | return this.dataStore.length; 29 | } 30 | 31 | function iqueue(element) { 32 | return this.dataStore.push(element); 33 | } 34 | 35 | function oqueue() { 36 | return this.dataStore.shift(); 37 | } 38 | 39 | function front() { 40 | return this.dataStore[0]; 41 | } 42 | 43 | function back() { 44 | return this.dataStore[this.dataStore.length - 1]; 45 | } 46 | 47 | function clear() { 48 | return this.dataStore = []; 49 | } 50 | 51 | var q = new queue(); 52 | console.log(q.iqueue(1)); 53 | console.log(q.length()); 54 | console.log(q.front()); 55 | console.log(q.oqueue()); 56 | console.log(q.length()); 57 | ``` 58 | 59 | ## 使用队列实现舞蹈员入场问题 60 | 61 | ``` 62 | function dancer(name, sex) { 63 | this.name = name; 64 | this.sex = sex; 65 | } 66 | 67 | 68 | function getDancer(males, females) { 69 | var datas = [{ 70 | name: 'a', 71 | sex: 1 72 | }, 73 | { 74 | name: 'b', 75 | sex: 0 76 | }, 77 | { 78 | name: 'c', 79 | sex: 1 80 | }, 81 | { 82 | name: 'd', 83 | sex: 0 84 | } 85 | ] 86 | for (var i = 0, len = datas.length; i < len; i++) { 87 | if (datas[i].sex === 0) { 88 | males.iqueue(new dancer(datas[i].name, datas[i].sex)); 89 | } else { 90 | females.iqueue(new dancer(datas[i].name, datas[i].sex)); 91 | } 92 | } 93 | } 94 | 95 | function dance(males, females) { 96 | var person; 97 | while (males.length() != 0 && females.length() != 0) { 98 | person = males.oqueue(); 99 | console.log('Males dancer is:' + person.name); 100 | person = females.oqueue(); 101 | console.log('Females dancer is:' + person.name); 102 | } 103 | } 104 | var males = new queue(); 105 | var females = new queue(); 106 | getDancer(males, females); 107 | dance(males, females); 108 | ``` 109 | -------------------------------------------------------------------------------- /src/content/posts/JS 数组扁平化之简单方法实现.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: JS 数组扁平化之简单方法实现 3 | pubDate: 2019-09-10 10:14:20 4 | categories: ["JS"] 5 | description: "" 6 | --- 7 | 8 | ## 什么是扁平化 9 | 10 | 一句话解释,数组扁平化是指将一个多维数组(含嵌套)变为一维数组 11 | 12 | ## 扁平化之 ES5 13 | 14 | ### toString 15 | 16 | ``` 17 | const arr = [1, 2, 3, [4, 5, [6, 7]]]; 18 | 19 | const flatten = arr.toString().split(','); 20 | 21 | console.log(flatten); 22 | ``` 23 | 24 | 优点:简单,方便,对原数据没有影响 25 | 26 | 缺点:最好数组元素全是数字或字符,不会跳过空位 27 | 28 | ### join 29 | 30 | ``` 31 | const arr = [1, 2, 3, [4, 5, [6, 7]]]; 32 | 33 | const flatten = arr.join(',').split(','); 34 | 35 | console.log(flatten); 36 | ``` 37 | 38 | 优点和缺点同 toString 39 | 40 | ## 扁平化之 ES6 41 | 42 | ### flat 43 | 44 | ``` 45 | const arr = [1, 2, 3, [4, 5, [6, 7]]]; 46 | 47 | const flatten = arr.flat(Infinity); 48 | 49 | console.log(flatten); 50 | ``` 51 | 52 | 优点:会跳过空位,返回新数组,不会修改原数组。 53 | 54 | 缺点:无 55 | 56 | ### 扩展运算符(...) 57 | 58 | ``` 59 | const arr = [1, 2, 3, [4, 5]]; 60 | 61 | console.log([].concat(...arr)); 62 | ``` 63 | 64 | 优点:简单,方便 65 | 66 | 缺点:只能扁平化一层 67 | 68 | ## 总结 69 | 70 | 推荐使用 `ES6` 的 `flat` 方法 71 | 72 | ## 博客 73 | 74 | [欢迎关注我的博客](https://github.com/xuya227939/LiuJiang-Blog) 75 | -------------------------------------------------------------------------------- /src/content/posts/JS 语法规范.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: JS 语法规范 3 | pubDate: 2019-07-23 10:59:08 4 | categories: ["JS"] 5 | description: "" 6 | --- 7 | 8 | ## 采用小写驼峰式命名 9 | 10 | ``` 11 | // good 12 | studentInfo 13 | 14 | // bad 15 | studentinfo 16 | STUDENTINFO 17 | ``` 18 | 19 | ## 常量命名方式 20 | 21 | ``` 22 | // good 23 | const COL_NUM = 10; 24 | 25 | // bad 26 | const col_num = 10; 27 | ``` 28 | 29 | ## 使用字面量 30 | 31 | ``` 32 | // good 33 | const obj = { 34 | name:'faker' 35 | } 36 | 37 | // bad 38 | let obj = {}; 39 | obj.name = 'faker'; 40 | ``` 41 | 42 | ## 函数参数使用解构 43 | 44 | ``` 45 | // good 46 | function createPerson({ name, age }) { 47 | // ... 48 | } 49 | createPerson({ 50 | name: 'Faker', 51 | age: 18, 52 | }); 53 | 54 | // bad 55 | function createPerson(name, age) { 56 | // ... 57 | } 58 | ``` 59 | 60 | ## 使用参数默认值 61 | 62 | ``` 63 | // good 64 | function createMicrobrewery(name = 'faker') { 65 | // ... 66 | } 67 | 68 | // bad 69 | function createMicrobrewery(name) { 70 | const breweryName = name || 'faker'; 71 | // ... 72 | } 73 | ``` 74 | 75 | ## 函数式编程 76 | 77 | ``` 78 | // good 79 | const programmerOutput = [ 80 | { 81 | name: 'Uncle Bobby', 82 | linesOfCode: 500 83 | }, { 84 | name: 'Suzie Q', 85 | linesOfCode: 1500 86 | }, { 87 | name: 'Jimmy Gosling', 88 | linesOfCode: 150 89 | }, { 90 | name: 'Gracie Hopper', 91 | linesOfCode: 1000 92 | } 93 | ]; 94 | let totalOutput = programmerOutput 95 | .map(output => output.linesOfCode) 96 | .reduce((totalLines, lines) => totalLines + lines, 0) 97 | 98 | // bad 99 | const programmerOutput = [ 100 | { 101 | name: 'Uncle Bobby', 102 | linesOfCode: 500 103 | }, { 104 | name: 'Suzie Q', 105 | linesOfCode: 1500 106 | }, { 107 | name: 'Jimmy Gosling', 108 | linesOfCode: 150 109 | }, { 110 | name: 'Gracie Hopper', 111 | linesOfCode: 1000 112 | } 113 | ]; 114 | 115 | let totalOutput = 0; 116 | 117 | for (let i = 0; i < programmerOutput.length; i++) { 118 | totalOutput += programmerOutput[i].linesOfCode; 119 | } 120 | ``` 121 | 122 | ## 缩进 123 | 124 | 统一使用一个 `tab` 作为缩进 125 | 126 | ## 空格 127 | 128 | 二元运算符两侧必须有一个空格,一元运算符与操作对象之间不允许有空格。 129 | 用作代码块起始的左花括号 { 前必须有一个空格。 130 | 131 | ``` 132 | // good 133 | var a = !arr.length; 134 | a++; 135 | a = b + c; 136 | 137 | // good 138 | if (condition) { 139 | } 140 | 141 | while (condition) { 142 | } 143 | 144 | function funcName() { 145 | } 146 | 147 | // bad 148 | if (condition){ 149 | } 150 | 151 | while (condition){ 152 | } 153 | 154 | function funcName(){ 155 | } 156 | ``` 157 | 158 | ## 禁止使用 var,使用 let、const 代替 159 | 160 | ``` 161 | // good 162 | let a = 123; 163 | 164 | // bad 165 | var a = 123; 166 | ``` 167 | 168 | ## JS 中使用单引号',在 DOM 中使用双引号" 169 | 170 | ``` 171 | // good 172 | const str = '我是一个字符串'; 173 |
    174 | 175 | // bad 176 | const str = "我是一个字符串"; 177 |
    178 | ``` 179 | 180 | ## 使用模板字符拼接字符串`` 181 | 182 | ``` 183 | // good 184 | const name = 'faker'; 185 | const str = `我叫${a}`; 186 | 187 | // bad 188 | const name = 'faker'; 189 | const str = '我叫' + a; 190 | ``` 191 | 192 | ## 变量命名语义化 193 | 194 | ``` 195 | // good 196 | const student = 'faker'; 197 | 198 | // bad 199 | const a = 'faker'; 200 | ``` 201 | 202 | ## 注释 203 | 204 | - 单行注释:必须独占一行。`//` 后跟一个空格,缩进与下一行被注释说明的代码一致 205 | - 多行注释:避免使用 `/_..._/` 这样的多行注释。有多行注释内容时,使用多个单行注释 206 | - 文档化注释:为了便于代码阅读和自文档化,以下内容必须包含以 `/\*_..._/` 形式的块注释中。 207 | 208 | ## 每个 JS 文件在头部需要给出该页面的信息 209 | 210 | ``` 211 | // good 212 | /* 213 | * 充值记录页面 214 | * @Author: Jiang 215 | * @Date: 2019-06-12 15:21:19 216 | * @Last Modified by: Jiang 217 | * @Last Modified time: 2024-04-27 15:42:23 218 | */ 219 | 220 | // bad 221 | 无任何注释 222 | ``` 223 | 224 | ## 不要省略分号 225 | 226 | ``` 227 | // good 228 | const student = 'faker'; 229 | 230 | // bad 231 | const student = 'faker' 232 | ``` 233 | 234 | ## 博客 235 | 236 | [欢迎关注我的博客](https://github.com/xuya227939/LiuJiang-Blog) 237 | -------------------------------------------------------------------------------- /src/content/posts/JS 运行机制类文章.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: JS 运行机制类文章 3 | pubDate: 2018-09-29 14:51:02 4 | categories: ["JS"] 5 | description: "" 6 | --- 7 | 8 | 这一次,彻底弄懂 JavaScript 执行机制 9 | 10 | https://juejin.im/post/59e85eebf265da430d571f89 11 | 12 | JavaScript 是如何工作的:在 V8 引擎里 5 个优化代码的技巧 13 | 14 | https://github.com/xitu/gold-miner/blob/master/TODO/how-javascript-works-inside-the-v8-engine-5-tips-on-how-to-write-optimized-code.md 15 | 16 | JavaScript 是如何工作的:内存管理 + 处理常见的 4 种内存泄漏 17 | 18 | https://github.com/xitu/gold-miner/blob/master/TODO/how-javascript-works-memory-management-how-to-handle-4-common-memory-leaks.md 19 | 20 | 从浏览器多进程到 JS 单线程,JS 运行机制最全面的一次梳理 (值得反复阅读) 21 | 22 | https://juejin.im/post/5a6547d0f265da3e283a1df7?utm_medium=fe&utm_source=weixinqun 23 | 24 | 深入了解 JavaScript 引擎精华 (值得反复阅读) 25 | 26 | http://developer.51cto.com/art/201806/576835.htm 27 | 28 | 跟着 Event loop 规范理解浏览器中的异步机制 29 | 30 | https://juejin.im/post/5b5873a1e51d4519133fbc35 31 | 32 | [译] JavaScript 是如何工作的:深入网络层 + 如何优化性能和安全 33 | 34 | https://juejin.im/post/5b02ae48518825429d1f9aff?utm_medium=fe&utm_source=weixinqun 35 | 36 | 微任务和宏任务 37 | 38 | https://juejin.im/entry/5b860983e51d4538980c22aa 39 | -------------------------------------------------------------------------------- /src/content/posts/MacBook Pro 外接显示屏开启 HiDPI.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: MacBook Pro 外接显示屏开启 HiDPI 3 | pubDate: 2021-06-25 19:29:35 4 | categories: ["HiDPI"] 5 | description: "" 6 | --- 7 | 8 | ## 前言 9 | 10 | 最近向公司申请了一台 2K 显示器,弄来之后,接上 MacBook Pro,结果,由于像素太高,导致整个屏幕都缩放,字体变的非常小,而且也没有达到 Retina 的效果。经查询,苹果需要开启 HiDPI 技术 11 | 12 | ## HiDPI 13 | 14 | 本质上是用软件的方式实现单位面积内的高密度像素。用四个像素点来表现一个像素,因此能够更加清晰细腻 15 | 16 | 高 PPI(硬件) + HiDPI 渲染(软件) = 更细腻的显示效果(Retina) 17 | 18 | WechatIMG2168 19 | 20 | ## 获取外接显示器 DisplayVendorID 和 DisplayProductID 21 | 22 | 在终端输入以下命令: 23 | 24 | ``` 25 | $ ioreg -l | grep "DisplayVendorID" 26 | 27 | $ ioreg -l | grep "DisplayProductID" 28 | ``` 29 | 30 | 如果获得两个,那说明的你的 Macbook 还在亮着,可以合盖来排除掉 31 | 32 | ## 制作外接显示屏系统配置文件 33 | 34 | ### 转换 16 进制 35 | 36 | 将 DisplayVendorID 和 DisplayProductID 的数值,转换为 16 进制,[在线转换工具](https://tool.lu/hexconvert/) 37 | 38 | ### 创建显示器配置文件夹 39 | 40 | 新建文件夹,命名为:DisplayVendorID-XXXX,其中 XXXX 是刚才转换的 DisplayVendorID 的 16 进制值小写 41 | 42 | ### 创建显示器配置内容 43 | 44 | 这里需要借助工具来生成,[在线生成工具](https://codeclou.github.io/Display-Override-PropertyList-File-Parser-and-Generator-with-HiDPI-Support-For-Scaled-Resolutions/),将显示器的名称、DisplayVendorID(16 进制) 和 DisplayProductID(16 进制) 对应填写进去,即可获得配置文件,下载文件到刚创建的 DisplayVendorID-XXXX 文件夹内,将 plist 的后缀去掉 45 | 46 | ![WechatIMG2169](https://user-images.githubusercontent.com/16217324/123499686-0c3b7500-d66b-11eb-96be-234022b2c651.png) 47 | 48 | 然后需要将文件放到系统的 `/System/Library/Displays/Contents/Resources/Overrides/` 文件夹中 49 | 50 | 如果 `Overrides` 没有权限操作,在终端中输入 51 | 52 | ``` 53 | $ csrutil status 54 | ``` 55 | 56 | ![WechatIMG2170](https://user-images.githubusercontent.com/16217324/123499683-05acfd80-d66b-11eb-843e-a70b6fc736f8.png) 57 | 58 | 如果是关闭状态,则需要开启 59 | 60 | ### 开启 rootless 61 | 62 | 1. 重启 MacBook,按住 command + R 直到屏幕上出现苹果的标志和进度条,进入 Recovery 模式 63 | 2. 在屏幕左上方的工具栏找到实用工具(左数第 3 个),打开终端 64 | ``` 65 | $ csrutil disable 66 | ``` 67 | 3. 重启 MacBook 68 | 69 | ### 关闭 rootless 70 | 71 | 1. 重启 MacBook,按住 command + R 直到屏幕上出现苹果的标志和进度条,进入 Recovery 模式 72 | 2. 在屏幕左上方的工具栏找到实用工具(左数第 3 个),打开终端 73 | ``` 74 | $ csrutil disable 75 | ``` 76 | 3. 重启 MacBook 77 | 78 | 如果已关闭 rootless,还是不行,那么需要在终端输入以下命令: 79 | 80 | ``` 81 | $ sudo mount -rw / 82 | ``` 83 | 84 | Tips: 85 | 86 | 1. 10.15 之后,系统的目录除了几个 rootless 可以修改的,都是只读的,所有对系统的修改都不支持,你的那个目录是在只读分区内的。要是写机器域的文件,在/Library 目录中操作。 87 | 88 | 2. macOS 有个内核保护机制 rootless,有时候你需要装什么软件时,需要一些 root 权限,但是在 macOS 上 root 虽然权力是最大的,但是苹果还是限制了它一下。 89 | 90 | ## 开启 HiDPI 91 | 92 | [one-key-hidpi](https://github.com/xzhih/one-key-hidpi/blob/master/README-zh.md) 93 | 94 | 此脚本的目的是为中低分辨率的屏幕开启 HiDPI 选项,并且具有原生的 HiDPI 设置,不需要 RDM 软件即可在系统显示器设置中设置 95 | 96 | macOS 的 DPI 机制和 Windows 下不一样,比如 1080p 的屏幕在 Windows 下有 125%、150% 这样的缩放选项,而同样的屏幕在 macOS 下,缩放选项里只是单纯的调节分辨率,这就使得在默认分辨率下字体和 UI 看起来很小,降低分辨率又显得模糊 97 | 98 | 同时,此脚本也可以通过注入修补后的 EDID 修复闪屏,或者睡眠唤醒后的闪屏问题,当然这个修复因人而异 99 | 100 | 开机的第二阶段 logo 总是会稍微放大,因为分辨率是仿冒的 101 | 102 | ### 使用方法 103 | 104 | 1. 远程模式: 在终端输入以下命令回车即可 105 | 106 | ``` 107 | $ bash -c "$(curl -fsSL https://raw.githubusercontent.com/xzhih/one-key-hidpi/master/hidpi.sh)" 108 | ``` 109 | 110 | 2. 本地模式: 下载项目解压,双击 `hidpi.command` 运行 111 | 112 | ![WechatIMG2171](https://user-images.githubusercontent.com/16217324/123499664-ec0bb600-d66a-11eb-85cb-524aff4757ef.jpeg) 113 | 114 | ![WechatIMG2172](https://user-images.githubusercontent.com/16217324/123499670-f5951e00-d66a-11eb-9a74-99e150806caa.jpeg) 115 | 116 | ## RDM 117 | 118 | RDM 全称为 Retina Display Manage,[安装地址](http://avi.alkalay.net/software/RDM) 119 | 120 | 重启后打开 RDM,选取带雷电符号的 1920x1080,即可开启 HiDPI。 121 | 122 | ![WechatIMG2173](https://user-images.githubusercontent.com/16217324/123499675-fc239580-d66a-11eb-8731-5fe3a320558f.png) 123 | 124 | ## 总结 125 | 126 | ![src=http___2d zol-img com cn_product_123_500x2000_269_cew9IvrLCwGQY jpg refer=http___2d zol-img com](https://user-images.githubusercontent.com/16217324/123500068-e06dbe80-d66d-11eb-814b-1780aea939ff.jpeg) 127 | -------------------------------------------------------------------------------- /src/content/posts/Node.js 爬虫获取网页内容乱码.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Node.js 爬虫获取网页内容乱码 3 | pubDate: 2019-08-08 09:47:41 4 | categories: ["Node.js"] 5 | description: "" 6 | --- 7 | 8 | ## 返回的 html 乱码 9 | 10 | 网页内容格式是 GBK 和头部用 gzip 压缩,设置属性`gzip: true`和`encoding:null`,再通过 iconv 转成 utf8 11 | 12 | ``` 13 | npm install request 14 | npm install iconv-lite 15 | ``` 16 | 17 | ``` 18 | const request = require('request'); 19 | const iconv = require('iconv-lite'); 20 | const options = { 21 | url: `http://xxxx`, 22 | proxy: 'http://127.0.0.1:8888', 23 | secureProtocol: 'TLSv1_method', 24 | gzip: true, 25 | encoding: null 26 | }; 27 | request.get(options, function (err, response, data) { 28 | const result = iconv.decode(data, 'utf-8').toString(); 29 | console.log(result); 30 | }); 31 | ``` 32 | 33 | ## 返回参数乱码 34 | 35 | 去掉 encoding 参数即可 36 | 37 | ``` 38 | const request = require('request'); 39 | const iconv = require('iconv-lite'); 40 | const options = { 41 | url: `http://xxxx`, 42 | proxy: 'http://127.0.0.1:8888', 43 | secureProtocol: 'TLSv1_method', 44 | gzip: true 45 | }; 46 | request.get(options, function (err, response, data) { 47 | console.log(data.toString()); 48 | }); 49 | ``` 50 | 51 | 如果头部没有压缩过的,去掉 gzip 参数,然后再把返回的参数`data.toString()`一下 52 | -------------------------------------------------------------------------------- /src/content/posts/PhantomJS not found on PATH.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: PhantomJS not found on PATH 3 | pubDate: 2019-05-06 16:02:32 4 | categories: ["PhantomJS"] 5 | description: "" 6 | --- 7 | 8 | 今天 Win 上进行`npm install`的时候遇到一个问题 9 | 10 | ``` 11 | PhantomJS not found on PATH 12 | Downloading https://github.com/Medium/phantomjs/releases/download/v2.1.1/phantomjs-2.1.1-windows.zip 13 | Saving to C:\Users\hezhi\AppData\Local\Temp\phantomjs\phantomjs-2.1.1-windows.zip 14 | Receiving... 15 | [=---------------------------------------] 2% 16 | ``` 17 | 18 | 这个 phantomjs-2.1.1-windows.zip 包一直下载不了,原来是天朝的网给墙了。 19 | 20 | [http://phantomjs.org/download.html](http://phantomjs.org/download.html) 通过这个网址进行下载对应的包 21 | 22 | 把包放进此路径下`C:\Users\hezhi\AppData\Local\Temp\phantomjs\`(Win) 23 | 24 | 解压,然后复制 phantomjs 所在的路径`C:\Users\hezhi\AppData\Local\Temp\phantomjs\phantomjs-2.1.1-windows\phantomjs-2.1.1-windows\bin` 25 | 26 | 再设置环境变量,添加刚才复制的路径。 27 | ![image](https://user-images.githubusercontent.com/16217324/57213064-3ad3dd00-7018-11e9-81ad-f873201c364a.png) 28 | -------------------------------------------------------------------------------- /src/content/posts/React 如何进行上传图片.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: React 如何进行上传图片 3 | pubDate: 2018-06-15 16:16:03 4 | categories: ["React"] 5 | description: "" 6 | --- 7 | 8 | ## 标签 9 | 10 | ``` 11 | 19 | ``` 20 | 21 | `input` //标签的 type 设置为 file 属性 22 | `accept` //属性,支持很多类型,这里设置为只上传图片 23 | `hidden` //隐藏文字,做下面这种效果的时候,就需要隐藏文字。 24 | ![image](https://user-images.githubusercontent.com/16217324/41458033-0f9a7d52-70b8-11e8-9037-ca6443298f97.png) 25 | onChange //上传完成后的回调 26 | 27 | ## JS 代码 28 | 29 | ``` 30 | upload() { 31 | let files; 32 | files = this.refs.upload.files 33 | let count = files.length; 34 | let formData = new FormData(); 35 | for (let i = 0; i < count; i++) { 36 | files[i].thumb = URL.createObjectURL(files[i]); 37 | formData.append('filedata', files[i]); 38 | } 39 | } 40 | ``` 41 | 42 | 这里主要是通过 `this.refs.upload`来获取上传之后的文件,然后通过`createObjectURL` 静态方法创建一个 `DOMString`(mac 测试通过 input 上传过来`webkitRelativePath` 是空的),然后追加进 formData。再通过`send(body: formData)`方法传进后端 43 | 44 | ## 后端 45 | 46 | ``` 47 | const express = require('express'); 48 | const multiparty = require('multiparty'); 49 | const gm = require('gm').subClass({ 50 | imageMagick: true 51 | }); 52 | const fs = require('fs'); 53 | router.put(`uploadImages`, function (req, res) { 54 | let datas = {}; 55 | if (!(fs.existsSync('./images/'))) { 56 | fs.mkdir('./images/', function (err, status) { 57 | 58 | }); 59 | } 60 | const form = new multiparty.Form({ 61 | uploadDir: './images/' 62 | }); 63 | form.parse(req, function (err, fields, files) { 64 | const filesTmp = files.filedata; 65 | if (err) { 66 | throw err; 67 | } else { 68 | const relPath = filesTmp; 69 | for (let i in relPath) { 70 | gm(relPath[i].path) 71 | .resize(240, 240) 72 | .noProfile() 73 | .write(relPath[i].path, function (err, data) { 74 | if (err) { 75 | throw err; 76 | } 77 | console.log(data); 78 | }); 79 | } 80 | } 81 | }); 82 | }); 83 | ``` 84 | 85 | 后端用的是 node.js,express 框架。fs 模块,来进行判断是否存在该文件夹,如果不存在,则创建。 86 | `fs.existsSync()` 返回值为 true or false `fs.mkdir()` 创建文件夹 multiparty 模块来解析 form 表单 87 | gm 进行裁剪图片。 88 | 89 | ## 错误处理 90 | 91 | 1、`Error: unsupported content-type` 92 | 93 | 这个错误是因为你的 content-type 设置错了,设置成`multipart/form-data`即可。 94 | 95 | 2、设置完成之后,还是不行。 96 | 去掉`headers`的设置 97 | `body: formData` //body 的内容为表单内容 98 | 99 | 3、上传一次图片之后,无法上传第二次,是因为 value 此时有值,没有进行清空处理,在上传成功回调里,进行`e.target.value = '';` 100 | -------------------------------------------------------------------------------- /src/content/posts/React全家桶建站教程-Express.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: React全家桶建站教程-Express 3 | pubDate: 2018-06-07 11:37:22 4 | categories: ["React"] 5 | description: "" 6 | --- 7 | 8 | ## 介绍 9 | 10 | - 丰富的 HTTP 快捷方法和任意排列组合的 Connect 中间件,让你创建健壮、友好的 API 变得既快速又简单。 11 | - Express 是一个基于 Node.js 平台的极简、灵活的 web 应用开发框架,它提供一系列强大的特性,帮助你创建各种 Web 和移动设备应用。 12 | 13 | ## 例子 14 | 15 | https://github.com/xuya227939/blog/tree/master/examples/express/myapp 16 | 17 | ## 安装 18 | 19 | ``` 20 | $ sudo npm install express-generator -g //因为是在mac下安装的,所以要注意权限问题,使用sudo 21 | $ express myapp //通过express生成器,生成项目 22 | $ cd myapp 23 | $ npm i //安装相关依赖 24 | $ npm install compression //安装compression 压缩请求 25 | $ npm start //开启 26 | ``` 27 | 28 | 访问 [http://localhost:3000](http://localhost:3000) 29 | 30 | ## 使用 31 | 32 | 在 app.js 中使用如下代码 33 | 34 | ``` 35 | const compression = require('compression'); 36 | app.use(compression()); 37 | ``` 38 | 39 | ``` 40 | app.use('/', function(req, res) { 41 | const count = 24; 42 | // const count = req.body.count; 43 | let listData = []; 44 | for (let i = 0; i < count; i++) { 45 | listData.push({ 46 | src: '', 47 | avatar: 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png', 48 | title: `Title Jiang ${i}`, 49 | description:'Rise n’ shine and don’t forget to smile', 50 | star: i * 2, 51 | like: i * 3 52 | }); 53 | } 54 | let data = {}; 55 | data.listData = listData; 56 | data.count = count; 57 | res.send(JSON.stringify(data)); 58 | }); 59 | ``` 60 | 61 | 在 app.js 中替换 app.use('/users', indexRouter); 即可,然后 npm start 访问下[http://localhost:3000](http://localhost:3000) 就会看到输出了。 62 | 63 | ## 更新代码 64 | 65 | 通过 npm start 开启之后,你会发现你修改代码之后,刷新没有效果?这是因为 npm start 不支持动态更改代码,这时候就需要 supervisor 来管理 node 进程 66 | 67 | ``` 68 | $ npm install supervisor 69 | $ supervisor bin/www 70 | ``` 71 | 72 | 然后试试? 73 | 线上的话,通过 pm2 管理。 74 | `$yum install pm2 ` 75 | 在根目录下新建 start.json `$ vim start.json` 76 | 输入以下代码 77 | 78 | ``` 79 | { 80 | "apps" : [{ 81 | "name" : "app", 82 | "script" : "bin/www", 83 | "log_date_format" : "YYYY-MM-DD HH:mm:SS", 84 | "log_file" : "logs/success.log", 85 | "error_file" : "logs/error.log", 86 | "out_file" : "logs/out.log", 87 | "pid_file" : "logs/app.pid", 88 | "watch" : true 89 | }] 90 | } 91 | ``` 92 | 93 | `$ :wq` //保存并退出 94 | 95 | ## pm2 常用命令 96 | 97 | - `$ pm2 start start.json` //进行启动,帮你管理 node 进程。 98 | - `$ pm2 stop all` //停止所有应用。 99 | - `$ pm2 restart all` //重启所有应用。 100 | - `$ pm2 log` //查看应用日志。 101 | 102 | ## 欢迎在此 issue 下进行交流、学习 103 | 104 | ## 结语 105 | 106 | ~~https://github.com/xuya227939/m4a1~~ 可以参考这个项目。 107 | 通过 express 框架,建立后端服务速度还是蛮快的。简单方便,适合初学者入门。 108 | -------------------------------------------------------------------------------- /src/content/posts/React全家桶建站教程-React&Ant.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: React全家桶建站教程-React&Ant 3 | pubDate: 2018.06.08 4 | categories: ["React"] 5 | description: "" 6 | --- 7 | 8 | ## 介绍 9 | 10 | 这里使用到的 UI 库是蚂蚁金服开源的 ant-design,为啥使用?我觉得是使用人数比较多,坑比较少吧。 11 | 12 | ## 例子 13 | 14 | https://github.com/xuya227939/blog/tree/master/examples/react/my-app 15 | 16 | ## 安装 17 | 18 | ``` 19 | $ sudo npm install -g create-react-app //全局安装的话,需要权限,所以使用sudo 20 | $ create-react-app my-app 21 | $ cd my-app 22 | $ npm install antd 23 | $ npm start 24 | ``` 25 | 26 | ## 使用 27 | 28 | 1.引用官方代码,修改 App.js 文件,引入 ant 组件 29 | 30 | ``` 31 | import React, { Component } from 'react'; 32 | import Button from 'antd/lib/button'; 33 | import './App.css'; 34 | 35 | class App extends Component { 36 | render() { 37 | return ( 38 |
    39 | 40 |
    41 | ); 42 | } 43 | } 44 | 45 | export default App; 46 | ``` 47 | 48 | 2.引用官方代码,修改 App.css 49 | 50 | ``` 51 | @import '~antd/dist/antd.css'; 52 | .App { 53 | text-align: center; 54 | } 55 | 56 | .App-logo { 57 | animation: App-logo-spin infinite 20s linear; 58 | height: 80px; 59 | } 60 | 61 | .App-header { 62 | background-color: #222; 63 | height: 150px; 64 | padding: 20px; 65 | color: white; 66 | } 67 | 68 | .App-title { 69 | font-size: 1.5em; 70 | } 71 | 72 | .App-intro { 73 | font-size: large; 74 | } 75 | 76 | @keyframes App-logo-spin { 77 | from { transform: rotate(0deg); } 78 | to { transform: rotate(360deg); } 79 | } 80 | ``` 81 | 82 | 你就可以看到蓝色的按钮了。 83 | 84 | ## 问题处理 85 | 86 | 1.如果报类似这样的错,react-scripts command not found 那么就 $ rm -rf node_modules 模块,重新安装下 $ npm i,再重新 npm start 87 | 88 | ## 结语 89 | 90 | react 入门,首先从搭建 react 开始。 91 | -------------------------------------------------------------------------------- /src/content/posts/React全家桶建站教程-Redux&Saga.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: React全家桶建站教程-Redux&Saga 3 | pubDate: 2018-06-08 10:16:34 4 | categories: ["React"] 5 | description: "" 6 | --- 7 | 8 | ## Redux 介绍 9 | 10 | Redux 是 JavaScript 状态容器,提供可预测化的状态管理。Redux 除了和 React 一起用外,还支持其它界面库。 它体小精悍(只有 2kB,包括依赖)。 11 | 12 | ## 方法 13 | 14 | - action //通过 action 把数据传递给 reducer 15 | - reducer //纯函数式,负责把数据发送给 render 16 | - dispatch //触发器 17 | - store //数据源 18 | 19 | ## Redux 例子 20 | 21 | https://github.com/xuya227939/blog/tree/master/examples/react-redux/my-app 22 | 23 | ## Redux 安装 24 | 25 | `$ sudo npm install -g create-react-app` 26 | `$ create-react-app my-app` 27 | `$ cd my-app` 28 | `$ npm install --save redux` 29 | 30 | ## 使用 31 | 32 | 1.修改 App.js,引用官方代码。 33 | 34 | ``` 35 | import React from 'react'; 36 | import { createStore } from 'redux'; 37 | function counter(state = 0, action) { 38 | switch (action.type) { 39 | case 'INCREMENT': 40 | return state + 1 41 | case 'DECREMENT': 42 | return state - 1 43 | default: 44 | return state 45 | } 46 | } 47 | let store = createStore(counter) 48 | store.subscribe(() => 49 | console.log(store.getState()) 50 | ) 51 | store.dispatch({ type: 'INCREMENT' }) 52 | store.dispatch({ type: 'INCREMENT' }) 53 | store.dispatch({ type: 'DECREMENT' }) 54 | const BasicExample = () => ( 55 |
    123
    56 | ) 57 | export default BasicExample 58 | ``` 59 | 60 | 2.npm start 调出开发者工具,看 console.log 输出。 61 | 62 | ## Saga 介绍 63 | 64 | - 官方介绍:是一个旨在使应用程序副作用(即数据获取等异步事件和访问浏览器缓存等不纯的内容)更容易管理,更高效执行,易于测试并更好地处理故障的库。 65 | - 个人理解:类似于 thunk,就是解决异步 callBack 过多的问题。 66 | 67 | ## 方法 68 | 69 | - call //发送异步请求 70 | - takeEvery //每次发送 saga,都会更新数据 71 | - takeLatest //会取消上次的 saga,更新最后一个 saga 72 | - put //类似 dispatch,发送数据到 reducer 73 | 74 | ## Saga 例子 75 | 76 | 这里以计数器为例子 77 | https://github.com/xuya227939/blog/tree/master/examples/saga/my-app 78 | 79 | ## Saga 安装 80 | 81 | `$ sudo npm install -g create-react-app` 82 | `$ create-react-app my-app` 83 | `$ cd my-app` 84 | `$ npm install --save redux` 85 | `$ npm install --save redux-saga` 86 | 87 | ## 使用 88 | 89 | ``` 90 | export function* addFun() { 91 | yield put({ 92 | type: "ADD" 93 | }); 94 | } 95 | function* homeSaga() { 96 | yield takeEvery("ADD_SAGA", addFun); 97 | } 98 | ``` 99 | 100 | `function*` Generato 函数 101 | `homeSaga函数` 监听 action 102 | `addFun函数` 逻辑处理 103 | 104 | ``` 105 | yield put({ 106 | type: "ADD" 107 | }); 108 | ``` 109 | 110 | 类似 action 的 dispatch,发送数据到 reducer 111 | 112 | ## 问题处理 113 | 114 | 1.如果报类似这样的错,react-scripts command not found 那么就 $ rm -rf node_modules 模块,重新安装下 $ npm i,再重新 npm start 115 | 116 | ## 欢迎在此 issue 下进行交流、学习 117 | 118 | ## 结语 119 | 120 | - Redux:只能通过 dispatch 去改变,让这一切变得可以预测。这是我认为 redux 做得最好的地方,类似时间管理器的概念。任何时刻发生的事,都可以预测到结果。方便追踪 BUG 的产生。而不像原先 H5 一样,任何人都可以任意修改数据。从而是数据的发生变得很混乱。 121 | - saga:核心功能,解决了异步事件的回调地狱和浏览器刷新这些副作用。 122 | -------------------------------------------------------------------------------- /src/content/posts/React全家桶建站教程-Router.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: React全家桶建站教程-Router 3 | pubDate: 2018-06-08 10:16:04 4 | categories: ["React"] 5 | description: "" 6 | --- 7 | 8 | ## 介绍 9 | 10 | React Router 是一个基于 React 之上的强大路由库,它可以让你向应用中快速地添加视图和数据流,同时保持页面与 URL 间的同步。 11 | 12 | ## 例子 13 | 14 | https://github.com/xuya227939/blog/tree/master/examples/react-router/my-app 15 | 16 | ## 安装 17 | 18 | `$ sudo npm install -g create-react-app` 19 | `$ create-react-app my-app` 20 | `$ cd my-app` 21 | `$ npm install react-router-dom` 22 | 23 | ## 使用 24 | 25 | 1.引用的官方代码,在 App.js 插入以下代码 26 | 27 | ``` 28 | import React from 'react' 29 | import { 30 | BrowserRouter as Router, 31 | Route, 32 | Link 33 | } from 'react-router-dom' 34 | 35 | const Home = () => ( 36 |
    37 |

    Home

    38 |
    39 | ) 40 | 41 | const About = () => ( 42 |
    43 |

    About

    44 |
    45 | ) 46 | 47 | const Topic = ({ match }) => ( 48 |
    49 |

    {match.params.topicId}

    50 |
    51 | ) 52 | 53 | const Topics = ({ match }) => ( 54 |
    55 |

    Topics

    56 |
      57 |
    • 58 | 59 | Rendering with React 60 | 61 |
    • 62 |
    • 63 | 64 | Components 65 | 66 |
    • 67 |
    • 68 | 69 | Props v. State 70 | 71 |
    • 72 |
    73 | 74 | 75 | ( 76 |

    Please select a topic.

    77 | )}/> 78 |
    79 | ) 80 | 81 | const BasicExample = () => ( 82 | 83 |
    84 |
      85 |
    • Home
    • 86 |
    • About
    • 87 |
    • Topics
    • 88 |
    89 | 90 |
    91 | 92 | 93 | 94 | 95 |
    96 |
    97 | ) 98 | export default BasicExample 99 | ``` 100 | 101 | 2.npm start 102 | 103 | ## 标签 104 | 105 | - Link //类似 a 标签的跳转。 106 | - Router //与 Route 一样都是 react 组件,它的 history 对象是整个路由系统的核心,它暴露了很多属性和方法在路由系统中使用; 107 | - Route //path 属性表示路由组件所对应的路径,可以是绝对或相对路径,相对路径可继承; 108 | - 4.0 版本之后,history 通过父组件传递进来,this.props.history.push('/user'); //进行路由之间跳转 109 | 110 | ## 问题处理 111 | 112 | 1.如果报类似这样的错,react-scripts command not found 那么就 $ rm -rf node_modules 模块,重新安装下 $ npm i,再重新 npm start 113 | 114 | ## 欢迎在此 issue 下进行交流、学习 115 | 116 | ## 结语 117 | 118 | 使用 react-router 可以更方便的管理页面刷新、跳转。 119 | -------------------------------------------------------------------------------- /src/content/posts/SaaS架构图.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: SaaS架构图 3 | pubDate: 2021-03-30 11:10:43 4 | categories: ["SaaS"] 5 | description: "" 6 | --- 7 | 8 | ![通用系统架构图](https://user-images.githubusercontent.com/16217324/112928035-87fb5380-9148-11eb-8035-dbf7e9c311f5.png) 9 | -------------------------------------------------------------------------------- /src/content/posts/Taro init遇到权限问题(mac环境).md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Taro init遇到权限问题(mac环境) 3 | pubDate: 2020-10-26 14:21:27 4 | categories: ["React", "Taro"] 5 | description: "" 6 | --- 7 | 8 | ## 问题描述 9 | 10 | Taro init 遇到没有权限创建项目,具体报错如下: 11 | 12 | ``` 13 | (node:71338) UnhandledPromiseRejectionWarning: Error: EACCES: permission denied, mkdir '/usr/local/lib/node_modules/@tarojs/cli/templates/taro-temp' 14 | (node:71338) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1) 15 | (node:71338) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code. 16 | ``` 17 | 18 | ## 解决办法 19 | 20 | `$ sudo chown -R $(whoami) $(npm config get prefix)/{lib/node_modules,bin,share}` 21 | -------------------------------------------------------------------------------- /src/content/posts/Ubuntu 下安装 WeChat.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Ubuntu 下安装 WeChat 3 | pubDate: 2024-03-14 16:50:19 4 | categories: ["Ubuntu", "Wechat"] 5 | description: "" 6 | --- 7 | 8 | ## 背景 9 | 10 | 工作电脑上装了 Ubuntu 系统,如何安装微信成了问题。目前业界主流有两种方案,[参考 Ubuntu 安装微信的两种方法 11 | ](https://www.yydnas.cn/2023/08/2023.08.16-Ubuntu%E5%AE%89%E8%A3%85%E5%BE%AE%E4%BF%A1%E7%9A%84%E4%B8%A4%E7%A7%8D%E6%96%B9%E6%B3%95/index.html),我选择的是 Wine 安装方式,但 BUG 太多了(比如无法正常截图,表情无法正常显示,无法放入图片等等),无奈,看看 Linux 社区有没有微信发行版 12 | 13 | 最近 WeChat 新版本现已上架统信应用商店,UOS 用户可直接在统信应用商店搜索“微信(Universal)”下载体验!(支持 AMD64/ARM/Loongarch 三大主流架构) 14 | 15 | 本次微信(Universal)UOS 版是基于原生跨平台方案开展的一次大型版本重构与更新,以提高软件功能开发与迭代速度,旨在逐步实现微信 Windows/Mac/Linux 版本在功能与更新节奏保持一致。 16 | 17 | 什么? WeChat 发行了 Linux Beta 版?(第三方发布的) 18 | 19 | ## 安装 20 | 21 | 开始尝试 22 | 23 | [beta 安装地址](https://aur.archlinux.org/packages/wechat-beta-bwrap) 24 | 25 | ``` 26 | // 安装下载的软件包 27 | $ sudo dpkg -i bf103d215f344e4e8d0e17a7a37eebe8.deb 28 | ``` 29 | 30 | 安装过程中,出现错误,碰到了 `deepin-elf-verify >= 1.2.0`,`libssl >= 1.1.0`,两个软件版本要求版本号 31 | 32 | ``` 33 | (正在读取数据库 ... 系统当前共安装有 226651 个文件和目录。) 34 | 准备解压 e3546883de40401381ecd621a74d0cfe.deb ... 35 | 正在解压 com.tencent.weixin (2.1.5) 并覆盖 (2.1.5) ... 36 | dpkg: 依赖关系问题使得 com.tencent.weixin 的配置工作不能继续: 37 | com.tencent.weixin 依赖于 deepin-elf-verify (>= 1.1.1-1);然而: 38 | 软件包 deepin-elf-verify 尚未配置。 39 | 40 | dpkg: 处理软件包 com.tencent.weixin (--install)时出错: 41 | 依赖关系问题 - 仍未被配置 42 | 正在处理用于 mailcap (3.70+nmu1ubuntu1) 的触发器 ... 43 | 正在处理用于 gnome-menus (3.36.0-1ubuntu3) 的触发器 ... 44 | 正在处理用于 desktop-file-utils (0.26-1ubuntu3) 的触发器 ... 45 | 正在处理用于 hicolor-icon-theme (0.17-2) 的触发器 ... 46 | 正在处理用于 libc-bin (2.35-0ubuntu3.6) 的触发器 ... 47 | 在处理时有错误发生: 48 | com.tencent.weixin 49 | ``` 50 | 51 | 1. 解决 deepin-elf-verify 52 | 53 | [deepin-elf-verify.deb](https://mirrors.bfsu.edu.cn/deepin/pool/main/d/deepin-elf-verify/) 54 | 55 | 2. 解决 libssl 56 | 57 | [libssl1.1-udeb_1.1.1f-1ubuntu2_amd64.udeb](http://archive.ubuntu.com/ubuntu/pool/main/o/openssl/) 58 | 59 | 更新下两个软件的版本就行 60 | 61 | 安装完之后,就会有相应的图标 62 | 63 | ![](https://files.mdnice.com/user/27515/9a8f3add-28fc-4380-938b-7293758bd6ff.jpg) 64 | 65 | ## 使用 66 | 67 | 微信扫码之后,TM 反复登录,我屌!!!随后报错,有朋友能告诉我如何解决吗? 68 | 69 | ![](https://files.mdnice.com/user/27515/1a004c5d-0a67-4641-ae29-dc65f953abc6.jpg) 70 | 71 | ## 总结 72 | 73 | cnm 张一龙 74 | 75 | 文章同步更新平台:掘金、CSDN、知乎、思否、博客,公众号(野生程序猿江辰) 76 | 我的联系方式,v:Jiang9684,欢迎和我一起学习交流 77 | 78 | 完 79 | -------------------------------------------------------------------------------- /src/content/posts/VS Code 提高前端开发效率插件.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: VS Code 提高前端开发效率插件 3 | pubDate: 2019-11-15 09:45:16 4 | categories: ["React", "VS Code"] 5 | description: "" 6 | --- 7 | 8 | ## Auto Close Tag 9 | 10 | 自动添加 `HTML/XML` 关闭标记,与 `Visual Studio IDE` 或 `Sublime` 文本相同 11 | 12 | ![usage](https://user-images.githubusercontent.com/16217324/76220978-5076ec00-6253-11ea-8a0c-3a45fbf8cad0.gif) 13 | 14 | 键入开始标签的结束括号后,将自动插入结束标签。 15 | 16 | ## Auto Rename Tag 17 | 18 | 自动重命名配对的 `HTML/XML` 标记 19 | 20 | ![usage](https://user-gold-cdn.xitu.io/2020/3/9/170bfe5fe438ed72?w=1440&h=938&f=gif&s=158502) 21 | 22 | ## Beautify 23 | 24 | 为 `Visual Studio` 代码美化代码 25 | 26 | 选中需要美化的代码,右键 `Format Document` 27 | 28 | ## GitLens 29 | 30 | 增强 `Visual Studio` 代码中内置的 `Git` 功能-通过 `Git` 责怪注释和代码镜头一目了然地可视化代码作者,无缝导航和浏览 `Git` 存储库,通过强大的比较命令获得有价值的见解,等等 31 | 32 | ![7bf310ecae2e4fb92499bdcc3ea723e](https://user-gold-cdn.xitu.io/2020/3/9/170bfe50b60cb655?w=683&h=118&f=png&s=5530) 33 | 34 | ## JavaScript (ES6) code snippets 35 | 36 | `ES6` 语法中 `JavaScript` 的代码段 37 | 38 | ## Path Autocomplete 39 | 40 | 提供 `Visual Studio` 代码的路径完成。 41 | 42 | ![path-autocomplete](https://user-gold-cdn.xitu.io/2020/3/9/170bfe66dd619f57?w=817&h=439&f=gif&s=251155) 43 | 44 | ## Path Intellisense 45 | 46 | 自动完成文件名的 `Visual Studio` 代码插件 47 | 48 | ![iaHeUiDeTUZuo](https://user-gold-cdn.xitu.io/2020/3/9/170bfe66dd73b02d?w=480&h=270&f=gif&s=87934) 49 | 50 | ## React-Native/React/Redux snippets for es6/es7 51 | 52 | 在 `JS/TS` 中使用 `ES7` 语法对 `React`、`Redux` 和 `Graphql` 进行简单扩展 53 | 54 | ## StandardJS - JavaScript Standard Style 55 | 56 | 将 `JavaScript` 标准样式集成到 `Visual Studio` 代码中。 57 | 58 | 1. 安装 "JavaScript 标准样式" 扩展 59 | 60 | 如果您不知道如何在 `Visual Studio` 中安装扩展,请查看文档。 61 | 62 | 您将需要重新加载 `Visual Studio` 才能使用新的扩展。 63 | 64 | 2. 安装 standard 或 semistandard 65 | 66 | 这可以在全局或本地完成。我们建议您在本地安装它们(即保存在项目的中 `devDependencies`),以确保在开发项目时其他开发人员也已安装它们。 67 | 68 | 3. 禁用内置的 Visual Studio 验证器 69 | 70 | 为此,请 `"javascript.validate.enable": false` 在 `Visual Studio` 中进行设置 `settings.json` 71 | 72 | ## Vetur 73 | 74 | VS 代码的 Vue 工具 75 | 76 | ## vscode wxml 77 | 78 | 微信 `wxml` 支持 `/vscode` 片段 79 | 80 | ## vscode-fileheader 81 | 82 | 插入标题注释,并自动更新时间。 83 | 84 | ![fileheader](https://user-gold-cdn.xitu.io/2020/3/9/170bfe7f0061bd4a?w=921&h=510&f=gif&s=120076) 85 | 86 | 在 `“settings.json”` 中,设置并修改创建者的名称。 87 | 88 | ``` 89 | "fileheader.Author": "Jiang", 90 | "fileheader.LastModifiedBy": "Jiang", 91 | ``` 92 | 93 | 热键 94 | 95 | ``` 96 | ctrl+alt+i 97 | ``` 98 | 99 | ## vscode-icons 100 | 101 | `Visual Studio` 代码的图标 102 | 103 | ![image](https://user-gold-cdn.xitu.io/2020/3/9/170bfe50cd3f173d?w=1920&h=1041&f=png&s=180390) 104 | 105 | ## wxml 106 | 107 | 微信小程序 `wxml` 格式化以及高亮组件(高度自定义) 108 | 109 | ## ESLint 110 | 111 | 将 `ESLint JavaScript` 集成到 `Visual Studio` 代码中。 112 | 113 | 以下设置为包括 `ESLint` 在内的所有提供程序都启用了自动修复: 114 | 115 | ``` 116 | "editor.codeActionsOnSave": { 117 | "source.fixAll": true 118 | } 119 | ``` 120 | 121 | 相反,此配置仅在 `ESLint` 上将其打开: 122 | 123 | ``` 124 | "editor.codeActionsOnSave": { 125 | "source.fixAll.eslint": true 126 | } 127 | ``` 128 | 129 | 您还可以通过以下方式有选择地禁用 ESLint: 130 | 131 | ``` 132 | "editor.codeActionsOnSave": { 133 | "source.fixAll": true, 134 | "source.fixAll.eslint": false 135 | } 136 | ``` 137 | 138 | ## Import Cost 139 | 140 | 在编辑器中显示导入/要求包大小 141 | 142 | ![import-cost](https://user-images.githubusercontent.com/16217324/76226912-799a7b00-6259-11ea-986b-a5613e35312c.gif) 143 | 144 | ## Beautify css/sass/scss/less 145 | 146 | 美化 `CSS`、`Sass` 和更少的代码(`Visual Studio` 代码的扩展) 147 | 148 | 选中需要美化的代码,右键 `Format Document` 149 | 150 | ## TSLint 151 | 152 | 对 `Visual Studio` 代码的 `TSLint` 支持 153 | 154 | ## Settings Sync 155 | 156 | 使用 `GitHub Gist` 跨多台计算机同步设置、代码段、主题、文件图标、启动、键绑定、工作区和扩展名。 157 | 158 | ## CSS Peek 159 | 160 | 允许查看 `CSS ID` 和类字符串作为从 `HTML` 文件到相应 `CSS` 的定义。允许查看和转到定义。 161 | 162 | ![symbolProvider](https://user-images.githubusercontent.com/16217324/76229270-e105fa00-625c-11ea-8b48-6a48506b725c.gif) 163 | 164 | ## Stylelint 165 | 166 | 使用 `stylelint` 对 `lint CSS/SCSS/Less` 的 `Visual Studio` 代码扩展,进行格式校验。 167 | 168 | ## 博客 169 | 170 | 欢迎关注我的[博客](https://github.com/xuya227939/LiuJiang-Blog) 171 | -------------------------------------------------------------------------------- /src/content/posts/VS Code 用户自定义配置推荐.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: VS Code 用户自定义配置推荐 3 | pubDate: 2019-05-07 10:31:15 4 | categories: ["VS Code"] 5 | description: "" 6 | --- 7 | 8 | ## settings.json 9 | 10 | ``` 11 | { 12 | // 启用或禁用在 VS Code 中重命名或移动文件时自动更新 import 语句的路径 13 | "javascript.updateImportsOnFileMove.enabled": "always", 14 | // 禁止vscode的默认制表符 15 | "editor.detectIndentation": false, 16 | // 自动换行 17 | "editor.wordWrap": "on", 18 | // 字体大小 19 | "editor.fontSize": 16, 20 | // 选中糟糕的- 21 | "editor.wordSeparators": "./\\()\"':,.;<>~!@#$%^&*|+=[]{}`~?", 22 | // 启用后,将不会显示扩展程序建议的通知。 23 | "extensions.ignoreRecommendations": true, 24 | // 关闭默认的js验证,js中使用flow或ts语法会报错,需要关闭 25 | "javascript.validate.enable": false, 26 | "fileheader.Author": "Jiang", 27 | "fileheader.LastModifiedBy": "Jiang", 28 | "files.associations": { 29 | "*.wxml": "xml", 30 | "*.wxss": "css", 31 | "*.cjson": "jsonc", 32 | "*.wxs": "javascript" 33 | }, 34 | "eslint.trace.server": "messages", 35 | "eslint.validate": [ 36 | "javascript", 37 | "javascriptreact" 38 | ], 39 | "typescript.tsdk": "node_modules/typescript/lib", 40 | "emmet.includeLanguages": { 41 | "wxml": "html" 42 | }, 43 | "[json]": { 44 | "editor.defaultFormatter": "HookyQR.beautify" 45 | }, 46 | "[css]": { 47 | "editor.defaultFormatter": "HookyQR.beautify" 48 | }, 49 | "[html]": { 50 | "editor.defaultFormatter": "HookyQR.beautify" 51 | }, 52 | "[less]": { 53 | "editor.defaultFormatter": "michelemelluso.code-beautifier" 54 | }, 55 | "[javascript]": { 56 | "editor.defaultFormatter": "HookyQR.beautify" 57 | }, 58 | "workbench.colorTheme": "Monokai", 59 | "workbench.iconTheme": "vscode-icons", 60 | "[javascriptreact]": { 61 | "editor.defaultFormatter": "vscode.typescript-language-features" 62 | }, 63 | "[typescriptreact]": { 64 | "editor.defaultFormatter": "vscode.typescript-language-features" 65 | }, 66 | "[jsonc]": { 67 | "editor.defaultFormatter": "vscode.json-language-features" 68 | } 69 | } 70 | ``` 71 | -------------------------------------------------------------------------------- /src/content/posts/Webpack 如何配置代理.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Webpack 如何配置代理 3 | pubDate: 2021-06-10 09:22:49 4 | categories: ["Webpakc"] 5 | description: "" 6 | --- 7 | 8 | ## 前言 9 | 10 | `Webpack` 提供的 `devServer` 配置,使我们可以非常方便的设置请求代理目标,通过改配置,有时候可以帮我们解决本地环境的跨域问题。 11 | 12 | ## 正向代理 13 | 14 | 当拥有单独的 `API` 后端开发服务器并且希望在同一域上发送 `API` 请求时,代理某些 `URL` 可能会很有用。 15 | 16 | webpack.dev.js 17 | 18 | ``` 19 | devServer: { 20 | // ... 21 | proxy: { 22 | '/api2': { 23 | target: 'http://192.168.10.183:8103', 24 | changeOrigin: true 25 | } 26 | } 27 | }, 28 | ``` 29 | 30 | axios: 31 | 32 | ``` 33 | axios({ 34 | baseURL: '/api2/', 35 | url: '/user/login', 36 | method: 'GET' 37 | }) 38 | ``` 39 | 40 | 配置 `proxy`,本地环境中的请求,以 `/api` 开头的,都会把请求代理转发到 `target` 目标中,但是在浏览器中查看 `network`,发现请求依旧没有改变,实际上可以看到控制台打印或看后端 `log`,请求已经转发。 41 | 42 | ![image](https://user-images.githubusercontent.com/16217324/122594087-24265e00-d099-11eb-974c-b46412e4c127.png) 43 | 44 | `proxy`,仅针对本地环境有效,对线上环境无效,一般线上环境是通过 `Nginx` 做转发。 45 | 46 | ## 反向代理 47 | 48 | 当需要对域名进行校验,比如企业微信或微信公众号的一些可信域名配置,需要通过域名来访问,会非常有用。 49 | 50 | 编辑你的本地 hosts,是本地转发到指定域名,这里不要带端口号,如果有端口号,输入域名的时候,带上端口号。 51 | 52 | ``` 53 | 127.0.0.1 order.downfuture.com 54 | ``` 55 | 56 | ![B A CLATTE zsx-test ikandy cn8080commonIndex html#userlogin](https://user-images.githubusercontent.com/16217324/122594621-d3fbcb80-d099-11eb-9e61-08a44d01ea6b.png) 57 | 58 | 如果访问报这个错误,需要在 59 | 60 | ``` 61 | devServer: { 62 | // ... 63 | disableHostCheck: true 64 | }, 65 | ``` 66 | 67 | 配置完之后,本地启动开发服务,输入域名和端口号跳转页面,则可以看到修改了,受缓存影响,最好用无痕浏览器噢。 68 | -------------------------------------------------------------------------------- /src/content/posts/Webpack 如何配置热更新.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Webpack 如何配置热更新 3 | pubDate: 2020-05-20 18:23:46 4 | categories: ["Webpack"] 5 | description: "" 6 | --- 7 | 8 | ## 什么是 HMR 9 | 10 | 是指 `Hot Module Replacement`,缩写为 `HMR`。对于你需要更新的模块,进行一个"热"替换,所谓的热替换是指在不需要刷新页面的情况下,对某个改动进行无缝更新。如果你没有配置 `HMR`,那么你每次改动,都需要刷新页面,才能看到改动之后的结果,对于调试来说,非常麻烦,而且效率不高,最关键的是,你在界面上修改的数据,随着刷新页面会丢失,而如果有类似 `Webpack` 热更新的机制存在,那么,则是修改了代码,不会导致刷新,而是保留现有的数据状态,只将模块进行更新替换。也就是说,既保留了现有的数据状态,又能看到代码修改后的变化。 11 | 12 | 总结: 13 | 14 | - 加载页面时保存应用程序状态 15 | - 只更新改变的内容,节省调试时间 16 | - 修改样式更快,几乎等同于在浏览器中更改样式 17 | 18 | ## 安装依赖 19 | 20 | ``` 21 | $ npm install webpack webpack-dev-server --save-dev 22 | ``` 23 | 24 | `package.json`: 25 | 26 | ``` 27 | "dependencies": { 28 | "webpack": "^4.41.2", 29 | "webpack-dev-server": "^3.10.1" 30 | }, 31 | ``` 32 | 33 | ## 配置 34 | 35 | `webpack`: 36 | 37 | ``` 38 | devServer: { 39 | contentBase: path.resolve(__dirname, 'dist'), 40 | hot: true, 41 | historyApiFallback: true, 42 | compress: true 43 | }, 44 | ``` 45 | 46 | - `hot` 为 `true`,代表开启热更新 47 | 48 | - `contentBase` 表示告诉服务器从哪里提供内容。(也就是服务器启动的根目录,默认为当前执行目录,一般不需要设置) 49 | 50 | - `historyApiFallback` 使用 `HTML5` 历史记录 `API` 时,`index.html` 很可能必须提供该页面来代替任何 404 响应 51 | 52 | - `compress` 对所有服务启用 `gzip` 压缩 53 | 54 | ``` 55 | plugins: { 56 | HotModuleReplacementPlugin: new webpack.HotModuleReplacementPlugin() 57 | }, 58 | ``` 59 | 60 | 配置热更新插件 61 | 62 | ``` 63 | module: { 64 | rules: [ 65 | { 66 | test: /\.(css|less)$/, 67 | use: [ 68 | process.env.NODE_ENV == 'development' ? { loader: 'style-loader' } : MiniCssExtractPlugin.loader, 69 | { 70 | loader: 'css-loader', 71 | options: { 72 | importLoaders: 1 73 | } 74 | } 75 | ] 76 | } 77 | ] 78 | }, 79 | ``` 80 | 81 | `style-loader` 库实现了 `HMR` 接口,当通过 `HMR` 收到更新时,它将用新样式替换旧样式。区分开发环境和生产环境,用不同 `loader。` 82 | 83 | `src/index.jsx`: 84 | 85 | ``` 86 | if (module.hot) { 87 | module.hot.accept(); 88 | } 89 | ``` 90 | 91 | 入口文件,新增上面代码,就可以了,非常简单。 92 | 93 | ## react-hot-loader 94 | 95 | `react-hot-loader` 插件,[传送门](https://github.com/gaearon/react-hot-loader) 96 | 97 | ### 如何使用 98 | 99 | 安装 100 | 101 | ``` 102 | $ npm install react-hot-loader --save-dev 103 | ``` 104 | 105 | 配置 `babelrc` 106 | 107 | ``` 108 | { 109 | "plugins": ["react-hot-loader/babel"] 110 | } 111 | ``` 112 | 113 | 将根组件标记为热导出 114 | 115 | ``` 116 | import { hot } from 'react-hot-loader/root'; 117 | const App = () =>
    Hello World!
    ; 118 | export default hot(App); 119 | ``` 120 | 121 | 在 `React` 和 `React Dom` 之前,确保需要 `React` 热加载程序 122 | 123 | ``` 124 | // webpack.config.js 125 | module.exports = { 126 | entry: ['react-hot-loader/patch', './src'], 127 | // ... 128 | }; 129 | ``` 130 | 131 | ### 遇到问题 132 | 133 | - 如果遇到 `You cannot change ` ,那么应该这样配置: 134 | 135 | ``` 136 | import { hot } from 'react-hot-loader/root'; 137 | const Routes = () => {}; 138 | export default hot(Routes); 139 | ``` 140 | 141 | - 配置完热更新之后,遇到`webpack`自动编译两次问题,很大概率出现,具体原因,没有分析,找到一个讨巧的解决办法,配置: 142 | 143 | ``` 144 | watchOptions: { 145 | aggregateTimeout: 600 146 | }, 147 | ``` 148 | 149 | 也有可能是其他问题,比如你在`index.html`页面,重复引入了`index.js`,又或者是全局安装了`webpack-dev-server`,与本地`webpack-dev-server`重复,卸载全局`webpack-dev-server`,即可。 150 | 151 | ## 案例 152 | 153 | [Tristana](https://github.com/xuya227939/tristana) 154 | 155 | ## 博客 156 | 157 | 欢迎关注我的[博客](https://github.com/xuya227939/LiuJiang-Blog) 158 | -------------------------------------------------------------------------------- /src/content/posts/Weex如何实现dialog.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Weex如何实现dialog 3 | pubDate: 2019-03-09 08:41:04 4 | categories: ["Weex"] 5 | description: "" 6 | --- 7 | 8 | ``` 9 | 45 | 111 | 171 | ``` 172 | -------------------------------------------------------------------------------- /src/content/posts/Win CMD 常用命令.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Win CMD 常用命令 3 | pubDate: 2023-04-04 15:42:42 4 | categories: ["Win", "CMD"] 5 | description: "" 6 | --- 7 | 8 | ### 查看进程号 9 | 10 | 1. 输入端口查看 11 | 12 | 输入端口号,如 8080,查看对应得 pid 号 13 | 14 | ``` 15 | netstat -aon | findstr 8080 16 | ``` 17 | 18 | 2. 输入服务查看 19 | 20 | ``` 21 | tasklist | findstr nginx 22 | ``` 23 | 24 | ### 杀掉进程 25 | 26 | [/F] 强制删除 27 | 28 | ``` 29 | taskkill /F /pid 17416 30 | ``` 31 | 32 | ### Nginx 启动命令 33 | 34 | 找到 Nginx 安装目录 35 | 36 | ``` 37 | start .\nginx.exe 38 | ``` 39 | 40 | ### Nginx 停止命令 41 | 42 | 找到 Nginx 安装目录 43 | 44 | ``` 45 | .\nginx.exe -s stop 46 | ``` 47 | 48 | ### 文件夹无法删除 49 | 50 | ![image](https://user-images.githubusercontent.com/16217324/229761007-84dd0cce-6cef-4ec3-a213-fb78d14a51e2.png) 51 | 52 | 比如这种文件夹无法删除,提示需要管理员权限或要删除得文件夹不存在 53 | 54 | 通过新建记事本 55 | 56 | ``` 57 | DEL /F /A /Q \\?\%1 58 | RD /S /Q \\?\%1 59 | ``` 60 | 61 | 另存为 `del.bat` 可执行文件,通过把要删除得项目,拖拽到可执行文件中,即可删除 62 | -------------------------------------------------------------------------------- /src/content/posts/pm2 The "data" argument must be of type string or an instance of Buffer 问题排查.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: pm2 The "data" argument must be of type string or an instance of Buffer 问题排查 3 | pubDate: 2023-10-15 22:54:13 4 | categories: ["PM2"] 5 | description: "" 6 | --- 7 | 8 | ## 问题 9 | 10 | ``` 11 | pm2 The "data" argument must be of type string or an instance of Buffer, TypedArray, or DataView. Received type number 12 | ``` 13 | 14 | ## 解决 15 | 16 | 在本地运行 Node.js 发现并没有这个问题,后面随想可能是 PM2 启动的时候,路径查找不到 17 | 18 | 两个问题要解决: 19 | 1、PM2 版本过低,服务器上的 PM2 版本还是 2 20 | 通过 pm2 update 更新到了 5 21 | 22 | 2、每次上传大文件,Node.js 进程一定会挂 23 | 24 | ``` 25 | module.exports = { 26 | apps: [ 27 | { 28 | watch: false, 29 | } 30 | ] 31 | }; 32 | ``` 33 | 34 | watch 设置为 false,即可解决 35 | -------------------------------------------------------------------------------- /src/content/posts/win node-sass 安装失败.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: win node-sass 安装失败 3 | pubDate: 2023-03-24 15:34:18 4 | categories: ["node-sass"] 5 | description: "" 6 | --- 7 | 8 | > 事件起因是由于最近在看一块业务代码,用到了 Vue + 装饰器,业务代码很庞大,很复杂,出于原子化原则,先从简单的 Demo 看起,随即从 GitHub 上拉了一个模板代码下来看看,git clone, yarn ,yarn 安装依赖,报错,发现是 node-sass 安装失败,这玩意确实很难安装,主要是不兼容 Python3 ,记录下解决流程 9 | 10 | ### 报错提示 11 | 12 | 1. Can't find Python executable "python" 13 | 14 | 报找不到 python 命令,从官网下载 python, [Python2.7](https://www.python.org/download/releases/2.7/),注意,这里要看下你的 node-sass 版本是多少的,对应 python2.7 还是 python3.x,找到自己的电脑所属版本,比如 win64, win32, macOS 等 15 | 16 | 2. gyp ERR! stack Error: `C:\Windows\Microsoft.NET\Framework\v4.0.30319\msbuild.exe` 17 | 18 | 第二步蛮复杂的,Win 上缺少相关的编译环境,先运行 `npm install -g node-gyp`,然后运行 `npm install --global --production windows-build-tools` 可以自动安装跨平台的编译器,注:第二句执行下载好 msi 文件卡着不懂不安装 , 手动去对应的目录底下安装一下 在执行一边。 19 | 20 | 3. Node Sass version 8.0.0 is incompatible with ^4.0.0 版本不兼容 21 | 22 | 安装的依赖版本不对,安装了 node-sass 高版本,重新手动安装下低版本 23 | 24 | ### 相关链接 25 | 26 | 1. https://blog.csdn.net/zeroheitao/article/details/112545324 27 | 2. https://proustibat.medium.com/how-to-fix-error-node-sass-does-not-yet-support-your-current-environment-os-x-64-bit-3.with-c1b3298e4af0 28 | 3. https://stackoverflow.com/questions/37415134/error-node-sass-does-not-yet-support-your-current-environment-windows-64-bit-w 29 | 4. https://www.zhaojun.ink/archives/node-sass-install 30 | 5. https://www.python.org/download/releases/2.7/ 31 | -------------------------------------------------------------------------------- /src/content/posts/【从前端入门到全栈】全栈是什么- 系列必读.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 【从前端入门到全栈】全栈是什么?- 系列必读 3 | pubDate: 2023-05-14 19:53:04 4 | categories: ["从前端入门到全栈"] 5 | description: "" 6 | --- 7 | 8 | 你好,我是刘江,我在互联网上的昵称是江辰,很高兴和你在这个课程见面 9 | 10 | 先介绍下我自己,最早我是在爱用科技公司工作,负责的是爱用商品业务线,在这家公司待了两年左右的时间,从一个完全不懂技术的应届生到全栈开发工程师,这段经历为我的全栈技能打下了一个很好的基础。 11 | 12 | 后来,我辗转进入多家公司任职,负责过很多产品的开发,比如有 ToB 视频产品、CRM 订单系统、直播电商等等。有从 0 到 1 ,也有半途进入。完整开发过微信小程序、H5、Web,也有自己偶尔捣鼓 APP,过往的经历,使我能自己一个人独挑大梁。这么多领域的工作让我快速成长,并积累了大量的宝贵经验。 13 | 14 | 入行这么多年,从后端到前端,从设计开发到线上运维,我都参与其中,带领过团队攻克很多壁垒。以全栈开发工程师的角色参与了不少的核心业务产品,也能让我在过需求评审的时候,站在前端的角度去思考后端设计,或是从后端的角度思考前端设计,再或是跟产品,设计同学极限拉扯 15 | 16 | 那提到全栈工程师,你可能第一时间会想到 "全能" "无所不能"等一些词语,也可能听到过其他人对它或褒或贬的评价,那么,我们应该怎么充分的认知这个角色呢?我想和你聊聊我的理解 17 | 18 | 我们来看看 chatGPT(3.5) 理解的全栈开发: 19 | 20 | > 全栈开发(Full-Stack Development)是指开发人员能够熟练掌握多个领域的技能,可以开发完整的应用程序,包括前端、后端和数据库等多个层面。全栈开发人员需要掌握多种编程语言、框架和技术,并能够快速适应不同的开发环境和工具。 21 | 22 | > 全栈开发可以实现更高效和协调的工作流程,可以更快地创建和部署应用程序,并且可以更好地理解整个应用程序的架构和流程。全栈开发人员不仅需要深入了解前端技术,如 HTML、CSS、JavaScript 及其框架,还需要熟悉后端技术,如数据库、服务器端脚本语言(如 PHP、Python、Java 等)及其框架。 23 | 24 | > 总的来说,全栈开发人员需要掌握多个领域的技能,能够独立完成整个应用程序的开发和部署,从而提高工作效率和技术竞争力。 25 | 26 | 提取几个关键字眼,**掌握多个领域的技能**、**可以开发完整的应用程序**、**能适应不同的开发环境和工具** 27 | 28 | 从我的角度上来理解这段话,本质就是一个工程师不局限于某个领域的技能,从而扩展多个领域,点缀多棵技能树;使自己的路走的更加广阔 29 | 30 | 那职业发展路线上来说,为什么我推荐你应该成为全栈开发工程师呢? 31 | 32 | ### 提高自己的综合能力 33 | 34 | 全栈开发工程师需要具备前端、后端、数据库、网络、安全等方面的知识和技能,能够更全面地理解软件开发的全过程,包括需求分析、系统设计、编码实现、测试和部署等环节,通过多项能力的学习过程,不断的总结和复盘知识点,能让你出现一门新的技术,快速学习,掌握核心知识点 35 | 36 | 以我为例,最开始接触 Java(学校),PHP(工作后),发现这些语言无非是些基础知识、能做什么事、怎么做的,我经常会拿这样类似的语言模板去套,方便让我快速入门,经过这些整理之后,不管是后端语言,还是前端语言,对我而言都是实现业务的手段工具 37 | 38 | ### 高效的团队协作 39 | 40 | 全栈开发工程师可以更好地协调前后端的开发工作,使得前后端的开发能够更加高效地协作,从而提高项目的效率。 41 | 42 | 举例来说,当你是前端角色,如果你拥有后端知识。恰巧跟你经常合作的后端同学突然遇到个问题,卡住了。又恰好你懂,可以帮助他,对吧,关系就这么起来了。作为后端角色来说,依然如此。 43 | 44 | 我个人经常会遇到后端、运维问我一些问题,比如问这个接口报错了,报错信息,以常见的为例:入库操作错误、没有权限操作服务器上的文件、Nginx/Docker 服务挂了,这都是很常见的问题,拥有全面的知识点,能让你在团队中建立威信 45 | 46 | ### 就业和创业 47 | 48 | 由上面可知,全栈开发工程师本身拥有多技能属性,团队会非常欢迎这样的人。往往可以站在自己的角度来思考问题,又可以站在他人的角度来思考问题,这无疑提高了团队的沟通和配合,这使得他们的职业机会更加广泛,可以在不同领域的公司中找到合适的工作。 49 | 50 | 创业,全栈开发工程师是创业的最佳技术角色,有了产品原型和基础设计,可以迅速实现第一个版本 51 | 52 | 总之,全栈开发工程师的优势在于综合能力、团队协作、职业机会和发展前景等方面。 53 | 54 | [根据领英 2022 年的就业报告,“全栈开发者”也在热门新兴工作名单上。至于未来前景,美国劳工统计局表示,网络开发人员的就业市场将增长 13%(至少到 2030 年),这比平均水平要快。](https://www.linkedin.com/pulse/full-stack-development-2022-key-trends-most-in-demand-at-careervira) 55 | 56 | 最后,我个人认为对于很多程序员来说,全栈开发工程师这个职位对于自己的发展的而言,是个非常好的进阶方向。 57 | -------------------------------------------------------------------------------- /src/content/posts/【从前端入门到全栈】登录 Token 鉴权设计.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 【从前端入门到全栈】登录 Token 鉴权设计 3 | pubDate: 2023-09-29 23:13:13 4 | categories: ["从前端入门到全栈"] 5 | description: "" 6 | --- 7 | -------------------------------------------------------------------------------- /src/content/posts/【从前端入门到全栈】系列介绍.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 【从前端入门到全栈】系列介绍 3 | pubDate: 2023-05-14 18:17:36 4 | categories: ["从前端入门到全栈"] 5 | description: "" 6 | --- 7 | 8 | 1. 你会学到什么? 9 | 10 | 可能学不到什么东西,该系列是作者本人工作和学习积累,用于复习 11 | 12 | 2. 作者介绍 13 | 14 | 江辰,网易高级前端工程师 15 | 16 | 3. 系列介绍 17 | 18 | 现在的 Web 前端已经离不开 Node.js,我们广泛使用的 Babel、Webpack、工程化都是基于 Node 的,各个互联网大厂也早已大规模落地 Node 项目。因此,想要成为一名优秀的前端工程师,提升个人能力、进入大厂,掌握 Node.js 技术非常有必要。 19 | 20 | Node.js 不仅可以用来完善手头的开发环境,实现减少代码和 HTTP 请求,降低网页请求消耗的时间,提升服务质量。还可以扩展前端工程师的工作领域,用作 HTTP 服务,让前端也能完成一部分后端的工作,减少对后端的依赖,降低沟通成本,提升开发效率。 21 | 22 | 而且,Node.js 和浏览器的 JavaScript 只是运行时环境不同,编程语言都是 JavaScript ,所以掌握 Node.js 基础对前端工程师来说并不难,难点在于应用。由于浏览器的 JavaScript 主要是负责内容呈现与交互,而 Node.js 应用领域包括工具开发、Web 服务开发和客户端开发,这些都与传统的 Web 前端领域不一样,用来应对不同的问题。 23 | 24 | 4. 适宜人群 25 | 26 | - 对 Node.js 感兴趣的 JavaScript 程序员 27 | - 希望拓展知识边界,往全栈方向发展的前端工程师 28 | -------------------------------------------------------------------------------- /src/content/posts/【从前端入门到全栈】网站 SEO 提升.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 【从前端入门到全栈】网站 SEO 提升 3 | pubDate: 2023-10-20 18:36:57 4 | categories: ["从前端入门到全栈"] 5 | description: "" 6 | --- 7 | -------------------------------------------------------------------------------- /src/content/posts/【从前端入门到全栈】网站版本更新通知.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 【从前端入门到全栈】网站版本更新通知 3 | pubDate: 2023-09-29 23:12:45 4 | categories: ["从前端入门到全栈"] 5 | description: "" 6 | --- 7 | -------------------------------------------------------------------------------- /src/content/posts/串串房维权.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 串串房维权 3 | pubDate: 2024-07-27 09:41:34 4 | categories: ["串串房", "12345", "甲醛房"] 5 | description: "" 6 | --- 7 | 8 | ![](https://files.mdnice.com/user/27515/6dd73bae-13c2-4452-9d22-ccdd9489a1f9.png) 9 | 10 | > 五月底租了一个串串房,其中经历了漫长的维权过程。想了想,发出来,给大家分享下。 11 | 12 | ## 事件起因 13 | 14 | 五月底,本人通过某房地产中介公司,租了滨浦新村五苑的房子,中介带看房的当天,觉得还可以。没有啥刺鼻的味道,装修也都是新的(画重点,此处伏笔)。第二天和房东约好时间,付了 7200 租金 + 1800 的中介费,定好房子。房租 3600/月。 15 | 16 | ## 事情经过 17 | 18 | 六月第一周,周末搬家,早上搬完的,搬完家的当天下午,在房间里待了一会儿,感觉味道非常刺鼻!本人和表哥都有预感,该不会是串串房吧!仔细闻了下,是家具引起的刺鼻气味(二手家具,质量堪忧),注意:甲醛无色无味哦!后面,再次确认中介这房装修了多久,中介说装修了蛮久,就是不知道具体时间。然后问了房东,房东说刚装修两个多月!刚装修两个多月就出租,坑人阿! 19 | 20 | ## 维权 21 | 22 | 房东,先是跟房东协商,反复沟通。甚至,我都打算买甲醛测量了(注意,先跟房东沟通哦,以免不必要的资金损耗),跟房东讲述其中利害关系,房东最终同意,退还租金 23 | 24 | 中介,再就是找到房地产中介协商,第一次和第二次我都是去线下协商,协商不通过,不可能退还中介费。我就拨打 12345 维权,12345 效率极高(此处点个赞),房地产那边也是同意退中介费了(电话沟通),直接跟中介公司店长沟通,中介公司店长一直拖着,不肯给,多次电话沟通,答应退还,并给了相关退还时间。然后,一二再,再二三的拖着,实在忍无可忍,我就发出警告!发出警告没多久,加了我微信,把中介费推给我了! 25 | 26 | 最终是在七月第三周,完成了整个维权过程。 27 | 28 | 注意: 29 | 30 | 1. 维权过程中,不要害怕,要敢于维权哦 31 | 2. 维权过程中,时刻录音(保留好证据 32 | 3. 维权过程中,一定要理性,冷静沟通,跟对方阐述其中利害关系 33 | 4. 多次沟通无效,申请 12345 热线的帮助,只要是合理诉求,都是会受理的哦 34 | 35 | ![房东](https://files.mdnice.com/user/27515/0d425553-ef1a-4b4c-b466-67f981b4820f.jpg) 36 | 37 | ![中介公司](https://files.mdnice.com/user/27515/a54bc318-011f-401b-ba01-066d4d49214f.jpg) 38 | 39 | ![中介公司](https://files.mdnice.com/user/27515/35727bd6-39d2-4dd8-aff7-76778c27ca40.jpg) 40 | 41 | ![中介公司](https://files.mdnice.com/user/27515/0c2b8df4-faff-4877-b071-e7ebd58e6557.jpg) 42 | 43 | ![](https://files.mdnice.com/user/27515/f8ace59b-eed3-4f1f-b893-87bdc0d54197.jpg) 44 | 45 | ![](https://files.mdnice.com/user/27515/188f1b49-0563-4adc-a5c9-11565f0e410a.jpg) 46 | 47 | ![](https://files.mdnice.com/user/27515/3f401d1f-a2d5-4cea-9fcc-3b319c2502d2.jpg) 48 | 49 | ![](https://files.mdnice.com/user/27515/45032487-465b-4bc5-86d4-bf7a8247ac38.jpg) 50 | 51 | ![](https://files.mdnice.com/user/27515/6e51c80a-0939-4c9c-940f-c59af8f056f6.jpg) 52 | -------------------------------------------------------------------------------- /src/content/posts/人生思考.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 人生思考 3 | pubDate: 2023-04-02 21:18:55 4 | categories: ["思考"] 5 | description: "" 6 | --- 7 | 8 | ## 传送门 9 | 10 | ### 前言 11 | 12 | > 时光流逝,没想到已经一年多了,很久很久未在技术社区更新过文章,惭愧。也终于有勇气写个流水账了。在过去的一年中,也在不断的思考,自己将来(35 岁后)能做什么?乃至于我在失业期间,也想过是否要留学?考研?(毕竟还年轻,本人 97 年)甚至是说专门待业 1-2 年,学习英语 run 到外企,再通过外企 remove 国外?一直认为国内 35 岁以后,还在一线开发,不现实。理由如下: 13 | 14 | 1. 精力比不上年轻人 15 | 2. 刚从学校出来的哪种激情也不在 16 | 3. 身体也越来越不行 17 | 18 | 国内这个焦虑确实蛮严重的,起码对我本人来说,一直存在。受限于学历的影响, 想做管理,也很难,爬又爬不上去。 19 | 20 | 下面就简单聊聊过去一年发生了什么 21 | 22 | ### 失业 23 | 24 | 我本人从未想过会被裁员,现在我也这么认为(当时的我不会被裁员),直到现实给我重拳出击。去年,B 站大概有三波裁员潮,分别是年初,年中,年末。年中的时候,我组内有一位非常优秀的同学被裁了。哪会儿其实蛮慌的,生怕是自己。而且是在上海疫情期间。会经历裁员 + 疫情双重 Buffer 的叠加,很容易让人崩溃。 25 | 26 | 在上海疫情期间,我人已经在崩溃的边缘,我去过方舱,四月份阳的,在方舱待了一周左右的时间,真实体会过,什么叫人间疾苦。也压根不敢跟同事,家人,网上说,怕被歧视,哪会儿在小区的时候,就被居委会的人给歧视了。 27 | 28 | 年末,通知我被裁员。裁员的前几天,我领导跟我们组内的人都聊了聊,看谁能主动承担这个名额,领个大礼包。到我的时候,我当然是不愿意的,我有车贷,去年 11 月底提的车,所以很希望有一份稳定的工作,能够维持基本的生计。但是吧,裁员这事,一般是综合维度来考量,我复盘了一些衡量的点: 29 | 30 | 1. 刚入职(一年内 31 | 2. 跟领导的关系 32 | 3. 性价比 33 | 4. 可替代性(工作内容 34 | 35 | 大概以上这些,具体原因,直到现在我也不清楚,当时也想了解清楚具体原因,但都不说,也让我不要再问,ok,哪我也就不想再知道了 36 | 37 | 通知我被裁员的时候,我跟前端老板聊了蛮多的,当时我还跟天哥(敖天羽)聊了下(她内推我进来的)看是否能活水,转到她部门下,就不让活水,后面心态就放平了,哪就这样吧。技术总监过来找我的时候,言下之意,你肯定要走的,早走,早超生吧,大概这意思,对大家都好。其实他不找我,我也要走的,没有拖的意义,而且我个人觉得没必要浪费时间,再到后面 HR 过来找我的时候,我一轮谈判就同意了,不想跟他们再耗下去。 38 | 39 | ## 面试 40 | 41 | 在去年 12 月中旬被裁员之后,整个人就很 emo 的状态,做什么事都没有干劲,整天就是把自己锁在屋里,刷刷剧,打打游戏,饿了就叫外卖,简单的途径获取多巴胺和内啡肽,满足一天的基本需求。 42 | 43 | 现在回想起来,要是当时自驾从上海到新疆-西藏,整个人会舒服很多,又或者是专门开一个月的滴滴,体会下提前下岗再就业,这是我在最有闲的时间,没有去做的一些事,有点遗憾。希望后面可以补上,哈哈哈哈 44 | 45 | 12 月下旬简单投了投简历,比如投了长沙腾讯云,想回老家看看机会,也收到了面试邀约,但由于本人心理没准备好,就拒绝了面试,想等到年后再面,年后再看就没机会了 46 | 47 | 躺平了 12 月份,1 月份,过完年后,差不多到 2 月份了,再看机会,再投简历,很不幸,各大社区传来前端已死 48 | 49 | #### 脉脉 50 | 51 | ![image](https://user-images.githubusercontent.com/16217324/229353100-f3fed57d-25a9-49d3-8db6-185c131678fb.png) 52 | 53 | #### BOSS 直聘 54 | 55 | ![image](https://user-images.githubusercontent.com/16217324/229352982-b18540d9-3857-4c61-aa54-8e9787602584.png) 56 | 57 | #### 拉勾 58 | 59 | ![image](https://user-images.githubusercontent.com/16217324/229353058-4f426c10-5d0c-45f7-993f-6acc32c6d8c2.png) 60 | 61 | ![image](https://user-images.githubusercontent.com/16217324/229353160-3850d4f5-2360-40c7-9ec0-7643ae2c53ef.png) 62 | 63 | 很不幸,前端目前就是这么个现状,招聘软件上,基本都是已读不回,打招呼,没反应 64 | 65 | 我是这么看待前端已死的。先说结论,我本人是反对前端已死,理由大概如下: 66 | 67 | #### 技术出海 68 | 69 | 最近几年,大厂的科技产品都在出海,寻石问路。抖音,拼多多,都做的不错,不再局限国内的人口红利,谋求更多的发展空间。同理,我们做技术的是否也可以寻找技术出海?我的答案是肯定的。国外项目平台有很多,比如 Upwork,在这个平台上,我们可以看到各个国家的机会,不会英语也没关系,借助翻译软件,简历包装等,寻求更多合作的机会,不要把自己局限住! 70 | 71 | #### 核心竞争力 72 | 73 | 作为一名技术人,关键是创造自己的核心竞争力,什么叫核心竞争力,也就是你跟别人与众不同的地方,别人能做的,你能做。别人不能做的,你也能做,哪自然择优而优,这对所有公司来说都是这样,本质上就是花最小的代价,选择更合适的的人。举个简单的例子,我们以上面这份大厂 JD(Job Describe)为例,基础部分就不讲了,关注几个指标:学历、工作经验、C 端业务经验,加分项。通过这几个条件,可以筛选出一批人,如果你对加分项更加深入,哪你的优势很大 74 | 75 | #### 人口红利 76 | 77 | 我们国内有个特点,哪个行业挣钱,哪个行业火,一堆人会进来(培训机构到处宣传),人口优势固然存在,这是一大优势,也是一大弱势。优势就不讲了。弱势就是会导致这个行业出奇的内卷。以互联网行业为例,早几年会个 ajax ,写个静态页面,专科以上学历,一堆人要。现在呢,你得懂源码、你得统招全日制学历以上、你得会多个技能,这都是基本的要求,哪如何找到差一点,比如熟练英语的读写,你又比一些人要优秀,机会更多,可以关注外企机会 78 | 79 | #### 技术发展趋势 80 | 81 | 作为一名技术人,一定要关注前沿技术的发展趋势,为什么要关注?简单来说,这关乎你的生存空间。近几年 web3、区块链、元宇宙发展很快,如果你跟不上时代,很快就会被淘汰,很简单的道理。比如你看各大招聘软件上,各种 web3 的岗位,工资很高以及要求熟练英语读写 82 | 83 | 再说回来,2 月份,3 月份,基本盘,就没啥面试机会和合适的岗位。靠朋友内推,面试了几家,技术面到 HR 面聊完,就没啥消息。面试纯看缘分 84 | 85 | 2 月底开始面的网易,网易是三轮技术面 + HR 面,整个流程持续了大概 20 多天,在三月份的时候入职了,属实场景复刻。去年也差不多这个时间点面的网易严选,同样是三轮技术面 + HR 面 86 | 87 | ### 未来 88 | 89 | 1. 博客改造升级 90 | 2. 组件库重构 91 | 3. 脚手架升级 92 | 93 | 持续关注前沿技术 94 | 95 | 也很庆幸自己是在 30 岁前经历了裁员,也让我更加坚定,互勉 96 | 97 | ### 总结 98 | 99 | 我其实是个非常不喜欢面试的人,面试这东西,个人觉得很看眼缘,我是一个很容易紧张的人,如果面试官不能给我有效的引导,我其实内心蛮慌的,这次能够有幸进入网易,真的很感谢老板,面试官 + 运气不错 100 | 101 | 文章更新平台:掘金、CSDN、知乎、思否、[博客](https://github.com/xuya227939/blog) 102 | 103 | 我的联系方式,v:Jiang9684,欢迎入群交流,和我沟通 104 | 105 | 完 106 | -------------------------------------------------------------------------------- /src/content/posts/企业微信自建应用.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 企业微信自建应用 3 | pubDate: 2021-07-18 14:58:23 4 | categories: ["企业微信"] 5 | description: "" 6 | --- 7 | 8 | ## 企业微信自建应用 9 | 10 | ### 创建应用 11 | 12 | ![WechatIMG2483](https://user-images.githubusercontent.com/16217324/126058529-a8840285-13a4-4c87-9b59-4ac035cd7960.png) 13 | 14 | 进入企业微信,打开【应用管理】,在【自建】下选择【创建应用】 15 | 16 | ### 完善应用信息 17 | 18 | ![WechatIMG2481](https://user-images.githubusercontent.com/16217324/126058532-ea351695-2d1f-4ff9-be57-3d25dda15552.png) 19 | 20 | 在【可见范围】内【选择部门/成员】,建议选择全公司,后续可修改 21 | 22 | ### 获取密钥 23 | 24 | ![WechatIMG2482](https://user-images.githubusercontent.com/16217324/126058537-6f65c9f6-784f-4116-9697-ae53dd0aa2a4.png) 25 | 26 | ### 配置应用属性 27 | 28 | ![WechatIMG2484](https://user-images.githubusercontent.com/16217324/126058539-b2f535fa-978d-4a8f-8a6b-e82c03b35435.png) 29 | 30 | ![WechatIMG2485](https://user-images.githubusercontent.com/16217324/126058541-462406c4-9769-408d-9b98-8c570912e5e9.png) 31 | 32 | 把可信任的域名配置下,申请校验文件,放入到域名的根目录,保证 Http 请求能访问该文件即可 33 | 34 | ### 配置聊天工具侧边栏 35 | 36 | ![WechatIMG2486](https://user-images.githubusercontent.com/16217324/126058543-d00d2919-a1f6-4e54-b3c4-df4a9a395da0.png) 37 | 38 | ![WechatIMG2488](https://user-images.githubusercontent.com/16217324/126058544-ecb814ea-e988-4a80-b942-d676ab6dc89d.png) 39 | 40 | 1. 填写页面名称 41 | 42 | 2. 填写页面内容,选择自定义,链接后面需要带上参数,corp_id 和 app_id 43 | 44 | ## 授权流程 45 | 46 | ![WechatIMG2489](https://user-images.githubusercontent.com/16217324/126058546-e0d06829-3446-490b-b590-ba6a184a038b.jpeg) 47 | 48 | 1. 构造网页授权链接 49 | 50 | 2. 获取访问用户身份 51 | 52 | ## FAQ 53 | 54 | 1. 如何让 localhost 设置为可信域名? 55 | 56 | 当需要对域名进行校验,比如企业微信或微信公众号的一些可信域名配置,需要通过域名来访问,会非常有用。 57 | 58 | 编辑你的本地 hosts,是本地转发到指定域名,这里不要带端口号,如果有端口号,输入域名的时候,带上端口号。 59 | 60 | ``` 61 | 127.0.0.1 order.downfuture.com 62 | ``` 63 | 64 | WechatIMG2490 65 | 66 | 如果访问报这个错误,需要在 67 | 68 | ``` 69 | devServer: { 70 | // ... 71 | disableHostCheck: true 72 | } 73 | ``` 74 | 75 | 配置完之后,本地启动开发服务,输入域名和端口号跳转页面,则可以看到修改了,受缓存影响,最好用无痕浏览器噢。 76 | 77 | 2. Windows 企业微信浏览器内核版本过低,如何解决? 78 | 79 | 由于 Windows 企业微信浏览器内核版本在 53,导致 `async` 使用不了,配置 `Babel`,支持到 53 即可。 80 | 81 | ``` 82 | { 83 | "presets": [ 84 | [ 85 | "@babel/preset-env", 86 | { 87 | "targets": { 88 | "edge": "17", 89 | "firefox": "60", 90 | "chrome": "53", 91 | "safari": "11.1" 92 | }, 93 | "useBuiltIns": "usage", 94 | "corejs": 3 95 | } 96 | ], 97 | ["@babel/preset-react"] 98 | ] 99 | } 100 | 101 | ``` 102 | -------------------------------------------------------------------------------- /src/content/posts/使用 Sourcetree 提示需要输入密码.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 使用 Sourcetree 提示需要输入密码 3 | pubDate: 2019-06-10 11:30:47 4 | categories: ["React"] 5 | description: "" 6 | --- 7 | 8 | ![image](https://user-images.githubusercontent.com/16217324/59170575-e3261580-8b71-11e9-8610-0c9679a1ef54.png) 9 | 使用公司`GitLab`提交的时候,每次都需要输入密码 10 | 11 | ## 解决方案(mac) 12 | 13 | ### 命令行解决 14 | 15 | 输入以下命令 16 | `git config --global credential.helper osxkeychain` 17 | 执行完成后,再次在 SourceTree 里面输入一下 GitLab 里面的密码。注意勾选选项“store password in keychain”。 18 | 这个时候,会跳出钥匙串的对话框,这个时候要输入的密码,是 mac 的开机密码。并且一定要勾选始终允许。否则,还是要一直跳出现在的这个登陆窗口了 19 | 20 | ![image](https://user-images.githubusercontent.com/16217324/59170661-41eb8f00-8b72-11e9-89bf-d9db9f437f9b.png) 21 | 22 | ### 还有一种就是 Keychain 中产生了冲突,使勾选失效 23 | 24 | 将已失效的 git.a. Access Key for git 删除,再次操作输入密码后新的密码就会存储在 Keychain,以后就不用每次远程操作都手动输入密码了 25 | 26 | ![image](https://user-images.githubusercontent.com/16217324/59170836-f2f22980-8b72-11e9-9765-0cfbf1631e3b.png) 27 | -------------------------------------------------------------------------------- /src/content/posts/使用 react.lazy 打包之后得文件如何不显示数字.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 使用 react.lazy 打包之后得文件如何不显示数字 3 | pubDate: 2019-06-28 13:17:10 4 | categories: ["React"] 5 | description: "" 6 | --- 7 | 8 | ## 解决方法 9 | 10 | ``` 11 | /* webpackChunkName: "name"*/' '文件路径' 12 | const Chart = lazy(() => import(/* webpackChunkName: "chart"*/'./pages/Chart/index')); 13 | ``` 14 | -------------------------------------------------------------------------------- /src/content/posts/修改滚动条样式.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 修改滚动条样式 3 | pubDate: 2018-09-20 20:02:42 4 | categories: ["修改滚动条样式"] 5 | description: "" 6 | --- 7 | 8 | ## 滚动条组成 9 | 10 | ::-webkit-scrollbar 滚动条整体部分 11 | ::-webkit-scrollbar-thumb 滚动条里面的小方块,能向上向下移动(或往左往右移动,取决于是垂直滚动条还是水平滚动条) 12 | ::-webkit-scrollbar-track 滚动条的轨道(里面装有 Thumb) 13 | ::-webkit-scrollbar-button 滚动条的轨道的两端按钮,允许通过点击微调小方块的位置。 14 | ::-webkit-scrollbar-track-piece 内层轨道,滚动条中间部分(除去) 15 | ::-webkit-scrollbar-corner 边角,即两个滚动条的交汇处 16 | ::-webkit-resizer 两个滚动条的交汇处上用于通过拖动调整元素大小的小控件 17 | 18 | ``` 19 | /*定义滚动条高宽及背景 高宽分别对应横竖滚动条的尺寸*/ 20 | ::-webkit-scrollbar 21 | { 22 | width: 16px; 23 | height: 16px; 24 | background-color: #F5F5F5; 25 | } 26 | 27 | /*定义滚动条轨道 内阴影+圆角*/ 28 | ::-webkit-scrollbar-track 29 | { 30 | -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); 31 | border-radius: 10px; 32 | background-color: #F5F5F5; 33 | } 34 | 35 | /*定义滑块 内阴影+圆角*/ 36 | ::-webkit-scrollbar-thumb 37 | { 38 | border-radius: 10px; 39 | -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3); 40 | background-color: #555; 41 | } 42 | ``` 43 | 44 | ## 详细设置 45 | 46 | 定义滚动条就是利用伪元素与伪类,那什么是伪元素和伪类呢? 47 | 48 | 伪类大家应该很熟悉:link,:focus,:hover,此外 CSS3 中又增加了许多伪类选择器,如:nth-child,:last-child,:nth-last-of-type()等。 49 | 50 | CSS 中的伪元素大家以前看过::first-line,:first-letter,:before,:after。那么在 CSS3 中,伪元素进行了调整,在以前的基础上增加了一个“:”也就是现在变成了“::first-letter,::first-line,::before,::after”,另外 CSS3 还增加了一个“::selection”。两个“::”和一个“:”在 css3 中主要用来区分伪类和伪元素。 51 | 52 | webkit 的伪类和伪元素的实现很强,可以把滚动条当成一个页面元素来定义,再结合一些高级的 CSS3 属性,比如渐变、圆角、RGBa 等等。然后如果有些地方要用图片,可以把图片也可以转换成 Base64,不然每次都得加载那个多个图片,增加请求数。 53 | 54 | 任何对象都可以设置:边框、阴影、背景图片等等,创建的滚动条任然会按照操作系统本身的设置来完成其交互的行为。下面的伪类可以应用到上面的伪元素中。有点小复杂,具体怎么写可以看第一个 demo,那里也有注释。 55 | 56 | ``` 57 | :horizontal 58 | //horizontal伪类适用于任何水平方向上的滚动条 59 | 60 | :vertical 61 | //vertical伪类适用于任何垂直方向的滚动条 62 | 63 | :decrement 64 | //decrement伪类适用于按钮和轨道碎片。表示递减的按钮或轨道碎片,例如可以使区域向上或者向右移动的区域和按钮 65 | 66 | :increment 67 | //increment伪类适用于按钮和轨道碎片。表示递增的按钮或轨道碎片,例如可以使区域向下或者向左移动的区域和按钮 68 | 69 | :start 70 | //start伪类适用于按钮和轨道碎片。表示对象(按钮 轨道碎片)是否放在滑块的前面 71 | 72 | :end 73 | //end伪类适用于按钮和轨道碎片。表示对象(按钮 轨道碎片)是否放在滑块的后面 74 | 75 | :double-button 76 | //double-button伪类适用于按钮和轨道碎片。判断轨道结束的位置是否是一对按钮。也就是轨道碎片紧挨着一对在一起的按钮。 77 | 78 | :single-button 79 | //single-button伪类适用于按钮和轨道碎片。判断轨道结束的位置是否是一个按钮。也就是轨道碎片紧挨着一个单独的按钮。 80 | 81 | :no-button 82 | no-button伪类表示轨道结束的位置没有按钮。 83 | 84 | :corner-present 85 | //corner-present伪类表示滚动条的角落是否存在。 86 | 87 | :window-inactive 88 | //适用于所有滚动条,表示包含滚动条的区域,焦点不在该窗口的时候。 89 | 90 | ::-webkit-scrollbar-track-piece:start { 91 | /*滚动条上半边或左半边*/ 92 | } 93 | 94 | ::-webkit-scrollbar-thumb:window-inactive { 95 | /*当焦点不在当前区域滑块的状态*/ 96 | } 97 | 98 | ::-webkit-scrollbar-button:horizontal:decrement:hover { 99 | /*当鼠标在水平滚动条下面的按钮上的状态*/ 100 | } 101 | ``` 102 | 103 | ## 参考来源 104 | 105 | https://www.cnblogs.com/kugeliu/p/7196656.html 106 | -------------------------------------------------------------------------------- /src/content/posts/关于 tristana.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 关于 tristana 3 | pubDate: 2021-04-19 10:20:19 4 | categories: ["Tristana"] 5 | description: "" 6 | --- 7 | 8 | ## 前言 9 | 10 | 大概在 2019 年的时候,为公司搭建了一套项目框架,截止到今天,用起来很不错,最近 `Vite` 太火,折腾了下,花了两天的时间,用 `Vite` 替换了 `Webpack`(`Webpack5`、`Webpack4` 都有,切换分支即可),体验直接起飞,基于 `Vite` + `React` + `Ant Design` + `Mobx` + `ESLint` + `TypeScript` 的项目框架。 11 | 12 | ## 特点 13 | 14 | - **快速开始**,只要您了解 `react`、`mobx`、`webpack` 和 `react router`,就可以快速搭建中后台管理平台。 15 | 16 | - **路由匹配**,包括 `url` 输入,`js` 跳转,菜单切换。 17 | 18 | - **Loading**,不需要重复写组件 `loading` 判断。 19 | 20 | ## Demo 21 | 22 | [tristana](https://order.downfuture.com/) 23 | 24 | ## 项目地址 25 | 26 | [tristana](https://github.com/xuya227939/tristana.git) 27 | 28 | ## 关于命名 29 | 30 | 由于本人非常喜欢玩 LOL 射手小炮,所以叫 tristana 31 | 32 | ## 能否使用在生产环境? 33 | 34 | 当然,目前我司多个产品线在使用中。 35 | 36 | ## 启动 37 | 38 | ``` 39 | $ git clone https://github.com/xuya227939/tristana.git 40 | 41 | $ cd tristana 42 | 43 | $ git checkout vite 44 | 45 | $ npm install 46 | 47 | $ npm run dev 48 | ``` 49 | 50 | ## 打包 51 | 52 | ``` 53 | $ npm run build 54 | ``` 55 | 56 | ## 是否支持 IE8? 57 | 58 | 不支持 59 | 60 | ## 结语 61 | 62 | 大佬们觉得不错的话,可以给个 Star 🌟,也欢迎给项目提 issues ~ 63 | -------------------------------------------------------------------------------- /src/content/posts/创业艰难,道阻且长.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 创业艰难,道阻且长 3 | pubDate: 2025-07-13 15:39:34 4 | categories: ["创业"] 5 | description: "创业真的好难!时间过得飞快,转眼半年多了。" 6 | --- 7 | 8 | 创业真的好难!时间过得飞快,转眼半年多了。 9 | 10 | 去年 9 月离职后,先放松了几个月,11 月开始动手做第一款产品 —— SnapVee 社媒提取器。 11 | 初衷是帮助内容创作者更高效地获取社交媒体上的视频、字幕、封面图等信息。 12 | 13 | 到了 3 月,决定继续深入做下去,陆续加入了音频提取、字幕翻译、视频总结等功能。但实际效果并不理想,用户只把它当作一个「社媒下载工具」,难以支撑持续增长与付费——而这类工具,竞品多、免费多、头部强(比如 BD 下载器)。 14 | 15 | 我尝试做差异化,但没有找到突破口。 16 | 最后决定砍掉所有扩展功能,只保留最基础的提取功能,免费开放。 17 | 18 | 4 月解散团队。 19 | 5 月开始构思新的方向——一个端到端的社媒创作链路,覆盖:脚本生成、图文创作、视频生成、数据分析,全链路闭环。 20 | 21 | 刚开始从「AI Agent 自动生成文案」切入,思路类似天工智能体 —— 用户提问 + 辅助信息输入,自动生成社媒文案。但很快发现一个关键问题:「去 AI 味很难」 22 | 23 | 6 月开始做深入竞品调研,发现:你想做的事,已经有人在做,而且不止一家,有的体验比你好、功能比你全、团队还比你大。 24 | 25 | 这时候,我有点道心破碎了。 26 | 27 | 在做产品这条路上,有很长一段时间,你是一个人在黑暗中走,没有用户反馈、没有数据指引,也不知道自己是不是在往对的方向走。 28 | 29 | 我自认为,我在心性,心态,坚韧,思维上还算不错了,但在创业面前,不值一提,于是,摆烂了一段时间。 30 | 31 | 完 32 | -------------------------------------------------------------------------------- /src/content/posts/前端如何支持PDF、Excel、Word在线预览.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 前端如何支持PDF、Excel、Word在线预览 3 | pubDate: 2018-12-27 15:54:14 4 | categories: ["前端", "Excel", "Word"] 5 | description: "" 6 | --- 7 | 8 | ## 注意一下几点: 9 | 10 | - url 必须经过 encodeURIComponent 转移,且是能够打开的文件域名。 11 | - 谷歌文件在线预览,必须使用代理,各种文件都支持。 12 | - 不想用代理,可以用微软这个,但是微软这个,不支持最新的 xlsx 格式,xls 格式可以。 13 | - 谷歌格式:https://docs.google.com/viewer?url=[url] 14 | - 微软格式:https://view.officeapps.live.com/op/view.aspx?src=[url] 15 | 16 | ## 例子(Word) 17 | 18 | ``` 19 | 谷歌: 20 | https://docs.google.com/viewer?url=http%3A%2F%2Fsruserfiletest.oss-cn-hangzhou.aliyuncs.com%2Fcrm%2Fcc604886ae8d4be9afffab02313d646d.docx%3FExpires%3D1545898717%26OSSAccessKeyId%3DLTAIm573A7RmsqeQ%26Signature%3DVOaSsvyYy9f%252BF6R1GcSnCG%252BaVI4%253D 21 | 22 | 微软: 23 | https://view.officeapps.live.com/op/view.aspx?src=http%3A%2F%2Fsruserfiletest.oss-cn-hangzhou.aliyuncs.com%2Fcrm%2Fcc604886ae8d4be9afffab02313d646d.docx%3FExpires%3D1545898717%26OSSAccessKeyId%3DLTAIm573A7RmsqeQ%26Signature%3DVOaSsvyYy9f%252BF6R1GcSnCG%252BaVI4%253D 24 | ``` 25 | 26 | ## 例子(Excel) 27 | 28 | ``` 29 | 谷歌: 30 | https://docs.google.com/viewer?url=http%3A%2F%2Fsruserfiletest.oss-cn-hangzhou.aliyuncs.com%2Fcrm%2F981f08e66ffa4f64934b37e543f5700b.xlsx%3FExpires%3D1545898717%26OSSAccessKeyId%3DLTAIm573A7RmsqeQ%26Signature%3DFgItdsB%252BPrm2%252BOQShja1HkfqKyY%253D 31 | 32 | 微软: 33 | https://view.officeapps.live.com/op/view.aspx?src=http%3A%2F%2Fsruserfiletest.oss-cn-hangzhou.aliyuncs.com%2Fcrm%2F981f08e66ffa4f64934b37e543f5700b.xlsx%3FExpires%3D1545898717%26OSSAccessKeyId%3DLTAIm573A7RmsqeQ%26Signature%3DFgItdsB%252BPrm2%252BOQShja1HkfqKyY%253D 34 | ``` 35 | 36 | ## PDF 37 | 38 | window.open([url]) 39 | -------------------------------------------------------------------------------- /src/content/posts/前端技术架构选型.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 前端技术架构选型 3 | pubDate: 2018-11-20 17:46:12 4 | categories: ["前端技术架构"] 5 | description: "" 6 | --- 7 | 8 | ![web](https://user-images.githubusercontent.com/16217324/48764959-1bf0c100-ecec-11e8-821b-3e6cb4972a54.png) 9 | 10 | ## 结语 11 | 12 | 可以从以下几个指标,来选择一套适合自己团队的项目。 13 | 14 | - 是否符合团队的技术栈 15 | - 是否符合项目需求 16 | - 生态圈是否完善、社区是否活跃 17 | - 开发效率是否会降低 18 | - 团队的学习能力如何 19 | -------------------------------------------------------------------------------- /src/content/posts/告前端同学的小记.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 告前端同学的小记 3 | pubDate: 2023-10-15 17:44:07 4 | categories: ["前端"] 5 | description: "" 6 | --- 7 | 8 | ![](https://files.mdnice.com/user/27515/6180249e-9a23-4ae1-a0d4-7999faf42614.jpg) 9 | 10 | 转载周爱民老师《告前端同学书》,有些思绪需要发散,随联想到自己的过往。 11 | 12 | 文中提到'领域'一词,用在前端,个人认为非常合适。领域一词在我印象中,还需追溯到初中时期,我读的一本叫《吞噬星空》的小说,书籍大概 400 多万字,读起来却是昼夜不分(这也是我唯一看完的一本小说),此书也提到了领域,行星级武者可以展开自己的领域。我个人所理解的领域,用大白话通俗解释,就是当你的本领到达一定阶段时,你会在这个范围拥有一定的话语权。类似的阿里技术等级序列也是,通常来说,P7 一般拥有自己的领域。 13 | 14 | 从疫情后开始,互联网的就业环境非常糟糕。前端就业,每况愈下,不断传出前端已死。之前,我也有发文,并不认同前端已死,如今还是持有该观点。周爱民老师的观点也与我一致。 15 | 16 | 我从 16 年入行至今,快 7 年的时光,经历了从 jQuery 时代到前端三大框架,如今相持不下的,只剩 React 和 Vue 。Angular 基本被社区抛弃,算是赶上了前端黄金时期的末尾阶段,前五年我是在做一个大前端方向,微信小程序、H5、Web、APP 开发都有涉及,前端工程化链路搭建也小有涉及。所谓的技术广泛度。也向往大厂。想见识见识大厂的工作氛围,大厂的技术链路到底是怎样的。后面,我进去了,但也经历了裁员,之前的想法是想靠大厂背书 + 前端工具链路这个方向,做可持续性的发展。随后,我搞了微前端,想在这个领域深入下去,但这条路走失败了,让我不得不重新思考,未来的出路在那?35 岁以后呢?我可不想去送外卖,开滴滴。 17 | 18 | 后面,我接触到了 WebGL,前端 3D 图形化的概念和场景非常吸引我,我也慢慢开始在往这个方向去做探索 + 转型,转型是一件非常痛苦的事,因为你要从自己的舒适区进入到坑洼区,你不得不去做很多东西,从 0 - 1 。辛运的是,我还年轻(但也不年轻了)。我个人坚信选择大于努力,时代会不断的造就一批人,之前的土木,现今的互联网,自媒体,直播带货等。 19 | 20 | ![](https://files.mdnice.com/user/27515/c4583b68-456b-43f0-9767-1588ed85364f.jpg) 21 | 22 | 很多人问我搞矩阵有什么用,你的目的是什么? 23 | 24 | 其实很简单,学习。我本人的学习模式是,需要通过大量的实战摸索 + 理论知识,梳理成一套成熟的方法论,单纯的去学理论知识,对于我本人而言,非常枯燥,况且 3D 图形化知识体系,非常困难。 25 | 26 | > 这是在一个新阶段的前夜。故此,有很多信息并不那么明朗,比如说像前后端分离这样的标志性事件并没有出现,亦或者出现了也还没有形成典型影响。我倾向于认为引领新时代的,或者说开启下一个阶段的运动将会发生在交互领域,也就是说新的交互方式决定了前端的未来。之前行业里在讲的 VR 和 AR(虚拟现实和增强实景)是在这个方向上的典型技术,但不唯于此。几乎所有在交互方式上的变革,都会成为人们认识与改变这个世界的全新动力,像语音识别、视觉捕捉、脑机接口等等,这些半成熟的或者实验性的技术都在影响着我们对'交互'的理解,从而重新定义了前端 27 | 28 | 周爱民老师这段文字,讲得非常透彻。AI、VR、AR 的不断发展,对前端的要求越来越高,如果还是停留在写业务页面的思想层面上,估计很快会被替代,大量的培训班造就一批人,已经告诉了我们。培训几个月,稍微伪造下履历,就可以快速上手业务开发,那么对比一个五年的,你有什么优势?,何况随着低代码不断的演进,让本不会写代码的一批人,也可以通过低代码引擎平台,快速生成页面,如果 AI 再持续迭代呢?这是一件很可怕的事 29 | 30 | 前端的很多理念来自于后端,并没有造就出真正属于自己的革命。底子很薄,真正要下功夫的其实是系统、网络、编译、机器学习、图形化等层面,或许 WASM + RUST 又会产生一点不一样的东西 31 | 32 | 在这个行业待了很久,见识了很多人转到其他行业。互联网的发展非常迅速,如果做不到持之以恒的学习,那将会被慢慢淘汰。以汽车为例,燃油车"固若金汤",谁会想到互联网 + 纯电,基本上算是革了燃油车的命,这些传统燃油车商,也要做出相应的改变,以应对时代的变化。 33 | 34 | 眼光高远而脚踏实地 35 | -------------------------------------------------------------------------------- /src/content/posts/命令行 MySQL 基本操作(CentOS).md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 命令行 MySQL 基本操作(CentOS) 3 | pubDate: 2020-07-24 10:54:53 4 | categories: ["MySQL", "CentOS"] 5 | description: "" 6 | --- 7 | 8 | ## 安装 9 | 10 | ``` 11 | $ brew install mysql 12 | ``` 13 | 14 | ## 登录 15 | 16 | ``` 17 | $ mysql -uroot -p 18 | ``` 19 | 20 | 连接到 mysql 数据库,默认没有密码的,直接按回车进入。 21 | 22 | ## 启动数据库 23 | 24 | ``` 25 | $ service mysqld start 26 | ``` 27 | 28 | ## 停止数据库 29 | 30 | ``` 31 | $ service mysqld stop 32 | ``` 33 | 34 | ## 重启数据库 35 | 36 | ``` 37 | $ service mysqld restart  38 | ``` 39 | 40 | ## 显示所有数据库 41 | 42 | ``` 43 | $ show databases; 44 | ``` 45 | 46 | ![image](https://user-images.githubusercontent.com/16217324/88373512-8e237e00-cdca-11ea-9f5f-a97a5cd0aef4.png) 47 | 48 | ## 进入数据库 49 | 50 | ``` 51 | $ use mysql; 52 | ``` 53 | 54 | ## 显示所有表 55 | 56 | ``` 57 | $ show tables; 58 | ``` 59 | 60 | ![image](https://user-images.githubusercontent.com/16217324/88373543-9b406d00-cdca-11ea-83ab-dc217892ffd7.png) 61 | 62 | ## 创建表名和字段 63 | 64 | ``` 65 | $ CREATE TABLE data (images VARCHAR(20)); 66 | ``` 67 | 68 | ## 修改字段大小 69 | 70 | ``` 71 | $ ALTER TABLE data images MODIFY VARCHAR(100); 72 | ``` 73 | 74 | ## 查询表 75 | 76 | ``` 77 | $ select * from card; 78 | ``` 79 | 80 | ## 修改用户密码 81 | 82 | ``` 83 | $ mysql -u root -p 84 | 85 | $ show databases; 86 | 87 | $ use mysql; 88 | 89 | $ desc user; # 用户表存放用户和面膜 90 | 91 | $ update user set authentication_string=PASSWORD('123') where user = 'root'; 92 | ``` 93 | -------------------------------------------------------------------------------- /src/content/posts/垂直居中.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: CSS-垂直居中 3 | pubDate: 2018-09-27 19:14:57 4 | categories: ["CSS"] 5 | description: "" 6 | --- 7 | 8 | ## line-height 9 | 10 | ``` 11 |
    12 |

    13 | 7777 777777777 777777777777 77777 7777 77777 77777 7777 77777 7777777 7777 777777 77777 7777 777777 7777 777 777 77 7 7777 7777 14 |

    15 |
    16 | div:nth-of-type(1) { 17 | height: 40px; 18 | line-height: 40px; 19 | } 20 | ``` 21 | 22 | 优点:兼容性好 23 | 缺点:只能用于单行行内内容;要知道高度的值 24 | 25 | ## vertical-align 26 | 27 | ``` 28 |
    29 |

    30 | 7777 777777777 777777777777 77777 7777 77777 77777 7777 77777 7777777 7777 777777 77777 7777 777777 7777 777 777 77 7 7777 7777 31 |

    32 |
    33 | div:nth-of-type(1) { 34 | height: 60px; 35 | vertical-align: middle; 36 | } 37 | ``` 38 | 39 | 优点:兼容性好 40 | 缺点:需要添加 font-size: 0; 才可以完全的垂直居中; 41 | 42 | ## 绝对定位 43 | 44 | ``` 45 |
    46 |

    47 | 7777 777777777 777777777777 77777 7777 77777 77777 7777 77777 7777777 7777 777777 77777 7777 777777 7777 777 777 77 7 7777 7777 48 |

    49 |
    50 | div:nth-of-type(1) { 51 | position: relative; 52 | height: 100px; 53 | } 54 | 55 | div:nht-of-type(1) p { 56 | position: absolute; 57 | top: 50%; 58 | transform: translateY(-50%); 59 | height: 50px; 60 | } 61 | ``` 62 | 63 | 优点:简单;兼容性较好(ie8+) 64 | 缺点:脱离文档流 65 | 66 | ## flex 67 | 68 | ``` 69 |
    70 |

    71 | 7777 777777777 777777777777 77777 7777 77777 77777 7777 77777 7777777 7777 777777 77777 7777 777777 7777 777 777 77 7 7777 7777 72 |

    73 |
    74 | div:nth-of-type(1) { 75 | display: flex; 76 | align-items: center; 77 | } 78 | ``` 79 | 80 | 优点:大众主流;强大;简单 81 | 缺点:PC 端兼容性不好,移动端(Android4.0+) 82 | -------------------------------------------------------------------------------- /src/content/posts/基于MobX 封装 Action 接口 Loading.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 基于MobX 封装 Action 接口 Loading 3 | pubDate: 2019-11-19 17:37:42 4 | categories: ["Mobx"] 5 | description: "" 6 | --- 7 | 8 | # 代码如下 9 | 10 | ``` 11 | import { action, observable } from 'mobx'; 12 | 13 | export default class BasicStore { 14 | @observable isLoading = observable.map({ }); 15 | 16 | @action 17 | changeLoadingStatus (loadingType, type) { 18 | this.isLoading.set(loadingType, type); 19 | } 20 | } 21 | 22 | // 初始化loading 23 | export function initLoading(target, key, descriptor) { 24 | const oldValue = descriptor.value; 25 | 26 | descriptor.value = async function(...args) { 27 | this.changeLoadingStatus(key, true); 28 | let res; 29 | try { 30 | res = await oldValue.apply(this, args); 31 | } catch (error) { 32 | // 做一些错误上报之类的处理 33 | throw error; 34 | } finally { 35 | this.changeLoadingStatus(key, false); 36 | } 37 | return res; 38 | }; 39 | 40 | return descriptor; 41 | } 42 | ``` 43 | -------------------------------------------------------------------------------- /src/content/posts/微信如何打开 VConsole.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 微信如何打开 VConsole 3 | pubDate: 2020-07-21 07:15:37 4 | categories: ["微信"] 5 | description: "" 6 | --- 7 | 8 | 微信打开这个网址`http://debugx5.qq.com`或扫描下方二维码 9 | 10 | ![image](https://user-images.githubusercontent.com/16217324/87995302-b046a300-cb21-11ea-9811-8ada4639a290.png) 11 | 12 | 进去后,选择中间"信息",一直往下翻,把 VConsole 打开,把下面两个 Content Cache 关掉。 13 | 14 | ![image](https://user-images.githubusercontent.com/16217324/87995370-e5eb8c00-cb21-11ea-917a-db678581c11f.png) 15 | 16 | 然后,打开你要调试的页面,就能看到右下角有个绿色按钮 VConsole。 17 | -------------------------------------------------------------------------------- /src/content/posts/微信小程序 web-view 下渲染 cover-view,文字消失.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 微信小程序 web-view 下渲染 cover-view,文字消失 3 | pubDate: 2021-09-26 11:16:37 4 | categories: ["微信小程序"] 5 | description: "" 6 | --- 7 | 8 | ## 问题背景 9 | 10 | 上半年最开始做的一版是展业大厅页面和互动白板页面(以下统称 web-view)分离,后面由于腾讯那边对交互方式不满意,强调一定要展业大厅页面和白板页面在同一个页面进行交互,最开始我们没有思路,因为在小程序官方中的描述,web-view 页面不允许叠加任何组件,后面是产品找到一个 demo,发现可以叠加,我这边去翻了下他们的源码(renderingMode: 'seperated'),最终解决了该问题,也就导致后面很多问题的产生。 11 | 12 | ## 现有问题 13 | 14 | ### web-view 存在的情况 15 | 16 | 1. 安卓更新组件不生效,比如 tab 切换,tab1 切换到 tab2 ,不生效,内容不会更新 17 | 18 | 2. 安卓更新图片不生效 19 | 20 | 3. 安卓更新样式不生效 21 | 22 | 4. cover-view 文字消失 23 | 24 | 5. 按钮响应慢,机型性能低的手机比较明显 25 | 26 | 针对问题 2,目前的 hack 方案,先渲染一张透明的图片,然后再渲染其他图片,可以生效 27 | 28 | 针对问题 1、2、3,仅在安卓端出现,苹果手机上没有发现,目前有一个比较 hack 的方案,通过卸载组件,重新渲染,可以达到目的,但是产生的性能损耗比较大,对交互体验不友好,而且也导致了第四点问题的产生 29 | 30 | 针对问题 4 安卓复现频率比较高,苹果出现过一次 31 | 32 | 针对问题 5 安卓跟苹果都存在 33 | 34 | ### web-view 不存在的情况 35 | 36 | 都正常 37 | 38 | ## 尝试过的方案 39 | 40 | 针对 cover-view 文字消失 41 | 42 | - 设置组件宽高 43 | - 设置字体颜色和背景颜色 44 | - 刷新 45 | 46 | 以上方案,都不行,也没法在开发者工具上查看 DOM 视图 47 | 48 | ### console 49 | 50 | ![WechatIMG13](https://user-images.githubusercontent.com/16217324/134792081-a01ea541-d204-400a-96e1-09edf9c95ff8.jpeg) 51 | 52 | 元素的宽高都在,偏移位置也正常,就是文字消失 53 | 54 | ### DOM 55 | 56 | WechatIMG3085 57 | 58 | 无法在开发者工具上查看 DOM 视图 59 | 60 | ## 现象 61 | 62 | ### 正常 63 | 64 | ![WechatIMG20](https://user-images.githubusercontent.com/16217324/134792087-47a6e113-fd23-4da0-b60c-c412c00a1bb4.jpeg) 65 | 66 | ### 文字消失 67 | 68 | ![WechatIMG19](https://user-images.githubusercontent.com/16217324/134792095-d09471ef-d4e6-468b-9b86-473120d61a76.jpeg) 69 | 70 | 这个元素的宽高都在,就是文字消失 71 | 72 | ## 微信小程序架构图 73 | 74 | ![WechatIMG3201](https://user-images.githubusercontent.com/16217324/134792105-ee98881e-38b0-4a5a-81b8-8d169ea68093.jpeg) 75 | 76 | ## 展业小程序架构图 77 | 78 | ![mini](https://user-images.githubusercontent.com/16217324/134792107-093c03ef-7fff-49fe-b56c-b777ebfca044.png) 79 | -------------------------------------------------------------------------------- /src/content/posts/微信小程序 web-view 问题讨论.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 微信小程序 web-view 问题讨论 3 | pubDate: 2021-10-14 12:51:25 4 | categories: ["微信小程序"] 5 | description: "" 6 | --- 7 | 8 | 大家好,我是江辰,这篇文章记录一次在真实的线上环境中,关于 web-view 的问题,大家可以跟随作者一起看看心路历程。 9 | 10 | 本文首发于微信公众号:野生程序猿江辰 11 | 12 | 欢迎大家点赞,收藏,关注 13 | 14 | ## 问题背景 15 | 16 | 上半年最开始做的一版是展业大厅页面和互动白板页面(以下统称 `web-view`)分离,后面由于腾讯那边对交互方式不满意,强调一定要展业大厅页面和白板页面在同一个页面进行交互,最开始我们没有思路,因为在小程序官方中的描述,`web-view` 页面不允许叠加任何组件,后面是产品找到一个 demo,发现可以叠加,我这边去翻了下他们的源码(`renderingMode: 'seperated'`),最终解决了该问题,也就导致后面很多问题的产生。 17 | 18 | ## 现存问题 19 | 20 | ### web-view 存在的情况 21 | 22 | 1. 安卓更新组件不生效,比如 tab 切换,tab1 切换到 tab2 ,不生效,内容不会更新 23 | 24 | 2. 安卓更新图片不生效 25 | 26 | 3. 安卓更新样式不生效 27 | 28 | 4. cover-view 文字消失 29 | 30 | 5. 按钮响应慢,机型性能低的手机比较明显 31 | 32 | 针对问题 2,目前的 `hack` 方案,先渲染一张透明的图片,然后再渲染其他图片,可以生效 33 | 34 | 针对问题 1、2、3,仅在安卓端出现,苹果手机上没有发现,目前有一个比较 `hack` 的方案,通过卸载组件,重新渲染,可以达到目的,但是产生的性能损耗比较大,对交互体验不友好,而且也导致了第四点问题的产生 35 | 36 | 针对问题 4 安卓复现频率比较高,苹果出现过一次 37 | 38 | 针对问题 5 安卓跟苹果都存在 39 | 40 | ### web-view 不存在的情况 41 | 42 | 都正常 43 | 44 | ## 尝试过的方案 45 | 46 | 针对 `cover-view` 文字消失 47 | 48 | - 设置组件宽高 49 | - 设置字体颜色和背景颜色 50 | - 刷新 51 | 52 | 以上方案,都不行,也没法在开发者工具上查看 `DOM` 视图 53 | 54 | ### Console 55 | 56 | ![](https://files.mdnice.com/user/27515/4d21dadd-b397-4e38-87b4-a71e18369ee5.jpeg) 57 | 58 | 元素的宽高都在,偏移位置也正常,就是文字消失 59 | 60 | ### DOM 61 | 62 | ![](https://files.mdnice.com/user/27515/e7d6cc0c-c02d-4978-bcf4-2ae87595f5d2.png) 63 | 64 | 无法在开发者工具上查看 DOM 视图 65 | 66 | ## 现象 67 | 68 | ### 正常 69 | 70 | ![](https://files.mdnice.com/user/27515/dfbeb70c-5e83-4c77-91e1-b337d1c4a3d7.jpeg) 71 | 72 | ### 文字消失 73 | 74 | ![](https://files.mdnice.com/user/27515/e3a76308-5456-482d-831a-6d03779ecb23.jpeg) 75 | 76 | 这个元素的宽高都在,就是文字消失 77 | 78 | ## 微信小程序架构图 79 | 80 | ![](https://files.mdnice.com/user/27515/900a15d9-7bac-4aee-8ce4-1148bfac3851.jpeg) 81 | 82 | ## 展业小程序架构图 83 | 84 | ![](https://files.mdnice.com/user/27515/4a20f41d-6dac-4117-b641-2b990f975fe3.png) 85 | 86 | ## 解决方案 87 | 88 | - 所有人的视频流不再全部获取,而是只显示四路视频流,其他人员要显示,在成员列表进行切换显示 89 | 90 | - 重点⼯作中花费精⼒最多的是模块化解耦的重构、我简单说下背景。因为之前我们代码共建的、 但是因为客户这边定制化的需求有很多,并且不是那么简单的能⽤抽象的⽅式把这些⾮通⽤功能的功能实现的、所以我们想出来的⽅案是:把⼩程序代码⾥划分重点模块,把每个模块都做成可插拔的,这样我们只需要把差异化很多的部分抽出来完全独⽴交给⾃⼰开发即可。同时这个⽅案实现好后,如果后续我们要开发新形态的应⽤,可以通过实现模块的⽅式实现⼀套新的应⽤形态 91 | 92 | 这些优化工作总共时间大概花了一个月左右,完成之后,目前我们的产品能够支持到 **20+** 人同时进行音视频,这块实际测试过。对我们的产品稳定性越来越好! 93 | -------------------------------------------------------------------------------- /src/content/posts/桌面、生产力和泛娱乐 .md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 桌面、生产力和泛娱乐 3 | pubDate: 2023-06-07 12:52:41 4 | categories: ["办公桌面"] 5 | description: "" 6 | --- 7 | 8 | ![](https://files.mdnice.com/user/27515/307e78e5-19bf-4f53-b694-7650e0f38edb.png) 9 | 10 | 感谢室友送的显示器支架和宜家办公桌!周末的时候,总算把家庭生产工具组装起来了,之前用的 MBP2017 款,实在太老了。性能渐渐缺失,最近 618 大促,更换了一套生产力 + 娱乐设备,致力提升在家办公体验,桌面有点乱,总算能兼顾生产和娱乐 11 | 12 | ### 显示器 13 | 14 | 我用过 4k 显示器之后,发现再也回不去了。1080,2k 都是些什么东西?(手动狗头)趁着 618 大促期间,购入了红米 4k 显示器,最开始买的是 WESCOM 27 英寸 4K(感觉性价比很高阿,800 多大洋就能有 27 寸 4k),显示器到手之后,测试发现,整体画面偏白,举个简单的例子,微信的消息框,你看不到它的背景色,我调教了很久,发现都不行,也就是色域太低。还在七天无理由内,赶紧退货。然后又考虑买那台呢?戴尔,lG?细细思考之后,我不是专业的设计人士,没有必要去追求极致的参数,最终选定了红米 4k,价格也很合适,性价比拉满,到手测试之后,很满意,比 WESCOM 好太多,毕竟一分价钱一分货 15 | 16 | ### 显示器支架手臂 17 | 18 | 看过很多手臂来支撑显示器,觉得很酷,有没有!而且还能把桌面上的空间给空出来,最终选定了北弧的显示器支架 19 | 20 | ![](https://files.mdnice.com/user/27515/c23912f2-9b4e-438f-b48d-f65bae705d84.png) 21 | 22 | 如图所示,很有科幻感。装上之后,脖子舒服不少,还能各个方向调动,如果后面再把手臂提升下,接入 chatGPT + 语音会咋样?或许正如钢铁侠中吧?空间层次感上升+1 23 | 24 | ### 桌面主机 25 | 26 | 用过两台 macbook pro ,一直想换 mac mini,M 系列的 mac mini ,主打就是一个性价比,而且不占空间。这次终于尝试了,确实很舒服,mac mini + 双显示器,体验拉满,性能又够,平时剪剪视频,写写代码,很优雅 27 | 28 | ### 三合一无线充 29 | 30 | 网上看过很多款无线充。看了评论,基本比较差劲,比如功率不够,充电发热等等。最终目光锁定在了贝尔金的三合一无线充,跟苹果官方合作的厂商,京东自营有卖,价格偏贵,我买的二手,解放了很多线 31 | 32 | 清单: 33 | 34 | - Mac Mini M2 25G + 512G 35 | - 红米 4K 显示器 36 | - 红米 1080 显示器 37 | - 阿米诺迷你洛键盘 68 键 38 | - 阿米洛手托 39 | - 罗技 Master 3s 40 | - Switch Oled 喷喷限定 41 | - Xbox Series X 42 | - 飞利浦桌面音箱 43 | - 绿联扩展坞 44 | - 希捷 2TB 移动硬盘 45 | - iPad mini6 46 | - 贝尔金三合一无线充 47 | - Apple Watch S7 48 | - iPhone 12 mini 49 | - AirPods 2 50 | - 宜家办公桌:140x65 51 | 52 | 完 53 | -------------------------------------------------------------------------------- /src/content/posts/检测浏览器刷新还是退出代码.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 检测浏览器刷新还是退出代码 3 | pubDate: 2021-09-27 15:17:23 4 | categories: ["React"] 5 | description: "" 6 | --- 7 | 8 | ``` 9 | let beginTime = 0; 10 | let differTime = 0; 11 | window.onunload = function () { 12 | differTime = new Date().getTime() - beginTime; 13 | if (differTime <= 5) { 14 | console.log('浏览器关闭'); 15 | } else { 16 | console.log('浏览器刷新'); 17 | } 18 | }; 19 | window.onbeforeunload = function () { 20 | beginTime = new Date().getTime(); 21 | }; 22 | ``` 23 | -------------------------------------------------------------------------------- /src/content/posts/水平居中.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 水平居中 3 | pubDate: 2018-09-27 19:14:10 4 | categories: ["CSS"] 5 | description: "" 6 | --- 7 | 8 | ## text-align 9 | 10 | ``` 11 |
    12 |

    13 | 7777 777777777 777777777777 77777 7777 77777 77777 7777 77777 7777777 7777 777777 77777 7777 777777 7777 777 777 77 7 7777 7777 14 |

    15 |
    16 | div:nth-of-type(1) { 17 | text-align: center; 18 | } 19 | ``` 20 | 21 | 优点:兼容性好 22 | 缺点:只对行内元素有效;属性会影响到后续内容;子元素的宽度必须小于父元素 23 | 24 | ## 绝对定位 25 | 26 | ``` 27 |
    28 |

    29 | 7777 777777777 777777777777 77777 7777 77777 77777 7777 77777 7777777 7777 777777 77777 7777 777777 7777 777 777 77 7 7777 7777 30 |

    31 |
    32 | div:nth-of-type(1) { 33 | position: relative; 34 | } 35 | 36 | div:nth-of-type(1) p { 37 | position: absolute; 38 | left: 50%; 39 | transform: translateX(-50%); 40 | } 41 | ``` 42 | 43 | 优点:不管是块级还是行内元素都可以实现 44 | 缺点:代码较多;脱离文档流;使用 transform 兼容性不好(ie9+) 45 | 46 | ## margin 47 | 48 | ``` 49 |
    50 |

    51 | 7777 777777777 777777777777 77777 7777 77777 77777 7777 77777 7777777 7777 777777 77777 7777 777777 7777 777 777 77 7 7777 7777 52 |

    53 |
    54 | div:nth-of-type(1) { 55 | width: 100px; 56 | margin: 0 auto; 57 | } 58 | ``` 59 | 60 | 优点:兼容性好 61 | 缺点:必须设置宽度,子元素的宽度必须小于父元素 62 | 63 | ## flex 64 | 65 | ``` 66 |
    67 |

    68 | 7777 777777777 777777777777 77777 7777 77777 77777 7777 77777 7777777 7777 777777 77777 7777 777777 7777 777 777 77 7 7777 7777 69 |

    70 |
    71 | div:nth-of-type(1) { 72 | display: flex; 73 | justify-content: center; 74 | } 75 | ``` 76 | 77 | 优点:大众主流;强大;简单 78 | 缺点:PC 端兼容性不好,移动端(Android4.0+) 79 | -------------------------------------------------------------------------------- /src/content/posts/缓存.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 网页缓存 3 | pubDate: 2018-07-23 15:36:15 4 | categories: ["网页缓存"] 5 | description: "" 6 | --- 7 | 8 | ## 本地缓存 9 | 10 | sessionStorage 、localStorage 、cookie 11 | 12 | ## 共同点 13 | 14 | 都是保存在浏览器端,且同源的。 15 | 16 | ## 区别 17 | 18 | - cookie 数据始终在同源的 http 请求中携带(即使不需要),即 cookie 在浏览器和服务器间来回传递;cookie 数据还有路径(path)的概念,可以限制 cookie 只属于某个路径下。存储大小限制也不同,cookie 数据不能超过 4k,同时因为每次 http 请求都会携带 cookie,所以 cookie 只适合保存很小的数据,如会话标识。而 sessionStorage 和 localStorage 不会自动把数据发给服务器,仅在本地保存。sessionStorage 和 localStorage 虽然也有存储大小的限制,但比 cookie 大得多,可以达到 5M 或更大 19 | 20 | - 数据有效期不同:sessionStorage:仅在当前浏览器窗口关闭前有效,自然也就不可能持久保持;localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;cookie 只在设置的 cookie 过期时间之前一直有效,即使窗口或浏览器 21 | 22 | - 作用域不同:sessionStorage 不在不同的浏览器窗口中共享,即使是同一个页面;localStorage 在所有同源窗口中都是共享的;cookie 也是在所有同源窗口中都是共享的。Web Storage 支持事件通知机制,可以将数据更新的通知发送给监听者。Web Storage 的 api 接口使用更方便。 23 | -------------------------------------------------------------------------------- /src/content/posts/网站性能优化几个点.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 网站性能优化几个点 3 | pubDate: 2018-08-16 10:18:12 4 | categories: ["性能优化"] 5 | description: "" 6 | --- 7 | 8 | - 减少重排和重绘 9 | - 图片优化:体积调整、建立最佳格式,比如 jpeg 就比 png 好、降低质量、压缩 10 | - 减少 http 请求:组合资源:css 文件合并、js 文件合并 11 | - CDN 加速 12 | - 资源打包压缩(js 压缩、HTML 压缩、JS 压缩) 13 | - 雪碧图 14 | - 使用字体图标 15 | - 页面渲染优化 16 | - 异步加载 17 | -------------------------------------------------------------------------------- /src/content/posts/聊聊我对前端已死的看法.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 聊聊我对前端已死的看法 3 | pubDate: 2023-03-22 09:42:22 4 | categories: ["前端"] 5 | description: "" 6 | --- 7 | 8 | > 谁也不曾设想,今年的就业环境比起去年更差劲。客户端已死、前端已死、后端已死、BOSS 直聘打招呼不回,拉勾拉不动。互联网上各种不好的消息充斥着人们的神经,一定要保证自己的核心竞争力,才能在一波又一波的浪潮冲击下,生存!生存!生存! 9 | 10 | ### 技术出海 11 | 12 | 最近几年,大厂的科技产品都在出海,寻石问路。抖音,拼多多,都做的不错,不再局限国内的人口红利,谋求更多的发展空间。同理,我们做技术的是否也可以寻找技术出海?我的答案是肯定的。国外项目平台有很多,比如 Upwork,在这个平台上,我们可以看到各个国家的机会,不会英语也没关系,借助翻译软件,简历包装等,寻求更多合作的机会,不要把自己局限住! 13 | 14 | ### 核心竞争力 15 | 16 | 作为一名技术人,关键是创造自己的核心竞争力,什么叫核心竞争力,也就是你跟别人与众不同的地方,别人能做的,你能做。别人不能做的,你也能做,哪自然择优而优,这对所有公司来说都是这样,本质上就是花最小的代价,选择更合适的的人。举个简单的例子,我们以上面这份大厂 JD(Job Describe)为例,基础部分就不讲了,关注几个指标:学历、工作经验、C 端业务经验,加分项。通过这几个条件,可以筛选出一批人,如果你对加分项更加深入,哪你的优势很大 17 | 18 | ### 人口红利 19 | 20 | 我们国内有个特点,哪个行业挣钱,哪个行业火,一堆人会进来(培训机构到处宣传),人口优势固然存在,这是一大优势,也是一大弱势。优势就不讲了。弱势就是会导致这个行业出奇的内卷。以互联网行业为例,早几年会个 ajax ,写个静态页面,专科以上学历,一堆人要。现在呢,你得懂源码、你得统招全日制学历以上、你得会多个技能,这都是基本的要求,哪如何找到差一点,比如熟练英语的读写,你又比一些人要优秀,机会更多,可以关注外企机会 21 | 22 | ### 技术发展趋势 23 | 24 | 作为一名技术人,一定要关注前沿技术的发展趋势,为什么要关注?简单来说,这关乎你的生存空间。近几年 web3、区块链、元宇宙发展很快,如果你跟不上时代,很快就会被淘汰,很简单的道理。比如你看各大招聘软件上,各种 web3 的岗位,工资很高以及要求熟练英语读写 25 | 26 | 完。 27 | -------------------------------------------------------------------------------- /src/content/posts/解决PDF.js转Canvas图片,toDataURL空白问题.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 解决toDataURL空白问题 3 | pubDate: 2020-04-04 16:19:30 4 | categories: ["Canvans"] 5 | description: "" 6 | --- 7 | 8 | ## 起因 9 | 10 | 最近,笔者接到一个需求,需要在前端把 PDF 转成图片,再把图片转成 Base64,传给小程序。然后发现,只有 PDF.js 这个库,才能满足我们的需求。 11 | 12 | ## 核心代码 13 | 14 | ``` 15 | renderPage(url) { 16 | return new Promise((resolve, reject) => { 17 | const loadingTask = pdfjsLib.getDocument(url); 18 | loadingTask.promise.then((pdf) => { 19 | const pages = pdf.numPages; 20 | // 添加canvas, 根据pdf的页数添加 21 | for (let i = 1; i <= pages; i++) { 22 | const canvas = document.createElement('canvas'); 23 | const showPdf = document.getElementById('show-pdf'); 24 | canvas.setAttribute('id', 'canvas' + i.toString()); 25 | showPdf.appendChild(canvas); 26 | } 27 | let count = 0; 28 | for (let i = 1; i <= pages; i++) { 29 | pdf.getPage(i).then((page) => { 30 | const viewport = page.getViewport({ scale: 1.5 }); 31 | const canvas = document.getElementById(('canvas' + i).toString()); 32 | const context = canvas.getContext('2d'); 33 | canvas.height = viewport.height; 34 | canvas.width = viewport.width; 35 | const renderContext = { 36 | canvasContext: context, 37 | viewport: viewport 38 | }; 39 | page.render(renderContext) 40 | }); 41 | } 42 | }); 43 | }); 44 | } 45 | 46 | 47 | // 推送 48 | push(record) { 49 | this.renderPage(record.url).then(() => { 50 | const canvas = document.querySelectorAll('#show-pdf > canvas'); 51 | let datas = []; 52 | for(let i = 0; i < canvas.length; i++) { 53 | datas.push(canvas[i].toDataURL("image/png")); 54 | } 55 | }); 56 | } 57 | ``` 58 | 59 | 这段代码写完之后,调试,发现获取出来的 Base64,无法还原图片。才疏学浅,死活找不到原因。找了好久,终于看到一篇文章,要看全文,还得关注公众号,获取二维码解锁。emmm,然后,我就解锁了。看到最最关键的两行代码,抱着试试的心态,写上。 60 | 61 | ![481585839610_ pic_hd](https://user-images.githubusercontent.com/16217324/78422123-90cd5c80-768f-11ea-9e47-7b2b8410da34.jpg) 62 | 63 | 居然成功了!!! 64 | 65 | 然后,反思我的代码哪里不对,几次尝试之后,找到问题了,page.render 绘制有延迟。。。把 page.render 代码修改如下。 66 | 67 | ``` 68 | page.render(renderContext).promise.then(() => { 69 | count++; 70 | // 已全部完成绘制 71 | if(count == pages) { 72 | resolve(); 73 | } 74 | }); 75 | ``` 76 | 77 | 大功告成,完美解决。 78 | -------------------------------------------------------------------------------- /src/content/posts/解决canvas,toDataURL跨域问题.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 解决canvas,toDataURL跨域问题 3 | pubDate: 2020-04-05 23:03:45 4 | categories: ["Canvas"] 5 | description: "" 6 | --- 7 | 8 | ## 图片服务器需要配置 Access-Control-Allow-Origin 9 | 10 | 设置通配符,允许任何域名访问 11 | 12 | ``` 13 | header("Access-Control-Allow-Origin: *"); 14 | ``` 15 | 16 | 指定域名 17 | 18 | ``` 19 | header("Access-Control-Allow-Origin: www.xxx.com"); 20 | ``` 21 | 22 | 此时,Chrome 浏览器不会有 Access-Control-Allow-Origin 相关的错误信息,但是,还会有其他的跨域错误信息。 23 | 24 | ## HTML crossOrigin 属性解决资源跨域问题 25 | 26 | 在 HTML5 中,有些元素提供了`CORS(Cross-Origin Resource Sharing)`属性,这些元素包括