├── .DS_Store ├── .github └── workflows │ └── deploy.yml ├── .gitignore ├── .vscode └── settings.json ├── README.md ├── code ├── achieve │ ├── 01.手写快排.js │ ├── 02.手写深拷贝.js │ ├── 03.手写节流和防抖.js │ ├── 04.手写 call apply.js │ ├── 05.手写 bind.js │ ├── 06.手写Promise.js │ ├── 07. 手写 Promise.all race.js │ ├── 08.手写new.js │ ├── 09.手写instanceof.js │ ├── 10.手写继承.js │ ├── 11.手写useReducer.js │ ├── 12.手写useDidUpdate.js │ ├── 13.手写usePreious.js │ ├── 14.实现let关键字.js │ ├── 15.实现const关键字.js │ ├── 16.实现sleep.js │ ├── 17.实现compose.js │ └── 18.实现requestIdleCallback.js ├── commonJS │ ├── README.md │ ├── commonjs.test.js │ ├── package.json │ └── src │ │ ├── index.js │ │ └── utils.js ├── github-oauth2 │ ├── index.html │ ├── package.json │ └── server.js ├── github-webhook-demo │ ├── README.md │ └── app.js ├── h5-drag-demo │ ├── .gitignore │ ├── README.md │ ├── index.html │ ├── package.json │ ├── src │ │ ├── App.tsx │ │ ├── Demo1.tsx │ │ ├── Demo2.tsx │ │ ├── favicon.svg │ │ ├── index.css │ │ └── main.tsx │ ├── tsconfig.json │ └── vite.config.ts ├── learn-angular │ ├── .browserslistrc │ ├── .editorconfig │ ├── .gitignore │ ├── .vscode │ │ ├── extensions.json │ │ ├── launch.json │ │ └── tasks.json │ ├── README.md │ ├── angular.json │ ├── karma.conf.js │ ├── package.json │ ├── pnpm-lock.yaml │ ├── src │ │ ├── app │ │ │ ├── app-routing.module.ts │ │ │ ├── app.component.html │ │ │ ├── app.component.scss │ │ │ ├── app.component.spec.ts │ │ │ ├── app.component.ts │ │ │ ├── app.module.ts │ │ │ ├── direct │ │ │ │ ├── direct.component.html │ │ │ │ ├── direct.component.scss │ │ │ │ ├── direct.component.spec.ts │ │ │ │ ├── direct.component.ts │ │ │ │ ├── upper.directive.spec.ts │ │ │ │ └── upper.directive.ts │ │ │ ├── form │ │ │ │ ├── form.component.html │ │ │ │ ├── form.component.scss │ │ │ │ ├── form.component.spec.ts │ │ │ │ └── form.component.ts │ │ │ ├── interaction │ │ │ │ ├── interaction.component.html │ │ │ │ ├── interaction.component.scss │ │ │ │ ├── interaction.component.spec.ts │ │ │ │ ├── interaction.component.ts │ │ │ │ └── title │ │ │ │ │ ├── title.component.html │ │ │ │ │ ├── title.component.scss │ │ │ │ │ ├── title.component.spec.ts │ │ │ │ │ └── title.component.ts │ │ │ ├── life-cycle │ │ │ │ ├── life-cycle-demo │ │ │ │ │ ├── life-cycle-demo.component.html │ │ │ │ │ ├── life-cycle-demo.component.scss │ │ │ │ │ ├── life-cycle-demo.component.spec.ts │ │ │ │ │ └── life-cycle-demo.component.ts │ │ │ │ ├── life-cycle.component.html │ │ │ │ ├── life-cycle.component.scss │ │ │ │ ├── life-cycle.component.spec.ts │ │ │ │ └── life-cycle.component.ts │ │ │ ├── operation │ │ │ │ ├── operation.module.ts │ │ │ │ └── opt1 │ │ │ │ │ ├── opt1.component.html │ │ │ │ │ ├── opt1.component.scss │ │ │ │ │ ├── opt1.component.spec.ts │ │ │ │ │ └── opt1.component.ts │ │ │ ├── pipe │ │ │ │ ├── pipe.component.html │ │ │ │ ├── pipe.component.scss │ │ │ │ ├── pipe.component.spec.ts │ │ │ │ ├── pipe.component.ts │ │ │ │ └── test.pipe.ts │ │ │ ├── service-demo │ │ │ │ ├── service-child │ │ │ │ │ ├── service-child.component.html │ │ │ │ │ ├── service-child.component.scss │ │ │ │ │ ├── service-child.component.spec.ts │ │ │ │ │ └── service-child.component.ts │ │ │ │ ├── service-demo.component.html │ │ │ │ ├── service-demo.component.scss │ │ │ │ ├── service-demo.component.spec.ts │ │ │ │ └── service-demo.component.ts │ │ │ ├── services │ │ │ │ ├── list.service.spec.ts │ │ │ │ └── list.service.ts │ │ │ └── template │ │ │ │ ├── counter │ │ │ │ ├── counter.component.html │ │ │ │ ├── counter.component.scss │ │ │ │ └── counter.component.ts │ │ │ │ ├── template.component.html │ │ │ │ ├── template.component.scss │ │ │ │ └── template.component.ts │ │ ├── assets │ │ │ └── .gitkeep │ │ ├── environments │ │ │ ├── environment.prod.ts │ │ │ └── environment.ts │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── main.ts │ │ ├── polyfills.ts │ │ ├── styles.scss │ │ └── test.ts │ ├── tsconfig.app.json │ ├── tsconfig.json │ └── tsconfig.spec.json ├── mf │ ├── app1 │ │ ├── package.json │ │ ├── public │ │ │ └── index.html │ │ ├── src │ │ │ ├── App.js │ │ │ ├── Button.js │ │ │ └── main.js │ │ └── webpack.config.js │ └── app2 │ │ ├── package.json │ │ ├── public │ │ └── index.html │ │ ├── src │ │ ├── App.js │ │ ├── Button.js │ │ └── main.js │ │ └── webpack.config.js ├── my-promise │ ├── README.md │ ├── package.json │ ├── promise.js │ ├── test1.js │ ├── test2.js │ ├── test3.js │ ├── test4.js │ ├── test5.js │ ├── test6.js │ ├── test7.js │ └── test8.js ├── my-react-router │ ├── .gitignore │ ├── index.html │ ├── package.json │ ├── src │ │ ├── App.jsx │ │ ├── main.jsx │ │ └── react-router-dom │ │ │ ├── HashRouter.jsx │ │ │ ├── Link.jsx │ │ │ ├── Redirect.jsx │ │ │ ├── Route.jsx │ │ │ ├── Switch.jsx │ │ │ ├── context.js │ │ │ └── index.js │ └── vite.config.js ├── my-redux │ ├── .gitignore │ ├── README.md │ ├── index.html │ ├── middleware │ │ ├── exception.js │ │ ├── logger.js │ │ └── time.js │ ├── package.json │ ├── react-redux │ │ ├── connect.jsx │ │ ├── index.js │ │ └── provider.jsx │ ├── redux │ │ ├── applyMiddleware.js │ │ ├── bindActionCreators.js │ │ ├── combineReducers.js │ │ ├── compose.js │ │ ├── createStore.js │ │ └── index.js │ ├── src │ │ ├── App.jsx │ │ └── main.jsx │ ├── store │ │ ├── couter │ │ │ ├── actions.js │ │ │ └── reducer.js │ │ ├── index.js │ │ └── rootReducers.js │ └── vite.config.js ├── react-context │ ├── README.md │ └── package.json ├── react-ssr-demo │ ├── README.md │ ├── package.json │ ├── src │ │ ├── App.jsx │ │ ├── client │ │ │ ├── index.js │ │ │ └── request.js │ │ ├── containers │ │ │ ├── Home.jsx │ │ │ └── Login.jsx │ │ ├── redux │ │ │ ├── contants.js │ │ │ ├── index.js │ │ │ └── user │ │ │ │ ├── actions.js │ │ │ │ └── reducer.js │ │ ├── routes.js │ │ └── server │ │ │ ├── index.js │ │ │ └── request.js │ ├── webpack.base.js │ ├── webpack.client.js │ └── webpack.server.js ├── systemjs-demo │ ├── .gitignore │ ├── package.json │ ├── src │ │ ├── App.js │ │ ├── index.html │ │ └── main.js │ └── webpack.config.js ├── vdom-diff │ ├── .gitignore │ ├── core │ │ ├── contants.js │ │ ├── diff.js │ │ ├── element.js │ │ ├── index.js │ │ └── patch.js │ ├── index.html │ ├── package.json │ ├── src │ │ └── main.jsx │ └── vite.config.js ├── vite-antd │ ├── .gitignore │ ├── README.md │ ├── index.html │ ├── package.json │ ├── pnpm-lock.yaml │ ├── public │ │ └── vite.svg │ ├── src │ │ ├── App.scss │ │ ├── App.tsx │ │ ├── ProComponent.tsx │ │ ├── assets │ │ │ └── react.svg │ │ ├── main.tsx │ │ └── vite-env.d.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts ├── vite-react-demo │ ├── .gitignore │ ├── index.html │ ├── package.json │ ├── pnpm-lock.yaml │ ├── src │ │ ├── App.tsx │ │ ├── favicon.svg │ │ ├── main.tsx │ │ └── vite-env.d.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts ├── vite-ts-demo │ ├── app.ts │ ├── index.html │ └── package.json ├── webrtc-demo │ ├── README.md │ ├── assets │ │ ├── 1.png │ │ ├── 2.png │ │ └── 3.png │ ├── demo1 │ │ └── index.html │ ├── demo2 │ │ ├── index.html │ │ └── style.css │ ├── demo3 │ │ ├── index.html │ │ └── style.css │ ├── demo4 │ │ ├── .DS_Store │ │ ├── app.js │ │ └── package.json │ ├── demo5 │ │ ├── package.json │ │ ├── peer1.html │ │ └── peer2.html │ └── demo6 │ │ ├── getUserMedia.js │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── peer1.html │ │ └── peer2.html └── 并发.js ├── docs ├── .vitepress │ ├── cache │ │ └── deps │ │ │ ├── @theme_index.js │ │ │ ├── @theme_index.js.map │ │ │ ├── _metadata.json │ │ │ ├── package.json │ │ │ ├── vue.js │ │ │ └── vue.js.map │ └── config.ts ├── algorithm │ ├── bfs-dfs.md │ ├── binarySearch.md │ ├── index.md │ ├── math.md │ ├── sort │ │ ├── bubbleSort.md │ │ ├── countSort.md │ │ ├── heapSort.md │ │ ├── insertSort.md │ │ ├── mergeSort.md │ │ ├── quickSort.md │ │ ├── selectionSort.md │ │ └── shellSort.md │ ├── 二叉树 │ │ ├── 二叉树其他题目.md │ │ ├── 二叉树的修改与构造.md │ │ ├── 二叉树的修改与构造2.md │ │ ├── 二叉树的公共祖先.md │ │ ├── 二叉树的属性.md │ │ ├── 二叉树的遍历方式.md │ │ └── 求二叉搜索树的属性.md │ ├── 动态规划 │ │ ├── 不同路径.md │ │ ├── 买卖股票的最佳时机.md │ │ ├── 动态规划理论基础.md │ │ ├── 打家劫舍系列.md │ │ ├── 爬楼梯.md │ │ ├── 背包系列.md │ │ └── 连续.md │ ├── 回溯 │ │ ├── N皇后.md │ │ ├── 回溯分割.md │ │ ├── 回溯排列.md │ │ ├── 子集.md │ │ ├── 子集II.md │ │ ├── 字符串的排列.md │ │ ├── 总结.md │ │ └── 组合总和.md │ ├── 字符串 │ │ ├── 千位分隔数.md │ │ ├── 反转字符串II.md │ │ ├── 回文系列.md │ │ ├── 字符串的排列.md │ │ ├── 最小覆盖子串.md │ │ └── 最长不含重复字符的子字符串.md │ ├── 数组 │ │ ├── 两数之和.md │ │ ├── 双指针.md │ │ ├── 合并两个有序数组.md │ │ └── 长度最小的子数组.md │ ├── 深度遍历 │ │ ├── 单词搜索.md │ │ ├── 岛屿数量.md │ │ └── 螺旋矩阵.md │ ├── 贪心算法 │ │ └── 贪心入门.md │ └── 链表 │ │ ├── k个一组翻转链表.md │ │ ├── 删除链表的倒数第n个结点.md │ │ ├── 反转链表.md │ │ ├── 环形链表II.md │ │ ├── 相交链表.md │ │ └── 移除链表元素.md ├── certificate.md ├── eventloop.md ├── gc.md ├── github-action.md ├── iframe 打开全屏无效.md ├── index.md ├── javascript.md ├── live-codes │ ├── h5-drag │ │ ├── code │ │ │ ├── App.tsx │ │ │ ├── Demo1.tsx │ │ │ ├── Demo2.tsx │ │ │ └── index.css │ │ └── h5-drag.md │ └── iframe-postmessage │ │ ├── code │ │ ├── Child.tsx │ │ └── Father.tsx │ │ └── iframe-postmessage.md ├── network-protocol │ ├── 01.网络模型.md │ ├── 02.tcp报文.md │ ├── 03.tcp三次握手.md │ ├── 04.tcp四次挥手.md │ ├── 05.tcp中syn攻击.md │ ├── 06.tcp和udp的区别.md │ ├── 07.http报文结构.md │ ├── 08.http的请求方法.md │ ├── 09.http状态码.md │ ├── 10.http请求体和请求头.md │ ├── 11.cookie.md │ ├── 12.http优缺点.md │ ├── 13.http队头阻塞.md │ ├── 14.https改进了什么.md │ ├── 15.https的tsl连接过程.md │ ├── 16.https证书.md │ ├── 17.http2新功能.md │ ├── 18.http2剖析.md │ ├── 19.http2服务器推送功能.md │ ├── 20.http3.md │ ├── cdn.md │ ├── dns.md │ └── index.md ├── nginx gzip 不生效.md ├── oAuth2-github.md ├── oAuth2.md ├── safe │ ├── cookie.md │ ├── csrf.md │ ├── jwt.md │ └── xss.md ├── ssh.md └── 浏览器缓存.md ├── package.json ├── pnpm-lock.yaml └── push.js /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvin0216/note/74be1e13c9bd9b94d9b73dc975c8aa22bae62638/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn.lock 3 | yarn.error 4 | /public 5 | .cache 6 | dist -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hello World 2 | 3 | Hi, I'm Alvin, a front-end developer who comes from China. -------------------------------------------------------------------------------- /code/achieve/01.手写快排.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 算法复杂度 O(nlog(n) 3 | */ 4 | function quickSort(arr) { 5 | let len = arr.length; 6 | if (len < 2) return arr; 7 | 8 | let pv = arr[0]; 9 | let [left, right] = [[], []]; 10 | 11 | for (let i = 1; i < arr.length; i++) { 12 | arr[i] < pv ? left.push(arr[i]) : right.push(arr[i]); 13 | } 14 | 15 | return [...quickSort(left), pv, ...quickSort(right)]; 16 | } 17 | -------------------------------------------------------------------------------- /code/achieve/02.手写深拷贝.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 如何写出一个惊艳面试官的深拷贝? https://juejin.cn/post/6844903929705136141 3 | */ 4 | 5 | // 需要深拷贝的类型,这里就列出一部分 6 | let deepTags = ['Array', 'Object', 'Set', 'Map']; 7 | 8 | function clone(target, map = new WeakMap()) { 9 | // 1. 类型判断 10 | let type = Object.prototype.toString.call(target).slice(8, -1); 11 | 12 | if (deepTags.includes(type)) { 13 | // 防止循环引用 14 | if (map.has(target)) return map.get(target); 15 | // 拿到构造函数新建一个实例 这里有可能是 Map 等结构 16 | let cloneTarget = new target.constructor(); 17 | map.set(target, cloneTarget); 18 | 19 | if (type === 'Set') { 20 | for (let key of target) { 21 | cloneTarget.add(clone(key, target)); 22 | } 23 | return cloneTarget; 24 | } 25 | 26 | if (type === 'Map') { 27 | for (let [key, value] of target) { 28 | cloneTarget.set(key, clone(value, map)); 29 | } 30 | return cloneTarget; 31 | } 32 | 33 | // 克隆对象 34 | for (const key in target) { 35 | cloneTarget[key] = clone(target[key]); 36 | } 37 | 38 | return cloneTarget; 39 | } 40 | 41 | return target; 42 | } 43 | 44 | let map = new Map([['age', 18]]); 45 | console.log(map, clone(map)); 46 | 47 | let obj = { age: 18 }; 48 | let obj2 = clone(obj); 49 | obj2.age = 20; 50 | 51 | console.log(obj, obj2); 52 | -------------------------------------------------------------------------------- /code/achieve/03.手写节流和防抖.js: -------------------------------------------------------------------------------- 1 | /** 防抖 */ 2 | function debounce(fn, delay) { 3 | let timer = null; 4 | return function (...args) { 5 | clearTimeout(timer); 6 | let that = this; 7 | timer = setTimeout(() => { 8 | fn.apply(that, args); 9 | }, delay); 10 | }; 11 | } 12 | 13 | /** 节流 */ 14 | function throttle(fn, delay) { 15 | let timer = null; 16 | return function (...args) { 17 | let that = this; 18 | if (!timer) { 19 | timer = setTimeout(() => { 20 | fn.apply(that, args); 21 | timer = null; 22 | }, delay); 23 | } 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /code/achieve/04.手写 call apply.js: -------------------------------------------------------------------------------- 1 | Function.prototype.mycall = function (obj, ...args) { 2 | // 默认 window 环境 3 | let context = obj || window; 4 | context._fn = this; 5 | let result = context._fn(...args); 6 | delete context._fn; 7 | return result; 8 | }; 9 | 10 | // apply 11 | Function.prototype.myApply = function (obj, args) { 12 | let context = obj || window; 13 | context._fn = this; 14 | let result = context._fn(...args); 15 | delete context._fn; 16 | return result; 17 | }; 18 | 19 | function fn(age) { 20 | console.log(this.name, age); 21 | } 22 | 23 | fn.mycall({ name: 'alvin' }, 18); 24 | fn.myApply({ name: 'alvin' }, [18]); 25 | -------------------------------------------------------------------------------- /code/achieve/05.手写 bind.js: -------------------------------------------------------------------------------- 1 | Function.prototype.myBind = function (obj, ...args) { 2 | let that = this; 3 | return function (...args2) { 4 | let context = obj || window; 5 | context._fn = that; 6 | let result = context._fn(...args, ...args2); 7 | delete context._fn; 8 | return result; 9 | }; 10 | }; 11 | 12 | // better 13 | 14 | Function.prototype.myBind = function (context = window, ...bindArgs) { 15 | let closure = this; // 使用闭包保存 this 16 | 17 | let fBound = function (...args) { 18 | // 当作为构造函数时,this 指向实例 19 | // 当作为普通函数时,this 指向 context 20 | return closure.call(this instanceof fBound ? this : context, ...bindArgs, ...args); 21 | }; 22 | 23 | let fNOP = function () {}; 24 | fNOP.prototype = this.prototype; 25 | fBound.prototype = new fNOP(); 26 | return fBound; 27 | }; 28 | 29 | function fn(age, count) { 30 | console.log(this.name, age, count); 31 | } 32 | 33 | let newFunc = fn.myBind({ name: 'alvin' }, 18); 34 | newFunc(20); 35 | 36 | // test 2 37 | let person = { name: 'alvin' }; 38 | function log(age, hobby) { 39 | console.log(this.name, age, hobby); 40 | } 41 | log.prototype.friend = 'kevin'; 42 | 43 | let logFunc = log.myBind(person, 18); 44 | const p = new logFunc('dance'); // undefined 18 dance ==> this 指向了 p 45 | console.log(p.friend); // kevin 46 | -------------------------------------------------------------------------------- /code/achieve/08.手写new.js: -------------------------------------------------------------------------------- 1 | function myNew(Father, ...args) { 2 | let obj = Object.create({}); 3 | obj.__proto__ = Father.prototype; 4 | let result = Father.apply(obj, args); 5 | return typeof result === 'object' ? result : obj; 6 | } 7 | 8 | function Person(name) { 9 | this.age = 18; 10 | this.name = name; 11 | } 12 | 13 | let p1 = new Person('foo'); 14 | 15 | console.log(p1, myNew(Person, 'boo')); 16 | -------------------------------------------------------------------------------- /code/achieve/09.手写instanceof.js: -------------------------------------------------------------------------------- 1 | function myInstanceof(a, b) { 2 | let current = a.__proto__; 3 | let prototype = b.prototype; 4 | 5 | while (true) { 6 | if (current === prototype) return true; 7 | if (current === null) return false; 8 | current = current.__proto__; 9 | } 10 | } 11 | 12 | console.log(myInstanceof('aaaa', String)); 13 | console.log(myInstanceof(1, String)); 14 | console.log(myInstanceof([], Array)); 15 | -------------------------------------------------------------------------------- /code/achieve/10.手写继承.js: -------------------------------------------------------------------------------- 1 | // function Father() {} 2 | // function Child() { 3 | // Father.call(this); 4 | // } 5 | 6 | // Child.prototype = new Father(); 7 | // Child.prototype.constructor = Child; 8 | 9 | function Father(age) { 10 | this.age = age; 11 | } 12 | 13 | function Child(name, age) { 14 | this.name = name; 15 | Father.call(this, age); 16 | } 17 | 18 | function Temp() {} 19 | Temp.prototype = new Father(); 20 | Child.prototype = new Temp(); 21 | Child.prototype.constructor = Child; 22 | 23 | // done 24 | -------------------------------------------------------------------------------- /code/achieve/11.手写useReducer.js: -------------------------------------------------------------------------------- 1 | /** 2 | import { useReducer } from 'react'; 3 | 4 | function reducer(action, state) { 5 | if (action.type === 'increment') return { count: state.count + 1 }; 6 | if (action.type === 'decrement') return { count: state.count - 1 }; 7 | 8 | return state; 9 | } 10 | 11 | export default function AppPage() { 12 | let [state, dispatch] = useReducer(reducer, { count: 0 }); 13 | return ; 14 | } 15 | */ 16 | 17 | function useReducer(reducer, initialState) { 18 | let [state, setState] = useState(initialState); 19 | 20 | const dispatch = (action) => { 21 | const newState = reducer(action, state); 22 | setState(newState); 23 | }; 24 | 25 | return [state, dispatch]; 26 | } 27 | -------------------------------------------------------------------------------- /code/achieve/12.手写useDidUpdate.js: -------------------------------------------------------------------------------- 1 | // useDidUpdate((preProps, prevState) => {}); 2 | 3 | function usePreious(state) { 4 | const ref = useRef(); 5 | 6 | useEffect(() => { 7 | ref.current = state; 8 | }); 9 | 10 | return ref.current; 11 | } 12 | 13 | export function useDidUpdate(effect, props, state) { 14 | const isFirstRender = useRef(true); 15 | const preProps = usePreious(props); 16 | const preState = usePreious(state); 17 | 18 | useEffect(() => { 19 | if (!isFirstRender.current) effect(preProps, preState); 20 | return () => {}; 21 | }); 22 | 23 | if (isFirstRender.current) isFirstRender.current = false; 24 | } 25 | 26 | // demo... 27 | export default function App() { 28 | const [count, setCount] = useState(1); 29 | 30 | useDidUpdate( 31 | (prevProps, prevState) => { 32 | console.log('prevState', prevState); 33 | }, 34 | undefined, 35 | count 36 | ); 37 | 38 | return ; 39 | } 40 | -------------------------------------------------------------------------------- /code/achieve/13.手写usePreious.js: -------------------------------------------------------------------------------- 1 | function usePreious(state) { 2 | const ref = useRef(); 3 | 4 | useEffect(() => { 5 | ref.current = state; 6 | }); 7 | 8 | return ref.current; 9 | } 10 | -------------------------------------------------------------------------------- /code/achieve/14.实现let关键字.js: -------------------------------------------------------------------------------- 1 | // 1、具有块级作用域 2 | // 2、没有变量提升 3 | // 3、具有暂时性死区 4 | // 4、不能重复声明 5 | 6 | (function () { 7 | var c = 3; 8 | console.log(c); //1 9 | })(); 10 | console.log(c); //c is not defined 11 | -------------------------------------------------------------------------------- /code/achieve/15.实现const关键字.js: -------------------------------------------------------------------------------- 1 | // configurable:是否可以被 delete 删除或者改变特征值 2 | // enumerable:是否能通过 for-in 循环遍历返回属性 3 | // writable:是否可以修改属性的值 4 | // value:保存这个属性的数据值 5 | 6 | function _const(key, value) { 7 | window[key] = value; 8 | Object.defineProperty(window, key, { 9 | enumerable: false, 10 | configurable: false, 11 | get: function () { 12 | return value; 13 | }, 14 | set: function (newValue) { 15 | if (newValue !== value) { 16 | throw TypeError('这是只读变量,不可修改'); 17 | } else { 18 | return value; 19 | } 20 | }, 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /code/achieve/16.实现sleep.js: -------------------------------------------------------------------------------- 1 | async function test() { 2 | console.log('Hello'); 3 | await sleep(1000); 4 | console.log('world!'); 5 | } 6 | 7 | function sleep(ms) { 8 | return new Promise((resolve) => setTimeout(resolve, ms)); 9 | } 10 | 11 | test(); 12 | -------------------------------------------------------------------------------- /code/achieve/17.实现compose.js: -------------------------------------------------------------------------------- 1 | function compose(...funcs) { 2 | if (funcs.length === 1) { 3 | return funcs[0]; 4 | } 5 | return funcs.reduce( 6 | (a, b) => 7 | (...args) => 8 | a(b(...args)) 9 | ); 10 | } 11 | 12 | function a() { 13 | console.log('a'); 14 | } 15 | 16 | function b() { 17 | console.log('b'); 18 | } 19 | 20 | let fn = compose(a, b); 21 | 22 | fn(); 23 | -------------------------------------------------------------------------------- /code/commonJS/commonjs.test.js: -------------------------------------------------------------------------------- 1 | // describe('测试 commonjs, () => { 2 | // it('') 3 | // }); 4 | 5 | it('commonjs demo 测试', () => { 6 | console.log(222); 7 | }); 8 | -------------------------------------------------------------------------------- /code/commonJS/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "commonjs", 3 | "version": "1.0.0", 4 | "description": "> TODO: description", 5 | "author": "alvin ", 6 | "homepage": "", 7 | "license": "ISC", 8 | "main": "lib/commonJS.js", 9 | "directories": { 10 | "lib": "lib", 11 | "test": "__tests__" 12 | }, 13 | "files": [ 14 | "lib" 15 | ], 16 | "scripts": { 17 | "test": "echo \"Error: run tests from root\" && exit 1" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /code/commonJS/src/utils.js: -------------------------------------------------------------------------------- 1 | console.log('run utils'); 2 | module.exports = 'utils'; 3 | -------------------------------------------------------------------------------- /code/github-oauth2/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | github 授权登录 8 | 9 | 10 | 11 | 12 | 13 |

14 | 
15 |   
44 | 
45 | 
46 | 


--------------------------------------------------------------------------------
/code/github-oauth2/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "github-oauth2",
 3 |   "version": "1.0.0",
 4 |   "description": "> TODO: description",
 5 |   "author": "alvin ",
 6 |   "homepage": "https://github.com/alvin0216/awsome-demo#readme",
 7 |   "license": "ISC",
 8 |   "repository": {
 9 |     "type": "git",
10 |     "url": "git+https://github.com/alvin0216/awsome-demo.git"
11 |   },
12 |   "scripts": {
13 |     "dev": "nodemon server.js"
14 |   },
15 |   "bugs": {
16 |     "url": "https://github.com/alvin0216/awsome-demo/issues"
17 |   },
18 |   "dependencies": {
19 |     "axios": "^0.21.1",
20 |     "koa": "^2.13.1",
21 |     "koa-router": "^10.0.0",
22 |     "koa-static": "^5.0.0",
23 |     "nodemon": "^2.0.7"
24 |   }
25 | }
26 | 


--------------------------------------------------------------------------------
/code/github-oauth2/server.js:
--------------------------------------------------------------------------------
 1 | const Koa = require('koa');
 2 | const path = require('path');
 3 | const Router = require('koa-router');
 4 | const koaStatic = require('koa-static');
 5 | const axios = require('axios');
 6 | 
 7 | const app = new Koa();
 8 | const router = new Router();
 9 | 
10 | app.use(koaStatic(path.resolve(__dirname)));
11 | 
12 | function decodeQuery(url) {
13 |   const params = {};
14 |   const paramsStr = url.replace(/\.*\?/, ''); // a=1&b=2&c=&d=xxx&e
15 |   paramsStr.split('&').forEach((v) => {
16 |     const d = v.split('=');
17 |     if (d[1] && d[0]) params[d[0]] = d[1];
18 |   });
19 |   return params;
20 | }
21 | 
22 | router.get('/github', async (ctx) => {
23 |   const { code } = ctx.query;
24 | 
25 |   const client_id = '6a45431255c2bce17377';
26 |   const client_secret = '73c0dae65694927d43c5ed053d4c73143eea62cd';
27 |   const access_token_url = 'https://github.com/login/oauth/access_token';
28 |   const fetch_user_url = 'https://api.github.com/user';
29 | 
30 |   // 拿到 code, 请求 access_token
31 |   const result = await axios.post(access_token_url, {
32 |     code,
33 |     client_id,
34 |     client_secret,
35 |   });
36 | 
37 |   // 返回带有 access_token 的字符串
38 |   // access_token=d59801d53b60486c8bcaba9e49858b5a3c04695f&scope=&token_type=bearer
39 |   const callbackUrl = result.data;
40 |   const { access_token } = decodeQuery(callbackUrl);
41 | 
42 |   // 拿 token 取用户的数据
43 |   const userInfo = await axios.get(
44 |     `${fetch_user_url}?access_token=${access_token}`,
45 |   );
46 |   ctx.body = userInfo.data;
47 | });
48 | 
49 | app.use(router.routes(), router.allowedMethods());
50 | 
51 | app.listen(3000, () => {
52 |   console.log('please open http://localhost:3000');
53 | });
54 | 


--------------------------------------------------------------------------------
/code/github-webhook-demo/app.js:
--------------------------------------------------------------------------------
 1 | const chalk = require('chalk');
 2 | const http = require('http');
 3 | const createHandler = require('node-github-webhook');
 4 | const exec = require('child_process').exec;
 5 | const handler = createHandler([
 6 |   // 多个仓库
 7 |   { path: '/note', secret: 'xxx' },
 8 | ]);
 9 | const log = console.log;
10 | 
11 | http
12 |   .createServer(function (req, res) {
13 |     handler(req, res, function (err) {
14 |       res.statusCode = 404;
15 |       res.end('no such location');
16 |     });
17 |   })
18 |   .listen(5000);
19 | 
20 | handler.on('error', function (err) {
21 |   console.error('Error:', err.message);
22 | });
23 | 
24 | handler.on('push', function (event) {
25 |   log(
26 |     chalk.blue('Received a push event for %s to %s'),
27 |     event.payload.repository.name,
28 |     event.payload.ref,
29 |   );
30 | 
31 |   switch (event.path) {
32 |     case '/note':
33 |       if (event.payload.ref === 'refs/heads/master') {
34 |         execute(
35 |           `
36 |           cd /code/note
37 |           git pull
38 |           tyarn
39 |           tyarn run build
40 |           rm -rf /site/note
41 |           cp -r public /site/note
42 |         `,
43 |           () => {
44 |             console.log('====> note 构建成功');
45 |           },
46 |         );
47 |       }
48 |       break;
49 | 
50 |     default:
51 |       // 处理其他
52 |       console.log('======= 暂无配置 ======');
53 |       break;
54 |   }
55 | });
56 | 
57 | function execute(cmd, onSuccess) {
58 |   exec(cmd, function (error, stdout, stderr) {
59 |     if (error) {
60 |       log(chalk.bold.red(`shell 执行失败`, error));
61 |     } else {
62 |       onSuccess && onSuccess();
63 |     }
64 |   });
65 | }
66 | 
67 | log(chalk.white.bold.bgMagenta('webhook 启动.......'));
68 | 


--------------------------------------------------------------------------------
/code/h5-drag-demo/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | dist
4 | dist-ssr
5 | *.local
6 | 


--------------------------------------------------------------------------------
/code/h5-drag-demo/README.md:
--------------------------------------------------------------------------------
 1 | ---
 2 | title: H5 拖拽
 3 | ---
 4 | 
 5 | 相关资料
 6 | 
 7 | - [html5 原生拖拽研究](https://zhuanlan.zhihu.com/p/101307532)
 8 | - [React 实现拖拽功能](https://www.cnblogs.com/wenruo/p/10225377.html)
 9 | 
10 | 
11 | 
12 | - 区域外:dragleave,离开范围
13 | - 区域内:dragenter,用来确定放置目标是否接受放置。
14 | - 区域内移动:dragover,用来确定给用户显示怎样的反馈信息
15 | - 完成拖拽(落下)drop:,允许放置对象。
16 | 


--------------------------------------------------------------------------------
/code/h5-drag-demo/index.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |   
 4 |     
 5 |     
 6 |     
 7 |     Vite App
 8 |   
 9 |   
10 |     
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /code/h5-drag-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "h5-drag-demo", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "tsc && vite build", 7 | "serve": "vite preview" 8 | }, 9 | "dependencies": { 10 | "react": "^17.0.0", 11 | "react-dom": "^17.0.0" 12 | }, 13 | "devDependencies": { 14 | "@types/react": "^17.0.0", 15 | "@types/react-dom": "^17.0.0", 16 | "@vitejs/plugin-react-refresh": "^1.3.1", 17 | "typescript": "^4.1.2", 18 | "vite": "^2.3.0" 19 | } 20 | } -------------------------------------------------------------------------------- /code/h5-drag-demo/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import './index.css'; 3 | 4 | import Demo1 from './Demo1'; 5 | import Demo2 from './Demo2'; 6 | 7 | interface AppProps {} 8 | 9 | const App: React.FC = props => { 10 | return ( 11 |
12 |

H5 拖拽

13 |
14 | 15 | 16 |
17 |
18 | ); 19 | }; 20 | 21 | export default App; 22 | -------------------------------------------------------------------------------- /code/h5-drag-demo/src/Demo1.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import styles from './styles.less'; 3 | 4 | interface Demo1Props {} 5 | 6 | const Demo1: React.FC = props => { 7 | const onDragStart = (e: React.DragEvent) => { 8 | // @ts-ignore 9 | e.dataTransfer.setData('index', e.target.dataset.index); 10 | }; 11 | 12 | const onDragOver = (e: React.DragEvent) => { 13 | e.preventDefault(); 14 | }; 15 | 16 | /** 拖拽到该区域 */ 17 | const onDrop = (e: React.DragEvent) => { 18 | alert(e.dataTransfer.getData('index')); 19 | }; 20 | 21 | return ( 22 |
23 |

Demo1 - 基础拖拽

24 |
25 |
31 | 源对象 32 |
33 |
34 | 目标对象 35 |
36 |
37 |
38 | ); 39 | }; 40 | 41 | export default Demo1; 42 | -------------------------------------------------------------------------------- /code/h5-drag-demo/src/Demo2.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | 3 | interface Demo2Props {} 4 | 5 | const Demo2: React.FC = props => { 6 | const onDragStart = (e: React.DragEvent) => { 7 | // @ts-ignore 8 | e.dataTransfer.setData('index', e.target.dataset.index); 9 | }; 10 | 11 | const onDragOver = (e: React.DragEvent) => { 12 | // 13 | e.dataTransfer.dropEffect = 'link'; // 修改移动时的光标样式 默认 copy 14 | e.preventDefault(); 15 | }; 16 | 17 | /** 拖拽到该区域 */ 18 | const onDrop = (e: React.DragEvent) => { 19 | alert(e.dataTransfer.getData('index')); 20 | }; 21 | 22 | return ( 23 |
24 |

Demo2 - dropEffect 设置移动的光标

25 |
26 |
32 | 源对象 33 |
34 |
35 | 目标对象 36 |
37 |
38 |
39 | ); 40 | }; 41 | 42 | export default Demo2; 43 | -------------------------------------------------------------------------------- /code/h5-drag-demo/src/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /code/h5-drag-demo/src/index.css: -------------------------------------------------------------------------------- 1 | .flex { 2 | display: flex; 3 | } 4 | 5 | .box1 { 6 | width: 100px; 7 | height: 100px; 8 | margin-right: 50px; 9 | background-color: cyan; 10 | } 11 | 12 | .box2 { 13 | width: 200px; 14 | height: 200px; 15 | background-color: #ccc; 16 | } 17 | 18 | .demo-list .demo { 19 | padding: 20px; 20 | margin-right: 20px; 21 | box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%); 22 | } 23 | -------------------------------------------------------------------------------- /code/h5-drag-demo/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root'), 10 | ); 11 | -------------------------------------------------------------------------------- /code/h5-drag-demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 5 | "types": ["vite/client"], 6 | "allowJs": false, 7 | "skipLibCheck": false, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react" 18 | }, 19 | "include": ["./src"] 20 | } 21 | -------------------------------------------------------------------------------- /code/h5-drag-demo/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import reactRefresh from '@vitejs/plugin-react-refresh'; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [reactRefresh()], 7 | }); 8 | -------------------------------------------------------------------------------- /code/learn-angular/.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # For the full list of supported browsers by the Angular framework, please see: 6 | # https://angular.io/guide/browser-support 7 | 8 | # You can see what browsers were selected by your queries by running: 9 | # npx browserslist 10 | 11 | last 1 Chrome version 12 | last 1 Firefox version 13 | last 2 Edge major versions 14 | last 2 Safari major versions 15 | last 2 iOS major versions 16 | Firefox ESR 17 | -------------------------------------------------------------------------------- /code/learn-angular/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /code/learn-angular/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # Compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | /bazel-out 8 | 9 | # Node 10 | /node_modules 11 | npm-debug.log 12 | yarn-error.log 13 | 14 | # IDEs and editors 15 | .idea/ 16 | .project 17 | .classpath 18 | .c9/ 19 | *.launch 20 | .settings/ 21 | *.sublime-workspace 22 | 23 | # Visual Studio Code 24 | .vscode/* 25 | !.vscode/settings.json 26 | !.vscode/tasks.json 27 | !.vscode/launch.json 28 | !.vscode/extensions.json 29 | .history/* 30 | 31 | # Miscellaneous 32 | /.angular/cache 33 | .sass-cache/ 34 | /connect.lock 35 | /coverage 36 | /libpeerconnection.log 37 | testem.log 38 | /typings 39 | 40 | # System files 41 | .DS_Store 42 | Thumbs.db 43 | -------------------------------------------------------------------------------- /code/learn-angular/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 3 | "recommendations": ["angular.ng-template"] 4 | } 5 | -------------------------------------------------------------------------------- /code/learn-angular/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "name": "ng serve", 7 | "type": "pwa-chrome", 8 | "request": "launch", 9 | "preLaunchTask": "npm: start", 10 | "url": "http://localhost:4200/" 11 | }, 12 | { 13 | "name": "ng test", 14 | "type": "chrome", 15 | "request": "launch", 16 | "preLaunchTask": "npm: test", 17 | "url": "http://localhost:9876/debug.html" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /code/learn-angular/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 3 | "version": "2.0.0", 4 | "tasks": [ 5 | { 6 | "type": "npm", 7 | "script": "start", 8 | "isBackground": true, 9 | "problemMatcher": { 10 | "owner": "typescript", 11 | "pattern": "$tsc", 12 | "background": { 13 | "activeOnStart": true, 14 | "beginsPattern": { 15 | "regexp": "(.*?)" 16 | }, 17 | "endsPattern": { 18 | "regexp": "bundle generation complete" 19 | } 20 | } 21 | } 22 | }, 23 | { 24 | "type": "npm", 25 | "script": "test", 26 | "isBackground": true, 27 | "problemMatcher": { 28 | "owner": "typescript", 29 | "pattern": "$tsc", 30 | "background": { 31 | "activeOnStart": true, 32 | "beginsPattern": { 33 | "regexp": "(.*?)" 34 | }, 35 | "endsPattern": { 36 | "regexp": "bundle generation complete" 37 | } 38 | } 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /code/learn-angular/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvin0216/note/74be1e13c9bd9b94d9b73dc975c8aa22bae62638/code/learn-angular/README.md -------------------------------------------------------------------------------- /code/learn-angular/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | jasmine: { 17 | // you can add configuration options for Jasmine here 18 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html 19 | // for example, you can disable the random execution with `random: false` 20 | // or set a specific seed with `seed: 4321` 21 | }, 22 | clearContext: false // leave Jasmine Spec Runner output visible in browser 23 | }, 24 | jasmineHtmlReporter: { 25 | suppressAll: true // removes the duplicated traces 26 | }, 27 | coverageReporter: { 28 | dir: require('path').join(__dirname, './coverage/demo1'), 29 | subdir: '.', 30 | reporters: [ 31 | { type: 'html' }, 32 | { type: 'text-summary' } 33 | ] 34 | }, 35 | reporters: ['progress', 'kjhtml'], 36 | port: 9876, 37 | colors: true, 38 | logLevel: config.LOG_INFO, 39 | autoWatch: true, 40 | browsers: ['Chrome'], 41 | singleRun: false, 42 | restartOnFileChange: true 43 | }); 44 | }; 45 | -------------------------------------------------------------------------------- /code/learn-angular/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "learn-angular", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "dev": "npm run start", 8 | "build": "ng build", 9 | "watch": "ng build --watch --configuration development", 10 | "test": "ng test" 11 | }, 12 | "private": true, 13 | "dependencies": { 14 | "@angular/animations": "^14.1.0", 15 | "@angular/common": "^14.1.0", 16 | "@angular/compiler": "^14.1.0", 17 | "@angular/core": "^14.1.0", 18 | "@angular/forms": "^14.1.0", 19 | "@angular/platform-browser": "^14.1.0", 20 | "@angular/platform-browser-dynamic": "^14.1.0", 21 | "@angular/router": "^14.1.0", 22 | "rxjs": "~7.5.0", 23 | "tslib": "^2.3.0", 24 | "zone.js": "~0.11.4" 25 | }, 26 | "devDependencies": { 27 | "@angular-devkit/build-angular": "^14.1.3", 28 | "@angular/cli": "~14.1.3", 29 | "@angular/compiler-cli": "^14.1.0", 30 | "@types/jasmine": "~4.0.0", 31 | "jasmine-core": "~4.2.0", 32 | "karma": "~6.4.0", 33 | "karma-chrome-launcher": "~3.1.0", 34 | "karma-coverage": "~2.2.0", 35 | "karma-jasmine": "~5.1.0", 36 | "karma-jasmine-html-reporter": "~2.0.0", 37 | "typescript": "~4.7.2" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { DirectComponent } from './direct/direct.component'; 5 | import { FormComponent } from './form/form.component'; 6 | import { InteractionComponent } from './interaction/interaction.component'; 7 | import { LifeCycleComponent } from './life-cycle/life-cycle.component'; 8 | import { Opt1Component } from './operation/opt1/opt1.component'; 9 | import { PipeComponent } from './pipe/pipe.component'; 10 | import { ServiceDemoComponent } from './service-demo/service-demo.component'; 11 | import { TemplateComponent } from './template/template.component'; 12 | 13 | export const routes: Routes = [ 14 | { path: 'template', component: TemplateComponent, title: '模板' }, 15 | { path: 'form', component: FormComponent, title: '表单' }, 16 | { path: 'pipe', component: PipeComponent, title: '管道' }, 17 | { path: 'life-cycle', component: LifeCycleComponent, title: '生命周期' }, 18 | { path: 'interaction', component: InteractionComponent, title: '组件交互' }, 19 | { path: 'service', component: ServiceDemoComponent, title: '服务' }, 20 | { path: 'direct', component: DirectComponent, title: '指令' }, 21 | { path: 'opt1', component: Opt1Component, title: 'ng-module' }, 22 | ]; 23 | 24 | @NgModule({ 25 | imports: [RouterModule.forRoot(routes)], 26 | exports: [RouterModule], 27 | }) 28 | export class AppRoutingModule {} 29 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 | 15 | 16 | 17 |
18 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | .app-container { 2 | display: flex; 3 | } 4 | 5 | .app-nav { 6 | width: 200px; 7 | height: 98vh; 8 | margin-right: 24px; 9 | border-right: 1px solid #ccc; 10 | } 11 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { AppComponent } from './app.component'; 4 | 5 | describe('AppComponent', () => { 6 | beforeEach(async () => { 7 | await TestBed.configureTestingModule({ 8 | imports: [ 9 | RouterTestingModule 10 | ], 11 | declarations: [ 12 | AppComponent 13 | ], 14 | }).compileComponents(); 15 | }); 16 | 17 | it('should create the app', () => { 18 | const fixture = TestBed.createComponent(AppComponent); 19 | const app = fixture.componentInstance; 20 | expect(app).toBeTruthy(); 21 | }); 22 | 23 | it(`should have as title 'demo1'`, () => { 24 | const fixture = TestBed.createComponent(AppComponent); 25 | const app = fixture.componentInstance; 26 | expect(app.title).toEqual('demo1'); 27 | }); 28 | 29 | it('should render title', () => { 30 | const fixture = TestBed.createComponent(AppComponent); 31 | fixture.detectChanges(); 32 | const compiled = fixture.nativeElement as HTMLElement; 33 | expect(compiled.querySelector('.content span')?.textContent).toContain('demo1 app is running!'); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | import { routes } from './app-routing.module'; 4 | 5 | @Component({ 6 | selector: 'app-root', 7 | templateUrl: './app.component.html', 8 | styleUrls: ['./app.component.scss'], 9 | }) 10 | export class AppComponent { 11 | title = 'demo1'; 12 | routes = routes; 13 | } 14 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/direct/direct.component.html: -------------------------------------------------------------------------------- 1 |

direct works!

2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/direct/direct.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvin0216/note/74be1e13c9bd9b94d9b73dc975c8aa22bae62638/code/learn-angular/src/app/direct/direct.component.scss -------------------------------------------------------------------------------- /code/learn-angular/src/app/direct/direct.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { DirectComponent } from './direct.component'; 4 | 5 | describe('DirectComponent', () => { 6 | let component: DirectComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ DirectComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(DirectComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/direct/direct.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-direct', 5 | templateUrl: './direct.component.html', 6 | styleUrls: ['./direct.component.scss'], 7 | }) 8 | export class DirectComponent implements OnInit { 9 | constructor() {} 10 | 11 | ngOnInit(): void {} 12 | } 13 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/direct/upper.directive.spec.ts: -------------------------------------------------------------------------------- 1 | import { UpperDirective } from './upper.directive'; 2 | 3 | describe('UpperDirective', () => { 4 | it('should create an instance', () => { 5 | const directive = new UpperDirective(); 6 | expect(directive).toBeTruthy(); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/direct/upper.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, ElementRef } from '@angular/core'; 2 | 3 | @Directive({ 4 | selector: '[appUpper]', 5 | }) 6 | export class UpperDirective { 7 | constructor(e: ElementRef) { 8 | e.nativeElement.value = e.nativeElement.value.toUpperCase(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/form/form.component.html: -------------------------------------------------------------------------------- 1 |

base

2 | 3 | 4 | 5 | 6 | 7 |

ngModel

8 | 9 | 10 | 11 | 12 | 13 |

formControl 表单控件

14 | 15 | 16 | 17 | 18 | 19 |

Form Group

20 | 21 |
22 | 23 | 24 | 25 |
26 | 27 |

Form 校验

28 | 48 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/form/form.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvin0216/note/74be1e13c9bd9b94d9b73dc975c8aa22bae62638/code/learn-angular/src/app/form/form.component.scss -------------------------------------------------------------------------------- /code/learn-angular/src/app/form/form.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { FormComponent } from './form.component'; 4 | 5 | describe('FormComponent', () => { 6 | let component: FormComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ FormComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(FormComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/form/form.component.ts: -------------------------------------------------------------------------------- 1 | import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; 2 | import { Component, OnInit } from '@angular/core'; 3 | 4 | @Component({ 5 | selector: 'app-form', 6 | templateUrl: './form.component.html', 7 | styleUrls: ['./form.component.scss'], 8 | }) 9 | export class FormComponent implements OnInit { 10 | title = 'title'; 11 | desc = new FormControl('desc'); 12 | 13 | loginForm = new FormGroup({ 14 | name: new FormControl(''), 15 | pwd: new FormControl(''), 16 | }); 17 | 18 | hero = { name: '' }; 19 | 20 | validateForm = this.fb.group({}); 21 | 22 | constructor(private fb: FormBuilder) {} 23 | 24 | ngOnInit(): void {} 25 | 26 | log(v: any) { 27 | console.log('log:', v); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/interaction/interaction.component.html: -------------------------------------------------------------------------------- 1 |

interaction works!

2 | 3 | 4 | 9 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/interaction/interaction.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvin0216/note/74be1e13c9bd9b94d9b73dc975c8aa22bae62638/code/learn-angular/src/app/interaction/interaction.component.scss -------------------------------------------------------------------------------- /code/learn-angular/src/app/interaction/interaction.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { InteractionComponent } from './interaction.component'; 4 | 5 | describe('InteractionComponent', () => { 6 | let component: InteractionComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ InteractionComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(InteractionComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/interaction/interaction.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewChild } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-interaction', 5 | templateUrl: './interaction.component.html', 6 | styleUrls: ['./interaction.component.scss'], 7 | }) 8 | export class InteractionComponent implements OnInit { 9 | count = 1; 10 | 11 | // 通过 viewChild 访问子组件实例 12 | // 13 | 14 | @ViewChild('title') child: any; 15 | constructor() {} 16 | 17 | ngOnInit(): void {} 18 | 19 | handleClick() { 20 | this.child.setTitle(`title is setted ${Date.now()}`); 21 | } 22 | 23 | increaseCount(c: number) { 24 | this.count = c; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/interaction/title/title.component.html: -------------------------------------------------------------------------------- 1 | 4 | 5 |

{{ title }}

6 | 7 |

从父组件接收的 count:{{ count }}

8 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/interaction/title/title.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvin0216/note/74be1e13c9bd9b94d9b73dc975c8aa22bae62638/code/learn-angular/src/app/interaction/title/title.component.scss -------------------------------------------------------------------------------- /code/learn-angular/src/app/interaction/title/title.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { TitleComponent } from './title.component'; 4 | 5 | describe('TitleComponent', () => { 6 | let component: TitleComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ TitleComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(TitleComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/interaction/title/title.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-title', 5 | templateUrl: './title.component.html', 6 | styleUrls: ['./title.component.scss'], 7 | }) 8 | export class TitleComponent implements OnInit { 9 | @Input() count!: number; 10 | @Output() increaseCount = new EventEmitter(); 11 | 12 | title = 'This is title'; 13 | 14 | constructor() {} 15 | 16 | ngOnInit(): void {} 17 | 18 | setTitle(str: string): void { 19 | this.title = str; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/life-cycle/life-cycle-demo/life-cycle-demo.component.html: -------------------------------------------------------------------------------- 1 |
    2 |
  • {{ l }}
  • 3 |
4 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/life-cycle/life-cycle-demo/life-cycle-demo.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvin0216/note/74be1e13c9bd9b94d9b73dc975c8aa22bae62638/code/learn-angular/src/app/life-cycle/life-cycle-demo/life-cycle-demo.component.scss -------------------------------------------------------------------------------- /code/learn-angular/src/app/life-cycle/life-cycle-demo/life-cycle-demo.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { LifeCycleDemoComponent } from './life-cycle-demo.component'; 4 | 5 | describe('LifeCycleDemoComponent', () => { 6 | let component: LifeCycleDemoComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ LifeCycleDemoComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(LifeCycleDemoComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/life-cycle/life-cycle.component.html: -------------------------------------------------------------------------------- 1 |

life-cycle works!

2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/life-cycle/life-cycle.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvin0216/note/74be1e13c9bd9b94d9b73dc975c8aa22bae62638/code/learn-angular/src/app/life-cycle/life-cycle.component.scss -------------------------------------------------------------------------------- /code/learn-angular/src/app/life-cycle/life-cycle.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { LifeCycleComponent } from './life-cycle.component'; 4 | 5 | describe('LifeCycleComponent', () => { 6 | let component: LifeCycleComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ LifeCycleComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(LifeCycleComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/life-cycle/life-cycle.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-life-cycle', 5 | templateUrl: './life-cycle.component.html', 6 | styleUrls: ['./life-cycle.component.scss'], 7 | }) 8 | export class LifeCycleComponent implements OnInit { 9 | count = 1; 10 | 11 | constructor() {} 12 | 13 | ngOnInit(): void {} 14 | 15 | increase() { 16 | this.count++; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/operation/operation.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { NgModule } from '@angular/core'; 3 | 4 | import { Opt1Component } from './opt1/opt1.component'; 5 | 6 | @NgModule({ 7 | declarations: [Opt1Component], 8 | imports: [CommonModule], 9 | // 给其他 modules 使用 10 | exports: [Opt1Component], 11 | }) 12 | export class OperationModule {} 13 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/operation/opt1/opt1.component.html: -------------------------------------------------------------------------------- 1 |

opt1 works!

2 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/operation/opt1/opt1.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvin0216/note/74be1e13c9bd9b94d9b73dc975c8aa22bae62638/code/learn-angular/src/app/operation/opt1/opt1.component.scss -------------------------------------------------------------------------------- /code/learn-angular/src/app/operation/opt1/opt1.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { Opt1Component } from './opt1.component'; 4 | 5 | describe('Opt1Component', () => { 6 | let component: Opt1Component; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ Opt1Component ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(Opt1Component); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/operation/opt1/opt1.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-opt1', 5 | templateUrl: './opt1.component.html', 6 | styleUrls: ['./opt1.component.scss'] 7 | }) 8 | export class Opt1Component implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit(): void { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/pipe/pipe.component.html: -------------------------------------------------------------------------------- 1 |

pipe works!

2 | 3 |
    4 |
  • {{ "alvin" | uppercase }} {{ "ALVIN" | lowercase }}
  • 5 |
  • {{ now | date: "yyyy-MM-dd HH:mm:ss" }}
  • 6 |
7 | 8 |
    9 | 10 |
  • A: {{ a | currency }}
  • 11 | 12 | 13 |
  • A: {{ a | currency: "CAD" }}
  • 14 | 15 | 16 |
  • A: {{ a | currency: "CAD":"code" }}
  • 17 | 18 | 19 |
  • B: {{ b | currency: "CAD":"symbol":"4.2-2" }}
  • 20 | 21 | 22 |
  • B: {{ b | currency: "CAD":"symbol-narrow":"4.2-2" }}
  • 23 | 24 | 25 |
  • B: {{ b | currency: "CAD":"symbol":"4.2-2":"fr" }}
  • 26 | 27 | 28 |
  • B: {{ b | currency: "CLP" }}
  • 29 |
30 | 31 |

自定义管道

32 | 33 |

{{ "hello" | test }}

34 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/pipe/pipe.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvin0216/note/74be1e13c9bd9b94d9b73dc975c8aa22bae62638/code/learn-angular/src/app/pipe/pipe.component.scss -------------------------------------------------------------------------------- /code/learn-angular/src/app/pipe/pipe.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { PipeComponent } from './pipe.component'; 4 | 5 | describe('PipeComponent', () => { 6 | let component: PipeComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ PipeComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(PipeComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/pipe/pipe.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-pipe', 5 | templateUrl: './pipe.component.html', 6 | styleUrls: ['./pipe.component.scss'], 7 | }) 8 | export class PipeComponent implements OnInit { 9 | now = Date.now(); 10 | a: number = 0.259; 11 | b: number = 1.3495; 12 | 13 | constructor() {} 14 | 15 | ngOnInit(): void {} 16 | } 17 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/pipe/test.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ name: 'test' }) 4 | export class TestPipe implements PipeTransform { 5 | transform(value: unknown, ...args: unknown[]): unknown { 6 | return `管道输出 >>> ${value}`; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/service-demo/service-child/service-child.component.html: -------------------------------------------------------------------------------- 1 |

service-child works!

2 | 3 |
    4 |
  • {{ item }}
  • 5 |
6 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/service-demo/service-child/service-child.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvin0216/note/74be1e13c9bd9b94d9b73dc975c8aa22bae62638/code/learn-angular/src/app/service-demo/service-child/service-child.component.scss -------------------------------------------------------------------------------- /code/learn-angular/src/app/service-demo/service-child/service-child.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ServiceChildComponent } from './service-child.component'; 4 | 5 | describe('ServiceChildComponent', () => { 6 | let component: ServiceChildComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ ServiceChildComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(ServiceChildComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/service-demo/service-child/service-child.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | import { ListService } from '../../services/list.service'; 4 | 5 | @Component({ 6 | selector: 'app-service-child', 7 | templateUrl: './service-child.component.html', 8 | styleUrls: ['./service-child.component.scss'], 9 | }) 10 | export class ServiceChildComponent implements OnInit { 11 | list: string[] = []; 12 | 13 | constructor(private listService: ListService) {} 14 | 15 | ngOnInit(): void { 16 | this.list = this.listService.getList(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/service-demo/service-demo.component.html: -------------------------------------------------------------------------------- 1 |

service-demo works!

2 | 3 | 4 | 5 |
    6 |
  • {{ item }}
  • 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/service-demo/service-demo.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvin0216/note/74be1e13c9bd9b94d9b73dc975c8aa22bae62638/code/learn-angular/src/app/service-demo/service-demo.component.scss -------------------------------------------------------------------------------- /code/learn-angular/src/app/service-demo/service-demo.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ServiceDemoComponent } from './service-demo.component'; 4 | 5 | describe('ServiceDemoComponent', () => { 6 | let component: ServiceDemoComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ ServiceDemoComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(ServiceDemoComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/service-demo/service-demo.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | import { ListService } from '../services/list.service'; 4 | 5 | @Component({ 6 | selector: 'app-service-demo', 7 | templateUrl: './service-demo.component.html', 8 | styleUrls: ['./service-demo.component.scss'], 9 | }) 10 | export class ServiceDemoComponent implements OnInit { 11 | list: string[] = []; 12 | 13 | constructor(private listService: ListService) {} 14 | 15 | ngOnInit(): void { 16 | this.getList(); 17 | } 18 | 19 | addItem() { 20 | this.listService.addListItem('Vue'); 21 | } 22 | 23 | getList() { 24 | this.list = this.listService.getList(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/services/list.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { ListService } from './list.service'; 4 | 5 | describe('ListService', () => { 6 | let service: ListService; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(ListService); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/services/list.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | // Injectable 装饰器声明服务 4 | @Injectable({ 5 | // 作用域设定 root 表示默认注入,注入到 AppModule 里面 6 | providedIn: 'root', 7 | }) 8 | export class ListService { 9 | list: string[] = ['Angular', 'React']; 10 | constructor() {} 11 | 12 | getList() { 13 | return this.list; 14 | } 15 | 16 | addListItem(str: string) { 17 | this.list.push(str); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/template/counter/counter.component.html: -------------------------------------------------------------------------------- 1 |

counter works!

2 | 3 | 6 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/template/counter/counter.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvin0216/note/74be1e13c9bd9b94d9b73dc975c8aa22bae62638/code/learn-angular/src/app/template/counter/counter.component.scss -------------------------------------------------------------------------------- /code/learn-angular/src/app/template/counter/counter.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | EventEmitter, 4 | Input, 5 | OnInit, 6 | Output, 7 | } from '@angular/core'; 8 | 9 | @Component({ 10 | selector: 'app-counter', 11 | templateUrl: './counter.component.html', 12 | styleUrls: ['./counter.component.scss'], 13 | }) 14 | export class CounterComponent implements OnInit { 15 | @Input() count!: number; 16 | @Output() increaseCount = new EventEmitter(); 17 | 18 | constructor() {} 19 | 20 | ngOnInit(): void {} 21 | } 22 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/template/template.component.html: -------------------------------------------------------------------------------- 1 |

类、样式绑定

2 | 3 | 4 |
    5 |
  • 类绑定
  • 6 |
  • 类绑定
  • 7 |
  • 类绑定
  • 8 |
9 | 10 |
    11 |
  • style
  • 12 |
  • style
  • 13 |
  • 14 | foo 15 |
  • 16 |
17 | 18 |

列表

19 | 20 |
    21 |
  • 22 | 事件绑定 23 | 24 |
  • 25 |
  • 35 | {{ idx }}-{{ item.name }} {{ odd ? "奇数" : "偶数" }} 36 | {{ isFirst ? "isFirst" : "" }} 37 | {{ isLast ? "isLast" : "" }} 38 |
  • 39 |
40 | 41 |

条件判断

42 | 43 |

count 偶数

44 | 45 |

count 奇数

46 |
47 | 48 | 55 | 56 | 57 | switch 58 |
59 |
count 条件1
60 |
count 条件2
61 |
count 条件3
62 |
count default
63 |
64 | 65 |

传值

66 | 67 | 68 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/template/template.component.scss: -------------------------------------------------------------------------------- 1 | .red { 2 | color: red; 3 | } 4 | 5 | .text20 { 6 | font-size: 20px; 7 | } 8 | -------------------------------------------------------------------------------- /code/learn-angular/src/app/template/template.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | OnInit, 4 | } from '@angular/core'; 5 | 6 | @Component({ 7 | selector: 'app-template', 8 | templateUrl: './template.component.html', 9 | styleUrls: ['./template.component.scss'], 10 | }) 11 | export class TemplateComponent implements OnInit { 12 | list = [ 13 | { name: 'A', value: 1 }, 14 | { name: 'B', value: 2 }, 15 | { name: 'C', value: 3 }, 16 | { name: 'D', value: 4 }, 17 | ]; 18 | 19 | count = 1; 20 | 21 | constructor() {} 22 | 23 | ngOnInit(): void {} 24 | 25 | addItem(len: number) { 26 | this.list.push({ name: `${len}`, value: len }); 27 | } 28 | 29 | increaseCount() { 30 | this.count++; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /code/learn-angular/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvin0216/note/74be1e13c9bd9b94d9b73dc975c8aa22bae62638/code/learn-angular/src/assets/.gitkeep -------------------------------------------------------------------------------- /code/learn-angular/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /code/learn-angular/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /code/learn-angular/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvin0216/note/74be1e13c9bd9b94d9b73dc975c8aa22bae62638/code/learn-angular/src/favicon.ico -------------------------------------------------------------------------------- /code/learn-angular/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | learn angular 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /code/learn-angular/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /code/learn-angular/src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /code/learn-angular/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: { 11 | context(path: string, deep?: boolean, filter?: RegExp): { 12 | (id: string): T; 13 | keys(): string[]; 14 | }; 15 | }; 16 | 17 | // First, initialize the Angular testing environment. 18 | getTestBed().initTestEnvironment( 19 | BrowserDynamicTestingModule, 20 | platformBrowserDynamicTesting(), 21 | ); 22 | 23 | // Then we find all the tests. 24 | const context = require.context('./', true, /\.spec\.ts$/); 25 | // And load the modules. 26 | context.keys().forEach(context); 27 | -------------------------------------------------------------------------------- /code/learn-angular/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts", 10 | "src/polyfills.ts" 11 | ], 12 | "include": [ 13 | "src/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /code/learn-angular/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "baseUrl": "./", 6 | "outDir": "./dist/out-tsc", 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "noImplicitOverride": true, 10 | "noPropertyAccessFromIndexSignature": true, 11 | "noImplicitReturns": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "sourceMap": true, 14 | "declaration": false, 15 | "downlevelIteration": true, 16 | "experimentalDecorators": true, 17 | "moduleResolution": "node", 18 | "importHelpers": true, 19 | "target": "es2020", 20 | "module": "es2020", 21 | "lib": [ 22 | "es2020", 23 | "dom" 24 | ] 25 | }, 26 | "angularCompilerOptions": { 27 | "enableI18nLegacyMessageIdFormat": false, 28 | "strictInjectionParameters": true, 29 | "strictInputAccessModifiers": true, 30 | "strictTemplates": true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /code/learn-angular/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /code/mf/app1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app1", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "devDependencies": { 7 | "@babel/core": "7.17.8", 8 | "@babel/preset-react": "7.16.7", 9 | "babel-loader": "8.2.4", 10 | "html-webpack-plugin": "5.5.0", 11 | "serve": "13.0.2", 12 | "webpack": "5.70.0", 13 | "webpack-cli": "4.9.2", 14 | "webpack-dev-server": "4.7.4" 15 | }, 16 | "scripts": { 17 | "dev": "webpack-cli serve", 18 | "build": "webpack --mode production", 19 | "serve": "serve dist -p 4001", 20 | "clean": "rm -rf dist" 21 | }, 22 | "dependencies": { 23 | "react": "^17.0.2", 24 | "react-dom": "^17.0.2" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /code/mf/app1/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | -------------------------------------------------------------------------------- /code/mf/app1/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Button from './Button'; 3 | const RemoteButton = React.lazy(() => import('app2/Button')); 4 | 5 | const App = (props) => { 6 | return ( 7 | <> 8 |

app 1

9 | ; 5 | }; 6 | 7 | export default Button; 8 | -------------------------------------------------------------------------------- /code/mf/app1/src/main.js: -------------------------------------------------------------------------------- 1 | import App from './App'; 2 | import React from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | 5 | ReactDOM.render(, document.getElementById('root')); 6 | -------------------------------------------------------------------------------- /code/mf/app1/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 2 | const { ModuleFederationPlugin } = require('webpack').container; 3 | const path = require('path'); 4 | 5 | const deps = require('./package.json').dependencies; 6 | 7 | module.exports = { 8 | entry: './src/main', 9 | mode: 'development', 10 | devServer: { 11 | static: { 12 | directory: path.join(__dirname, 'dist'), 13 | }, 14 | port: 4001, 15 | }, 16 | target: 'web', 17 | output: { 18 | publicPath: 'auto', 19 | }, 20 | module: { 21 | rules: [ 22 | { 23 | test: /\.jsx?$/, 24 | loader: 'babel-loader', 25 | exclude: /node_modules/, 26 | options: { 27 | presets: ['@babel/preset-react'], 28 | }, 29 | }, 30 | ], 31 | }, 32 | plugins: [ 33 | new ModuleFederationPlugin({ 34 | name: 'app1', // 其他项目引用时的名字 35 | filename: 'remoteEntry.js', //暴露的模块统一打包在这个名字,可以任意取 36 | remotes: { 37 | app2: 'app2@http://localhost:4002/remoteEntry.js', 38 | }, 39 | exposes: { 40 | './Button': './src/Button', // 暴露的组件 41 | }, 42 | }), 43 | new HtmlWebpackPlugin({ 44 | template: './public/index.html', 45 | }), 46 | ], 47 | }; 48 | -------------------------------------------------------------------------------- /code/mf/app2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app2", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "devDependencies": { 7 | "@babel/core": "7.17.8", 8 | "@babel/preset-react": "7.16.7", 9 | "babel-loader": "8.2.4", 10 | "html-webpack-plugin": "5.5.0", 11 | "serve": "13.0.2", 12 | "webpack": "5.70.0", 13 | "webpack-cli": "4.9.2", 14 | "webpack-dev-server": "4.7.4" 15 | }, 16 | "scripts": { 17 | "dev": "webpack-cli serve", 18 | "build": "webpack --mode production", 19 | "serve": "serve dist -p 4002", 20 | "clean": "rm -rf dist" 21 | }, 22 | "dependencies": { 23 | "react": "^17.0.2", 24 | "react-dom": "^17.0.2" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /code/mf/app2/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | -------------------------------------------------------------------------------- /code/mf/app2/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Button from './Button'; 3 | const RemoteButton = React.lazy(() => import('app1/Button')); 4 | 5 | const App = (props) => { 6 | return ( 7 | <> 8 |

app 2

9 | ; 5 | }; 6 | 7 | export default Button; 8 | -------------------------------------------------------------------------------- /code/mf/app2/src/main.js: -------------------------------------------------------------------------------- 1 | import App from './App'; 2 | import React from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | 5 | ReactDOM.render(, document.getElementById('root')); 6 | -------------------------------------------------------------------------------- /code/mf/app2/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 2 | const { ModuleFederationPlugin } = require('webpack').container; 3 | const path = require('path'); 4 | 5 | const deps = require('./package.json').dependencies; 6 | 7 | module.exports = { 8 | entry: './src/main', 9 | mode: 'development', 10 | devServer: { 11 | static: { 12 | directory: path.join(__dirname, 'dist'), 13 | }, 14 | port: 4002, 15 | }, 16 | target: 'web', 17 | output: { 18 | publicPath: 'auto', 19 | }, 20 | module: { 21 | rules: [ 22 | { 23 | test: /\.jsx?$/, 24 | loader: 'babel-loader', 25 | exclude: /node_modules/, 26 | options: { 27 | presets: ['@babel/preset-react'], 28 | }, 29 | }, 30 | ], 31 | }, 32 | plugins: [ 33 | new ModuleFederationPlugin({ 34 | name: 'app2', // 其他项目引用时的名字 35 | filename: 'remoteEntry.js', //暴露的模块统一打包在这个名字,可以任意取 36 | remotes: { 37 | app1: 'app1@http://localhost:4001/remoteEntry.js', 38 | }, 39 | exposes: { 40 | './Button': './src/Button', // 暴露的组件 41 | }, 42 | }), 43 | new HtmlWebpackPlugin({ 44 | template: './public/index.html', 45 | }), 46 | ], 47 | }; 48 | -------------------------------------------------------------------------------- /code/my-promise/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-promise", 3 | "version": "1.0.0", 4 | "description": "> TODO: description", 5 | "author": "alvin ", 6 | "homepage": "https://github.com/alvin0216/awsome-demo#readme", 7 | "license": "ISC", 8 | "scripts": { 9 | "test": "promises-aplus-tests test8.js" 10 | }, 11 | "bugs": { 12 | "url": "https://github.com/alvin0216/awsome-demo/issues" 13 | }, 14 | "dependencies": { 15 | "promises-aplus-tests": "^2.1.2" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /code/my-promise/test1.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 测试 new Promise(executor) 的参数校验 3 | * 4 | * new Promise() 5 | * TypeError: Promise resolver undefined is not a function 6 | */ 7 | const Promise = require('./promise'); 8 | 9 | new Promise(); 10 | -------------------------------------------------------------------------------- /code/my-promise/test2.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 1. 测试 new Promise(executor).then() 方法 3 | * 2. 测试链式调用 4 | */ 5 | const Promise = require('./promise'); 6 | 7 | new Promise((resolve, reject) => { 8 | resolve('A'); 9 | }).then( 10 | (value) => { 11 | console.log('执行成功态回调,value:', value); 12 | }, 13 | (error) => { 14 | console.log('执行拒绝态回调,error:', error); 15 | }, 16 | ); 17 | 18 | new Promise((resolve, reject) => { 19 | reject('B'); 20 | }).then( 21 | (value) => { 22 | console.log('执行成功态回调,value:', value); 23 | }, 24 | (error) => { 25 | console.log('执行拒绝态回调,error:', error); 26 | }, 27 | ); 28 | 29 | new Promise((resolve, reject) => { 30 | resolve('C'); 31 | }) 32 | .then() 33 | .then((value) => { 34 | console.log('链式调用测试,value:', value); 35 | }); 36 | 37 | /** 38 | * ? 即使支持了 then 方法。执行顺序也是不对的。Promise.reslove 方法是一个微任务,需要在上一个宏任务执行完后才执行 39 | * * 代码: 40 | console.log(1) 41 | new Promise((resolve, reject) => { 42 | console.log(2) 43 | resolve('A') 44 | }).then( 45 | value => { 46 | console.log(4) 47 | } 48 | ) 49 | console.log(3) 50 | * * 结果为 1 2 4 3 51 | * * 正确结果为:1 2 3 4 52 | * 53 | */ 54 | -------------------------------------------------------------------------------- /code/my-promise/test3.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 测试 then 方法 在异步下执行 3 | */ 4 | const Promise = require('./promise'); 5 | 6 | console.log(1); 7 | new Promise((resolve, reject) => { 8 | console.log(2); 9 | resolve('A'); 10 | }).then((value) => { 11 | console.log(4); 12 | }); 13 | console.log(3); 14 | 15 | /** 16 | * ? 如果在 executor 函数中异步 reslove 呢? 17 | * * 代码: 18 | console.log(1) 19 | new Promise((resolve, reject) => { 20 | console.log(2) 21 | setTimeout(() => { 22 | resolve('A') 23 | }) 24 | }).then(value => { 25 | console.log(4) 26 | }) 27 | console.log(3) 28 | * * 结果为 1 2 3 29 | * * 正确结果为 1 2 3 4 30 | */ 31 | -------------------------------------------------------------------------------- /code/my-promise/test4.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 测试 在 executor 函数中异步 reslove 3 | */ 4 | 5 | const Promise = require('./promise'); 6 | 7 | console.log(1); 8 | new Promise((resolve, reject) => { 9 | console.log(2); 10 | setTimeout(() => { 11 | resolve('A'); 12 | }); 13 | }).then((value) => { 14 | console.log(4); 15 | }); 16 | console.log(3); 17 | 18 | // 1 2 3 4 19 | 20 | /** 21 | * ? 如果在 then 函数中 return string 22 | * * 代码: 23 | * 24 | new Promise((resolve, reject) => { 25 | setTimeout(() => { 26 | resolve('A') 27 | }) 28 | }) 29 | .then( 30 | value => { 31 | return 'then1 return string' 32 | }, 33 | reason => { 34 | console.log('then1 reason', reason) 35 | } 36 | ) 37 | .then( 38 | value => { 39 | console.log(value) 40 | }, 41 | reason => { 42 | console.log('then2 reason', reason) 43 | } 44 | ) 45 | * * 结果 A 46 | * * 正确结果 then1 return string 47 | * 48 | */ 49 | -------------------------------------------------------------------------------- /code/my-promise/test5.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 测试 onFulfilled 方法返回一个字符串后的结果 3 | */ 4 | 5 | const Promise = require('./promise'); 6 | 7 | new Promise((resolve, reject) => { 8 | setTimeout(() => { 9 | resolve('A'); 10 | }); 11 | }) 12 | .then( 13 | (value) => { 14 | return 'then1 return string'; 15 | }, 16 | (reason) => { 17 | console.log('then1 reason', reason); 18 | }, 19 | ) 20 | .then( 21 | (value) => { 22 | console.log('then2 value', value); 23 | }, 24 | (reason) => { 25 | console.log('then2 reason', reason); 26 | }, 27 | ); 28 | 29 | // * 结果 then1 return string 30 | 31 | /** 32 | * ? 如果 onFulfilled 或 onRejected 返回值不为字符串而是 Promise 呢? 33 | * * 代码: 34 | new Promise((resolve, reject) => { 35 | setTimeout(() => { 36 | resolve('A') 37 | }) 38 | }) 39 | .then( 40 | value => { 41 | return new Promise(resolve => resolve('B')) 42 | }, 43 | reason => { 44 | console.log('then1 reason', reason) 45 | } 46 | ) 47 | .then( 48 | value => { 49 | console.log('then2 value', value) 50 | }, 51 | reason => { 52 | console.log('then2 reason', reason) 53 | } 54 | ) 55 | * * 结果 then2 value MyPromise { status: 'fulfilled', value: 'B'...} 56 | * * 正确结果 then2 value B 57 | */ 58 | -------------------------------------------------------------------------------- /code/my-promise/test6.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 测试 返回值 x 为 Promise 时值需要的操作 3 | */ 4 | const Promise = require('./promise'); 5 | 6 | new Promise((resolve, reject) => { 7 | resolve(1); 8 | }) 9 | .then( 10 | (value) => { 11 | return new Promise((reslove) => { 12 | reslove(2); 13 | }); 14 | }, 15 | (reason) => {}, 16 | ) 17 | .then( 18 | (value) => { 19 | console.log(value); 20 | }, 21 | (reason) => {}, 22 | ); 23 | 24 | // * 答案 2 25 | -------------------------------------------------------------------------------- /code/my-promise/test7.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 测试 返回值 x 为 Promise 时值需要的操作 3 | */ 4 | const Promise = require('./promise'); 5 | 6 | new Promise((resolve, reject) => { 7 | resolve(1); 8 | }) 9 | .then( 10 | (value) => { 11 | return new Promise((reslove) => { 12 | reslove( 13 | new Promise((resolve, reject) => { 14 | reslove(2); 15 | }), 16 | ); 17 | }); 18 | }, 19 | (reason) => {}, 20 | ) 21 | .then( 22 | (value) => { 23 | console.log(value); 24 | }, 25 | (reason) => {}, 26 | ) 27 | .then((value) => { 28 | console.log('then2 value', value); 29 | }); 30 | 31 | // 2 32 | // then2 value undefined 33 | -------------------------------------------------------------------------------- /code/my-promise/test8.js: -------------------------------------------------------------------------------- 1 | const MyPromise = require('./promise'); 2 | 3 | // 跑 promises-aplus-tests 验证是否符合 promise A+ 规范 4 | MyPromise.deferred = function () { 5 | var result = {}; 6 | result.promise = new MyPromise(function (resolve, reject) { 7 | result.resolve = resolve; 8 | result.reject = reject; 9 | }); 10 | 11 | return result; 12 | }; 13 | 14 | module.exports = MyPromise; 15 | -------------------------------------------------------------------------------- /code/my-react-router/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local -------------------------------------------------------------------------------- /code/my-react-router/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /code/my-react-router/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-react-router", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vite build", 7 | "serve": "vite preview" 8 | }, 9 | "dependencies": { 10 | "path-to-regexp": "^6.2.0", 11 | "react": "^17.0.0", 12 | "react-dom": "^17.0.0" 13 | }, 14 | "devDependencies": { 15 | "@vitejs/plugin-react-refresh": "^1.3.1", 16 | "vite": "^2.1.5" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /code/my-react-router/src/App.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { HashRouter, Route, Link, Redirect, Switch } from './react-router-dom'; 4 | 5 | const Home = props => { 6 | console.log(props); 7 | return

home

; 8 | }; 9 | 10 | const User = () =>

user

; 11 | const Profile = () =>

profile

; 12 | const NotFound = () =>

NotFound

; 13 | const List = props =>

List {JSON.stringify(props.match.params)}

; 14 | 15 | const App = props => { 16 | return ( 17 | 18 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | ); 35 | }; 36 | 37 | export default App; 38 | -------------------------------------------------------------------------------- /code/my-react-router/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root'), 10 | ); 11 | -------------------------------------------------------------------------------- /code/my-react-router/src/react-router-dom/HashRouter.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/exhaustive-deps */ 2 | import React, { useEffect, useState } from 'react'; 3 | import Context from './context'; 4 | 5 | const HashRouter = props => { 6 | const [location, setLocation] = useState({ 7 | hash: '', 8 | pathname: window.location.hash.slice(1) || '/', // 删除 # 9 | search: '', 10 | state: undefined, 11 | }); 12 | 13 | useEffect(() => { 14 | window.location.hash = window.location.hash || '/'; // 默认添加 hash 15 | 16 | // 监听 hash 值变化 17 | window.addEventListener('hashchange', onHashChange); 18 | return () => { 19 | window.removeEventListener('hashchange', onHashChange); 20 | }; 21 | }, []); 22 | 23 | function onHashChange() { 24 | setLocation({ 25 | ...location, 26 | pathname: window.location.hash.slice(1) || '/', 27 | }); 28 | } 29 | 30 | const value = { 31 | location, 32 | history: { 33 | push(to) { 34 | window.location.hash = to; 35 | }, 36 | }, 37 | }; 38 | 39 | return ( 40 | 41 | {/* */} 42 | {props.children} 43 | 44 | ); 45 | }; 46 | 47 | export default HashRouter; 48 | -------------------------------------------------------------------------------- /code/my-react-router/src/react-router-dom/Link.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import Context from './context'; 3 | 4 | const Link = props => { 5 | const { to } = props; 6 | 7 | return ( 8 | 9 | {state => { 10 | return state.history.push(to)}>{props.children}; 11 | }} 12 | 13 | ); 14 | }; 15 | 16 | export default Link; 17 | -------------------------------------------------------------------------------- /code/my-react-router/src/react-router-dom/Redirect.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import Context from './context'; 3 | 4 | const Redirect = props => { 5 | const { history } = useContext(Context); 6 | history.push(props.to); 7 | return null; 8 | }; 9 | 10 | export default Redirect; 11 | -------------------------------------------------------------------------------- /code/my-react-router/src/react-router-dom/Route.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Context from './context'; 3 | import { pathToRegexp } from 'path-to-regexp'; 4 | 5 | const Route = props => { 6 | const { path, component: Component, extract = false } = props; 7 | 8 | return ( 9 | 10 | {state => { 11 | // 也可以使用 useContext 12 | const { pathname } = state.location; 13 | let keys = []; 14 | // extract 严格匹配 15 | let reg = pathToRegexp(path, keys, { end: extract }); 16 | let result = pathname.match(reg); 17 | let [url, ...values] = result || []; 18 | 19 | let comProps = { 20 | location: state.location, 21 | history: state.history, 22 | match: { 23 | params: keys.reduce((obj, item, idx) => { 24 | const key = item.name; 25 | obj[key] = values[idx]; 26 | return obj; 27 | }, {}), 28 | }, 29 | }; 30 | 31 | return result ? : null; 32 | }} 33 | 34 | ); 35 | }; 36 | 37 | export default Route; 38 | -------------------------------------------------------------------------------- /code/my-react-router/src/react-router-dom/Switch.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import Context from './context'; 3 | import { pathToRegexp } from 'path-to-regexp'; 4 | 5 | const Switch = props => { 6 | const { location } = useContext(Context); 7 | const pathname = location.pathname; 8 | 9 | for (let i = 0; i < props.children.length; i++) { 10 | const child = props.children[i]; 11 | const path = child.props.path || ''; 12 | const reg = pathToRegexp(path, [], { end: false }); 13 | 14 | if (reg.test(pathname)) { 15 | return child; 16 | } 17 | } 18 | 19 | return null; 20 | }; 21 | 22 | export default Switch; 23 | -------------------------------------------------------------------------------- /code/my-react-router/src/react-router-dom/context.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | let Context = React.createContext(); 4 | 5 | export default Context; 6 | -------------------------------------------------------------------------------- /code/my-react-router/src/react-router-dom/index.js: -------------------------------------------------------------------------------- 1 | import HashRouter from './HashRouter'; 2 | import Route from './Route'; 3 | import Link from './Link'; 4 | import Redirect from './Redirect'; 5 | import Switch from './Switch'; 6 | 7 | export { HashRouter, Route, Link, Redirect, Switch }; 8 | -------------------------------------------------------------------------------- /code/my-react-router/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import reactRefresh from '@vitejs/plugin-react-refresh'; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [reactRefresh()], 7 | }); 8 | -------------------------------------------------------------------------------- /code/my-redux/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local -------------------------------------------------------------------------------- /code/my-redux/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /code/my-redux/middleware/exception.js: -------------------------------------------------------------------------------- 1 | const exceptionMiddleware = store => next => action => { 2 | try { 3 | next(action); 4 | } catch (err) { 5 | console.error('错误报告: ', err); 6 | } 7 | }; 8 | 9 | export default exceptionMiddleware; 10 | -------------------------------------------------------------------------------- /code/my-redux/middleware/logger.js: -------------------------------------------------------------------------------- 1 | const loggerMiddleware = store => next => action => { 2 | console.log('this state', store.getState()); 3 | console.log('action', action); 4 | next(action); 5 | console.log('next state', store.getState()); 6 | }; 7 | 8 | export default loggerMiddleware; 9 | -------------------------------------------------------------------------------- /code/my-redux/middleware/time.js: -------------------------------------------------------------------------------- 1 | const timeMiddleware = store => next => action => { 2 | console.log('time', new Date().getTime()); 3 | next(action); 4 | }; 5 | 6 | export default timeMiddleware; 7 | -------------------------------------------------------------------------------- /code/my-redux/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-redux", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vite build", 7 | "serve": "vite preview" 8 | }, 9 | "dependencies": { 10 | "react": "^17.0.0", 11 | "react-dom": "^17.0.0" 12 | }, 13 | "devDependencies": { 14 | "@vitejs/plugin-react-refresh": "^1.3.1", 15 | "vite": "^2.1.5" 16 | } 17 | } -------------------------------------------------------------------------------- /code/my-redux/react-redux/connect.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import bindActionCreators from '../redux/bindActionCreators'; 4 | 5 | export default function connect(mapStateToProps, mapDispatchToProps) { 6 | return function(Component) { 7 | class Connect extends React.Component { 8 | componentDidMount() { 9 | // 从context获取store并订阅更新 10 | this.context.store.subscribe(this.handleStoreChange.bind(this)); 11 | } 12 | handleStoreChange() { 13 | // 触发的方法有多种,这里为了简洁起见,直接forceUpdate强制更新,读者也可以通过setState来触发子组件更新 14 | this.forceUpdate(); 15 | } 16 | 17 | render() { 18 | const dispathProps = 19 | typeof mapDispatchToProps && 20 | bindActionCreators(mapDispatchToProps, this.context.store.dispatch); 21 | 22 | return ( 23 | 31 | ); 32 | } 33 | } 34 | // 接收context的固定写法 35 | Connect.contextTypes = { 36 | store: PropTypes.object, 37 | }; 38 | return Connect; 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /code/my-redux/react-redux/index.js: -------------------------------------------------------------------------------- 1 | import Provider from './provider'; 2 | import connect from './connect'; 3 | 4 | export { Provider, connect }; 5 | -------------------------------------------------------------------------------- /code/my-redux/react-redux/provider.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | export default class Provider extends React.Component { 5 | // 需要声明静态属性childContextTypes来指定context对象的属性,是context的固定写法 6 | static childContextTypes = { 7 | store: PropTypes.object, 8 | }; 9 | 10 | // 实现getChildContext方法,返回context对象,也是固定写法 11 | getChildContext() { 12 | return { store: this.store }; 13 | } 14 | 15 | constructor(props, context) { 16 | super(props, context); 17 | this.store = props.store; 18 | } 19 | 20 | // 渲染被Provider包裹的组件 21 | render() { 22 | return this.props.children; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /code/my-redux/redux/applyMiddleware.js: -------------------------------------------------------------------------------- 1 | import compose from './compose'; 2 | 3 | export default function applyMiddleware(...middlewares) { 4 | /* 返回一个重写createStore的方法*/ 5 | return function rewriteCreateStoreFunc(oldCreateStore) { 6 | /* 返回重写后新的 createStore*/ 7 | return function newCreateStore(reducer, initState) { 8 | /* 1. 生成store*/ 9 | const store = oldCreateStore(reducer, initState); 10 | /* 给每个 middleware 传下store,相当于 const logger = loggerMiddleware(store);*/ 11 | /* const chain = [exception, time, logger]*/ 12 | const chain = middlewares.map(middleware => middleware(store)); 13 | let dispatch = store.dispatch; 14 | dispatch = compose(...chain)(store.dispatch); 15 | 16 | /* 2. 重写 dispatch*/ 17 | store.dispatch = dispatch; 18 | return store; 19 | }; 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /code/my-redux/redux/bindActionCreators.js: -------------------------------------------------------------------------------- 1 | /* 核心的代码在这里,通过闭包隐藏了 actionCreator 和 dispatch*/ 2 | function bindActionCreator(actionCreator, dispatch) { 3 | return function() { 4 | return dispatch(actionCreator.apply(this, arguments)); 5 | }; 6 | } 7 | 8 | /* actionCreators 必须是 function 或者 object */ 9 | export default function bindActionCreators(actionCreators, dispatch) { 10 | if (typeof actionCreators === 'function') { 11 | return bindActionCreator(actionCreators, dispatch); 12 | } 13 | 14 | if (typeof actionCreators !== 'object' || actionCreators === null) { 15 | throw new Error(); 16 | } 17 | 18 | const keys = Object.keys(actionCreators); 19 | const boundActionCreators = {}; 20 | for (let i = 0; i < keys.length; i++) { 21 | const key = keys[i]; 22 | const actionCreator = actionCreators[key]; 23 | if (typeof actionCreator === 'function') { 24 | boundActionCreators[key] = bindActionCreator(actionCreator, dispatch); 25 | } 26 | } 27 | return boundActionCreators; 28 | } 29 | -------------------------------------------------------------------------------- /code/my-redux/redux/combineReducers.js: -------------------------------------------------------------------------------- 1 | export default function combineReducers(reducers) { 2 | /* reducerKeys = ['counter', 'info']*/ 3 | const reducerKeys = Object.keys(reducers); 4 | 5 | /* 返回合并后的新的reducer函数*/ 6 | return function combination(state = {}, action) { 7 | /* 生成的新的state*/ 8 | const nextState = {}; 9 | 10 | /* 遍历执行所有的reducers,整合成为一个新的state*/ 11 | for (let i = 0; i < reducerKeys.length; i++) { 12 | const key = reducerKeys[i]; 13 | const reducer = reducers[key]; 14 | /* 之前的 key 的 state*/ 15 | const previousStateForKey = state[key]; 16 | /* 执行 分 reducer,获得新的state*/ 17 | const nextStateForKey = reducer(previousStateForKey, action); 18 | 19 | nextState[key] = nextStateForKey; 20 | } 21 | return nextState; 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /code/my-redux/redux/compose.js: -------------------------------------------------------------------------------- 1 | export default function compose(...funcs) { 2 | if (funcs.length === 1) { 3 | return funcs[0]; 4 | } 5 | return funcs.reduce((a, b) => (...args) => a(b(...args))); 6 | } 7 | -------------------------------------------------------------------------------- /code/my-redux/redux/createStore.js: -------------------------------------------------------------------------------- 1 | export default function createStore( 2 | reducer, 3 | initState, 4 | rewriteCreateStoreFunc, 5 | ) { 6 | let state = initState; 7 | const listeners = []; 8 | 9 | if (typeof initState === 'function') { 10 | rewriteCreateStoreFunc = initState; 11 | initState = undefined; 12 | } 13 | 14 | /* 如果有 rewriteCreateStoreFunc,那就采用新的 createStore */ 15 | if (rewriteCreateStoreFunc) { 16 | const newCreateStore = rewriteCreateStoreFunc(createStore); 17 | return newCreateStore(reducer, initState); 18 | } 19 | 20 | /* 否则按照正常的流程走*/ 21 | /* 订阅函数 */ 22 | function subscribe(listener) { 23 | listeners.push(listener); 24 | } 25 | 26 | /* state 值的修改 */ 27 | function dispatch(action) { 28 | state = reducer(state, action); 29 | /* 执行通知 */ 30 | for (let i = 0; i < listeners.length; i++) { 31 | const listener = listeners[i]; 32 | listener(); 33 | } 34 | } 35 | 36 | /* 注意!!!只修改了这里,用一个不匹配任何计划的 type,来获取初始值 */ 37 | dispatch({ type: Symbol() }); 38 | 39 | function getState() { 40 | return state; 41 | } 42 | 43 | return { subscribe, dispatch, getState }; 44 | } 45 | -------------------------------------------------------------------------------- /code/my-redux/redux/index.js: -------------------------------------------------------------------------------- 1 | import createStore from './createStore'; 2 | import combineReducers from './combineReducers'; 3 | import applyMiddleware from './applyMiddleware'; 4 | import compose from './compose'; 5 | 6 | export { createStore, combineReducers, applyMiddleware, compose }; 7 | -------------------------------------------------------------------------------- /code/my-redux/src/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from '../react-redux'; 3 | import { addCount } from '../store/couter/actions'; 4 | 5 | class App extends Component { 6 | render() { 7 | console.log(this.props); 8 | return ( 9 |
10 |

{this.props.count}

11 | 12 |
13 | ); 14 | } 15 | } 16 | 17 | export default connect(state => ({ count: state.counter.count }), { 18 | addCount, 19 | })(App); 20 | -------------------------------------------------------------------------------- /code/my-redux/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | import { Provider } from '../react-redux'; 6 | import store from '../store'; 7 | 8 | ReactDOM.render( 9 | 10 | 11 | 12 | 13 | , 14 | document.getElementById('root'), 15 | ); 16 | -------------------------------------------------------------------------------- /code/my-redux/store/couter/actions.js: -------------------------------------------------------------------------------- 1 | export const addCount = () => ({ type: 'INCREMENT' }); 2 | -------------------------------------------------------------------------------- /code/my-redux/store/couter/reducer.js: -------------------------------------------------------------------------------- 1 | // ====== state 2 | const defaultState = { 3 | count: 0, 4 | }; 5 | 6 | /** 7 | * UserReducer 8 | */ 9 | export default function countReducer(state = defaultState, action) { 10 | switch (action.type) { 11 | case 'INCREMENT': 12 | return { count: state.count + 1 }; 13 | default: 14 | return state; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /code/my-redux/store/index.js: -------------------------------------------------------------------------------- 1 | import { compose, createStore, applyMiddleware } from '../redux'; 2 | import logger from '../middleware/logger'; 3 | import exception from '../middleware/exception'; 4 | import time from '../middleware/time'; 5 | 6 | import rootReducer from './rootReducers'; 7 | 8 | const storeEnhancers = compose(applyMiddleware(exception, time, logger)); 9 | 10 | export default createStore(rootReducer, storeEnhancers); 11 | -------------------------------------------------------------------------------- /code/my-redux/store/rootReducers.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from '../redux'; 2 | import counter from './couter/reducer'; 3 | 4 | export default combineReducers({ counter }); 5 | -------------------------------------------------------------------------------- /code/my-redux/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import reactRefresh from '@vitejs/plugin-react-refresh'; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [reactRefresh()], 7 | }); 8 | -------------------------------------------------------------------------------- /code/react-context/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: react-context demo 3 | --- 4 | 5 | ```tsx | pure 6 | import React from 'react'; 7 | import { useImmerReducer } from 'use-immer'; 8 | 9 | export const initialState: FRInitialState = { 10 | widgetList: [], // 数据列表 11 | template: null, // 组件模板数据 12 | channelPort1: null, 13 | validationList: [], 14 | }; 15 | 16 | type ACTIONTYPE = 17 | | { type: 'setFR'; payload: Partial } 18 | | { type: 'setWipWidgetIndex'; payload: any } // for example number 19 | | { type: 'setWidgetList'; payload: WidgetItem[] }; 20 | 21 | export const FRContext = React.createContext< 22 | [FRInitialState, React.Dispatch] 23 | >([initialState, action => action]); 24 | 25 | export const reducer = (state: FRInitialState, action: ACTIONTYPE) => { 26 | const { type, payload } = action; 27 | switch (type) { 28 | // 更新 widgetList 29 | case 'setWidgetList': 30 | state.widgetList = payload; 31 | break; 32 | 33 | default: 34 | } 35 | 36 | return state; 37 | }; 38 | 39 | export const FRProvider = ({ children }: any) => { 40 | const contextValue = useImmerReducer(reducer, initialState); // 初始化数据 41 | return ( 42 | {children} 43 | ); 44 | }; 45 | ``` 46 | -------------------------------------------------------------------------------- /code/react-context/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-context", 3 | "version": "1.0.0", 4 | "description": "> TODO: description", 5 | "author": "alvin ", 6 | "homepage": "https://github.com/alvin0216/awsome-demo#readme", 7 | "license": "ISC", 8 | "main": "lib/context.js", 9 | "directories": { 10 | "lib": "lib", 11 | "test": "__tests__" 12 | }, 13 | "files": [ 14 | "lib" 15 | ], 16 | "publishConfig": { 17 | "registry": "http://registry.cnpm.3weijia.com/" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/alvin0216/awsome-demo.git" 22 | }, 23 | "scripts": { 24 | "test": "echo \"Error: run tests from root\" && exit 1" 25 | }, 26 | "bugs": { 27 | "url": "https://github.com/alvin0216/awsome-demo/issues" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /code/react-ssr-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-ssr-demo", 3 | "version": "1.0.0", 4 | "description": "> TODO: description", 5 | "author": "alvin ", 6 | "homepage": "https://github.com/alvin0216/awsome-demo#readme", 7 | "license": "ISC", 8 | "main": "lib/react-ssr-demo.js", 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/alvin0216/awsome-demo.git" 12 | }, 13 | "scripts": { 14 | "dev": "npm-run-all --parallel dev:**", 15 | "dev:start": "nodemon --watch build --exec node \"./build/bundle.js\"", 16 | "dev:build:server": "webpack --config webpack.server.js --watch", 17 | "dev:build:client": "webpack --config webpack.client.js --watch" 18 | }, 19 | "bugs": { 20 | "url": "https://github.com/alvin0216/awsome-demo/issues" 21 | }, 22 | "devDependencies": { 23 | "@babel/cli": "^7.10.5", 24 | "@babel/core": "^7.11.0", 25 | "@babel/plugin-transform-runtime": "^7.11.0", 26 | "@babel/preset-env": "^7.11.0", 27 | "@babel/preset-react": "^7.10.4", 28 | "@babel/runtime-corejs2": "^7.11.0", 29 | "babel-loader": "^8.1.0", 30 | "nodemon": "^2.0.4", 31 | "npm-run-all": "^4.1.5", 32 | "webpack": "^4.44.1", 33 | "webpack-cli": "^3.3.12", 34 | "webpack-merge": "^5.0.9", 35 | "webpack-node-externals": "^2.5.0" 36 | }, 37 | "dependencies": { 38 | "axios": ">=0.21.2", 39 | "express": "^4.17.1", 40 | "express-http-proxy": "^1.6.2", 41 | "react": "^16.13.1", 42 | "react-dom": "^16.13.1", 43 | "react-redux": "^7.2.1", 44 | "react-router-config": "^5.1.1", 45 | "react-router-dom": "^5.2.0", 46 | "redux": "^4.0.5", 47 | "redux-thunk": "^2.3.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /code/react-ssr-demo/src/App.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { renderRoutes } from 'react-router-config'; 3 | 4 | const App = (props) => { 5 | return ( 6 | <> 7 |

App...

8 | {renderRoutes(props.route.routes)} 9 | 10 | ); 11 | }; 12 | 13 | export default App; 14 | -------------------------------------------------------------------------------- /code/react-ssr-demo/src/client/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDom from 'react-dom'; 3 | import { BrowserRouter, Route } from 'react-router-dom'; 4 | import { Provider } from 'react-redux'; 5 | import { getClientStore } from '../redux'; 6 | import { renderRoutes } from 'react-router-config'; 7 | import routes from '../routes'; 8 | 9 | const Root = ( 10 | 11 | {renderRoutes(routes)} 12 | 13 | ); 14 | 15 | ReactDom.hydrate(Root, document.getElementById('root')); 16 | -------------------------------------------------------------------------------- /code/react-ssr-demo/src/client/request.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | const instance = axios.create({ 4 | baseURL: 'https://mock.yonyoucloud.com/mock/13592', 5 | }); 6 | 7 | export default instance; 8 | -------------------------------------------------------------------------------- /code/react-ssr-demo/src/containers/Home.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { useSelector, useDispatch } from 'react-redux'; 3 | import { fetchName } from '../redux/user/actions'; 4 | 5 | const Home = (props) => { 6 | const dispatch = useDispatch(); 7 | const user = useSelector((state) => state.user); 8 | 9 | // useEffect(() => { 10 | // dispatch(fetchName()).then(res => { 11 | // console.log(res) 12 | // }) 13 | // }, []) 14 | 15 | return ( 16 | <> 17 |

Home, {user.name}

18 | 19 | 20 | ); 21 | }; 22 | 23 | Home.loadData = (store) => { 24 | return store.dispatch(fetchName()); 25 | }; 26 | 27 | export default Home; 28 | -------------------------------------------------------------------------------- /code/react-ssr-demo/src/containers/Login.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Login = (props) =>

Login

; 4 | 5 | export default Login; 6 | -------------------------------------------------------------------------------- /code/react-ssr-demo/src/redux/contants.js: -------------------------------------------------------------------------------- 1 | export const SET_NAME = Symbol.for('SET_NAME'); 2 | -------------------------------------------------------------------------------- /code/react-ssr-demo/src/redux/index.js: -------------------------------------------------------------------------------- 1 | import thunk from 'redux-thunk'; 2 | import { createStore, applyMiddleware, combineReducers } from 'redux'; 3 | import clientAxios from '../client/request'; 4 | import serverAxios from '../server/request'; 5 | 6 | import UserReducer from './user/reducer'; 7 | 8 | const reducer = combineReducers({ 9 | user: UserReducer, 10 | }); 11 | 12 | //服务端的store创建函数 13 | export const getStore = (req) => { 14 | return createStore( 15 | reducer, 16 | applyMiddleware(thunk.withExtraArgument(serverAxios(req))), 17 | ); 18 | }; 19 | //客户端的store创建函数 20 | export const getClientStore = () => { 21 | const defaultState = window.context ? window.context.state : {}; 22 | return createStore( 23 | reducer, 24 | defaultState, 25 | applyMiddleware(thunk.withExtraArgument(clientAxios)), 26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /code/react-ssr-demo/src/redux/user/actions.js: -------------------------------------------------------------------------------- 1 | import { SET_NAME } from '../contants'; 2 | 3 | //异步操作的action(采用thunk中间件) 4 | export const fetchName = () => { 5 | return (dispatch, getState, axiosInstance) => { 6 | return axiosInstance.get('/random/name').then((res) => { 7 | console.log(res.data); 8 | return dispatch({ type: SET_NAME, payload: res.data.name }); 9 | }); 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /code/react-ssr-demo/src/redux/user/reducer.js: -------------------------------------------------------------------------------- 1 | import { SET_NAME } from '../contants'; 2 | 3 | let defaultState = { 4 | name: 'alvin', 5 | }; 6 | 7 | export default function UserReducer(state = defaultState, action) { 8 | const { type, payload } = action; 9 | switch (type) { 10 | case SET_NAME: 11 | return { ...state, name: payload }; 12 | 13 | default: 14 | return state; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /code/react-ssr-demo/src/routes.js: -------------------------------------------------------------------------------- 1 | import Home from './containers/Home'; 2 | import Login from './containers/Login'; 3 | import App from './App'; 4 | 5 | //这里出现了多级路由 6 | export default [ 7 | { 8 | path: '/', 9 | component: App, 10 | routes: [ 11 | { 12 | path: '/', 13 | component: Home, 14 | exact: true, 15 | loadData: Home.loadData, 16 | key: 'home', 17 | }, 18 | { 19 | path: '/login', 20 | component: Login, 21 | exact: true, 22 | key: 'login', 23 | }, 24 | ], 25 | }, 26 | ]; 27 | -------------------------------------------------------------------------------- /code/react-ssr-demo/src/server/request.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | const createInstance = (req) => 4 | axios.create({ 5 | baseURL: 'https://mock.yonyoucloud.com/mock/13592', 6 | headers: { 7 | cookie: req.get('cookie') || '', 8 | }, 9 | }); 10 | 11 | export default createInstance; 12 | -------------------------------------------------------------------------------- /code/react-ssr-demo/webpack.base.js: -------------------------------------------------------------------------------- 1 | // base config 2 | module.exports = { 3 | mode: 'development', // 开发模式 4 | 5 | resolve: { 6 | extensions: ['.js', '.json', '.jsx'], 7 | }, 8 | 9 | module: { 10 | rules: [ 11 | { 12 | test: /\.jsx?$/, 13 | loader: 'babel-loader', // .babelrc 单独配置 babel 14 | exclude: /node_modules/, 15 | options: { 16 | presets: [ 17 | [ 18 | '@babel/preset-env', 19 | { 20 | targets: { 21 | browsers: ['>1%', 'last 2 versions', 'not ie <= 8'], 22 | }, 23 | }, 24 | ], 25 | '@babel/preset-react', 26 | ], 27 | plugins: [['@babel/plugin-transform-runtime', { corejs: 2 }]], 28 | cacheDirectory: true, // 构建优化 第二次构建时,会读取之前的缓存 29 | }, 30 | }, 31 | ], 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /code/react-ssr-demo/webpack.client.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path'); 2 | const { merge } = require('webpack-merge'); 3 | const config = require('./webpack.base.js'); 4 | 5 | module.exports = merge(config, { 6 | entry: resolve(__dirname, './src/client/index.js'), 7 | output: { 8 | filename: 'index.js', 9 | path: resolve(__dirname, 'public'), 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /code/react-ssr-demo/webpack.server.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path'); 2 | const nodeExternals = require('webpack-node-externals'); 3 | const { merge } = require('webpack-merge'); 4 | const config = require('./webpack.base.js'); 5 | 6 | module.exports = merge(config, { 7 | entry: resolve(__dirname, './src/server/index.js'), 8 | output: { 9 | filename: 'bundle.js', 10 | path: resolve(__dirname, 'build'), 11 | }, 12 | // target: node 不会将 node 的核心模块打包进来 13 | target: 'node', 14 | externals: [nodeExternals()], // 排除 node_modules 目录中所有模块 15 | }); 16 | -------------------------------------------------------------------------------- /code/systemjs-demo/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /code/systemjs-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "systemjs-demo", 3 | "scripts": { 4 | "dev": "webpack serve", 5 | "build": "webpack build" 6 | }, 7 | "dependencies": { 8 | "@babel/cli": "^7.14.8", 9 | "@babel/core": "^7.14.8", 10 | "@babel/preset-env": "^7.14.8", 11 | "@babel/preset-react": "^7.14.5", 12 | "babel-loader": "^8.2.2", 13 | "html-webpack-plugin": "^5.3.2", 14 | "webpack": "^5.46.0", 15 | "webpack-cli": "^4.7.2", 16 | "webpack-dev-server": "^3.11.2" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /code/systemjs-demo/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default () =>

App...

; 4 | -------------------------------------------------------------------------------- /code/systemjs-demo/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | systemjs-react 3 | 4 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 23 | 24 | -------------------------------------------------------------------------------- /code/systemjs-demo/src/main.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDom from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDom.render(, document.querySelector('#root')); 6 | -------------------------------------------------------------------------------- /code/systemjs-demo/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | 4 | module.exports = { 5 | mode: 'development', 6 | entry: './src/main.js', 7 | output: { 8 | path: path.join(__dirname, 'build'), 9 | filename: 'main.js', 10 | libraryTarget: 'system', // 指定构建时所需要的库 11 | }, 12 | devtool: 'source-map', 13 | // 服务器运行配置 14 | devServer: { 15 | port: 9000, // 端口 16 | contentBase: path.join(__dirname, 'build'), 17 | historyApiFallback: true, 18 | }, 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.js$/, 23 | exclude: /node_modules/, 24 | use: { 25 | loader: 'babel-loader', 26 | options: { presets: ['@babel/preset-env', '@babel/react'] }, 27 | }, 28 | }, 29 | ], 30 | }, 31 | 32 | plugins: [ 33 | // 插件 34 | new HtmlWebpackPlugin({ 35 | /* 打包时,不需要自动引入JS文件( 12 | 13 | 14 | -------------------------------------------------------------------------------- /code/vdom-diff/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vdom-diff", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vite build", 7 | "serve": "vite preview" 8 | }, 9 | "dependencies": { 10 | "preact": "^10.5.13" 11 | }, 12 | "devDependencies": { 13 | "@preact/preset-vite": "^2.0.0", 14 | "vite": "^2.1.5" 15 | } 16 | } -------------------------------------------------------------------------------- /code/vdom-diff/src/main.jsx: -------------------------------------------------------------------------------- 1 | import '../core'; 2 | -------------------------------------------------------------------------------- /code/vdom-diff/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import preact from '@preact/preset-vite'; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [preact()], 7 | }); 8 | -------------------------------------------------------------------------------- /code/vite-antd/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /code/vite-antd/README.md: -------------------------------------------------------------------------------- 1 | - [antd](https://ant-design.gitee.io/components/overview-cn/) 2 | - [unocss](https://uno.antfu.me/) 3 | - [procomponents](https://procomponents.ant.design) 4 | -------------------------------------------------------------------------------- /code/vite-antd/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /code/vite-antd/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-antd", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "@ant-design/pro-components": "^2.3.52", 13 | "antd": "^5.1.7", 14 | "immer": "^9.0.19", 15 | "react": "^18.2.0", 16 | "react-dom": "^18.2.0", 17 | "sass": "^1.58.0", 18 | "use-immer": "^0.8.1" 19 | }, 20 | "devDependencies": { 21 | "@types/react": "^18.0.26", 22 | "@types/react-dom": "^18.0.9", 23 | "@vitejs/plugin-react": "^3.0.0", 24 | "typescript": "^4.9.3", 25 | "unocss": "^0.49.2", 26 | "vite": "^4.0.0", 27 | "vite-plugin-imp": "^2.3.1" 28 | } 29 | } -------------------------------------------------------------------------------- /code/vite-antd/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /code/vite-antd/src/App.scss: -------------------------------------------------------------------------------- 1 | .logo { 2 | height: 6em; 3 | padding: 1.5em; 4 | will-change: filter; 5 | } 6 | .logo:hover { 7 | filter: drop-shadow(0 0 2em #646cffaa); 8 | } 9 | .logo.react:hover { 10 | filter: drop-shadow(0 0 2em #61dafbaa); 11 | } 12 | 13 | #root { 14 | ul, 15 | li { 16 | list-style: none; 17 | padding: 0; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /code/vite-antd/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import reactLogo from './assets/react.svg'; 3 | import './App.scss'; 4 | import { Button } from 'antd'; 5 | import { useImmer } from 'use-immer'; 6 | import ProComponent from './ProComponent'; 7 | 8 | function App() { 9 | const [count, setCount] = useState(0); 10 | const [info, updateInfo] = useImmer({ name: 'Alvin', age: 18 }); 11 | 12 | return ( 13 |
14 |
15 | React logo 16 |

Vite + React

17 | 20 | 29 |
30 |
31 |
{JSON.stringify(info, null, 2)}
32 |
33 | 34 | 35 |
36 | ); 37 | } 38 | 39 | export default App; 40 | -------------------------------------------------------------------------------- /code/vite-antd/src/ProComponent.tsx: -------------------------------------------------------------------------------- 1 | import { ProForm, ProFormText } from '@ant-design/pro-components'; 2 | 3 | interface ProComponentProps {} 4 | 5 | const ProComponent: React.FC = (props) => { 6 | return ( 7 | { 10 | console.log(values); 11 | }}> 12 | 13 | 14 | ); 15 | }; 16 | 17 | export default ProComponent; 18 | -------------------------------------------------------------------------------- /code/vite-antd/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import App from './App'; 4 | 5 | import 'antd/dist/reset.css'; 6 | import 'uno.css'; 7 | 8 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( 9 | 10 | 11 | 12 | ); 13 | -------------------------------------------------------------------------------- /code/vite-antd/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /code/vite-antd/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "ESNext", 5 | "useDefineForClassFields": true, 6 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 7 | "allowJs": false, 8 | "skipLibCheck": true, 9 | "esModuleInterop": false, 10 | "allowSyntheticDefaultImports": true, 11 | "strict": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "module": "ESNext", 14 | "moduleResolution": "Node", 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "noEmit": true, 18 | "jsx": "react-jsx" 19 | }, 20 | "include": ["src"], 21 | "references": [{ "path": "./tsconfig.node.json" }] 22 | } 23 | -------------------------------------------------------------------------------- /code/vite-antd/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /code/vite-antd/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import react from '@vitejs/plugin-react'; 3 | import vitePluginImp from 'vite-plugin-imp'; 4 | import Unocss from 'unocss/vite'; 5 | import { presetAttributify, presetUno } from 'unocss'; 6 | import path from 'path'; 7 | 8 | // https://vitejs.dev/config/ 9 | export default defineConfig({ 10 | resolve: { 11 | alias: { 12 | '@': path.join(__dirname, 'src'), 13 | }, 14 | }, 15 | plugins: [ 16 | react(), 17 | Unocss({ 18 | presets: [presetAttributify({}), presetUno()], 19 | }), 20 | vitePluginImp({ 21 | libList: [ 22 | { 23 | libName: 'antd', 24 | style: (name) => `antd/lib/${name}/style/index.css`, 25 | }, 26 | ], 27 | }), 28 | ], 29 | }); 30 | -------------------------------------------------------------------------------- /code/vite-react-demo/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /code/vite-react-demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /code/vite-react-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-react-demo", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "tsc && vite build", 8 | "preview": "vite preview" 9 | }, 10 | "dependencies": { 11 | "react": "^17.0.2", 12 | "react-dom": "^17.0.2" 13 | }, 14 | "devDependencies": { 15 | "@types/react": "^17.0.33", 16 | "@types/react-dom": "^17.0.10", 17 | "@vitejs/plugin-react": "^1.0.7", 18 | "typescript": "^4.5.4", 19 | "vite": "^2.8.0" 20 | } 21 | } -------------------------------------------------------------------------------- /code/vite-react-demo/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | 3 | interface AppProps {} 4 | 5 | const App: React.FC = (props) => { 6 | const [count, setCount] = useState(0); 7 | const [age, setAge] = useState(18); 8 | 9 | const runSetState = () => { 10 | setCount((prev) => prev + 1); 11 | setAge((prev) => prev + 1); 12 | }; 13 | 14 | const trigger = (isBatchedUpdate: boolean) => { 15 | if (isBatchedUpdate) { 16 | runSetState(); 17 | } else { 18 | Promise.resolve().then(() => { 19 | runSetState(); 20 | }); 21 | // setTimeout(runSetState, 0); 22 | } 23 | }; 24 | 25 | console.log('render'); 26 | return ( 27 | <> 28 | {count} - {age} 29 | 30 | 31 | 32 | ); 33 | }; 34 | 35 | export default App; 36 | -------------------------------------------------------------------------------- /code/vite-react-demo/src/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /code/vite-react-demo/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import App from './App'; 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById('root') 11 | ); 12 | -------------------------------------------------------------------------------- /code/vite-react-demo/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /code/vite-react-demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": false, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"], 20 | "references": [{ "path": "./tsconfig.node.json" }] 21 | } 22 | -------------------------------------------------------------------------------- /code/vite-react-demo/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "esnext", 5 | "moduleResolution": "node" 6 | }, 7 | "include": ["vite.config.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /code/vite-react-demo/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()] 7 | }) 8 | -------------------------------------------------------------------------------- /code/vite-ts-demo/app.ts: -------------------------------------------------------------------------------- 1 | console.log('执行 app.ts'); 2 | -------------------------------------------------------------------------------- /code/vite-ts-demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /code/vite-ts-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-ts-demo", 3 | "version": "1.0.0", 4 | "description": "> TODO: description", 5 | "author": "alvin ", 6 | "homepage": "", 7 | "license": "ISC", 8 | "main": "lib/vite-ts-demo.js", 9 | "directories": { 10 | "lib": "lib", 11 | "test": "__tests__" 12 | }, 13 | "files": [ 14 | "lib" 15 | ], 16 | "scripts": { 17 | "dev": "vite", 18 | "dev:node": "vite-node" 19 | }, 20 | "devDependencies": { 21 | "vite": "^2.1.5" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /code/webrtc-demo/assets/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvin0216/note/74be1e13c9bd9b94d9b73dc975c8aa22bae62638/code/webrtc-demo/assets/1.png -------------------------------------------------------------------------------- /code/webrtc-demo/assets/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvin0216/note/74be1e13c9bd9b94d9b73dc975c8aa22bae62638/code/webrtc-demo/assets/2.png -------------------------------------------------------------------------------- /code/webrtc-demo/assets/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvin0216/note/74be1e13c9bd9b94d9b73dc975c8aa22bae62638/code/webrtc-demo/assets/3.png -------------------------------------------------------------------------------- /code/webrtc-demo/demo2/style.css: -------------------------------------------------------------------------------- 1 | .box { 2 | display: flex; 3 | justify-content: center; 4 | } 5 | video { 6 | width: 400px; 7 | height: 300px; 8 | margin-left: 20px; 9 | background-color: #ddd; 10 | } 11 | 12 | .item h3 { 13 | text-align: center; 14 | } 15 | 16 | .item .btn-group { 17 | width: 120px; 18 | margin: 0 auto; 19 | display: flex; 20 | justify-content: space-between; 21 | } 22 | -------------------------------------------------------------------------------- /code/webrtc-demo/demo3/style.css: -------------------------------------------------------------------------------- 1 | .box { 2 | display: flex; 3 | justify-content: center; 4 | } 5 | video { 6 | width: 400px; 7 | height: 300px; 8 | margin-left: 20px; 9 | background-color: #ddd; 10 | } 11 | 12 | .item h3 { 13 | text-align: center; 14 | } 15 | 16 | .item .btn-group { 17 | width: 120px; 18 | margin: 0 auto; 19 | display: flex; 20 | justify-content: space-between; 21 | } 22 | 23 | .item .message-box h4 { 24 | text-align: center; 25 | } 26 | 27 | .item .message-box { 28 | margin-left: 20px; 29 | width: 400px; 30 | display: flex; 31 | justify-content: center; 32 | flex-direction: column; 33 | } 34 | -------------------------------------------------------------------------------- /code/webrtc-demo/demo4/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvin0216/note/74be1e13c9bd9b94d9b73dc975c8aa22bae62638/code/webrtc-demo/demo4/.DS_Store -------------------------------------------------------------------------------- /code/webrtc-demo/demo4/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo3", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "dependencies": { 7 | "koa": "^2.12.0", 8 | "koa-socket": "^4.4.0", 9 | "koa-static": "^5.0.0", 10 | "nodemon": "^2.0.4" 11 | }, 12 | "scripts": { 13 | "dev": "nodemon app.js" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /code/webrtc-demo/demo5/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo5", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "dependencies": { 7 | "peer": "^0.5.3" 8 | }, 9 | "scripts": { 10 | "dev": "npx peerjs -p 9000" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /code/webrtc-demo/demo5/peer1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | receiver 8 | 9 | 10 | 11 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /code/webrtc-demo/demo5/peer2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | sender 8 | 9 | 10 | 11 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /code/webrtc-demo/demo6/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "peer-demo", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "dependencies": { 7 | "peer": "^0.5.3" 8 | }, 9 | "scripts": { 10 | "dev": "npx peerjs -p 9000" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /code/webrtc-demo/demo6/peer1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | receiver 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /code/webrtc-demo/demo6/peer2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | sender 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /docs/.vitepress/cache/deps/_metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "hash": "c537208e", 3 | "browserHash": "a0098e51", 4 | "optimized": { 5 | "vue": { 6 | "src": "../../../../node_modules/.pnpm/vue@3.4.27/node_modules/vue/dist/vue.runtime.esm-bundler.js", 7 | "file": "vue.js", 8 | "fileHash": "f8f50e54", 9 | "needsInterop": false 10 | }, 11 | "@theme/index": { 12 | "src": "../../../../node_modules/.pnpm/vitepress@1.0.0-alpha.61_@algolia+client-search@4.23.3_search-insights@2.14.0/node_modules/vitepress/dist/client/theme-default/index.js", 13 | "file": "@theme_index.js", 14 | "fileHash": "90eb9f98", 15 | "needsInterop": false 16 | } 17 | }, 18 | "chunks": {} 19 | } -------------------------------------------------------------------------------- /docs/.vitepress/cache/deps/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module" 3 | } 4 | -------------------------------------------------------------------------------- /docs/algorithm/bfs-dfs.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: DFS & BFS 3 | date: 2020-05-24 23:37:51 4 | sidebar: auto 5 | tags: 6 | - 深度优先遍历 7 | - 广度优先遍历 8 | categories: 9 | - 算法与数据结构 10 | --- 11 | 12 | ## DFS 深度优先遍历 递归实现 13 | 14 | ```js 15 | /** 16 | * Definition for a binary tree node. 17 | * function TreeNode(val) { 18 | * this.val = val; 19 | * this.left = this.right = null; 20 | * } 21 | */ 22 | 23 | // 3 24 | // / \ 25 | // 1 4 26 | // \ 27 | // 2 28 | // 输出: 1 2 3 4 29 | 30 | function dfs(node) { 31 | if (!node) return; 32 | node.left && dfs(node.left); 33 | console.log(node.val); // 中序遍历 34 | node.right && dfs(node.right); 35 | } 36 | ``` 37 | 38 | ## DFS 深度优先遍历 栈实现 39 | 40 | ```js 41 | function dfs(root) { 42 | const stack = []; 43 | let nums = []; 44 | let current = root; 45 | 46 | while (current || stack.length > 0) { 47 | while (current) { 48 | stack.push(current); 49 | current = current.left; 50 | } 51 | current = stack.pop(); 52 | nums.push(current.val); 53 | current = current.right; 54 | } 55 | 56 | console.log(nums); // [ 1, 2, 3, 4 ] 57 | } 58 | ``` 59 | 60 | ## BFS 广度优先遍历 队列实现 61 | 62 | ```js 63 | // 3 64 | // / \ 65 | // 1 4 66 | // \ 67 | // 2 68 | // 输出: 3 1 4 2 69 | function bfs(root) { 70 | let queue = [root]; 71 | 72 | while (queue.length) { 73 | const node = queue.shift(); 74 | console.log(node.val); // 75 | node.left && queue.push(node.left); 76 | node.right && queue.push(node.right); 77 | } 78 | } 79 | ``` 80 | -------------------------------------------------------------------------------- /docs/algorithm/math.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 数学推算 3 | date: 2020-05-24 23:37:51 4 | sidebar: auto 5 | tags: 6 | - 数学推算 7 | categories: 8 | - 算法与数据结构 9 | --- 10 | 11 | ## 求和 12 | 13 | 求和公式 1+2+…+n 14 | 15 | ```js 16 | ((1 + n) * n) / 2; 17 | ``` 18 | 19 | 1+2+3+4+5 = (1 + 5) + (2 + 4) + 3 =.... 20 | -------------------------------------------------------------------------------- /docs/algorithm/sort/countSort.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 基数排序 3 | date: 2020-05-19 15:05:55 4 | sidebar: auto 5 | tags: 6 | - 算法与数据结构 7 | - 排序算法 8 | categories: 9 | - 算法与数据结构 10 | --- 11 | 12 | 动画来源 13 | 14 | --- 15 | 16 | - [图解面试算法](https://github.com/MisterBooo/LeetCodeAnimation) 17 | -------------------------------------------------------------------------------- /docs/algorithm/sort/heapSort.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 堆排序 3 | date: 2020-05-19 15:05:55 4 | sidebar: auto 5 | tags: 6 | - 算法与数据结构 7 | - 排序算法 8 | categories: 9 | - 算法与数据结构 10 | --- 11 | 12 | ## 简明解释 13 | 14 | 堆的定义 15 | 16 | 堆其实是一种特殊的树。只要满足这两点,它就是一个堆。 17 | 18 | 堆排序可以认为是选择排序的改进版,像选择排序一样将输入划分为已排序和待排序。 19 | 20 | 不一样的是堆排序利用堆这种近似完全二叉树的良好的数据结构来实现排序,本质上使用了二分的思想。 21 | 22 | ![](https://alvin-cdn.oss-cn-shenzhen.aliyuncs.com/images/heapSort.png) 23 | 24 | ## 算法步骤 25 | 26 | - 创建一个堆 H[0……n-1]; 27 | - 把堆首(最大值)和堆尾互换; 28 | - 把堆的尺寸缩小 1,并调用 shift_down(0),目的是把新的数组顶端数据调整到相应位置; 29 | - 重复步骤 2,直到堆的尺寸为 1。 30 | 31 | ## 基本实现 32 | 33 | ```js 34 | function heapSort(arr) { 35 | let size = arr.length; 36 | 37 | // 初始化堆,i 从最后一个父节点开始调整,直到节点均调整完毕 38 | for (let i = Math.floor(size / 2) - 1; i >= 0; i--) { 39 | heapify(arr, i, size); 40 | } 41 | // 堆排序:先将第一个元素和已拍好元素前一位作交换,再重新调整,直到排序完毕 42 | for (let i = size - 1; i > 0; i--) { 43 | swap(arr, 0, i); 44 | size -= 1; 45 | heapify(arr, 0, size); 46 | } 47 | 48 | return arr; 49 | } 50 | 51 | function heapify(arr, index, size) { 52 | let largest = index; 53 | let left = 2 * index + 1; 54 | let right = 2 * index + 2; 55 | 56 | if (left < size && arr[left] > arr[largest]) { 57 | largest = left; 58 | } 59 | if (right < size && arr[right] > arr[largest]) { 60 | largest = right; 61 | } 62 | if (largest !== index) { 63 | swap(arr, index, largest); 64 | heapify(arr, largest, size); 65 | } 66 | } 67 | 68 | // test 69 | const arr = [91, 60, 96, 7, 35, 65, 10, 65, 9, 30, 20, 31, 77, 81, 24]; 70 | console.log(heapSort(arr)); 71 | ``` 72 | 73 | --- 74 | 75 | - 动画来源 [图解面试算法](https://github.com/MisterBooo/LeetCodeAnimation) 76 | - 参考 [优雅的 JavaScript 排序算法(ES6)](https://juejin.im/post/5ab62ec36fb9a028cf326c49) 77 | -------------------------------------------------------------------------------- /docs/algorithm/sort/mergeSort.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 归并排序(分治) 3 | date: 2020-05-19 15:05:55 4 | sidebar: auto 5 | tags: 6 | - 算法与数据结构 7 | - 排序算法 8 | categories: 9 | - 算法与数据结构 10 | --- 11 | 12 | 13 | 14 | 用到分治算法,将大数组二分为一为两个小数组,递归去比较排序。算法复杂度 $O(nlog(n))$ 15 | 16 | ```js 17 | var mergeSort = function (arr) { 18 | const len = arr.length; 19 | if (len === 1) return arr; 20 | const mid = Math.floor(len / 2); 21 | 22 | // 分 23 | const left = mergeSort(arr.slice(0, mid)); 24 | const right = mergeSort(arr.slice(mid)); 25 | 26 | // 治 27 | let res = []; 28 | while (left.length > 0 || right.length > 0) { 29 | // shift 推出 30 | if (left.length > 0 && right.length > 0) { 31 | res.push(left[0] < right[0] ? left.shift() : right.shift()); 32 | } else if (left.length > 0) { 33 | res.push(left.shift()); 34 | } else { 35 | res.push(right.shift()); 36 | } 37 | } 38 | 39 | return res; 40 | }; 41 | ``` 42 | 43 | - 动画来源 [图解面试算法](https://github.com/MisterBooo/LeetCodeAnimation) 44 | - 参考 [优雅的 JavaScript 排序算法(ES6)](https://juejin.im/post/5ab62ec36fb9a028cf326c49) 45 | -------------------------------------------------------------------------------- /docs/algorithm/sort/quickSort.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 快速排序(基准) 3 | date: 2020-05-19 15:05:55 4 | sidebar: auto 5 | tags: 6 | - 算法与数据结构 7 | - 排序算法 8 | categories: 9 | - 算法与数据结构 10 | --- 11 | 12 | 和归并一样,采用分治思想。不同的是,快排不是将数组一分为二,而是采用一个 “基准” 值。 13 | 14 | 也即 `[...rec(left), 基准, ...rec(right)]`,算法复杂度 $O(nlog(n))$ 15 | 16 | ```js 17 | function quickSort(arr) { 18 | let len = arr.length; 19 | if (len < 2) return arr; // 临界条件 20 | 21 | const pivot = arr[0]; 22 | const left = []; 23 | const right = []; 24 | 25 | // 分治 26 | for (let i = 1; i < len; i++) { 27 | arr[i] < pivot ? left.push(arr[i]) : right.push(arr[i]); 28 | } 29 | 30 | return [...quickSort(left), pivot, ...quickSort(right)]; 31 | } 32 | ``` 33 | -------------------------------------------------------------------------------- /docs/algorithm/sort/selectionSort.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 选择排序 3 | date: 2020-05-19 15:05:55 4 | sidebar: auto 5 | tags: 6 | - 算法与数据结构 7 | - 排序算法 8 | categories: 9 | - 算法与数据结构 10 | --- 11 | 12 | 选择排序是外层循环,内循环找到最小或者最大的值的 index, 如果当前的元素不等于 index,则交换位置,算法复杂度 $O(n^2)$ 13 | 14 | ```TS 15 | function swap(arr, i, j) { 16 | [arr[i], arr[j]] = [arr[j], arr[i]] 17 | } 18 | 19 | function selectionSort(arr) { 20 | for (let i = 0; i < arr.length; i++) { 21 | let minIndex = i 22 | 23 | for (let j = i + 1; j < arr.length; j++) { 24 | if (arr[minIndex] > arr[j]) { 25 | minIndex = j 26 | } 27 | } 28 | 29 | if (minIndex !== i) swap(arr, minIndex, i) 30 | } 31 | return arr 32 | } 33 | 34 | 35 | // test 36 | let arr = [4, 2, 3, 6, 5] 37 | console.log(selectionSort(arr)) // [ 2, 3, 4, 5, 6 ] 38 | ``` 39 | 40 | ![](https://alvin-cdn.oss-cn-shenzhen.aliyuncs.com/images/selectionSort.png) 41 | 42 | - 动画来源 [图解面试算法](https://github.com/MisterBooo/LeetCodeAnimation) 43 | - 参考 [优雅的 JavaScript 排序算法(ES6)](https://juejin.im/post/5ab62ec36fb9a028cf326c49) 44 | -------------------------------------------------------------------------------- /docs/algorithm/二叉树/二叉树其他题目.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 二叉树其他题目 3 | date: 2022-05-14 22:22:39 4 | sidebar: auto 5 | tags: 6 | - 二叉树 7 | categories: 8 | - leetcode 9 | --- 10 | 11 | ## 求根节点到叶节点数字之和 12 | 13 | [leetcode](https://leetcode.cn/problems/sum-root-to-leaf-numbers) 14 | 15 | ![](https://assets.leetcode.com/uploads/2021/02/19/num1tree.jpg) 16 | 17 | ```js 18 | 输入:root = [1,2,3] 19 | 输出:25 20 | 解释: 21 | 从根到叶子节点路径 1->2 代表数字 12 22 | 从根到叶子节点路径 1->3 代表数字 13 23 | 因此,数字总和 = 12 + 13 = 25 24 | ``` 25 | 26 | ```js 27 | /** 28 | * @param {TreeNode} root 29 | * @return {number} 30 | */ 31 | var sumNumbers = function (root) { 32 | if (!root) return 0; 33 | let sum = 0; 34 | function dfs(root, value) { 35 | if (!root) return; 36 | let v = `${value}${root.val}`; 37 | 38 | if (!root.left && !root.right) { 39 | sum += Number(v); 40 | return; 41 | } 42 | dfs(root.left, v); 43 | dfs(root.right, v); 44 | } 45 | 46 | dfs(root, ''); 47 | 48 | return sum; 49 | }; 50 | ``` 51 | -------------------------------------------------------------------------------- /docs/algorithm/动态规划/动态规划理论基础.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 动态规划理论基础 3 | date: 2022-03-31 20:56:00 4 | sidebar: auto 5 | tags: 6 | - 动态规划 7 | categories: 8 | - leetcode 9 | --- 10 | 11 | 个人理解动态规划是递推公式,是后面的结果需要依赖前面的计算结果才能成立。 12 | 13 | 举个例子 [打家劫舍 I](./打家劫舍系列.md) 14 | 15 | 求小偷偷到最大金额。 16 | 17 | ```js 18 | [1, 2, 3, 4]; 19 | // 只有 1 间房可以偷: 1 20 | // 只有 2 间房可以偷: Math.max(1, 2) 21 | // 只有 3 间房可以偷: 1 + 3 > 2 => 4 22 | // 第 i 间房:dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]) 23 | // Math.max(隔壁那间房,相邻那件房 + 现在这间房) 24 | ``` 25 | 26 | 递推得公式: 27 | 28 | ```js 29 | var rob = function (nums) { 30 | let dp = [nums[0], Math.max(nums[0], nums[1])]; 31 | let len = nums.length; 32 | for (let i = 2; i < len; i++) { 33 | dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]); 34 | } 35 | return dp[len - 1]; 36 | }; 37 | ``` 38 | -------------------------------------------------------------------------------- /docs/algorithm/动态规划/背包系列.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 背包系列 3 | date: 2022-06-05 12:32:19 4 | sidebar: auto 5 | tags: 6 | - 动态规划 7 | categories: 8 | - leetcode 9 | --- 10 | 11 | ## 416. 分割等和子集 12 | 13 | [力扣题目链接](https://leetcode-cn.com/problems/partition-equal-subset-sum/): 给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。 14 | 15 | 注意: 每个数组中的元素不会超过 100,数组的大小不会超过 200 16 | 17 | ```js 18 | 示例 1: 19 | 输入: [1, 5, 11, 5] 20 | 输出: true 21 | 解释: 数组可以分割成 [1, 5, 5] 和 [11]. 22 | 23 | 示例 2: 24 | 输入: [1, 2, 3, 5] 25 | 输出: false 26 | 解释: 数组不能分割成两个元素和相等的子集. 27 | ``` 28 | 29 | 提示: 30 | 31 | - 1 <= nums.length <= 200 32 | - 1 <= nums[i] <= 100 33 | 34 | ### 思路 35 | 36 | 本题可以看成是 0-1 背包问题,给一个可装载重量为 `sum / 2` 的背包和 N 个物品,每个物品的重量记录在 nums 数组中,问是否在一种装法,能够恰好将背包装满?`dp[i][j]`表示前 i 个物品是否能装满容积为 j 的背包,当 `dp[i][j]`为 true 时表示恰好可以装满。每个数都有放入背包和不放入两种情况,分析方法和 0-1 背包问题一样。 37 | -------------------------------------------------------------------------------- /docs/algorithm/回溯/子集.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 子集 3 | date: 2022-03-31 20:46:00 4 | sidebar: auto 5 | tags: 6 | - 回溯算法 7 | categories: 8 | - leetcode 9 | --- 10 | 11 | [力扣题目链接](https://leetcode-cn.com/problems/subsets/) 12 | 13 | 给定一组不含重复元素的整数数组  nums,返回该数组所有可能的子集(幂集)。 14 | 15 | 说明:解集不能包含重复的子集。 16 | 17 | ```js 18 | 输入: nums = [1, 2, 3]; 19 | 输出: [[3], [1], [2], [1, 2, 3], [1, 3], [2, 3], [1, 2], []]; 20 | ``` 21 | 22 | ```js 23 | /** 24 | * @param {number[]} nums 25 | * @return {number[][]} 26 | */ 27 | var subsets = function (nums) { 28 | let result = []; 29 | function backtrack(track, idx) { 30 | result.push([...track]); 31 | 32 | for (var i = idx; i < nums.length; i++) { 33 | track.push(nums[i]); 34 | backtrack(track, i + 1); 35 | track.pop(); 36 | } 37 | } 38 | 39 | backtrack([], 0); 40 | 41 | return result; 42 | }; 43 | ``` 44 | -------------------------------------------------------------------------------- /docs/algorithm/回溯/子集II.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 子集II 3 | date: 2022-03-31 20:46:00 4 | sidebar: auto 5 | tags: 6 | - 回溯算法 7 | categories: 8 | - leetcode 9 | --- 10 | 11 | [力扣题目链接](https://leetcode-cn.com/problems/subsets-ii/) 12 | 13 | 给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。 14 | 15 | 说明:解集不能包含重复的子集。 16 | 17 | 示例: 18 | 19 | ```js 20 | - 输入: [1,2,2] 21 | - 输出: [[2], [1], [1, 2, 2], [2, 2], [1, 2], []]; 22 | ``` 23 | 24 | ```js 25 | /** 26 | * @param {number[]} nums 27 | * @return {number[][]} 28 | */ 29 | var subsetsWithDup = function (nums) { 30 | let result = []; 31 | nums.sort((a, b) => a - b); 32 | function backtrack(track, idx) { 33 | result.push([...track]); 34 | 35 | for (var i = idx; i < nums.length; i++) { 36 | if (nums[i] === nums[i - 1] && i > idx) continue; 37 | track.push(nums[i]); 38 | backtrack(track, i + 1); 39 | track.pop(); 40 | } 41 | } 42 | 43 | backtrack([], 0); 44 | 45 | return result; 46 | }; 47 | ``` 48 | -------------------------------------------------------------------------------- /docs/algorithm/回溯/字符串的排列.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 字符串的排列 3 | date: 2022-03-31 20:46:00 4 | sidebar: auto 5 | tags: 6 | - 回溯算法 7 | categories: 8 | - leetcode 9 | --- 10 | 11 | [剑指 Offer 38. 字符串的排列](https://leetcode-cn.com/problems/zi-fu-chuan-de-pai-lie-lcof/) 12 | 13 | ```js 14 | 输入:s = "abc" 15 | 输出:["abc","acb","bac","bca","cab","cba"] 16 | ``` 17 | 18 | ```js 19 | /** 20 | * @param {string} s 21 | * @return {string[]} 22 | */ 23 | var permutation = function (s) { 24 | let result = []; 25 | let visited = []; 26 | 27 | s = s.split('').sort((a, b) => a.charCodeAt() - b.charCodeAt()); 28 | 29 | function backtrack(track) { 30 | if (track.length === s.length) { 31 | result.push([...track].join('')); 32 | return; 33 | } 34 | 35 | for (let i = 0; i < s.length; i++) { 36 | if (visited[i] || (!visited[i - 1] && s[i] === s[i - 1])) continue; 37 | track.push(s[i]); 38 | visited[i] = true; 39 | backtrack(track); 40 | track.pop(); 41 | visited[i] = false; 42 | } 43 | } 44 | 45 | backtrack([]); 46 | return result; 47 | }; 48 | 49 | console.log(permutation('abb')); 50 | ``` 51 | -------------------------------------------------------------------------------- /docs/algorithm/回溯/总结.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 回溯法总结 3 | date: 2022-03-31 20:46:00 4 | sidebar: auto 5 | tags: 6 | - 回溯算法 7 | categories: 8 | - leetcode 9 | --- 10 | 11 | - [代码随想录](https://programmercarl.com/) 12 | 13 | ## 模板 14 | 15 | ```js 16 | result = []; 17 | 18 | function backtrack(路径, 选择列表) { 19 | if ('满足结束条件') { 20 | // 这里就是对答案做更新,依据实际题目出发 21 | result.push(路径); 22 | return; 23 | } else { 24 | for (let i = 0; i < 选择列表.length; i++) { 25 | // 对一个选择列表做相应的选择 26 | 27 | 做选择; 28 | 29 | backtrack(路径, 选择列表); 30 | 31 | // 既然是回溯算法,那么在一次分岔路做完选择后 32 | // 需要回退我们之前做的操作 33 | 34 | 撤销选择; 35 | } 36 | } 37 | } 38 | ``` 39 | -------------------------------------------------------------------------------- /docs/algorithm/回溯/组合总和.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 组合总和 3 | date: 2022-03-31 20:46:00 4 | sidebar: auto 5 | tags: 6 | - 回溯算法 7 | categories: 8 | - leetcode 9 | --- 10 | 11 | [力扣题目链接](https://leetcode-cn.com/problems/combination-sum/) 12 | 13 | 给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。 14 | 15 | candidates 中的数字可以无限制重复被选取。 16 | 17 | 说明: 18 | 19 | 所有数字(包括 target)都是正整数。 20 | 解集不能包含重复的组合。 21 | 22 | ```js 23 | 示例 1: 输入:candidates = [2,3,6,7], target = 7, 所求解集为: [ [7], [2,2,3] ] 24 | 25 | 示例 2: 输入:candidates = [2,3,5], target = 8, 所求解集为: [ [2,2,2,2], [2,3,3], [3,5] ] 26 | ``` 27 | 28 | ```js 29 | /** 30 | * @param {number[]} candidates 31 | * @param {number} target 32 | * @return {number[][]} 33 | */ 34 | var combinationSum = function (candidates, target) { 35 | let result = []; 36 | 37 | function backtrack(track, idx, sum) { 38 | if (sum === target) return result.push([...track]); 39 | if (sum > target) return; 40 | 41 | for (let i = idx; i < candidates.length; i++) { 42 | if (candidates[i] > target - sum) continue; 43 | track.push(candidates[i]); 44 | sum += candidates[i]; 45 | backtrack(track, i, sum); 46 | sum -= candidates[i]; 47 | track.pop(); 48 | } 49 | } 50 | 51 | backtrack([], 0, 0); 52 | return result; 53 | }; 54 | ``` 55 | -------------------------------------------------------------------------------- /docs/algorithm/字符串/千位分隔数.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 千位分隔数 3 | date: 2022-05-14 17:40:58 4 | sidebar: auto 5 | tags: 6 | - 字符串 7 | categories: 8 | - leetcode 9 | --- 10 | 11 | 给你一个整数 n,请你每隔三位添加点(即 "." 符号)作为千位分隔符,并将结果以字符串格式返回。 12 | 13 | ```js 14 | 输入:n = 987 15 | 输出:"987" 16 | 17 | 输入:n = 1234 18 | 输出:"1.234" 19 | 20 | 输入:n = 123456789 21 | 输出:"123.456.789" 22 | 23 | 输入:n = 0 24 | 输出:"0" 25 | ``` 26 | 27 | 无情的 api 杀手! 28 | 29 | ```js 30 | /** 31 | * @param {number} n 32 | * @return {string} 33 | */ 34 | var thousandSeparator = function (n) { 35 | return n.toLocaleString(3).replace(/,/g, '.'); 36 | }; 37 | ``` 38 | 39 | 当然不,正规解法: 40 | 41 | ```js 42 | /** 43 | * @param {number} n 44 | * @return {string} 45 | */ 46 | var thousandSeparator = function (n) { 47 | let s = n.toString(); 48 | let j = s.length; 49 | let result = []; 50 | while (j - 3 > 0) { 51 | result.unshift(s.slice(j - 3, j)); 52 | j -= 3; 53 | } 54 | // 前面的数 也要判断 55 | if (j > 0) result.unshift(s.slice(0, j)); 56 | 57 | return result.join('.'); 58 | }; 59 | ``` 60 | 61 | Better 62 | 63 | ```js 64 | function thousandSeparator(n: number): string { 65 | const rec = (v: string) => (v.length <= 3 ? v : rec(v.slice(0, -3)) + '.' + v.slice(-3)); 66 | return rec(String(n)); 67 | } 68 | ``` 69 | -------------------------------------------------------------------------------- /docs/algorithm/字符串/反转字符串II.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 反转字符串II 3 | date: 2022-03-31 20:46:00 4 | sidebar: auto 5 | tags: 6 | - 字符串 7 | categories: 8 | - leetcode 9 | --- 10 | 11 | ```js 12 | /** 13 | * @param {string} s 14 | * @param {number} k 15 | * @return {string} 16 | */ 17 | var reverseStr = function (s, k) { 18 | let arr = s.split(''); 19 | 20 | for (let i = 0; i < s.length; i += 2 * k) { 21 | let y = Math.min(i + k, s.length) - 1; 22 | for (let x = i; x < y; x++, y--) { 23 | [arr[x], arr[y]] = [arr[y], arr[x]]; 24 | } 25 | } 26 | 27 | return arr.join(''); 28 | }; 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/algorithm/字符串/字符串的排列.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 字符串的排列 3 | date: 2022-03-31 20:46:00 4 | sidebar: auto 5 | tags: 6 | - 字符串 7 | categories: 8 | - leetcode 9 | --- 10 | 11 | [567. 字符串的排列](https://leetcode.cn/problems/permutation-in-string): 给你两个字符串  `s1`  和  `s2` ,写一个函数来判断 `s2` 是否包含 `s1`  的排列。如果是,返回 `true` ;否则,返回 `false` 。 12 | 13 | 换句话说,s1 的排列之一是 s2 的 子串 。 14 | 15 | ```js 16 | 输入:s1 = "ab" s2 = "eidbaooo" 17 | 输出:true 18 | 解释:s2 包含 s1 的排列之一 ("ba"). 19 | 20 | 输入:s1= "ab" s2 = "eidboaoo" 21 | 输出:false 22 | ``` 23 | 24 | ```js 25 | /** 26 | * @param {string} s1 27 | * @param {string} s2 28 | * @return {boolean} 29 | */ 30 | var checkInclusion = function (s1, s2) { 31 | let map = {}; 32 | for (let char of s1) { 33 | if (map[char]) map[char] += 1; 34 | else map[char] = 1; 35 | } 36 | 37 | let slider = {}; 38 | let start = 0; 39 | let end = 0; 40 | while (end < s2.length) { 41 | let char = s2[end]; 42 | 43 | if (slider[char]) slider[char] += 1; 44 | else slider[char] = 1; 45 | 46 | end++; 47 | 48 | // 调整窗口,判断左侧窗口是否要收缩 49 | if (end - start > s1.length) { 50 | slider[s2[start]]--; 51 | start++; 52 | } 53 | 54 | if (Object.entries(map).every(([key, value]) => slider[key] === value)) { 55 | return true; 56 | } 57 | } 58 | 59 | return false; 60 | }; 61 | ``` 62 | -------------------------------------------------------------------------------- /docs/algorithm/字符串/最长不含重复字符的子字符串.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 最长不含重复字符的子串 3 | date: 2022-03-31 20:46:00 4 | sidebar: auto 5 | tags: 6 | - 字符串 7 | categories: 8 | - leetcode 9 | --- 10 | 11 | ```js 12 | 输入: "abcabcbb" 13 | 输出: 3 14 | 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。 15 | 16 | 输入: "bbbbb" 17 | 输出: 1 18 | 解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。 19 | 20 | 输入: "pwwkew" 21 | 输出: 3 22 | 解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。 23 |   请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。 24 | ``` 25 | 26 | ## 思路 27 | 28 | 1. 这里用哈希 + 双指针。end++,记录每次字符出现的位置 29 | 2. 如果发现字符在 map 里面,表示当前重复了,那么需要更新 start 指针 30 | 31 | ## 代码 32 | 33 | ```js 34 | /** 35 | * @param {string} s 36 | * @return {number} 37 | */ 38 | var lengthOfLongestSubstring = function (s) { 39 | let start = 0, 40 | end = 0, 41 | max = 0, 42 | map = new Map(); 43 | 44 | while (end < s.length) { 45 | if (map.has(s[end])) { 46 | // 更新左指针 47 | // 因为上次 char 的记录 可能未更新,所以判断 l 指针每次都是向前的! 48 | start = Math.max(start, map.get(s[end]) + 1); 49 | } 50 | map.set(s[end], end); 51 | end++; 52 | max = Math.max(max, end - start); 53 | } 54 | 55 | return max; 56 | }; 57 | ``` 58 | -------------------------------------------------------------------------------- /docs/algorithm/数组/两数之和.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 两数之和 3 | date: 2022-03-31 20:46:00 4 | sidebar: auto 5 | tags: 6 | - 哈希 7 | categories: 8 | - leetcode 9 | --- 10 | 11 | [力扣题目链接](https://leetcode-cn.com/problems/two-sum/) 12 | 13 | 给定一个整数数组 nums  和一个目标值 target,请你在该数组中找出和为目标值的那   两个   整数,并返回他们的数组下标。 14 | 15 | 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。 16 | 17 | **示例:** 18 | 19 | ```js 20 | 给定 nums = [2, 7, 11, 15], target = 9 21 | 22 | 因为 nums[0] + nums[1] = 2 + 7 = 9 23 | 24 | 所以返回 [0, 1] 25 | ``` 26 | 27 | 非常简单,看代码吧: 28 | 29 | ```js 30 | /** 31 | * @param {number[]} nums 32 | * @param {number} target 33 | * @return {number[]} 34 | */ 35 | var twoSum = function (nums, target) { 36 | let map = new Map(); 37 | for (let i = 0; i < nums.length; i++) { 38 | if (map.has(target - nums[i])) { 39 | return [map.get(target - nums[i]), i]; 40 | } 41 | map.set(nums[i], i); 42 | } 43 | return []; 44 | }; 45 | ``` 46 | -------------------------------------------------------------------------------- /docs/algorithm/数组/合并两个有序数组.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 合并两个有序数组 3 | date: 2022-05-14 12:15:34 4 | sidebar: auto 5 | tags: 6 | - 数组 7 | categories: 8 | - leetcode 9 | --- 10 | 11 | [leetcode](https://leetcode.cn/problems/merge-sorted-array): 给你两个按 非递减顺序 排列的整数数组  nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。 12 | 13 | 请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。 14 | 15 | 最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 16 | 17 | ```js 18 | 输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3 19 | 输出:[1,2,2,3,5,6] 20 | 解释:需要合并 [1,2,3] 和 [2,5,6] 。 21 | 合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。 22 | 23 | 输入:nums1 = [1], m = 1, nums2 = [], n = 0 24 | 输出:[1] 25 | 解释:需要合并 [1] 和 [] 。 26 | 合并结果是 [1] 。 27 | 28 | 输入:nums1 = [0], m = 0, nums2 = [1], n = 1 29 | 输出:[1] 30 | 解释:需要合并的数组是 [] 和 [1] 。 31 | 合并结果是 [1] 。 32 | 注意,因为 m = 0 ,所以 nums1 中没有元素。nums1 中仅存的 0 仅仅是为了确保合并结果可以顺利存放到 nums1 中。 33 | ``` 34 | 35 | [题解](https://leetcode.cn/problems/merge-sorted-array/solution/ni-xiang-shuang-zhi-zhen-he-bing-liang-g-ucgj/) 36 | 37 | --- 38 | 39 | 思路 后面往前合并 40 | 41 | ![](https://alvin-cdn.oss-cn-shenzhen.aliyuncs.com/images/leetcode-88.png) 42 | 43 | ```js 44 | /** 45 | * @param {number[]} nums1 46 | * @param {number} m 47 | * @param {number[]} nums2 48 | * @param {number} n 49 | * @return {void} Do not return anything, modify nums1 in-place instead. 50 | */ 51 | var merge = function (nums1, m, nums2, n) { 52 | let i = m - 1, 53 | j = n - 1, 54 | k = nums1.length - 1; 55 | 56 | while (i >= 0 && j >= 0) { 57 | if (nums1[i] > nums2[j]) nums1[k--] = nums1[i--]; 58 | else nums1[k--] = nums2[j--]; 59 | } 60 | 61 | // 到最后 nums2 还有数据,继续插入 nums1 前面 62 | while (j >= 0) { 63 | nums1[k--] = nums2[j--]; 64 | } 65 | }; 66 | ``` 67 | -------------------------------------------------------------------------------- /docs/algorithm/数组/长度最小的子数组.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 长度最小的子数组 3 | date: 2022-05-15 11:44:48 4 | sidebar: auto 5 | tags: 6 | - 数组 7 | categories: 8 | - leetcode 9 | --- 10 | 11 | 给定一个含有  n  个正整数的数组和一个正整数 target 。 12 | 13 | 找出该数组中满足其和 ≥ target 的长度最小的 连续子数组  [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。 14 | 15 | ```js 16 | 输入:target = 7, nums = [2,3,1,2,4,3] 17 | 输出:2 18 | 解释:子数组 [4,3] 是该条件下的长度最小的子数组。 19 | 20 | 输入:target = 4, nums = [1,4,4] 21 | 输出:1 22 | 23 | 输入:target = 11, nums = [1,1,1,1,1,1,1,1] 24 | 输出:0 25 | ``` 26 | 27 | - 1 <= target <= 109 28 | - 1 <= nums.length <= 105 29 | - 1 <= nums[i] <= 105 30 | 31 | 进阶: 32 | 33 | 如果你已经实现 O(n) 时间复杂度的解法, 请尝试设计一个 O(n log(n)) 时间复杂度的解法。 34 | 35 | ## O(n) 解法 36 | 37 | 从左往右滑; 满足条件了就压缩左边界,不满足条件就扩大右 38 | 39 | ```js 40 | /** 41 | * @param {number} target 42 | * @param {number[]} nums 43 | * @return {number} 44 | */ 45 | var minSubArrayLen = function (target, nums) { 46 | let sum = 0; 47 | let i = 0, 48 | j = 0, 49 | min = 0; 50 | 51 | while (j < nums.length) { 52 | // 主旋律是扩张,找可行解 53 | sum += nums[j]; 54 | 55 | // 间歇性收缩,优化可行解 56 | while (sum >= target) { 57 | if (min === 0) min = j - i + 1; 58 | else min = Math.min(min, j - i + 1); 59 | sum -= nums[i]; 60 | i++; 61 | } 62 | 63 | j++; 64 | } 65 | 66 | return min; 67 | }; 68 | ``` 69 | 70 | ## O(n log(n)) 71 | -------------------------------------------------------------------------------- /docs/algorithm/深度遍历/单词搜索.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 单词搜索 3 | date: 2022-05-17 16:21:09 4 | sidebar: auto 5 | tags: 6 | - 深度遍历 7 | categories: 8 | - leetcode 9 | --- 10 | 11 | ## 单词搜索 12 | 13 | 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。 14 | 15 | 单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。 16 | 17 | ![](https://assets.leetcode.com/uploads/2020/11/04/word2.jpg) 18 | 19 | ```js 20 | 输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED" 21 | 输出:true 22 | ``` 23 | 24 | ![](https://assets.leetcode.com/uploads/2020/11/04/word-1.jpg) 25 | 26 | ```js 27 | 输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "SEE" 28 | 输出:true 29 | ``` 30 | 31 | ![](https://assets.leetcode.com/uploads/2020/10/15/word3.jpg) 32 | 33 | ```js 34 | 输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCB" 35 | 输出:false 36 | ``` 37 | 38 | ```js 39 | /** 40 | * @param {character[][]} board 41 | * @param {string} word 42 | * @return {boolean} 43 | */ 44 | var exist = function (board, word) { 45 | let n = board.length, 46 | m = board[0].length; 47 | 48 | function dfs(i, j, word) { 49 | if (!word) return true; 50 | if (i < 0 || i >= n || j < 0 || j >= m) return false; 51 | let char = word[0]; 52 | let next = word.slice(1); 53 | if (board[i][j] !== char) return false; 54 | board[i][j] = undefined; 55 | let res = dfs(i - 1, j, next) || dfs(i + 1, j, next) || dfs(i, j + 1, next) || dfs(i, j - 1, next); 56 | board[i][j] = char; 57 | return res; 58 | } 59 | 60 | for (let i = 0; i < n; i++) { 61 | for (let j = 0; j < m; j++) { 62 | if (dfs(i, j, word)) return true; 63 | } 64 | } 65 | return false; 66 | }; 67 | ``` 68 | -------------------------------------------------------------------------------- /docs/algorithm/深度遍历/岛屿数量.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 岛屿数量 3 | date: 2022-05-10 15:27:04 4 | sidebar: auto 5 | tags: 6 | - 深度遍历 7 | categories: 8 | - leetcode 9 | --- 10 | 11 | [leetcode](https://leetcode.cn/problems/number-of-islands): 给你一个由  '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。 12 | 13 | 岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。 14 | 15 | 此外,你可以假设该网格的四条边均被水包围。 16 | 17 | ```js 18 | 输入:grid = [ 19 | ["1","1","1","1","0"], 20 | ["1","1","0","1","0"], 21 | ["1","1","0","0","0"], 22 | ["0","0","0","0","0"] 23 | ] 24 | 输出:1 25 | 26 | 输入:grid = [ 27 | ["1","1","0","0","0"], 28 | ["1","1","0","0","0"], 29 | ["0","0","1","0","0"], 30 | ["0","0","0","1","1"] 31 | ] 32 | 输出:3 33 | ``` 34 | 35 | ```js 36 | /** 37 | * @param {character[][]} grid 38 | * @return {number} 39 | */ 40 | var numIslands = function (grid) { 41 | let count = 0; 42 | let [m, n] = [grid.length, grid[0].length]; 43 | 44 | function toZero(i, j) { 45 | if (i < 0 || i >= m || j < 0 || j >= n || grid[i][j] === '0') return; 46 | grid[i][j] = '0'; 47 | toZero(i + 1, j); 48 | toZero(i - 1, j); 49 | toZero(i, j + 1); 50 | toZero(i, j - 1); 51 | } 52 | 53 | for (let i = 0; i < m; i++) { 54 | for (let j = 0; j < n; j++) { 55 | if (grid[i][j] === '1') { 56 | count++; 57 | toZero(i, j); 58 | } 59 | } 60 | } 61 | 62 | return count; 63 | }; 64 | ``` 65 | -------------------------------------------------------------------------------- /docs/algorithm/链表/k个一组翻转链表.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: k个一组翻转链表 3 | date: 2022-03-31 20:46:00 4 | sidebar: auto 5 | tags: 6 | - 链表 7 | categories: 8 | - leetcode 9 | --- 10 | 11 | ```js 12 | /** 13 | * @param {ListNode} head 14 | * @param {number} k 15 | * @return {ListNode} 16 | */ 17 | var reverseKGroup = function (head, k) { 18 | let p = head, 19 | len = 0; 20 | while (p) { 21 | p = p.next; 22 | len++; 23 | } 24 | 25 | function dfs(node, len) { 26 | if (len < k) return node; 27 | 28 | // 反转链表 29 | let prev = null, 30 | cur = node; 31 | for (let i = 0; i < k; i++) { 32 | let next = cur.next; 33 | cur.next = prev; 34 | prev = cur; 35 | cur = next; 36 | } 37 | 38 | node.next = dfs(cur, len - k); 39 | return prev; 40 | } 41 | 42 | return dfs(head, len); 43 | }; 44 | ``` 45 | -------------------------------------------------------------------------------- /docs/algorithm/链表/删除链表的倒数第n个结点.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 删除链表的倒数第n个结点 3 | date: 2022-03-31 20:46:00 4 | sidebar: auto 5 | tags: 6 | - 链表 7 | categories: 8 | - leetcode 9 | --- 10 | 11 | ```js 12 | /** 13 | * @param {ListNode} head 14 | * @param {number} n 15 | * @return {ListNode} 16 | */ 17 | var removeNthFromEnd = function (head, n) { 18 | let fast = head; 19 | while (n-- && fast) { 20 | fast = fast.next; 21 | } 22 | 23 | let prev = new ListNode(undefined, head); 24 | let slow = prev; 25 | 26 | while (fast) { 27 | fast = fast.next; 28 | slow = slow.next; 29 | } 30 | 31 | slow.next = slow.next.next; 32 | 33 | return prev.next; 34 | }; 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/algorithm/链表/反转链表.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 反转链表 3 | date: 2022-03-31 20:46:00 4 | sidebar: auto 5 | tags: 6 | - 链表 7 | categories: 8 | - leetcode 9 | --- 10 | 11 | ```js 12 | /** 13 | * @param {ListNode} head 14 | * @return {ListNode} 15 | */ 16 | var reverseList = function (head) { 17 | let cur = head; 18 | let prev = null; 19 | 20 | while (cur) { 21 | const next = cur.next; 22 | cur.next = prev; 23 | prev = cur; 24 | cur = next; 25 | } 26 | 27 | return prev; 28 | }; 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/algorithm/链表/环形链表II.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 环形链表II 3 | date: 2022-03-31 20:46:00 4 | sidebar: auto 5 | tags: 6 | - 链表 7 | categories: 8 | - leetcode 9 | --- 10 | 11 | [力扣题目链接](https://leetcode-cn.com/problems/linked-list-cycle-ii/) 12 | 13 | 题意: 14 | 给定一个链表,返回链表开始入环的第一个节点。  如果链表无环,则返回  null。 15 | 16 | 为了表示给定链表中的环,使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。 17 | 18 | **说明**:不允许修改给定的链表。 19 | 20 | ![循环链表](https://img-blog.csdnimg.cn/20200816110112704.png) 21 | 22 | ## 思路 23 | 24 | ![](https://img-blog.csdnimg.cn/20210318162938397.png) 25 | 26 | 指针 slow 每次走一步,fast 每次走两步,如果有环,则一定会出现相遇的情况,注意这里是要返回环链接的起始位置。那么可以进行推断。 27 | 28 | 第一次相遇时 29 | 30 | - `slow` 走了 `x + y` 31 | - `fast` 走了 `x + y + n(y + z)`, n 指的是 fast 走了 n 次环 32 | 33 | 指针 slow 每次走一步,fast 每次走两步,得到公式 `2(x + y) = x + y + n(y + z)` , 也即 `x= n(y + z) - y` 34 | 35 | 整理公式之后为如下公式: `x = (n - 1)(y + z) + z` 36 | 37 | 先拿 n 为 1 的情况来举例,意味着 fast 指针在环形里转了一圈之后,就遇到了 slow 指针了。 38 | 39 | 当 n 为 1 的时候,公式就化解为 x = z, 40 | 41 | 这就意味着,**从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点**。 42 | 43 | ## 代码 44 | 45 | ```js 46 | /** 47 | * @param {ListNode} head 48 | * @return {ListNode} 49 | */ 50 | var detectCycle = function (head) { 51 | let slow = head; 52 | let fast = head; 53 | 54 | while (fast && fast.next) { 55 | fast = fast.next.next; 56 | slow = slow.next; 57 | if (fast === slow) { 58 | // 相遇,但这里要求的是环的起始节点 59 | let slow = head; 60 | // slow 重头继续走,和相遇过的 fast 指针一样,每次只走一步,肯定会再头节点相遇 61 | while (slow !== fast) { 62 | slow = slow.next; 63 | fast = fast.next; 64 | } 65 | return slow; 66 | } 67 | } 68 | 69 | return null; 70 | }; 71 | ``` 72 | -------------------------------------------------------------------------------- /docs/algorithm/链表/相交链表.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 相交链表 3 | date: 2022-05-15 11:02:11 4 | sidebar: auto 5 | tags: 6 | - 链表 7 | categories: 8 | - leetcode 9 | --- 10 | 11 | 给你两个单链表的头节点  headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。 12 | 13 | 图示两个链表在节点 c1 开始相交: 14 | 15 | ![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2018/12/14/160_statement.png) 16 | 17 | 题目数据 保证 整个链式结构中不存在环。 18 | 19 | 注意,函数返回结果后,链表必须 保持其原始结构 。 20 | 21 | ## 哈希解法 22 | 23 | ```js 24 | /** 25 | * Definition for singly-linked list. 26 | * function ListNode(val) { 27 | * this.val = val; 28 | * this.next = null; 29 | * } 30 | */ 31 | 32 | /** 33 | * @param {ListNode} headA 34 | * @param {ListNode} headB 35 | * @return {ListNode} 36 | */ 37 | var getIntersectionNode = function (headA, headB) { 38 | // hash 39 | let set = new Set(); 40 | let cur = headA; 41 | while (cur) { 42 | set.add(cur); 43 | cur = cur.next; 44 | } 45 | cur = headB; 46 | while (cur) { 47 | if (set.has(cur)) return cur; 48 | cur = cur.next; 49 | } 50 | 51 | return null; 52 | }; 53 | ``` 54 | 55 | ## 双指针 56 | 57 | 若相交,链表 A: `a+c`, 链表 B : `b+c`. `a+c+b+c = b+c+a+c` 。则会在公共处 `c` 起点相遇。若不相交,`a +b = b+a` 。因此相遇处是 NULL 58 | 59 | ```js 60 | pA:1->2->3->4->5->6->null->9->5->6->null 61 | pB:9->5->6->null->1->2->3->4->5->6->null 62 | ``` 63 | 64 | ```js 65 | var getIntersectionNode = function (headA, headB) { 66 | if (!headA || !headB) return null; 67 | 68 | let pA = headA, 69 | pB = headB; 70 | while (pA !== pB) { 71 | pA = pA ? pA.next : headB; 72 | pB = pB ? pB.next : headA; 73 | } 74 | \ 75 | return pA; 76 | }; 77 | ``` 78 | -------------------------------------------------------------------------------- /docs/algorithm/链表/移除链表元素.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 移除链表元素 3 | date: 2022-03-31 20:46:00 4 | sidebar: auto 5 | tags: 6 | - 链表 7 | categories: 8 | - leetcode 9 | --- 10 | 11 | ```js 12 | /** 13 | * @param {ListNode} head 14 | * @param {number} val 15 | * @return {ListNode} 16 | */ 17 | var removeElements = function (head, val) { 18 | let prev = new ListNode(undefined, head); // 19 | let cur = prev; 20 | // 我们以下个节点的值作为评判标准 21 | // 命中 则 cur.next = cur.next.next 22 | // 所以我们可以构造前置节点 来排除头节点的情况 23 | while (cur.next) { 24 | if (cur.next.val === val) cur.next = cur.next.next; 25 | else cur = cur.next; 26 | } 27 | 28 | return prev.next; 29 | }; 30 | ``` 31 | -------------------------------------------------------------------------------- /docs/eventloop.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 关于 EventLoop 3 | date: 2020-05-16 20:31:29 4 | --- 5 | 6 | # EventLoop 7 | 8 | ## EventLoop 是什么? 9 | 10 | `Javascript` 采用单线程的运行机制,同一时刻只能执行特定的任务。一旦遇到大量任务或者遇到一个耗时的任务,那么就很可能造成阻塞。 11 | 12 | `EventLoop` 简单来说是一个用于统筹调度任务的一种机制,用于解决单线程问题而提出的。 13 | 14 | ## 宏仁务 & 微任务 15 | 16 | 常见的宏任务和微任务有: 17 | 18 | - 宏任务:`setTimeout`  `setInterval`  `MessageChannel` `setImmediate(ie 下生效)` 19 | - 微任务:` Promise.then`` MutationObserver ` `process.nextTick` 20 | 21 | ## 科普:为什么是 JS 是单线程的? 22 | 23 | 相对的是 JS 如果是多线程,假如我们在线程 A 和线程 B 里同时对 DOM 进行修改,那么浏览器要以哪个修改为主呢? 24 | 25 | ## 思考:有了宏任务为什么还要有微任务? 26 | 27 | ## 经典面试题 28 | 29 | ```ts 30 | async function async1() { 31 | console.log('async1 start'); 32 | await async2(); 33 | console.log('async1 end'); 34 | } 35 | 36 | async function async2() { 37 | console.log('async2'); 38 | } 39 | 40 | console.log('script start'); 41 | 42 | setTimeout(function () { 43 | console.log('settimeout'); 44 | }, 0); 45 | 46 | async1(); 47 | 48 | new Promise(function (resolve) { 49 | console.log('promise1'); 50 | resolve(); 51 | }).then(function () { 52 | console.log('promise2'); 53 | }); 54 | console.log('script end'); 55 | ``` 56 | 57 | :::details 答案 58 | 59 | - `script start` 60 | - `async1 start` 61 | - `async2` 62 | - `promise1` 63 | - `script end` 64 | - `async1 end` 65 | - `promise2` 66 | - `settimeout` 67 | 68 | ::: 69 | -------------------------------------------------------------------------------- /docs/iframe 打开全屏无效.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: iframe 打开全屏无效 3 | date: 2022-03-30 11:21:42 4 | sidebar: auto 5 | tags: 6 | - 全屏 7 | categories: 8 | - 技术漫谈 9 | --- 10 | 11 | 直接在 `iframe` 页面调用 `requestFullScreen()` 是没有效果的,需要在当前 `iframe` 的 `parent` 页面调用. 12 | 13 | ```js 14 | function fullScreen(iframeId) { 15 | /* 获取父类的document */ 16 | var parentDoc = parent.document; 17 | /* 定义一个接收元素的变量 */ 18 | var thisIframe = null; 19 | $('iframe', parentDoc).each(function (index, e) { 20 | if (e.id == iframeId) { 21 | thisIframe = e; 22 | } 23 | }); 24 | 25 | requestFullScreen(thisIframe); 26 | } 27 | 28 | /** 29 | * 调用全屏方法 30 | */ 31 | var requestFullScreen = function (element) { 32 | if (window.ActiveXObject) { 33 | var WsShell = new ActiveXObject('WScript.Shell'); 34 | WsShell.SendKeys('{F11}'); 35 | } 36 | //HTML W3C 提议 37 | else if (element.requestFullScreen) { 38 | element.requestFullScreen(); 39 | } 40 | //IE11 41 | else if (element.msRequestFullscreen) { 42 | element.msRequestFullscreen(); 43 | } 44 | // Webkit (works in Safari5.1 and Chrome 15) 45 | else if (element.webkitRequestFullScreen) { 46 | element.webkitRequestFullScreen(); 47 | } 48 | // Firefox (works in nightly) 49 | else if (element.mozRequestFullScreen) { 50 | element.mozRequestFullScreen(); 51 | } 52 | }; 53 | ``` 54 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | # layout: home 3 | 4 | title: VitePress 5 | titleTemplate: Vite & Vue Powered Static Site Generator 6 | 7 | hero: 8 | name: VitePress 9 | text: Vite & Vue Powered Static Site Generator 10 | tagline: Simple, powerful, and performant. Meet the modern SSG framework you've always wanted. 11 | actions: 12 | - theme: brand 13 | text: Get Started 14 | link: /home/accessibility 15 | - theme: alt 16 | text: View on GitHub 17 | link: https://github.com/vuejs/vitepress 18 | imgage: 19 | src: https://vitepress.dev/vitepress-logo-large.webp 20 | 21 | features: 22 | - title: "Vite: The DX that can't be beat" 23 | details: Feel the speed of Vite. Instant server start and lightning fast HMR that stays fast regardless of the app size. 24 | - title: Designed to be simplicity first 25 | details: With Markdown-centered content, it's built to help you focus on writing and deployed with minimum configuration. 26 | - title: Power of Vue meets Markdown 27 | details: Enhance your content with all the features of Vue in Markdown, while being able to customize your site with Vue. 28 | - title: Fully static yet still dynamic 29 | details: Go wild with true SSG + SPA architecture. Static on page load, but engage users with 100% interactivity from there. 30 | --- 31 | 32 | World's really stomping on your dreams, huh? 33 | 34 | - [Learn React](https://zh-hans.react.dev/learn) 35 | -------------------------------------------------------------------------------- /docs/javascript.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: JavaScript 系列 3 | date: 2019-07-15 13:00:28 4 | --- 5 | 6 | # Javascipt 基础 7 | 8 | [冴羽的博客](https://github.com/mqyqingfeng/Blog) 9 | 10 | 1. [JavaScript 深入之从原型到原型链](https://github.com/mqyqingfeng/Blog/issues/2) 11 | 2. [JavaScript 深入之词法作用域和动态作用域](https://github.com/mqyqingfeng/Blog/issues/3) 12 | 3. [JavaScript 深入之执行上下文栈](https://github.com/mqyqingfeng/Blog/issues/4) 13 | 4. [JavaScript 深入之变量对象](https://github.com/mqyqingfeng/Blog/issues/5) 14 | 5. [JavaScript 深入之作用域链](https://github.com/mqyqingfeng/Blog/issues/6) 15 | 6. [JavaScript 深入之从 ECMAScript 规范解读 this](https://github.com/mqyqingfeng/Blog/issues/7) 16 | 7. [JavaScript 深入之执行上下文](https://github.com/mqyqingfeng/Blog/issues/8) 17 | 8. [JavaScript 深入之闭包](https://github.com/mqyqingfeng/Blog/issues/9) 18 | 9. [JavaScript 深入之参数按值传递](https://github.com/mqyqingfeng/Blog/issues/10) 19 | 10. [JavaScript 深入之 call 和 apply 的模拟实现](https://github.com/mqyqingfeng/Blog/issues/11) 20 | 11. [JavaScript 深入之 bind 的模拟实现](https://github.com/mqyqingfeng/Blog/issues/12) 21 | 12. [JavaScript 深入之 new 的模拟实现](https://github.com/mqyqingfeng/Blog/issues/13) 22 | 13. [JavaScript 深入之类数组对象与 arguments](https://github.com/mqyqingfeng/Blog/issues/14) 23 | 14. [JavaScript 深入之创建对象的多种方式以及优缺点](https://github.com/mqyqingfeng/Blog/issues/15) 24 | 15. [JavaScript 深入之继承的多种方式以及优缺点](https://github.com/mqyqingfeng/Blog/issues/16) 25 | 16. [JavaScript 深入系列 15 篇正式完结!](https://github.com/mqyqingfeng/Blog/issues/17) 26 | 17. [JavaScript 深入之浮点数精度](https://github.com/mqyqingfeng/Blog/issues/155) 27 | 18. [JavaScript 深入之头疼的类型转换(上)](https://github.com/mqyqingfeng/Blog/issues/159) 28 | 19. [JavaScript 深入之头疼的类型转换(下)](https://github.com/mqyqingfeng/Blog/issues/164) 29 | -------------------------------------------------------------------------------- /docs/live-codes/h5-drag/code/App.tsx: -------------------------------------------------------------------------------- 1 | import './index.css'; 2 | import Demo1 from './Demo1'; 3 | import Demo2 from './Demo2'; 4 | 5 | const App = () => ( 6 |
7 |
8 | 9 | 10 |
11 |
12 | ); 13 | 14 | export default App; 15 | -------------------------------------------------------------------------------- /docs/live-codes/h5-drag/code/Demo1.tsx: -------------------------------------------------------------------------------- 1 | const Demo1 = () => { 2 | const onDragStart = (e) => { 3 | // @ts-ignore 4 | e.dataTransfer.setData('index', e.target.dataset.index); 5 | }; 6 | 7 | const onDragOver = (e) => { 8 | e.preventDefault(); 9 | }; 10 | 11 | /** 拖拽到该区域 */ 12 | const onDrop = (e) => { 13 | alert(e.dataTransfer.getData('index')); 14 | }; 15 | 16 | return ( 17 |
18 |

Demo1 - 基础拖拽

19 |
20 |
21 | 源对象 22 |
23 |
24 | 目标对象 25 |
26 |
27 |
28 | ); 29 | }; 30 | 31 | export default Demo1; 32 | -------------------------------------------------------------------------------- /docs/live-codes/h5-drag/code/Demo2.tsx: -------------------------------------------------------------------------------- 1 | const Demo2 = () => { 2 | const onDragStart = (e) => { 3 | // @ts-ignore 4 | e.dataTransfer.setData('index', e.target.dataset.index); 5 | }; 6 | 7 | const onDragOver = (e) => { 8 | // 9 | e.dataTransfer.dropEffect = 'link'; // 修改移动时的光标样式 默认 copy 10 | e.preventDefault(); 11 | }; 12 | 13 | /** 拖拽到该区域 */ 14 | const onDrop = (e) => { 15 | alert(e.dataTransfer.getData('index')); 16 | }; 17 | 18 | return ( 19 |
20 |

Demo2 - dropEffect 设置移动的光标

21 |
22 |
23 | 源对象 24 |
25 |
26 | 目标对象 27 |
28 |
29 |
30 | ); 31 | }; 32 | 33 | export default Demo2; 34 | -------------------------------------------------------------------------------- /docs/live-codes/h5-drag/code/index.css: -------------------------------------------------------------------------------- 1 | .flex { 2 | display: flex; 3 | flex-wrap: wrap; 4 | } 5 | 6 | .box1 { 7 | width: 100px; 8 | height: 100px; 9 | margin-right: 50px; 10 | background-color: cyan; 11 | } 12 | 13 | .box2 { 14 | width: 200px; 15 | height: 200px; 16 | background-color: #ccc; 17 | } 18 | 19 | .demo-list .demo { 20 | width: 50%; 21 | padding: 20px; 22 | box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%); 23 | } 24 | -------------------------------------------------------------------------------- /docs/live-codes/h5-drag/h5-drag.md: -------------------------------------------------------------------------------- 1 | # H5 拖拽 2 | 3 | 6 | 7 | 8 | 9 | ::: code-group 10 | 11 | <<< @/live-codes/h5-drag/code/Demo1.tsx 12 | 13 | <<< @/live-codes/h5-drag/code/Demo2.tsx 14 | 15 | <<< @/live-codes/h5-drag/code/App.tsx 16 | 17 | ::: 18 | 19 | - 区域外:dragleave,离开范围 20 | - 区域内:dragenter,用来确定放置目标是否接受放置。 21 | - 区域内移动:dragover,用来确定给用户显示怎样的反馈信息 22 | - 完成拖拽(落下)drop:,允许放置对象。 23 | -------------------------------------------------------------------------------- /docs/live-codes/iframe-postmessage/code/Child.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | 3 | function App() { 4 | useEffect(() => { 5 | window.addEventListener('message', (e) => { 6 | console.log(e.data); 7 | }); 8 | }, []); 9 | 10 | const sendMsgToFather = () => { 11 | parent?.postMessage('recived message from child', '*'); // [!code hl] 12 | }; 13 | 14 | return ( 15 |
16 |

Child

17 |
18 | Father 19 | 20 |
21 |
22 | ); 23 | } 24 | 25 | export default App; 26 | -------------------------------------------------------------------------------- /docs/live-codes/iframe-postmessage/code/Father.tsx: -------------------------------------------------------------------------------- 1 | import { useRef, useEffect } from 'react'; 2 | 3 | function App() { 4 | const iframeRef = useRef(null); 5 | 6 | const sendMsgToChild = () => { 7 | iframeRef.current?.contentWindow?.postMessage('message from father', '*'); // [!code hl] 8 | }; 9 | 10 | useEffect(() => { 11 | window.addEventListener('message', (e) => { 12 | console.log(e.data); 13 | }); 14 | }, []); 15 | 16 | return ( 17 |
18 |
19 | Father 20 | 21 |
22 | 23 |
24 | ); 25 | } 26 | 27 | export default App; 28 | -------------------------------------------------------------------------------- /docs/live-codes/iframe-postmessage/iframe-postmessage.md: -------------------------------------------------------------------------------- 1 | ::: code-group 2 | 3 | <<< @/live-codes/iframe-postmessage/code/Father.tsx 4 | 5 | <<< @/live-codes/iframe-postmessage/code/Child.tsx 6 | 7 | ::: 8 | -------------------------------------------------------------------------------- /docs/network-protocol/01.网络模型.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 网络模型 3 | date: 2018-09-28 18:00:28 4 | sidebar: 'auto' 5 | tags: 6 | - http 7 | categories: 8 | - 网络协议 9 | --- 10 | 11 | 数据链路层(mac) -> 网络层(IP)-> 传输层(TCP/UDP)-> 应用层(HTTP、SMTP、FTP) 12 | 13 | ![](https://alvin-cdn.oss-cn-shenzhen.aliyuncs.com/images/network-model.png) 14 | 15 | ## 数据链路层(Mac) 16 | 17 | 负责在以太网、WiFi 这样的底层网络上发送原始数据包,工作在网卡这个层次,使用 `MAC` 地址来标记网络上的设备,所以有时候也叫 `MAC` 层。 18 | 19 | ## 网络层(IP) 20 | 21 | IP 协议就处在这一层。因为 IP 协议定义了“IP 地址”的概念,所以就可以在“链接层”的基础上,用 IP 地址取代 MAC 地址,把许许多多的局域网、广域网连接成一个虚拟的巨大网络,在这个网络里找设备时只要把 IP 地址再“翻译”成 MAC 地址就可以了。 22 | 23 | ## 传输层(TCP/UDP) 24 | 25 | 传输层负责为两台主机中的进程提供通信服务,它使用 16 位的端口号来标识端口,当两个计算机中的进程要进行通讯时,除了要知道对方的 IP 地址外,还需要知道对方的端口。该层主要有以下两个协议:用户数据报协议(UDP,User Datagram Protocol)和传输控制协议(TCP,Transmission Control Protocol): 26 | 27 | ## 应用层(HTTP/SMTP/FTP...) 28 | 29 | 由于下面的三层把基础打得非常好,所以在这一层就“百花齐放”了,有各种面向具体应用的协议。例如 Telnet、SSH、FTP、SMTP 等等,当然还有我们的 HTTP。 30 | 31 | 相关链接 [详解 四层、五层、七层 计算机网络模型](https://juejin.im/post/6844904049800642568) 32 | -------------------------------------------------------------------------------- /docs/network-protocol/02.tcp报文.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: TCP 报文结构 3 | date: 2018-09-28 13:00:28 4 | sidebar: 'auto' 5 | tags: 6 | - tcp 7 | categories: 8 | - 网络协议 9 | --- 10 | 11 | TCP 报文是 TCP 层传输的数据单元,也叫报文段。 12 | 13 | ![](https://alvin-cdn.oss-cn-shenzhen.aliyuncs.com/images/tcp-structure.png) 14 | 15 | ## 源端口、目标端口 16 | 17 | 如何标识唯一标识一个连接?答案是 TCP 连接的四元组——源 **IP、源端口、目标 IP 和目标端口。** 18 | 19 | 那 TCP 报文怎么没有源 IP 和目标 IP 呢?这是因为在 IP 层就已经处理了 IP 。TCP 只需要记录两者的端口即可。 20 | 21 | ## 序列号 22 | 23 | 即 `Sequence number`, 指的是本报文段第一个字节的序列号。 24 | 25 | 序列号在 TCP 通信的过程中有两个作用: 26 | 27 | - 在 SYN 报文中交换彼此的初始序列号。 28 | - 保证数据包按正确的顺序组装。 29 | 30 | ## ISN 31 | 32 | 即 `Initial Sequence Number`(初始序列号),在三次握手的过程当中,双方会用过 SYN 报文来交换彼此的 ISN。 33 | 34 | ISN 并不是一个固定的值,而是每 4 ms 加一,溢出则回到 0,这个算法使得猜测 ISN 变得很困难。那为什么要这么做? 35 | 36 | 如果 ISN 被攻击者预测到,要知道源 IP 和源端口号都是很容易伪造的,当攻击者猜测 ISN 之后,直接伪造一个 RST 后,就可以强制连接关闭的,这是非常危险的。 37 | 38 | 而动态增长的 ISN 大大提高了猜测 ISN 的难度。 39 | 40 | ## 确认号 41 | 42 | 即 `ACK`(Acknowledgment number)。用来告知对方下一个期望接收的序列号,小于 ACK 的所有字节已经全部收到。 43 | 44 | ## 标记位 45 | 46 | 常见的标记位有 `SYN`,`ACK`,`FIN`,`RST`,`PSH`。 47 | 48 | `SYN` 和 `ACK` 已经在上文说过,后三个解释如下: FIN: 即 Finish,表示发送方准备断开连接。 49 | 50 | `RST`:即 `Reset`,用来强制断开连接。 51 | 52 | `PSH`: 即 `Push`, 告知对方这些数据包收到后应该马上交给上层的应用,不能缓存。 53 | 54 | ## 窗口大小 55 | 56 | 占用两个字节,也就是 16 位,但实际上是不够用的。因此 TCP 引入了窗口缩放的选项,作为窗口缩放的比例因子,这个比例因子的范围在 0 ~ 14,比例因子可以将窗口的值扩大为原来的 2 ^ n 次方。 57 | 58 | ## 校验和 59 | 60 | 占用两个字节,防止传输过程中数据包有损坏,如果遇到校验和有差错的报文,TCP 直接丢弃之,等待重传。 61 | 62 | ## 可选项 63 | 64 | 可选项的格式如下: 65 | 66 | ![](https://alvin-cdn.oss-cn-shenzhen.aliyuncs.com/images/tcp-structure2.png) 67 | 68 | 常用的可选项有以下几个: 69 | 70 | - TimeStamp: TCP 时间戳,后面详细介绍。 71 | - MSS: 指的是 TCP 允许的从对方接收的最大报文段。 72 | - SACK: 选择确认选项。 73 | - Window Scale: 窗口缩放选项。 74 | -------------------------------------------------------------------------------- /docs/network-protocol/05.tcp中syn攻击.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 半连接队列和 SYN Flood 攻击 3 | date: 2018-09-28 14:00:28 4 | sidebar: 'auto' 5 | tags: 6 | - tcp 7 | categories: 8 | - 网络协议 9 | --- 10 | 11 | ## 半连接队列 12 | 13 | 三次握手时,服务器第一次收到客户端的 SYN 之后,就会处于 SYN_RCVD 状态,此时双方还没有完全建立其连接,服务器会把此种状态下请求连接放在一个队列里,我们把这种队列称之为半连接队列。 14 | 15 | 当然还有一个全连接队列,就是已经完成三次握手,建立起连接的就会放在全连接队列中。如果队列满了就有可能会出现丢包现象。 16 | 17 | 这里在补充一点关于 **SYN-ACK 重传次数**的问题: 18 | 服务器发送完 `SYN-ACK` 包,如果未收到客户确认包,服务器进行首次重传,等待一段时间仍未收到客户确认包,进行第二次重传。如果重传次数超过系统规定的最大重传次数,系统将该连接信息从半连接队列中删除。 19 | 注意,每次重传等待的时间不一定相同,一般会是指数增长,例如间隔时间为 1s,2s,4s,8s...... 20 | 21 | ## SYN 攻击 22 | 23 | 服务器端的资源分配是在二次握手时分配的,而客户端的资源是在完成三次握手时分配的,所以服务器容易受到 `SYN 洪泛攻击`。 24 | 25 | :::warning SYN 攻击是一种典型的 DoS/DDoS 攻击。 26 | SYN 攻击就是 Client 在短时间内伪造大量不存在的 IP 地址,并向 Server 不断地发送 SYN 包,Server 则回复确认包,并等待 Client 确认,由于源地址不存在,因此 Server 需要不断重发直至超时,这些伪造的 SYN 包将长时间占用未连接队列,导致正常的 SYN 请求因为队列满而被丢弃,从而引起网络拥塞甚至系统瘫痪。 27 | ::: 28 | 29 | 检测 SYN 攻击非常的方便,当你在服务器上看到大量的半连接状态时,特别是源 IP 地址是随机的,基本上可以断定这是一次 SYN 攻击。在 `Linux/Unix` 上可以使用系统自带的 `netstats` 命令来检测 SYN 攻击。 30 | 31 | ```bash 32 | netstat -n -p TCP | grep SYN_RECV 33 | ``` 34 | 35 | 常见的防御 SYN 攻击的方法有如下几种: 36 | 37 | - 缩短超时(SYN Timeout)时间 38 | - 增加最大半连接数 39 | - 过滤网关防护 40 | - SYN cookies 技术 41 | -------------------------------------------------------------------------------- /docs/network-protocol/06.tcp和udp的区别.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: TCP 和 UDP 的区别概述 3 | date: 2018-09-28 15:00:28 4 | sidebar: 'auto' 5 | tags: 6 | - tcp 7 | categories: 8 | - 网络协议 9 | --- 10 | 11 | - `TCP` 是一个面向连接的、可靠的、基于字节流的传输层协议。 12 | - 而 `UDP` 是一个面向无连接的传输层协议。 13 | 14 | 1. **面向连接**。所谓的连接,指的是客户端和服务器的连接,在双方互相通信之前,TCP 需要三次握手建立连接,而 UDP 没有相应建立连接的过程。 15 | 2. **可靠性**。TCP 花了非常多的功夫保证连接的可靠,这个可靠性体现在哪些方面呢?一个是有状态,另一个是可控制。 16 | 17 | TCP 会精准记录哪些数据发送了,哪些数据被对方接收了,哪些没有被接收到,而且保证数据包按序到达,不允许半点差错。这是**有状态**。 18 | 19 | 当意识到丢包了或者网络环境不佳,TCP 会根据具体情况调整自己的行为,控制自己的发送速度或者重发。这是**可控制**。 20 | 21 | 相应的,UDP 就是无状态, 不可控的。 22 | 23 | 3. **面向字节流**。UDP 的数据传输是基于数据报的,这是因为仅仅只是继承了 IP 层的特性,而 TCP 为了维护状态,将一个个 IP 包变成了字节流。 24 | -------------------------------------------------------------------------------- /docs/network-protocol/07.http报文结构.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: HTTP 报文结构是怎样的? 3 | date: 2018-09-22 13:00:28 4 | sidebar: 'auto' 5 | tags: 6 | - http 7 | categories: 8 | - 网络协议 9 | --- 10 | 11 | 对于 TCP 而言,在传输的时候分为两个部分: **TCP 头**和**数据部分**。 12 | 13 | 而 HTTP 类似,也是 `header + body` 的结构,具体而言: 14 | 15 | ```ts 16 | 起始行 + 头部 + 空行 + 实体; 17 | ``` 18 | 19 | :::details 20 | 21 | ![](https://alvin-cdn.oss-cn-shenzhen.aliyuncs.com/images/http-wireshark.png) 22 | 23 | ::: 24 | 25 | ## 起始行 26 | 27 | 对于请求报文来说: 28 | 29 | ```ts 30 | GET /home HTTP/1.1 31 | ``` 32 | 33 | 也就是`方法` + `路径` + `http 版本`。 34 | 35 | 对于响应报文来说,起始行一般长这个样: 36 | 37 | ```ts 38 | HTTP/1.1 200 OK 39 | ``` 40 | 41 | `http 版本` + `状态码` + `原因`。 42 | 43 | ## 头部 44 | 45 | ![](https://alvin-cdn.oss-cn-shenzhen.aliyuncs.com/images/response-header.png) 46 | 47 | 不管是请求头还是响应头,其中的字段是相当多的,而且牵扯到 http 非常多的特性,这里就不一一列举的,重点看看这些头部字段的格式: 48 | 49 | - 字段名不区分大小写 50 | - 字段名不允许出现空格,不可以出现下划线 `_` 51 | - 字段名后面必须紧接着 `:` 52 | 53 | ## 空行 54 | 55 | `空行` 用于区分**头部**和**实体** 56 | 57 | :::warning 如果说在头部中间故意加一个空行会怎么样? 58 | 59 | 那么空行后的内容全部被视为实体。 60 | 61 | ::: 62 | 63 | ## 实体 64 | 65 | 就是具体的数据了,也就是 body 部分。请求报文对应请求体, 响应报文对应响应体。 66 | -------------------------------------------------------------------------------- /docs/network-protocol/12.http优缺点.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 简要概括一下 HTTP 的优缺点? 3 | date: 2018-09-17 13:00:28 4 | sidebar: 'auto' 5 | tags: 6 | - http 7 | categories: 8 | - 网络协议 9 | --- 10 | 11 | ## HTTP 优点 12 | 13 | 1. 灵活可扩展:不仅仅可以传输文本,还能传输图片、视频等任意数据,非常方便。 14 | 2. 可靠传输:HTTP 基于 TCP/IP,因此把这一特性继承了下来。这属于 TCP 的特性,不具体介绍了。 15 | 3. 请求-应答:也就是一发一收、有来有回, 16 | 4. 无状态:这服务器没有状态差异,可以很容易地组成集群。 17 | 18 | ## HTTP 缺点 19 | 20 | 1. 无状态:无法记录请求的对象信息,为此 cookie 存在的意义。。 21 | 2. 明文传输:容易被抓包,窃取、篡改、冒充。 22 | 3. 队头阻塞问题:由 HTTP 基本的“请求 - 应答”模型所导致的,因为 HTTP 规定报文必须是“一发一收”,这就形成了一个先进先出的“串行”队列。这个问题将 23 | 24 | 在如何解决 HTTP 的队头阻塞问题特殊讲解 25 | -------------------------------------------------------------------------------- /docs/network-protocol/13.http队头阻塞.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 如何解决 HTTP 的队头阻塞问题? 3 | date: 2018-09-15 13:00:28 4 | sidebar: 'auto' 5 | tags: 6 | - http 7 | - 队头阻塞 8 | categories: 9 | - 网络协议 10 | --- 11 | 12 | ## 什么是队头阻塞 13 | 14 | **http1.x 采用长连接(Connection:keep-alive),可以在一个 TCP 请求上,发送多个 http 请求。** 15 | 16 | 因为 HTTP 规定报文必须是 “**一发一收**”,这就形成了一个先进先出的“串行”队列。 17 | 18 | 队列里的请求没有轻重缓急的优先级,只有入队的先后顺序,排在最前面的请求被最优先处理。 19 | 20 | 如果队首的请求因为处理的太慢耽误了时间,那么队列里后面的所有请求也不得不跟着一起等待,结果就是其他的请求承担了不应有的时间成本。 21 | 22 | ## 并发连接 23 | 24 | 浏览器一个域名采用 `6-8` 个 TCP 连接,并发 `HTTP` 请求. 25 | 26 | 但其实,即使是提高了并发连接,还是不能满足人们对性能的需求。 27 | 28 | ## 域名分片 29 | 30 | 一个域名不是可以并发 6 个长连接吗?那我就多分几个域名。比如 content1.alvin.run 、content2.alvin.run。 31 | 32 | 这样一个 `alvin.run` 域名下可以分出非常多的二级域名,而它们都指向同样的一台服务器,能够并发的长连接数更多了,事实上也更好地解决了队头阻塞的问题。 33 | 34 | :::danger http1.1 没有真正解决了队头阻塞问题 35 | 36 | 即使使用上面的方式,也是治标不治本,http2 采用了**多路复用的方式**解决了这个问题,请看后续。 37 | 38 | ::: 39 | -------------------------------------------------------------------------------- /docs/network-protocol/15.https的tsl连接过程.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: tsl 握手的过程(1.2) 3 | date: 2018-09-28 13:00:28 4 | sidebar: 'auto' 5 | tags: 6 | - https 7 | categories: 8 | - 网络协议 9 | --- 10 | 11 | > 最终是使用会话密钥的方式进行对称加密的密文传输,所以拿到服务器的公钥加密随机数,让两端生成绘话密钥尤为重要! 12 | 13 | ![](https://alvin-cdn.oss-cn-shenzhen.aliyuncs.com/images/tsl12-0.png) 14 | 15 | - web `- client hellow ->` server 16 | - 客户端:我生成一个随机数 A,还有我支持的加密套件有哪些给你知道,当前我用的是 tsl 1.2 版本 17 | - web `<- server hello -` server 18 | - 服务端:我也生成一个随机数 B,我挑了一个加密套件 咱们使用这个套件进行加密 19 | - web `<- server key exchange -` server 20 | - 服务端:我给你发个证书,里面有我的公钥,你可以用公钥加密,黑客破解不了。 21 | - web `<- server hello done -` server 22 | - 服务端:通知一下你 我完成啦 23 | - web `- client key exchange ->` server 24 | - 客户端:检查了一下你的证书是有效的,拿到了公钥。我在生成了一个随机数 C,用你给我的公钥加密 发给你了 25 | - 服务端:我拿到了第三个随机数,我们使用同样的加密方式生成我们的会话密钥。以后咱们使用这个密钥进行通信吧 26 | - 客户端:好的,会话密钥只应用于当前会话,咱们传输很安全了。 27 | -------------------------------------------------------------------------------- /docs/network-protocol/cdn.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: CDN 3 | date: 2018-09-28 17:00:28 4 | sidebar: 'auto' 5 | tags: 6 | - cdn 7 | categories: 8 | - 网络协议 9 | --- 10 | 11 | ![](https://alvin-cdn.oss-cn-shenzhen.aliyuncs.com/images/cdn.png) 12 | 13 | - [高性能利器:CDN 我建议你好好学一下!](https://juejin.cn/post/7002781373014474759) 14 | -------------------------------------------------------------------------------- /docs/network-protocol/dns.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: DNS 解析 3 | date: 2018-09-28 17:00:28 4 | sidebar: 'auto' 5 | tags: 6 | - dns 7 | categories: 8 | - 网络协议 9 | --- 10 | 11 | ![](https://alvin-cdn.oss-cn-shenzhen.aliyuncs.com/images/dns.png) 12 | 13 | ## 域名结构 14 | 15 | 就以 `mail.baidu.com` 域名为例,域名最后一个.的右侧部分我们称之为顶级域名,倒数第二个.右侧部分称之为二级域名,以此类推,就会有三级域名,四级域名等等。 16 | 17 | 在 `mail.baidu.com` 的域名中,`com` 成为顶级域名,`baidu.com` 称为二级域名,`mail.baidu.com` 称为三级域名。 18 | 19 | 域名由两个或两个以上的词组成,常见域名为二级域名+顶级域名组成,所以一般我们会将域名分为顶级域名、二级域名,除此之外,还有国家代码顶级域名。 20 | 21 | ## 查询顺序 22 | 23 | 现在我们来看看怎么去根据域名查询一台服务器的 IP 地址。 24 | 25 | 1. **检查浏览器缓存**中是否存在该域名与 IP 地址的映射关系,如果有则解析结束,没有则继续 26 | 2. 到系统本地查找映射关系,一般在 **hosts 文件**中,如果有则解析结束,否则继续 27 | 3. 到**本地域名服务器**去查询,有则结束,否则继续 28 | 4. **本地域名服务器查询根域名服务器**,该过程并不会返回映射关系,只会告诉你去下级服务器(顶级域名服务器)查询 29 | 5. **本地域名服务器查询顶级域名服务器**(即 com 服务器),同样不会返回映射关系,只会引导你去二级域名服务器查询 30 | 6. **本地域名服务器查询二级域名服务器**(即 `baidu.com` 服务器),引导去三级域名服务器查询 31 | 7. **本地域名服务器查询三级域名服务器**(即 mail.baidu.com 服务器),此时已经是最后一级了,如果有则返回映射关系,则本地域名服务器加入自身的映射表中,方便下次查询或其他用户查找,同时返回给该用户的计算机,没有找到则网页报错 32 | 33 | - [浏览器之 DNS 解析过程详解](https://juejin.cn/post/6909041150728863752) 34 | -------------------------------------------------------------------------------- /docs/network-protocol/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: HTTP 脑图 3 | date: 2020-12-16 20:09:24 4 | sidebar: 'auto' 5 | tags: 6 | - http 7 | categories: 8 | - 网络协议 9 | --- 10 | 11 | 图片来源 [六张图从 HTTP/0.9 进化到 HTTP3.0](https://juejin.im/post/6856036933723521032?utm_source=gold_browser_extension) 12 | 13 | ## HTTP1.1 14 | 15 | ![](https://alvin-cdn.oss-cn-shenzhen.aliyuncs.com/images/http1.1.png) 16 | 17 | ## HTTP2 18 | 19 | ![](https://alvin-cdn.oss-cn-shenzhen.aliyuncs.com/images/http2.png) 20 | 21 | ## HTTP3 22 | 23 | ![](https://alvin-cdn.oss-cn-shenzhen.aliyuncs.com/images/http3.png) 24 | -------------------------------------------------------------------------------- /docs/nginx gzip 不生效.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: nginx gzip 不生效 3 | date: 2019-07-15 13:00:28 4 | sidebar: 'auto' 5 | tags: 6 | - nginx 7 | - 技术漫谈 8 | categories: 9 | - 技术漫谈 10 | --- 11 | 12 | 优化页面的时候,使用 nginx 开启 gzip ,发现并没有什么反映~ 13 | 14 | ```bash 15 | server { 16 | ..... 17 | gzip on; # 开启压缩 18 | gzip_buffers 32 4k; # 设置用于处理请求压缩的缓冲区数量和大小 19 | gzip_comp_level 6; # 设置gzip压缩级别,级别越底压缩速度越快文件压缩比越小,反之速度越慢文件压缩比越大 20 | gzip_min_length 200; # 当返回内容大于此值时才会使用gzip进行压缩,以K为单位,当值为0时,所有页面都进行压缩。 21 | gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; 22 | gzip_vary on; 23 | ..... 24 | } 25 | ``` 26 | 27 | **问题在于 gzip_types 中的 application/x-javascript 需要把 x- 去掉~** 28 | 29 | JavaScript 的 MIME 类型通常为“`application/x-javascript`”, 非 IE 的浏览器认“`application/javascript`”,用“`text/javscript`”最通用,因为 type 可以不指定默认是"text/javascript" 30 | 31 | [nginx gzip on 无效](https://www.cnblogs.com/qiangweikang/p/gzip_on.html) 32 | -------------------------------------------------------------------------------- /docs/safe/jwt.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: JWT 3 | date: 2020-06-10 16:01:15 4 | --- 5 | 6 | https://jwt.io/ 7 | 8 | ![](https://alvin-cdn.oss-cn-shenzhen.aliyuncs.com/images/jwt.png) 9 | 10 | ## 组成 11 | 12 | **Header.Payload.Signature** 13 | 14 | - **Header**: 头部通常由两部分组成,即令牌的类型(typ)和所使用的算法(alg)。例如,一个头部可能是 {"alg": "HS256", "typ": "JWT"},表示使用 HMAC SHA-256 算法对令牌进行签名。 15 | - **Payload**: 载荷包含了 JWT 的声明信息,用于描述令牌的相关内容。比如可以存储一些用户的基本信息。 16 | - **Signature**: 签名用于验证令牌的完整性和真实性。签名通常由头部、载荷和密钥一起计算而得。 17 | 18 | ## 优缺点 19 | 20 | JWT 的优点 21 | 22 | - 无状态性:JWT 是无状态的,服务器不需要在后端存储会话信息,减轻了服务器负担。 23 | - 安全性:JWT  使用签名来验证令牌的真实性,防止了篡改和伪造。 24 | - 可扩展性:JWT  可以通过在  Payload  中添加自定义的声明来传递任意数据,实现了更多的功能。 25 | - 跨语言:JWT 是一种跨语言的标准,因为它使用  JSON  格式作为数据载体,并且它的标准规范是语言无关的。这意味着无论使用哪种编程语言编写的应用程序,都可以轻松地生成和解析  JWT。 26 | 27 | JWT 的缺点 28 | 29 | - 无法撤销令牌:一旦颁发了  JWT,就无法在令牌的有效期内撤销它。即使用户的权限发生变化或者令牌被泄露,令牌也仍然有效,直到过期时间到期为止。 30 | - 安全性依赖于签名算法:JWT  的安全性取决于签名算法的强度。如果使用了弱算法或者密钥管理不当,可能会导致签名被破解,从而使得  JWT  变得不安全。 31 | - 无法处理令牌刷新:JWT  本身不提供令牌刷新功能,需要额外的机制来实现。这使得在令牌到期后,客户端需要重新进行身份验证,可能会增加一定的复杂性。 32 | - 不适合存储敏感信息:JWT  虽然可以对  Payload  进行加密,但是加密后的  Payload  仍然可以被解析出来。因此,不建议在  JWT  中存储敏感信息,如密码等。 33 | 34 | ## 如何撤销 JWT 35 | 36 | 通常和后端协同 RefreshToken 来主动刷新令牌权限 37 | -------------------------------------------------------------------------------- /docs/ssh.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ssh 免密登陆服务器配置 3 | date: 2019-07-15 13:00:28 4 | tags: 5 | - ssh 6 | - 前端工程化 7 | categories: 8 | - 前端工程化 9 | --- 10 | 11 | ## 创建 ssh 公钥 12 | 13 | ```bash 14 | # 进入ssh 查看公钥 15 | cat ~/.ssh/id_rsa.pub 16 | 17 | # 如果不存在 则需要创建公钥 18 | ssh-keygen -t rsa -C alvin0216@163.com 19 | ``` 20 | 21 | 复制完公钥后,我们先登陆进服务器。 22 | 23 | ## 在服务器的 ssh 中添加 authorized_keys 24 | 25 | 在云服务器中进行以下操作: 26 | 27 | ```bash 28 | cd ~/.ssh/ 29 | 30 | ls # 查看是否存在 authorized_keys 文件 31 | 32 | vim authorized_keys 33 | 34 | # 如果没有的话 35 | vim ~/.ssh/authorized_keys 36 | ``` 37 | 38 | 保存我们刚刚复制的公钥 39 | 40 | ## 设置登陆名 41 | 42 | 在进行完上面的操作后,可以发现登陆服务器已经不需要密码了,但是仍然需要输入 `IP`。 43 | 44 | 此时我们可以通过配置 `~/.ssh/config` 来做一个别名 45 | 46 | ```bash 47 | vim ~/.ssh/config 48 | 49 | Host server1 50 | User root 51 | HostName 'server1 的ip' # 注意⚠️ 这里不需要加引号 比如直接写上服务器地址 47.112.48.225 52 | Host server2 53 | User root 54 | HostName 'server2 的ip' 55 | ``` 56 | 57 | 然后我们打开控制台,输入 58 | 59 | ```bash 60 | ssh server1 61 | ``` 62 | 63 | 就可以快速登陆服务器了,退出登陆则 `control + d` 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "@vitejs/plugin-vue-jsx": "^3.0.1", 4 | "vitepress": "1.0.0-alpha.61" 5 | }, 6 | "scripts": { 7 | "dev": "vitepress dev docs", 8 | "build": "vitepress build docs", 9 | "push": "node push.js" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /push.js: -------------------------------------------------------------------------------- 1 | const { execSync } = require("child_process"); 2 | 3 | function executeGitPush() { 4 | const now = formatDateTime(); 5 | 6 | try { 7 | // 根据操作系统选择命令格式 8 | const isWindows = process.platform === "win32"; 9 | const command = isWindows 10 | ? `git add . && git commit -m "Updated at ${now}" && git push` 11 | : `git add .; git commit -m "Updated at ${now}"; git push`; 12 | 13 | execSync(command, { 14 | stdio: "inherit", 15 | shell: isWindows ? process.env.ComSpec : "/bin/bash", 16 | }); 17 | 18 | console.log(`\nSUCCESS: Pushed changes at ${now}`); 19 | } catch (error) { 20 | console.error("\nERROR: Push failed. Possible reasons:"); 21 | console.error("- No changes to commit"); 22 | console.error("- Network issues"); 23 | console.error("- Authentication problems"); 24 | process.exit(1); 25 | } 26 | } 27 | 28 | function formatDateTime(format = "YYYY-MM-DD HH:mm:ss") { 29 | const pad = (n, len) => String(n).padStart(len, "0"); 30 | const date = new Date(); 31 | 32 | return format 33 | .replace(/YYYY/g, pad(date.getFullYear(), 4)) 34 | .replace(/MM/g, pad(date.getMonth() + 1, 2)) 35 | .replace(/DD/g, pad(date.getDate(), 2)) 36 | .replace(/HH/g, pad(date.getHours(), 2)) 37 | .replace(/mm/g, pad(date.getMinutes(), 2)) 38 | .replace(/ss/g, pad(date.getSeconds(), 2)); 39 | } 40 | 41 | // 执行主函数 42 | executeGitPush(); 43 | --------------------------------------------------------------------------------