
+ 非常棒的JavaScript源码实现 +
+ 🤟🎮欢迎来到 JavaScript 源码实现 🎮🤟 +
简洁性
通过详细的原理描述(文字 or 动图)来介绍我们源码的实现
正确性
通过 Jest 来测试我们的代码, 每次 commit 都必须经过 test
开放性
完全开源, 并接受您的 PR 和 Issue
diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 05773dd..0000000 --- a/.editorconfig +++ /dev/null @@ -1,31 +0,0 @@ -# http://editorconfig.org -# https://github.com/editorconfig/editorconfig/wiki/EditorConfig-Properties - -# 表示是最顶层的配置文件, 发现设为true时, 才会停止查找.editorconfig文件 -root = true - -[*] -# tab space -indent_style = tab - -indent_size = 2 -# 设置换行符, 值为lf, cr, crlf -end_of_line = lf -charset = utf-8 - -# 用一个整数来设置tab缩进的列数。默认是indent_size -tab_width = indent_size - -#是否删除行尾的空格 -trim_trailing_whitespace = true - -#是否在文件的最后插入一个空行 -insert_final_newline = true - -[*.md] -indent_size = 2 -trim_trailing_whitespace = false -insert_final_newline = true - -[Makefile] -indent_style = tab diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 941e932..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: bug -assignees: Rain120 - ---- - -**Describe the bug** -A clear and concise description of what the bug is. diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md deleted file mode 100644 index 48d5f81..0000000 --- a/.github/ISSUE_TEMPLATE/custom.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -name: Custom issue template -about: Describe this issue template's purpose here. -title: '' -labels: '' -assignees: '' - ---- - - diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 799aa9d..0000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: '' -assignees: Rain120 - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 52adf70..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: Test, Build and Deploy Docs - -on: - push: - branches: - - master - pull_request: - branches: - - "!master" - - "dev**" - -jobs: - build-and-deploy: - name: Test and Deploy Docs - runs-on: ubuntu-latest - - steps: - - name: Checkout Master - uses: actions/checkout@master - - - name: Test - run: | - npm ci - # npm run build --if-present - # npm run deploy - npm run test - npm run docs:build '/awesome-javascript-code-implementation/' - # npm run changelog - - # - name: Coveralls Parallel - # uses: coverallsapp/github-action@master - # with: - # github-token: ${{ secrets.ACCESS_TOKEN }} - # parallel: true - # path-to-lcov: ./coverage/lcov.info - - # - name: Coveralls Finished - # uses: coverallsapp/github-action@master - # with: - # github-token: ${{ secrets.ACCESS_TOKEN }} - # parallel-finished: true - - - name: Deploy - uses: JamesIves/github-pages-deploy-action@4.1.4 - with: - access_token: ${{ secrets.ACCESS_TOKEN }} - branch: gh-pages - folder: docs/.vuepress/dist - build_script: npm run deploy diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 2b1631e..0000000 --- a/.gitignore +++ /dev/null @@ -1,105 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* - -# Diagnostic reports (https://nodejs.org/api/report.html) -report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage/ -*.lcov -.coveralls.yml - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# TypeScript v1 declaration files -typings/ - -# TypeScript cache -*.tsbuildinfo - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Microbundle cache -.rpt2_cache/ -.rts2_cache_cjs/ -.rts2_cache_es/ -.rts2_cache_umd/ - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env -.env.test - -# parcel-bundler cache (https://parceljs.org/) -.cache - -# Next.js build output -.next - -# Nuxt.js build / generate output -.nuxt -dist - -# Gatsby files -.cache/ -# Comment in the public line in if your project uses Gatsby and *not* Next.js -# https://nextjs.org/blog/next-9-1#public-directory-support -# public - -# vuepress build output -.vuepress/dist - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ - -# TernJS port file -.tern-port diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 0c701b6..0000000 --- a/.travis.yml +++ /dev/null @@ -1,22 +0,0 @@ -language: node_js - -node_js: - - 8 - - 10 - - 12 - -on: - branch: - - master - - dev - -install: - - npm install - -script: - - npm run test - - npm run changelog - # - npm run coverage - -after_script: - # - npm run coveralls diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 2eb4ac4..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "name": "Current TS File", - "type": "node", - "request": "launch", - "program": "${workspaceRoot}/node_modules/ts-node/dist/bin.js", - "args": [ - "${relativeFile}" - ] - } - ] -} \ No newline at end of file diff --git a/404.html b/404.html new file mode 100644 index 0000000..c8945b6 --- /dev/null +++ b/404.html @@ -0,0 +1,30 @@ + + +
+ + +Promise Process
- - - -Promise A+
- - - -JavaScript Promise 迷你书
- -## 实现代码 - -<<< @/src/promise/index.ts - -## 参考 - -[Promises/A+规范](https://promisesaplus.com/) -> [【翻译】Promises/A+规范](https://www.ituring.com.cn/article/66566) - -[Github Promise](https://github.com/then/promise) -> [Document](https://www.promisejs.org/) - -[JavaScript Promise 迷你书](http://liubin.org/promises-book) - -[MDN Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) - -[手写实现满足 Promise/A+ 规范的 Promise](https://www.jianshu.com/p/8d5c3a9e6181) - -[BAT 前端经典面试问题:史上最最最详细的手写 Promise 教程](https://juejin.im/post/5b2f02cd5188252b937548ab) - -[MDN Promise.finally](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally) diff --git a/docs/zh/function/README.md b/docs/zh/function/README.md deleted file mode 100644 index 60044c2..0000000 --- a/docs/zh/function/README.md +++ /dev/null @@ -1,11 +0,0 @@ -### 原生 Javascript - -::: tip -主要是对一些 **常见的方法** 进行学习并实现 -::: - -#### 目录 - -- [x] [debounce](./debounce/README.md) - -- [x] [throttle](./throttle/README.md) diff --git a/docs/zh/function/debounce/README.md b/docs/zh/function/debounce/README.md deleted file mode 100644 index 36a9e09..0000000 --- a/docs/zh/function/debounce/README.md +++ /dev/null @@ -1,19 +0,0 @@ -## 解题思路 or 实现原理 - -- Lodash - -从上一次被调用后, 延迟 `wait` 毫秒后调用 `func` 方法。 `debounced`(防抖动)函数提供一个 `cancel` 方法取消延迟的函数调用以及 `flush` 方法立即调用。 - -- Ours - -在事件被触发 `wait` 秒后, 再去执行回调函数。如果 `wait`秒内该事件被重新触发, 则重新计时。结果就是将频繁触发的事件合并为一次, 且在最后执行。提供一个 `cancel` 方法取消延迟的函数调用。 - - - -## 实现代码 - -<<< @/src/function/debounce/index.ts - -## 参考资料 - -[Debouncing and Throttling Explained Through Examples](https://css-tricks.com/debouncing-throttling-explained-examples/) diff --git a/docs/zh/function/throttle/README.md b/docs/zh/function/throttle/README.md deleted file mode 100644 index a8267dd..0000000 --- a/docs/zh/function/throttle/README.md +++ /dev/null @@ -1,19 +0,0 @@ -## 解题思路 or 实现原理 - -- Lodash - -创建一个节流函数, 在 `wait` 秒内最多执行func一次的函数。该函数提供一个 `cancel` 方法取消延迟的函数调用以及 `flush`方法立即调用。 - -- Ours - -规定一个时间 `wait`, `wait` 秒内,将触发的事件合并为一次并执行。 - - - -## 实现代码 - -<<< @/src/function/throttle/index.ts - -## 参考资料 - -[Debouncing and Throttling Explained Through Examples](https://css-tricks.com/debouncing-throttling-explained-examples/) diff --git a/docs/zh/guide/README.md b/docs/zh/guide/README.md deleted file mode 100644 index c8c9283..0000000 --- a/docs/zh/guide/README.md +++ /dev/null @@ -1,195 +0,0 @@ -Promise Process
- - - -Promise A+
- - - -JavaScript Promise 迷你书
- -### 参考 - -[Promises/A+规范](https://promisesaplus.com/) -> [【翻译】Promises/A+规范](https://www.ituring.com.cn/article/66566) - -[Github Promise](https://github.com/then/promise) -> [Document](https://www.promisejs.org/) - -[JavaScript Promise 迷你书](http://liubin.org/promises-book) - -[MDN Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) - -[手写实现满足 Promise/A+ 规范的 Promise](https://www.jianshu.com/p/8d5c3a9e6181) - -[BAT 前端经典面试问题:史上最最最详细的手写 Promise 教程](https://juejin.im/post/5b2f02cd5188252b937548ab) - -[MDN Promise.finally](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally) diff --git a/src/promise/images/promise.drawio b/src/promise/images/promise.drawio deleted file mode 100644 index a531143..0000000 --- a/src/promise/images/promise.drawio +++ /dev/null @@ -1,5 +0,0 @@ -<<<<<<< HEAD -在计算机科学中, 二叉树是每个节点最多只有两个分支的树结构。通常分支被称作 "左子树" 或 "右子树"。二叉树的分支具有左右次序, 不能随意颠倒。
二叉树的第i层至多有 个结点;
深度为k的二叉树至多有 个结点;
包含n个结点的二叉树的高度至少为 ;
对任何一颗 二叉树T, 如果其终端结点数为, 度为 2 的结点数为, 则
定义: 除最后一层结点均无任何子节点外, 每一层的所有结点都有两个子结点的树
性质:
定义: 一颗二叉树中, 只有最小面两层结点的度可以小于2, 并且最下一层的叶结点集中在靠左的若干位置上。这样的二叉树称为 完全二叉树。
特点: 叶子结点只能出现在最下层和次下层, 且最小层的叶子结点集中在树的左部。显然, 一颗满二叉树必定是一颗完全二叉树, 而完全二叉树未必是满二叉树。
Note: 完全二叉树 是效率很高的数据结构, 堆是一种完全二叉树或者近似完全二叉树, 所以效率极高, 像十分常用的排序算法、Dijkstra
算法、Prim
算法等都要用堆才能优化, 二叉排序树的效率也要借助平衡树来提高, 而平衡性基于完全二叉树。
/*
+ * @Author: Rainy
+ * @Date: 2019-11-14 19:25:01
+ * @LastEditors : Rainy
+ * @LastEditTime : 2020-02-04 17:10:36
+ */
+
+import { WithParamsFunction } from 'types';
+
+type TreeReturn = BinaryTree | null;
+
+export class BinaryTree {
+ key: any;
+ left: any = null;
+ right: any = null;
+ root: any = null;
+
+ constructor(key: any) {
+ this.key = key;
+ this.left = null;
+ this.right = null;
+ }
+
+ _insertNode(node: BinaryTree, newNode: BinaryTree): void {
+ if (node.key > newNode.key) {
+ if (node.left) {
+ this._insertNode(node.left, newNode);
+ } else {
+ node.left = newNode;
+ }
+ } else {
+ if (node.right) {
+ this._insertNode(node.right, newNode);
+ } else {
+ node.right = newNode;
+ }
+ }
+ }
+ insert(key: any) {
+ let newNode = new BinaryTree(key);
+ if (this.root) {
+ this._insertNode(this.root, newNode);
+ } else {
+ this.root = newNode;
+ }
+ }
+
+ _searchNode(node: BinaryTree, key: any): boolean {
+ if (!node) {
+ return false;
+ }
+ if (node.key > key) {
+ this._searchNode(node.left, key);
+ } else if (node.key < key) {
+ this._searchNode(node.right, key);
+ } else {
+ return true;
+ }
+ return false;
+ }
+ search(node: BinaryTree, key: any): any {
+ this._searchNode(this.root, key);
+ }
+
+ _findMinNode(node: BinaryTree): TreeReturn {
+ if (node) {
+ while (node && node.left) {
+ node = node.left;
+ }
+ return node;
+ }
+ return null;
+ }
+ _minNode(node: BinaryTree): BinaryTree | any {}
+ min(): any {}
+
+ _findMaxNode(node: BinaryTree): TreeReturn {
+ return null;
+ }
+ _maxNode(node: BinaryTree): BinaryTree | any {}
+ max(): any {}
+
+ _removeNode(node: BinaryTree, key: any): TreeReturn {
+ if (node === null) {
+ return null;
+ }
+ if (node.key > key) {
+ node.left = this._removeNode(node.left, key);
+ } else if (node.key < key) {
+ node.right = this._removeNode(node.right, key);
+ } else {
+ if (node.left === null && node.right === null) {
+ // @ts-ignore
+ node = null;
+ }
+ if (node.left === null) {
+ node = node.right;
+ }
+ if (node.right === null) {
+ node = node.left;
+ }
+
+ let aux: { key: any } | null = this._findMinNode(node.right);
+ if (aux) {
+ node.key = aux.key;
+ node.right = this._removeNode(node.right, aux.key);
+ }
+ }
+ return node;
+ }
+ remove(key: any) {
+ this.root = this._removeNode(this.root, key);
+ }
+
+ _preOrderTraverseNode(
+ node: BinaryTree,
+ callback: WithParamsFunction | any
+ ): void {
+ if (node) {
+ callback & callback(node.key);
+ this._preOrderTraverseNode(node.left, callback);
+ this._preOrderTraverseNode(node.right, callback);
+ }
+ }
+
+ preOrderTraverse(callback: WithParamsFunction | any): void {
+ this._preOrderTraverseNode(this.root, callback);
+ }
+
+ _inOrderTraverseNode(
+ node: BinaryTree,
+ callback: WithParamsFunction | any
+ ): void {
+ if (node) {
+ this._inOrderTraverseNode(node.left, callback);
+ callback & callback(node.key);
+ this._inOrderTraverseNode(node.right, callback);
+ }
+ }
+ inOrderTraverse(callback: WithParamsFunction | any): void {
+ this._inOrderTraverseNode(this.root, callback);
+ }
+
+ _postOrderTraverseNode(
+ node: BinaryTree,
+ callback: WithParamsFunction | any
+ ): void {
+ if (node) {
+ this._postOrderTraverseNode(node.left, callback);
+ this._postOrderTraverseNode(node.right, callback);
+ callback & callback(node.key);
+ }
+ }
+ postOrderTraverse(callback: WithParamsFunction | any): void {
+ this._postOrderTraverseNode(this.root, callback);
+ }
+}
+
通过 splice
方法去复制需要移动的数组元素,并拼接新数组。
/*
+ * @Author: Rainy
+ * @Date: 2019-11-14 19:25:01
+ * @LastEditors : Rainy
+ * @LastEditTime : 2019-12-29 10:24:02
+ */
+
+import { AnyArrayMap } from 'types';
+
+export function arrayMove(array: AnyArrayMap, from: number, to: number): AnyArrayMap {
+ array = array.slice();
+ array.splice(to < 0 ? array.length + to : to, 0, array.splice(from, 1)[0]);
+ return array;
+}
+
+ ← + + 手写Promise + + find + + → +
为了不污染原生的 Array 对象, 实现通过 function 来实现
Eg:
// prototype
+Array.prototype.find = function(predicate, /* thisArg*/) {}
+
+// ours
+function (collection, predicate) {}
+
find
方法对数组中的每一项元素执行一次 predicate
函数, 直至有一个 predicate
返回 true
. 当找到了这样一个元素后, 该方法会立即返回这个元素的值, 否则返回 undefined
. 注意predicate
函数会为数组中的每个索引调用即从 0 到 length - 1
, 而不仅仅是那些被赋值的索引, 这意味着对于稀疏数组来说, 该方法的效率要低于那些只遍历有值的索引的方法.
把传入的 this
转换成 Object
对象, 需要null
值处理 -> O
取出 Object
的 length
-> len
判断传入 predicate
的是否是 function
, 抛出异常 TypeError exception
设置计数器 k = 0
while k < len
kValue = O[k]
testResult = predicate(thisArg, kValue, k, O)
-> Boolean
testResult is true, return kValue
Set k to k + 1
Return undefined
predicate
在数组每一项上执行的函数, 接收 3 个参数:
element
当前遍历到的元素
index
可选
当前遍历到的索引
array
可选
数组本身
thisArg
可选
执行回调时用作this
的对象
返回数组中满足提供的测试函数的第一个元素的值. 否则返回 undefined
/*
+ * @Author: Rainy
+ * @Date: 2019-11-14 19:25:01
+ * @LastEditors : Rainy
+ * @LastEditTime : 2020-02-05 15:58:15
+ */
+
+import { _isArray } from '../isArray';
+import { ArrayMap, WithParamsFunction } from 'types';
+
+// ours
+export function _find(collection: ArrayMap<any>, predicate: WithParamsFunction): any {
+ if (!_isArray(collection)) {
+ throw new Error('The first parma must be a Array');
+ }
+ for(const index in collection) {
+ if (predicate(collection[index], index, collection)) {
+ return collection[index];
+ }
+ }
+ return null;
+}
+
+// @ts-ignore
+// prototype
+Array.prototype._find = function(callback: WithParamsFunction/*, thisArg*/) {
+ if (this == null) {
+ throw new TypeError('null or undefined');
+ }
+ if (typeof callback !== 'function') {
+ throw new TypeError('callback is not a function');
+ }
+ const oldArr = Object(this);
+ const len = oldArr.length >>> 0;
+ const thisArg = arguments.length >= 2 ? arguments[1] : void 0;
+ let k = 0;
+
+ while(k++ < len) {
+ if(k in oldArr) {
+ const val = oldArr[k];
+ if(callback.call(thisArg, val, k, oldArr)) {
+ return val;
+ }
+ }
+ }
+ return void 0;
+}
+
+ ← + + arrayMove + + isArray + + → +
使用 Object.prototype.toString
取得对象的一个内部属性 [[Class]]
,然后依据这个属性,返回 '[object Array]'
字符串作为结果(ECMA
标准中使用[[]]
来表示语言内部用到的、外部不可直接访问的属性,称为内部属性
)。利用这 个方法,再配合 call
,我们可以取得任何对象的内部属性 [[Class]]
,然后把类型检测转化为字符串比较,以达到我们的目的。
/*
+ * @Author: Rainy
+ * @Date: 2019-11-14 19:25:01
+ * @LastEditors : Rainy
+ * @LastEditTime : 2020-02-05 15:50:12
+ */
+
+import { isAbsType } from '../../utils/type';
+
+export function _isArray(target: any): boolean {
+ return isAbsType(target).toLowerCase() === 'array';
+}
+
适配器模式将一个接口转换成客户希望的另一个接口, 适配器模式使接口不兼容的那些类可以一起工作, 其别名为包装器(Wrapper
)。适配器模式既可以作为类结构型模式, 也可以作为对象结构型模式。
它是一种结构型设计模式。
使接口不兼容的对象能够相互合作。
可以让任何两个没有关联的类一起运行
提高了类的复用
增加了类的透明度
灵活性好
一次至多只能适配一个适配者类
过多地使用适配器, 会让系统非常零乱, 不易整体进行把握
旧接口的复用
Vue Computed
/*
+ * @Author: Rainy
+ * @Date: 2019-11-14 19:25:01
+ * @LastEditors : Rainy
+ * @LastEditTime : 2020-01-06 10:55:55
+ */
+
+class Adapter {
+ specificRequest(): void {}
+}
+
+class Target {
+ adapter: Adapter;
+
+ constructor(adapter: Adapter) {
+ this.adapter = adapter;
+ }
+
+ request(): void {
+ this.adapter.specificRequest()
+ }
+}
+
装饰器模式(Decorator Pattern
)允许向一个现有的对象添加新的功能, 同时又不改变其结构。
装饰器模式属于结构型模式。
动态地给一个对象添加一些额外的职责。
装饰器模式和继承的共同特点就是扩展对象的功能, 而装饰器模式比继承更加灵活。
装饰类和被装饰类可以独立发展,不会相互耦合。
/*
+ * @Author: Rainy
+ * @Date: 2019-11-14 19:25:01
+ * @LastEditors : Rainy
+ * @LastEditTime : 2020-01-06 10:56:04
+ */
+
+class Decorator {
+ circle: Circle;
+
+ constructor(circle: Circle) {
+ this.circle = circle;
+ }
+
+ draw(): void {
+ this.circle.draw();
+ }
+
+ setRedBorder(circle: Circle): void {
+ console.log('setRedBorder', this.circle);
+ }
+}
+
+class ShapeDecorator {
+ circleShape: CircleShape;
+
+ constructor(circleShape: CircleShape) {
+ this.circleShape = circleShape;
+ }
+
+ draw(): void {
+ console.log('ShapeDecorator Draw');
+ this.circleShape.draw();
+ }
+}
+
+class RectShape implements Rectangle {
+ draw(): void {
+ console.log('Draw Circle');
+ }
+}
+
+class CircleShape implements Circle {
+ draw(): void {
+ console.log('Draw Circle');
+ }
+}
+
+class Circle {
+ draw(): void {
+ console.log('Draw Circle');
+ }
+}
+
+class Rectangle {
+ draw(): void {
+ console.log('Draw Circle');
+ }
+}
+
外观模式隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。它属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。
降低访问复杂系统的内部子系统时的复杂度,简化客户端与之的接口。
减少系统相互依赖
提高灵活性
提高了安全性
没找到合适的
/*
+ * @Author: Rainy
+ * @Date: 2019-11-14 19:25:01
+ * @LastEditors : Rainy
+ * @LastEditTime : 2019-12-23 19:55:49
+ */
+
+class Shape {
+ draw(): void {}
+}
+
+class Square {
+ shape: Shape;
+
+ constructor(shape: Shape) {
+ this.shape = shape;
+ }
+
+ draw(): void {
+ this.shape.draw();
+ }
+}
+
+class Star {
+ shape: Shape;
+
+ constructor(shape: Shape) {
+ this.shape = shape;
+ }
+
+ draw(): void {
+ this.shape.draw();
+ }
+}
+
+class ShapeMaker {
+ square: Square;
+ star: Star;
+
+ constructor(square: Square, star: Star) {
+ this.square = square;
+ this.star = star;
+ }
+
+ drawStar(): void {
+ this.star.draw();
+ }
+
+ drawSquare(): void {
+ this.square.draw();
+ }
+}
+
最常用的设计模式之一, 在工厂模式中, 我们在创建对象时不会对客户端暴露创建逻辑, 并且是通过使用一个共同的接口来指向新创建的对象。
定义一个创建对象的接口, 让其子类自己决定实例化哪一个工厂类, 工厂模式使其创建过程延迟到子类进行。
解耦
扩展性高
降低代码重复
屏蔽产品的具体实现, 调用者只关心产品的接口
增加产品, 需要修改工厂类, 不符合开放-封闭原则
工厂类集中了所有实例的创建逻辑, 违反了高内聚责任分配原则
React.createElement
Vue 异步组件
/*
+ * @Author: Rainy
+ * @Date: 2019-11-14 19:25:01
+ * @LastEditors : Rainy
+ * @LastEditTime : 2019-12-22 17:29:47
+ */
+
+class Creator {
+ create(name: string): Product {
+ return new Product(name);
+ }
+}
+
+class Product {
+ name: string;
+
+ constructor(name: string) {
+ this.name = name;
+ }
+
+ init(): void {}
+
+ fn1(): void {}
+
+ fn2(): void {}
+}
+
迭代器模式(Iterator Pattern
)是最简单的设计模式之一。它可以让用户透过特定的接口访问容器中的每一个元素而不用了解底层的实现。
迭代器模式属于行为型模式。
不同的方式来遍历整个整合对象。
支持以不同的方式遍历一个聚合对象。
迭代器简化了聚合类。
在同一个聚合上可以有多个遍历。
在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。
/*
+ * @Author: Rainy
+ * @Date: 2019-11-14 19:25:01
+ * @LastEditors: Rainy
+ * @LastEditTime: 2019-12-12 20:29:44
+ */
+
+import { NumberArrayMap } from 'types'
+
+class Container {
+ list: NumberArrayMap;
+
+ constructor(list: NumberArrayMap) {
+ this.list = list
+ }
+
+ getIterator(): Iterator {
+ return new Iterator(this);
+ }
+}
+
+class Iterator {
+ private _list: NumberArrayMap;
+ private _index: number;
+
+ constructor(container: Container) {
+ this._index = 0;
+ this._list = container.list;
+ }
+
+ next(): number | null {
+ if (this.hasNext()) {
+ return this._list[this._index++];
+ }
+ return null;
+ }
+
+ hasNext(): boolean {
+ return this._index >= this._list.length;
+ }
+}
+
一个目标对象管理所有相依于它的观察者对象, 并且在它本身的状态改变时主动发出通知。
发布 & 订阅
一对 n
当抽象个体有两个互相依赖的层面时。封装这些层面在单独的对象内将可允许程序员单独地去变更与重复使用这些对象,而不会产生两者之间交互的问题。
当其中一个对象的变更会影响其他对象,却又不知道多少对象必须被同时变更时。
当对象应该有能力通知其他对象,又不应该知道其他对象的实做细节时。
观察者和被观察者是抽象耦合的。
建立一套触发机制。
循环依赖会导致系统崩溃。
观察者太多会浪费时间。
Vue watch
React, Vue 组件生命周期的触发
Nodejs 自定义事件
事件监听
/*
+ * @Author: Rainy
+ * @Date: 2019-11-14 19:25:01
+ * @LastEditors: Please set LastEditors
+ * @LastEditTime: 2019-12-09 22:30:14
+ */
+
+class Subject {
+ state: any;
+
+ observers: Observer[];
+
+ constructor() {
+ this.state = 0;
+ this.observers = [];
+ }
+
+ getState(): any {
+ return this.state;
+ }
+
+ setState(state: any): void {
+ this.state = state;
+ this.notify();
+ }
+
+ notify() {
+ this.observers.forEach(observer => {
+ observer.update();
+ })
+ }
+
+ attach(observer: Observer): void {
+ this.observers.push(observer);
+ }
+}
+
+class Observer {
+ name: string;
+ subject: Subject;
+
+ constructor(name: string, subject: Subject) {
+ this.name = name;
+ this.subject = subject;
+ this.subject.attach(this);
+ }
+ update() {
+ console.log(`update: ${this.name}, state: ${this.subject.state}`);
+ }
+}
+
一种设计模式, 为其他对象提供一个代理以控制对某个对象的访问, 即通过代理对象访问目标对象。
为其他对象提供一种代理以控制对这个对象的访问。
职责清晰
高扩展性, 可以在目标对象实现的基础上, 增强额外的功能操作, 即扩展目标对象的功能
智能化
由于在客户端和真实主题之间增加了代理对象, 因此有些类型的代理模式可能会造成请求的处理速度变慢。
实现代理模式需要额外的工作, 有些代理模式的实现非常复杂。
ES6 Proxy
代理
外网代理
远程代理
Cache代理
/*
+ * @Author: Rainy
+ * @Date: 2019-11-14 19:25:01
+ * @LastEditors: Please set LastEditors
+ * @LastEditTime: 2019-12-12 23:21:09
+ */
+
+class RealImg {
+ fileName: string;
+
+ constructor(fileName: string) {
+ this.fileName = fileName;
+ this.loadImg();
+ }
+
+ show(): void {
+ console.log('todo', this.fileName);
+ }
+
+ loadImg(): void {
+ console.log('todo', this.fileName);
+ }
+}
+
+class ProxyImg {
+ realImg: RealImg;
+
+ constructor(fileName: string) {
+ this.realImg = new RealImg(fileName);
+ }
+
+ show(): void {
+ this.realImg.show();
+ }
+}
+
单例模式(Singleton Pattern
)是最简单的设计模式之一。这种类型的设计模式属于创建型模式, 它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类, 该类负责创建自己的对象, 同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式, 可以直接访问, 不需要实例化该类的对象。
单例类只能有一个实例。
单例类必须自己创建自己的唯一实例。
单例类必须给所有其他对象提供这一实例。
一个类能返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法, 通常使用 getInstance
这个名称); 当我们调用这个方法时, 如果类持有的引用不为空就返回这个引用, 如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用; 同时我们还将该类的构造函数定义为私有方法, 这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象, 只有通过该类提供的静态方法来得到该类的唯一实例。
/*
+ * @Author: Rainy
+ * @Date: 2019-11-14 19:25:01
+ * @LastEditors: Please set LastEditors
+ * @LastEditTime: 2019-12-09 22:18:17
+ */
+
+class SingleObject {
+ showMessage(): string {
+ return '';
+ }
+}
+
+// @ts-ignore
+SingleObject.getInstance = (() => {
+ let _instance = null;
+ if (_instance === null) {
+ _instance = new SingleObject();
+ }
+ return _instance;
+})();
+
+class Singleton {
+ private _instance: SingleObject | null;
+
+ constructor() {
+ this._instance = null;
+ }
+
+ getInstance(): SingleObject {
+ if (!this._instance) {
+ this._instance = new SingleObject();
+ }
+ return this._instance;
+ }
+}
+
Promise
对象是一个代理对象(代理一个值),被代理的值在Promise
对象创建时可能是未知的。它允许你为异步操作的成功和失败分别绑定相应的处理方法(handlers)
。 这让异步方法可以像同步方法那样返回值,但并不是立即返回最终执行结果,而是一个能代表未来出现的结果的promise
对象。
Promise Process
Promise A+
JavaScript Promise 迷你书
/*
+ * @Author: Rainy
+ * @Date: 2019-11-14 19:25:01
+ * @LastEditors: Rainy
+ * @LastEditTime: 2020-03-05 09:38:05
+ */
+
+import { AnyFunction, WithParamsReturnAnyFunction, ArrayMap } from 'types';
+import { isAbsType } from 'utils/type';
+
+export interface MyPromise {}
+
+export class MyPromise {
+ readonly FULFILLED: string = 'Fulfilled';
+ readonly PENDING: string = 'pending';
+ readonly REJECTED: string = 'Rejected';
+
+ state: string = this.PENDING;
+ value: any = null;
+ reason: any = null;
+ onFulfilledCallback: ArrayMap<WithParamsReturnAnyFunction> = [];
+ onRejectedCallback: ArrayMap<WithParamsReturnAnyFunction> = [];
+
+ constructor(executor: WithParamsReturnAnyFunction) {
+ this.initBind();
+ try {
+ executor(this.resolve, this.reject);
+ } catch (error) {
+ this.reject(error);
+ }
+ }
+
+ initBind() {
+ this.resolve = this.resolve.bind(this);
+ this.reject = this.reject.bind(this);
+ }
+
+ resolve(value: any): void {
+ if (this.state === this.PENDING) {
+ setTimeout(() => {
+ this.state = this.FULFILLED;
+ this.value = value;
+ this.onFulfilledCallback.forEach((cb: WithParamsReturnAnyFunction) => {
+ cb && cb(this.value);
+ });
+ });
+ }
+ }
+
+ reject(reason: any): void {
+ if (this.state === this.PENDING) {
+ setTimeout(() => {
+ this.state = this.REJECTED;
+ this.reason = reason;
+ this.onRejectedCallback.forEach((cb: WithParamsReturnAnyFunction) => {
+ cb && cb(this.reason);
+ });
+ });
+ }
+ }
+
+ then(
+ onFulfilled?: WithParamsReturnAnyFunction,
+ onRejected?: WithParamsReturnAnyFunction
+ ) {
+ onFulfilled =
+ typeof onFulfilled === 'function' ? onFulfilled : value => value;
+ onRejected =
+ typeof onRejected === 'function'
+ ? onRejected
+ : err => {
+ throw err;
+ };
+
+ let promise2 = new MyPromise(
+ (
+ resolve: WithParamsReturnAnyFunction,
+ reject: WithParamsReturnAnyFunction
+ ) => {
+ if (this.state === this.FULFILLED) {
+ setTimeout(() => {
+ if (onFulfilled && onRejected) {
+ const x = this.tryCall(this.value, onFulfilled, onRejected);
+ this.resolvePromise(promise2, x, resolve, reject);
+ }
+ });
+ }
+
+ if (this.state === this.REJECTED) {
+ setTimeout(() => {
+ if (onRejected) {
+ const x = this.tryCall(this.reason, onRejected, onRejected);
+ this.resolvePromise(promise2, x, resolve, reject);
+ }
+ });
+ }
+
+ if (this.state === this.PENDING) {
+ this.onFulfilledCallback.push(value => {
+ if (onFulfilled && onRejected) {
+ const x = this.tryCall(value, onFulfilled, onRejected);
+ this.resolvePromise(promise2, x, resolve, reject);
+ }
+ });
+
+ this.onRejectedCallback.push(reason => {
+ onRejected && this.tryCall(reason, onRejected, onRejected);
+ if (onRejected) {
+ const x = this.tryCall(reason, onRejected, onRejected);
+ this.resolvePromise(promise2, x, resolve, reject);
+ }
+ });
+ }
+ }
+ );
+ return promise2;
+ }
+
+ private tryCall(
+ value: any,
+ resolve: WithParamsReturnAnyFunction,
+ reject: WithParamsReturnAnyFunction
+ ) {
+ let x: any = null;
+ try {
+ x = resolve(value);
+ } catch (error) {
+ x = reject(error);
+ }
+ return x;
+ }
+
+ // Promise 解决链式调用过程
+ private resolvePromise(
+ promise2: MyPromise,
+ x: any,
+ resolve: WithParamsReturnAnyFunction,
+ reject: WithParamsReturnAnyFunction
+ ) {
+ if (promise2 === x) {
+ throw new Error('Chaining cycle detected for promise #<MyPromise>');
+ }
+ let called: boolean = false;
+ if (x instanceof MyPromise) {
+ const then = x.then;
+ if (x.state === this.PENDING) {
+ then.call(
+ x,
+ y => this.resolvePromise(promise2, y, resolve, reject),
+ reason => reject(reason)
+ );
+ } else {
+ then(resolve, reject);
+ }
+ } else if (isAbsType(x) === 'function' || isAbsType(x) === 'object') {
+ try {
+ const then = x.then;
+ if (isAbsType(x) === 'function') {
+ then.call(
+ x,
+ (y: any) => {
+ if (called) {
+ return;
+ }
+ called = true;
+ this.resolvePromise(promise2, y, resolve, reject);
+ },
+ (reason: any) => {
+ if (called) {
+ return;
+ }
+ called = true;
+ reject(reason);
+ }
+ );
+ } else {
+ resolve(x);
+ }
+ } catch (err) {
+ if (called) {
+ return;
+ }
+ called = true;
+ reject(err);
+ }
+ } else {
+ resolve(x);
+ }
+ }
+}
+
+// Method: resolve reject race all
+
+(MyPromise as any).resolve = (value: any) => {
+ return new MyPromise((resolve, reject) => {
+ resolve(value);
+ });
+};
+
+(MyPromise as any).reject = (value: any) => {
+ return new MyPromise((resolve, reject) => {
+ reject(value);
+ });
+};
+
+(MyPromise as any).race = (promises: ArrayMap<MyPromise>) => {
+ return new MyPromise((resolve, reject) => {
+ promises.forEach((promise: any, index: number) => {
+ promise.then(resolve, reject);
+ });
+ });
+};
+
+(MyPromise as any).all = (promises: ArrayMap<MyPromise>) => {
+ let promiseArr: ArrayMap<any> = [];
+ return new MyPromise((resolve, reject) => {
+ promises.forEach((promise: any, index: number) => {
+ promise.then((data: any) => {
+ promiseArr[index] = data;
+ if (index + 1 === promises.length) {
+ resolve(promiseArr);
+ }
+ }, reject);
+ });
+ });
+};
+
+// prototype Method: done finally catch
+
+(MyPromise as any).prototype.done = (
+ onFulfilled: WithParamsReturnAnyFunction,
+ onRejected: WithParamsReturnAnyFunction
+) => {
+ // @ts-ignore
+ return this.then(onFulfilled, onRejected).catch(error => {
+ setTimeout(() => {
+ throw error;
+ });
+ });
+};
+
+(MyPromise as any).prototype.finally = (onFinally: AnyFunction) => {
+ // @ts-ignore
+ return this.then(
+ (value: any) => (MyPromise as any).resolve(onFinally()).then(() => value),
+ (error: any) =>
+ (MyPromise as any).resolve(onFinally()).then(() => {
+ throw error;
+ })
+ );
+};
+
+(MyPromise as any).prototype.catch = (
+ onRejected: WithParamsReturnAnyFunction
+) => {
+ // @ts-ignore
+ return this.then(void 0, onRejected);
+};
+
+// npx promises-aplus-tests index.ts -> ts 文件如何测试???
+(MyPromise as any).deferred = function() {
+ const dfd: any = {};
+ dfd.promise = new MyPromise((resolve, reject) => {
+ dfd.resolve = resolve;
+ dfd.reject = reject;
+ });
+ return dfd;
+};
+
Promises/A+规范 (opens new window) -> 【翻译】Promises/A+规范 (opens new window)
Github Promise (opens new window) -> Document (opens new window)
JavaScript Promise 迷你书 (opens new window)
MDN Promise (opens new window)
手写实现满足 Promise/A+ 规范的 Promise (opens new window)
+ ← + + 实现React/Vue DOM Diff算法 + + arrayMove + + → +
从上一次被调用后, 延迟 wait
毫秒后调用 func
方法。 debounced
(防抖动)函数提供一个 cancel
方法取消延迟的函数调用以及 flush
方法立即调用。
在事件被触发 wait
秒后, 再去执行回调函数。如果 wait
秒内该事件被重新触发, 则重新计时。结果就是将频繁触发的事件合并为一次, 且在最后执行。提供一个 cancel
方法取消延迟的函数调用。
/*
+ * @Author: Rainy
+ * @Date: 2020-01-02 18:29:32
+ * @LastEditors : Rainy
+ * @LastEditTime : 2020-01-02 19:57:51
+ */
+
+import { WithParamsReturnAnyFunction, ObjectMap } from 'types';
+import { isAbsType } from 'utils/type';
+
+export function _debounce(
+ callback: WithParamsReturnAnyFunction,
+ wait: number = 300,
+ options: ObjectMap<any> = {},
+): WithParamsReturnAnyFunction {
+ let timer: any = null;
+ let result: any = null;
+
+ if (!isAbsType(options)) {
+ throw new Error('options must be object');
+ }
+
+ const { leading = false, trailing = false, ...option } = options;
+
+ const debounce = () => {
+ const params = arguments;
+ if (!!timer) {
+ clearTimeout(timer);
+ }
+
+ if (leading) {
+ timer = setTimeout(() => {
+ timer = null;
+ }, wait);
+
+ // @ts-ignore
+ result = !timer && callback && callback.apply(this, params);
+ } else {
+ timer = setTimeout(() => {
+ // @ts-ignore
+ result = callback && callback.apply(this, params);
+ }, wait);
+ }
+ }
+
+ debounce.cancel = () => {
+ clearTimeout(timer);
+ timer = null;
+ }
+
+ return debounce;
+}
+
Debouncing and Throttling Explained Through Examples (opens new window)
创建一个节流函数, 在 wait
秒内最多执行func一次的函数。该函数提供一个 cancel
方法取消延迟的函数调用以及 flush
方法立即调用。
规定一个时间 wait
, wait
秒内,将触发的事件合并为一次并执行。
/*
+ * @Author: Rainy
+ * @Date: 2020-01-02 18:31:50
+ * @LastEditors : Rainy
+ * @LastEditTime : 2020-01-02 19:58:10
+ */
+
+import { WithParamsReturnAnyFunction, ObjectMap } from 'types';
+import { isAbsType } from 'utils/type';
+
+export function _throttle(
+ callback: WithParamsReturnAnyFunction,
+ wait: number = 300,
+ options: ObjectMap<any> = {},
+): WithParamsReturnAnyFunction {
+ let last: number = 0;
+ let timer: any = null;
+ let result: any = null;
+
+ if (!isAbsType(options)) {
+ throw new Error('options must be object');
+ }
+
+ const { leading = false, trailing = false, ...option } = options;
+
+ const throttle = () => {
+ let params: any = arguments
+ let now = +new Date();
+
+ if (leading) {
+ timer = setTimeout(() => {
+ timer = null;
+ }, wait);
+ // @ts-ignore
+ result = !timer && callback && callback.apply(this, params);
+ } else {
+ if (last && now < last + wait) {
+ clearTimeout(timer);
+
+ timer = setTimeout(() => {
+ last = now;
+
+ // @ts-ignore
+ result = callback && callback.apply(this, params)
+ }, wait);
+ } else {
+ last = now;
+
+ // @ts-ignore
+ result = callback && callback.apply(this, params)
+ }
+ }
+ return result;
+ }
+
+ throttle.cancel = () => {
+ clearTimeout(timer);
+ timer = null;
+ }
+
+ return throttle;
+}
+
Debouncing and Throttling Explained Through Examples (opens new window)
+ ← + + 防抖 + + 实现React/Vue DOM Diff算法 + + → +
(opens new window)
(opens new window)
(opens new window)
(opens new window)
(opens new window)
(opens new window)
Welcome to the Awesome Javascript Code Implementation.
Document Link:
⌨️ To be Continue...
Xmind Download (opens new window)
⌨️
生成 code
模板
npm run template
+
生成 docs
模板
npm run docs
+
同时生成 code
docs
模板
npm run template-docs
+
We welcome all contributions. You can submit any ideas as pull requests (opens new window) or as a GitHub issue (opens new window).
Primitive
Javascript Keys
Array
Object
ES6
Promise
Proxy
async / await
extends
方法
Algorithm
Sort
DP
Tree
Graph
Math
实用工具库
Lodash
Internal
函数式编程库
Algorithms
Visualising Data Structures and Algorithms Through Animation (opens new window)
Data Structure Visualizations (opens new window)
sort
Front-End development engineer, technology stack: React + Typescript + Mobx, also used Vue + Vuex for a while
Copyright © 2019-present Rain120 (opens new window).
+ 如何写文档? + + → +
提示
文档的规范, 以及如何方便快捷的书写 ✍️ 文档
在docs/zh
中新建一个文件, 并新建一个 README.md
的文件
警告
请保证每一个文件夹都有一个 README.md
文件
npm run docs
+
+# or
+
+touch docs/zh/test/README.md
+
# npm run docs
+? please input the docs model name: model
+? please input the docs model alias name (default same as model name)?
+? generator model path (etc: docs/zh/model) ? docs/zh
+
Eg: File Path
docs/zh
+├── algorithms
+│ ├── README.md
+│ └── binaryTree
+│ └── README.md
+├── array
+│ ├── README.md
+│ ├── arrayMove
+│ │ └── README.md
+│ ├── find
+│ │ └── README.md
+│ └── isArray
+│ └── README.md
+├── design-pattern
+│ ├── README.md
+│ ├── adapter-pattern
+│ │ └── README.md
+│ ├── decorator-pattern
+│ │ └── README.md
+│ ├── facade-pattern
+│ │ └── README.md
+
模板文件 plop-templates/docs/zh/README.md.hbs
## 题目描述
+
+## 解题思路 or 实现原理
+
+## 实现代码
+
+<<< @/{{templatePath}}/{{name}}/index.ts
+
+## 参考
+
npm run template-docs
+
? please input the name: template
+? please input the docs model alias name (default same as model name)?
+? Do you want test file ? Yes
+? generator path (etc: src/template) ? src
+? generator model path (etc: docs/zh/model) ? docs/zh
+
模板文件
plop-templates/code/__tests__.ts.hbs
/*
+ * @Author: Rainy
+ * @Date: 2020-02-06 19:05:18
+ * @LastEditors: Rainy
+ * @LastEditTime: 2020-07-25 11:12:14
+ */
+
+import { {{name}} } from '.';
+
+test('Test {{name}}', () => {
+ expect({{name}}()).toBe(undefined);
+});
+
plop-templates/code/index.ts.hbs
/*
+ * @Author: Rainy
+ * @Date: 2019-11-14 19:25:01
+ * @LastEditors: Rainy
+ * @LastEditTime: 2020-03-01 21:08:22
+ */
+
+export function {{name}}(params?: any): any {}
+
plop-templates/code/README.md.hbs
## 题目描述
+
+## 解题思路 or 实现原理
+
+## 参考
+
提示
alias.json
的顺序就是文档的顺序
提示
通过使用 新建文件 的脚本来动态配置 alias
, 妈妈再也不用担心我找不到配置了。
我们不支持中文名文件夹, 所以你需要给文件名配置别名, 只需要在docs/.vuepress/utils/alias.json
中配置 key-value
值即可
{
+ "guide": "介绍",
+ "how-to-write-docs": "如何写文档? ",
+ "math-------": "",
+ "math": "数学",
+ "factorial": "阶乘",
+ "fibonacci": "斐波那契数列",
+ "gcd": "最大公约数",
+ "lcm": "最小公倍数",
+ "binary": "二进制",
+ "primitive-------": "",
+ "primitive": "原生 Javascript",
+ "function-------": "",
+ "function": "函数",
+ "debounce": "防抖",
+ "throttle": "节流",
+ "vdom-diff-------": "",
+ "vdom-diff": "实现React/Vue DOM Diff算法",
+ "es6-------": "",
+ "es6": "ECMAScript 6(ES6)",
+ "promise": "手写Promise",
+ "Array-------": "",
+ "array": "Array(MDN + Function)",
+ "Object-------": "",
+ "object": "Object(MDN + Function)",
+ "types-------": "",
+ "types": "Types",
+ "utils-------": "",
+ "utils": "Utils",
+ "design-pattern-------": "",
+ "design-pattern": "设计模式",
+ "adapter-pattern": "适配器模式",
+ "decorator-pattern": "装饰器模式",
+ "factory-pattern": "工厂模式",
+ "facade-pattern": "外观模式",
+ "iterator-pattern": "迭代器模式",
+ "proxy-pattern": "代理模式",
+ "observer-pattern": "观察者模式",
+ "singleton-pattern": "单例模式",
+ "algorithms-------": "",
+ "algorithms": "算法",
+ "binaryTree": "二叉树",
+ "sort-------": "",
+ "sort": "排序算法",
+ "bubbleSort": "冒泡排序",
+ "bucketSort": "桶排序",
+ "countingSort": "计数排序",
+ "heapSort": "堆排序",
+ "insertionSort": "插入排序",
+ "mergeSort": "归并排序",
+ "quickSort": "快速排序",
+ "radixSort": "基数排序",
+ "selectionSort": "选择排序",
+ "shellSort": "希尔排序",
+ "sword-means-offer-------": "",
+ "sword-means-offer": "剑指 Offer",
+ "03-find-repeat-number": "03. 数组中重复的数字",
+ "64-sum-1-n-nums": "64 求1 ~ n的和",
+ "leetcode-------": "",
+ "leetcode": "Leetcode",
+ "p3": "无重复字符的最长子串"
+}
~@images
路径 ->
根路径

+
Eg:

+
你可以在文档中展示你的代码, 只需要使用下面方式即可
<<< @/filepath
+
Note: filepath
是你文档的路径
Eg:
<<< @/src/Math/factorial/index.ts
+
你可以在文档中使用 markdown
来书写一下数学公式
$$
+y=\begin{cases}
+-x,\quad x\leq 0 \\\\
+x,\quad x>0
+\end{cases}
+$$
+
Props:
text - string
type - string
, 可选值: 'tip' | 'warning' | 'error'
, 默认值是: 'tip'
vertical - string
, 可选值: 'top' | 'middle'
, 默认值是: 'top'
Usage:
你可以在标题中, 使用这个组件来为某些 API 添加一些状态:
Badge <Badge text='默认主题' /> <Badge text='warning' type='warning'/> <Badge text='error' type='error'/>
+
Badge 默认主题 warning error
::: tip
+This is a tip
+:::
+
+::: warning
+This is a warning
+:::
+
+::: danger
+This is a dangerous warning
+:::
+
提示
This is a tip
注意
This is a warning
警告
This is a dangerous warning
:tada: :100:
+
🎉 💯
提示
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。 +leetcode problems (opens new window)
提示
使用 set 存储数据。通过两个指针,i,j,当 !set.has(s[j]),maxLength 增加;否则,在判断set.has(s[i]),删除 set中的 s[j],指针向后偏移;将当前 s[i] 加入到 set 中
举个例子
第1步
i: 0
+j: 0
+maxLength: 0 -> 1
+set: [] -> [a]
+
+i j
+ |
+ v
+ a b a b
+
第2步
i: 1
+j: 0
+maxLength: 1 -> 2
+set: [a] -> [a, b]
+i j
+ |
+ v
+ a b a b
+
+ j i
+ | |
+ v v
+ a b a b
+
第3步
i: 2
+j: 1
+maxLength: 2
+set: [a, b] -> [a, b, a] -> [b, a]
+
+ j i
+ | |
+ v v
+ a b a b
+
+ j i
+ | |
+ v v
+ a b a b
+
第4步
i: 3
+j: 2
+maxLength: 2
+set: [b, a] -> [b, a, b] -> [a, b]
+
+ j i
+ | |
+ v v
+ a b a b
+
+ j i
+ | |
+ v v
+ a b a b
+
/*
+ * @Author: Rainy
+ * @Date: 2019-11-14 19:25:01
+ * @LastEditors: Rainy
+ * @LastEditTime: 2022-01-23 16:07:42
+ */
+
+export function lengthOfLongestSubstring(s: string) {
+ const set = new Set();
+ let maxLength = 0;
+ let i = 0;
+ let j = 0;
+
+ for(; i < s.length; i++) {
+ if (!set.has(s[j])) {
+ maxLength++;
+ } else {
+ while (set.has(s[i])) {
+ set.delete(s[j]);
+ j++;
+ }
+ }
+
+ set.add(s[i]);
+ maxLength = Math.max(maxLength, set.size);
+ }
+
+ return maxLength;
+};
+
+ ← + + 64 求1 ~ n的和 +
十进制计数是使用 10
作为基数, 二进制计数是使用 2
作为基数, 二进制的数位就是 2^n
的形式。
二进制的数据表达具有抗干扰能力强、可靠性高的优点; 二进制非常适合做逻辑运算。
移位操作
value << 1 = Math.floor(value * 2)
+
+value << n = Math.floor(value * 2 ^ n)
+
value >> 1 = Math.floor(value / 2)
+
+value >> n = Math.floor(value / 2 ^ n)
+
逻辑操作
&
见 0
出 0
|
见 1
出 1
~
0 -> 1, 1 -> 0
^
同为 0
, 异为 1
/*
+ * @Author: Rainy
+ * @Date: 2019-11-14 19:25:01
+ * @LastEditors: Rainy
+ * @LastEditTime: 2019-11-28 18:46:02
+ */
+export function bitLeftShift(val: number, bit: number): number {
+ return val << bit;
+}
+
+export function bitRightShift(val: number, bit: number): number {
+ return val >> bit;
+}
+
+export function bitAnd(left: number, right: number): number {
+ return left & right;
+}
+
+export function bitOr(left: number, right: number): number {
+ return left | right;
+}
+
+export function bitNot(val: number): number {
+ return ~val;
+}
+
+export function bitXOR(left: number, right: number): number {
+ return left ^ right;
+}
+
+
一个正整数 n
的阶乘 (写作 n!
), 就是所有小于等于 n
的正整数的乘积
数学公式
Eg:
n! = 1 * 2 * ... * (n - 1)
+
/*
+ * @Author: Rainy
+ * @Date: 2020-01-26 14:03:24
+ * @LastEditors : Rainy
+ * @LastEditTime : 2020-01-26 14:27:27
+ */
+export function factorial(num: number): number {
+ let result = 1;
+
+ for (let i = 2; i <= num; i += 1) {
+ result *= i;
+ }
+
+ // let count = 1;
+
+ // while(count++ < num) {
+ // result *= count;
+ // }
+
+ return result;
+}
+
+export function factorialRecursive(num: number): number {
+ return num > 1 ? num * factorialRecursive(num - 1) : 1;
+}
+
+ ← + + pow + + 斐波那契数列 + + → +
数学公式
/*
+ * @Author: Rainy
+ * @Date: 2020-01-27 13:38:10
+ * @LastEditors : Rainy
+ * @LastEditTime : 2020-01-27 14:11:53
+ */
+
+import { NumberArrayMap } from 'types';
+
+export function fibonacci(n: number): NumberArrayMap {
+ const fibSequence: NumberArrayMap = [1];
+
+ let currentValue = 1;
+ let previousValue = 0;
+
+ if (n === 1) {
+ return fibSequence;
+ }
+
+ let counter = n - 1;
+
+ while (counter--) {
+ currentValue += previousValue;
+ previousValue = currentValue - previousValue;
+
+ fibSequence.push(currentValue);
+ }
+
+ return fibSequence;
+}
+
能够整除多个整数的最大正整数。而多个整数不能都为零。
求两个整数最大公约数主要的方法:
穷举法 (opens new window): 分别列出两整数的所有约数, 并找出最大的公约数。
素因数分解 (opens new window): 分别列出两数的素因数分解式, 并计算共同项的乘积。
短除法 (opens new window): 两数除以其共同素因数, 直到两数互素时, 所有除数的乘积即为最大公约数。
辗转相除法 (opens new window): 又称欧几里得算法 (Euclidean Algorithm)
, 两数相除, 取余数重复进行相除, 直到余数为 0
时, 前一个除数即为最大公约数。
Note
/*
+ * @Author: Rainy
+ * @Date: 2020-01-30 11:42:57
+ * @LastEditors : Rainy
+ * @LastEditTime : 2020-01-31 13:28:43
+ */
+
+// the greatest common divisor
+export function gcd_enumeration(a: number, b: number): number {
+ let gcd = 1;
+
+ let smaller = Math.min(a, b);
+
+ for (let i = 2; i <= smaller; i++) {
+ if ((a % i === 0) && (b % i === 0)) {
+ gcd = i;
+ }
+ }
+
+ return gcd;
+}
+
+export function gcd_division_recursive(a: number, b: number): number {
+ if (b === 0) {
+ return a;
+ }
+
+ return gcd_division_recursive(b , a % b);
+}
+
+export function gcd_sub_recursive(a: number, b: number): number {
+ if (a === b) {
+ return a;
+ }
+
+ return a > b ? gcd_sub_recursive(a - b, b) : gcd_sub_recursive(a, b - a);
+}
+
+export function gcd_optimal(a: number, b: number): number {
+ if (a === b) {
+ return a;
+ }
+
+ if ((a & 1) === 0 && (b & 1) === 0) {
+ return gcd_optimal(a >> 1, b >> 1);
+ } else if ((a & 1) === 0 && (b & 1) !== 0) {
+ return gcd_optimal(a >> 1, b);
+ } else if ((a & 1) !== 0 && (b & 1) === 0) {
+ return gcd_optimal(a, b >> 1);
+ } else {
+ const max = Math.max(a, b);
+ const min = Math.min(a, b);
+ return gcd_optimal(max - min, min);
+ }
+}
+
+ ← + + 斐波那契数列 + + 最小公倍数 + + → +
若有一个数 X
, 可以被另外两个数 A
、 B
整除, 且 X
大于(或等于) A
和 B
, 则 X
为 A
和 B
的公倍数。 A
和 B
的公倍数有无限个, 而所有的公倍数中, 最小的公倍数就叫做最小公倍数。
Note
/*
+ * @Author: Rainy
+ * @Date: 2020-01-31 12:33:43
+ * @LastEditors : Rainy
+ * @LastEditTime : 2020-01-31 13:25:56
+ */
+
+import { gcd_optimal } from '../gcd';
+
+// the least common multiple
+export function lcm(a: number, b: number): number {
+ return ((a === 0) || (b === 0)) ? 0 : Math.abs(a * b) / gcd_optimal(a, b);
+}
+
暴力求解 O(N)
库函数
分支法 O(log2 N)
递归 _pow1
非递归 _pow2
/*
+ * @Author: Rainy
+ * @Date: 2020-01-08 19:20:35
+ * @LastEditors : Rainy
+ * @LastEditTime : 2020-01-08 20:12:31
+ */
+
+/**
+ * @description: Math.pow
+ * @param {number} x
+ * @param {number} n pow
+ * @return {number}
+ */
+export function _pow1(x: number, n: number): number {
+ if (!n) {
+ return 1;
+ }
+ if (n < 0) {
+ return 1 / _pow1(x, -n);
+ }
+ if (!!(n % 2)) {
+ return x * _pow1(x, n - 1);
+ }
+ return _pow1(x * x, n / 2);
+}
+
+export function _pow2(x: number, n: number): number {
+ if (n < 0) {
+ x = 1 / x;
+ n = -n;
+ }
+
+ let pow = 1;
+ while (!!n) {
+ // n % 2
+ if (n & 1) {
+ pow *= x;
+ }
+
+ x *= x;
+ // n = n / 2;
+ n >>= 1;
+ }
+
+ return pow;
+}
+
+ ← + + 如何写文档? + + 阶乘 + + → +
Object.is() 判断两个值是否相同. 如果下列任何一项成立, 则两个值相同:
这种相等性判断逻辑和传统的 ==
运算不同, ==
运算符会对它两边的操作数做隐式类型转换(如果它们类型不同), 然后才进行相等性比较, (所以才会有类似 ""
==
false
等于 true
的现象), 但 Object.is
不会做这种类型转换.
这与 ===
运算符的判定方式也不一样. ===
运算符(和==
运算符)将数字值 -0
和 +0
视为相等, 并认为 Number.NaN
不等于 NaN
.
/*
+ * @Author: Rainy
+ * @Date: 2019-11-14 19:25:01
+ * @LastEditors : Rainy
+ * @LastEditTime : 2020-01-02 19:18:50
+ */
+export function _is(source: any, target: any): boolean {
+ if (source === target) {
+ return source !== 0 || 1 / source === 1 / target;
+ } else {
+ return source !== source && target !== target;
+ }
+}
+
+ ← + + isArray + + Types + + → +
调用一个具有给定 this
值的函数, 以及作为一个数组(或类似数组对象)提供的参数。
首先 context
为可选参数, 如果不传的话默认上下文为 window
接下来给 context
创建一个 fn
属性, 并将值设置为需要调用的函数
因为 apply
可以传入多个参数作为调用函数的参数, 所以需要将参数剥离出来
然后调用函数并将对象上的函数删除
注意
注意: call()
方法的作用和 apply()
方法类似, 区别就是 call()
方法接受的是参数列表, 而 apply()
方法接受的是一个参数数组。
/*
+ * @Author: Rainy
+ * @Date: 2020-04-09 19:02:51
+ * @LastEditors: Rainy
+ * @LastEditTime: 2020-08-08 16:49:24
+ */
+
+import { ObjectMap } from 'types';
+
+// @ts-ignore
+Function.prototype._apply = function(context: ObjectMap<any>, arg: any): any {
+ /* istanbul ignore next */
+ if (typeof this !== 'function') {
+ throw new TypeError('Error');
+ }
+
+ /* istanbul ignore next */
+ context = context ? Object(context) : window
+
+ context.fn = this;
+
+ const result = context.fn(...arg);
+
+ delete context.fn;
+
+ return result;
+};
+
创建一个新的函数, 在 bind()
被调用时, 这个新函数的 this
被指定为 bind()
的第一个参数, 而其余参数将作为新函数的参数, 供调用时使用。
特点:
改变 this
指向
返回一个函数
可以传入参数
提示
Note:
new
的优先级大于 bind
, 如果 bind
绑定后的函数被 new
了, this
会指向当前函数的实例
需要保留 原函数的原型链 上的属性和方法
/*
+ * @Author: Rainy
+ * @Date: 2020-04-09 19:02:51
+ * @LastEditors: Rainy
+ * @LastEditTime: 2020-04-09 19:33:41
+ */
+
+import { ObjectMap } from 'types';
+
+// @ts-ignore
+Function.prototype._bind = function(context: ObjectMap<any>, ...args: any): any {
+ /* istanbul ignore next */
+ if (typeof this !== 'function') {
+ throw new TypeError('Bind must be called with a function');
+ }
+
+ const _context = this;
+
+ const fb = function() {
+ // @ts-ignore
+ _context.apply(this instanceof fb ? this : context, args.concat(Array.prototype.slice.call(arguments)));
+ };
+
+ fb.prototype = Object.create(_context.prototype);
+
+ return fb;
+};
+
使用一个指定的 this
值和单独给出的一个或多个参数来调用一个函数。
首先 context
为可选参数,如果不传的话默认上下文为 window
接下来给 context
创建一个 fn
属性,并将值设置为需要调用的函数
因为 call
可以传入多个参数作为调用函数的参数,所以需要将参数剥离出来
然后调用函数并将对象上的函数删除
/*
+ * @Author: Rainy
+ * @Date: 2019-11-14 19:25:01
+ * @LastEditors: Rainy
+ * @LastEditTime: 2020-04-09 19:00:57
+ */
+
+import { ObjectMap } from 'types';
+
+// @ts-ignore
+Function.prototype._call = function(context: ObjectMap<any>, ...arg: any): any {
+ /* istanbul ignore next */
+ if (typeof this !== 'function') {
+ throw new TypeError('Error');
+ }
+
+ /* istanbul ignore next */
+ context = context ? Object(context) : window
+
+ context.fn = this;
+
+ const result = context.fn(...arg);
+
+ delete context.fn;
+
+ return result;
+};
+
+ ← + + bind + + instanceof + + → +
判断实例对象的__proto__
和构造函数的prototype
是不是引用同一个地址
/*
+ * @Author: Rainy
+ * @Date: 2019-11-14 19:03:03
+ * @LastEditors: Rainy
+ * @LastEditTime: 2019-11-15 13:48:20
+ */
+
+export function _instanceof(left: any, right: any): boolean {
+ const rt = right.prototype;
+ let lt = left.__proto__;
+ while (true) {
+ if (lt === null) {
+ return false;
+ }
+ if (lt === rt) {
+ return true;
+ }
+ lt = lt.__proto__;
+ }
+}
+
new
关键字做了哪些事?
创建一个 新对象
将构造函数的作用域赋值给这个新对象 -> this
指向这个 新对象
执行构造函数中的代码 -> 为 新对象 添加属性
返回 新对象
/*
+ * @Author: Rainy
+ * @Date: 2019-11-14 19:25:01
+ * @LastEditors: Rainy
+ * @LastEditTime: 2022-01-24 11:37:39
+ */
+
+export function _new(...arg: any): any {
+ // Eg: class Person {} or function Person() {}
+ // Person.prototype
+ // Person.prototype.constructor === Person
+ // person = new Person => person.__proto__ === Person.prototype
+
+ // Array.prototype.shift.call(arg) => [...arg].shift()
+
+ // 1
+ // let obj: any = new Object();
+ // 2
+ // obj.__proto__ = _constructor.prototype;
+ // 1 2
+ const _constructor = Array.prototype.shift.call(arg);
+ const obj = Object.create(_constructor.prototype);
+ // 3
+ const result = _constructor.apply(obj, arg);
+ // 4
+ return typeof result === 'object' && result !== null ? result : obj;
+}
+
+ ← + + instanceof + + 防抖 + + → +
冒泡排序(Bubble Sort
)也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
比较相邻的元素。如果第一个比第二个大,就交换他们两个。
对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
针对所有的元素重复以上的步骤,除了最后一个。
持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
/*
+ * @Author: Rainy
+ * @Date: 2019-11-14 19:25:01
+ * @LastEditors: Rainy
+ * @LastEditTime: 2019-11-24 17:52:16
+ */
+
+import { BaseArrayMap } from 'types';
+
+export function bubbleSort(arr: BaseArrayMap): BaseArrayMap {
+ let len = arr.length;
+ if (len < 2) {
+ return arr;
+ }
+ for (let i = 0; i < len - 1; i++) {
+ for (let j = 0; j < len - 1 - i; j++) {
+ if (arr[j] > arr[j + 1]) {
+ [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
+ }
+ }
+ }
+ return arr;
+}
+
桶排序是计数排序的升级版。它利用了函数的映射关系, 高效与否的关键就在于这个映射函数的确定。为了使桶排序更加高效, 我们需要做到这两点:
N
个数据均匀的分配到 K
个桶中同时, 对于桶中元素的排序, 选择何种比较排序算法对于性能的影响至关重要。
/*
+ * @Author: Rainy
+ * @Date: 2019-11-14 19:25:01
+ * @LastEditors: Rainy
+ * @LastEditTime: 2019-12-01 11:59:42
+ */
+
+import { NumberArrayMap } from 'types';
+
+export function radixSort(
+ arr: NumberArrayMap,
+ maxDigit: number
+): NumberArrayMap {
+ let counter: any = [];
+ let mod = 10;
+ let dev = 1;
+ for (let i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {
+ for (let j = 0; j < arr.length; j++) {
+ let bucket = parseInt(((arr[j] % mod) / dev) + '', 10);
+ if (counter[bucket] == null) {
+ counter[bucket] = [];
+ }
+ counter[bucket].push(arr[j]);
+ }
+ let pos = 0;
+ for (let j = 0; j < counter.length; j++) {
+ let value = null;
+ if (counter[j] != null) {
+ while ((value = counter[j].shift()) != null) {
+ arr[pos++] = +value;
+ }
+ }
+ }
+ }
+ return arr;
+}
+
Data Structure Visualizations: BucketSort (opens new window)
计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
/*
+ * @Author: Rainy
+ * @Date: 2019-11-14 19:25:01
+ * @LastEditors: Rainy
+ * @LastEditTime: 2019-11-24 17:52:06
+ */
+
+import { NumberArrayMap } from 'types';
+
+export function countingSort(arr: NumberArrayMap, maxValue: number): NumberArrayMap {
+ let bucket: NumberArrayMap = new Array(maxValue + 1);
+ let sortedIndex = 0;
+ let arrLen = arr.length;
+ let bucketLen = maxValue + 1;
+ if (arr.length < 2) {
+ return arr;
+ }
+
+ for (let i: number = 0; i < arrLen; i++) {
+ if (!bucket[arr[i]]) {
+ bucket[arr[i]] = 0;
+ }
+ bucket[arr[i]]++;
+ }
+
+ for (let j = 0; j < bucketLen; j++) {
+ while (bucket[j] > 0) {
+ arr[sortedIndex++] = j;
+ bucket[j]--;
+ }
+ }
+
+ return arr;
+}
+
堆排序(Heap sort
)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构, 并同时满足堆积的性质: 即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:
大顶堆: 每个节点的值都大于或等于其子节点的值, 在堆排序算法中用于升序排列;
小顶堆: 每个节点的值都小于或等于其子节点的值, 在堆排序算法中用于降序排列;
堆排序的平均时间复杂度为 Ο(nlogn)
。
创建一个堆 H[0……n-1];
把堆首(最大值)和堆尾互换;
把堆的尺寸缩小 1, 并调用 shift_down(0), 目的是把新的数组顶端数据调整到相应位置;
重复步骤 2, 直到堆的尺寸为 1。
/*
+ * @Author: Rainy
+ * @Date: 2019-11-14 19:25:01
+ * @LastEditors: Rainy
+ * @LastEditTime: 2019-11-24 20:50:03
+ */
+
+import { NumberArrayMap } from 'types';
+
+export function heapSort(arr: NumberArrayMap): NumberArrayMap {
+ let len = arr.length;
+ if (len < 2) {
+ return arr;
+ }
+ buildMaxHeap(arr, len);
+
+ for (let i = arr.length - 1; i > 0; i--) {
+ swap(arr, 0, i);
+ len--;
+ heapify(arr, 0, len);
+ }
+ return arr;
+}
+
+function buildMaxHeap(arr: NumberArrayMap, len: number) {
+ for (let i = Math.floor(len / 2); i >= 0; i--) {
+ heapify(arr, i, len);
+ }
+}
+
+function heapify(arr: NumberArrayMap, i: number, len: number) {
+ let left = 2 * i + 1;
+ let right = 2 * i + 2;
+ let largest = i;
+
+ if (left < len && arr[left] > arr[largest]) {
+ largest = left;
+ }
+
+ if (right < len && arr[right] > arr[largest]) {
+ largest = right;
+ }
+
+ if (largest !== i) {
+ swap(arr, i, largest);
+ heapify(arr, largest, len);
+ }
+}
+
+function swap(arr: NumberArrayMap, i: number, j: number) {
+ [arr[i], arr[j]] = [arr[j], arr[i]];
+}
+
插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列, 对于未排序数据, 在已排序序列中从后向前扫描, 找到相应位置并插入。
n
个记录的直接选择排序可经过 n-1
趟直接选择排序得到有序结果。具体算法描述如下:
3
, 直到找到已排序的元素小于或者等于新元素的位置;2~5
。/*
+ * @Author: Rainy
+ * @Date: 2019-11-14 19:25:01
+ * @LastEditors: Rainy
+ * @LastEditTime: 2019-11-24 21:02:34
+ */
+
+import { BaseArrayMap } from 'types';
+
+export function insertionSort(arr: BaseArrayMap): BaseArrayMap {
+ let len = arr.length;
+ if (len < 2) {
+ return arr;
+ }
+ let preIndex: number = 0;
+ let current: any = null;
+ for (let i = 1; i < len; i++) {
+ preIndex = i - 1;
+ current = arr[i];
+ while (preIndex >= 0 && arr[preIndex] > current) {
+ arr[preIndex + 1] = arr[preIndex];
+ preIndex--;
+ }
+ arr[preIndex + 1] = current;
+ }
+ return arr;
+}
+
归并排序(Merge sort
)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer
)的一个非常典型的应用。
作为一种典型的分而治之思想的算法应用, 归并排序的实现由两种方法:
自上而下的递归
自下而上的迭代
申请空间, 使其大小为两个已经排序序列之和, 该空间用来存放合并后的序列;
设定两个指针, 最初位置分别为两个已经排序序列的起始位置;
比较两个指针所指向的元素, 选择相对小的元素放入到合并空间, 并移动指针到下一位置;
重复步骤 3 直到某一指针达到序列尾;
将另一序列剩下的所有元素直接复制到合并序列尾。
/*
+ * @Author: Rainy
+ * @Date: 2019-11-14 19:25:01
+ * @LastEditors: Rainy
+ * @LastEditTime: 2019-11-24 21:00:03
+ */
+
+import { BaseArrayMap } from 'types';
+
+export function mergeSort(arr: BaseArrayMap): BaseArrayMap {
+ let len = arr.length;
+ if (len < 2) {
+ return arr;
+ }
+ let middle = Math.floor(len / 2);
+ let left = arr.slice(0, middle);
+ let right = arr.slice(middle);
+ return merge(mergeSort(left), mergeSort(right));
+}
+
+export function merge(left: BaseArrayMap, right: BaseArrayMap): BaseArrayMap {
+ let result: any = [];
+
+ while (left.length && right.length) {
+ if (left[0] <= right[0]) {
+ result.push(left.shift());
+ } else {
+ result.push(right.shift());
+ }
+ }
+
+ while (left.length) {
+ result.push(left.shift());
+ }
+
+ while (right.length) {
+ result.push(right.shift());
+ }
+
+ return result;
+}
+
快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下, 排序 n
个项目要 Ο(nlogn)
次比较。在最坏状况下则需要 Ο(n2)
次比较, 但这种状况并不常见。事实上, 快速排序通常明显比其他 Ο(nlogn)
算法更快, 因为它的内部循环(inner loop
)可以在大部分的架构上很有效率地被实现出来。
快速排序使用分治法(Divide and conquer
)策略来把一个串行(list
)分为两个子串行(sub-lists
)。
快速排序又是一种分而治之思想在排序算法上的典型应用。本质上来看, 快速排序应该算是在冒泡排序基础上的递归分治法。
快速排序的最坏运行情况是 O(n²)
, 比如说顺序数列的快排。但它的平摊期望时间是 O(nlogn)
, 且 O(nlogn)
记号中隐含的常数因子很小, 比复杂度稳定等于 O(nlogn)
的归并排序要小很多。所以, 对绝大多数顺序性较弱的随机数列而言, 快速排序总是优于归并排序。
从数列中挑出一个元素, 称为'基准' (pivot
);
重新排序数列, 所有元素比基准值小的摆放在基准前面, 所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后, 该基准就处于数列的中间位置。这个称为分区(partition
)操作;
递归地(recursive
)把小于基准值元素的子数列和大于基准值元素的子数列排序;
递归的最底部情形, 是数列的大小是零或一, 也就是永远都已经被排序好了。虽然一直递归下去, 但是这个算法总会退出, 因为在每次的迭代(iteration
)中, 它至少会把一个元素摆到它最后的位置去。
/*
+ * @Author: Rainy
+ * @Date: 2019-11-14 19:25:01
+ * @LastEditors: Rainy
+ * @LastEditTime: 2019-11-24 20:51:18
+ */
+
+import { NumberArrayMap } from 'types';
+
+export function quickSort(arr: NumberArrayMap, left?: number, right?: number) {
+ let len: number = arr.length;
+ let partitionIndex: number = 0;
+ let lIndex: number = typeof left !== 'number' ? 0 : left;
+ let rIndex: number = typeof right !== 'number' ? len - 1 : right;
+
+ if (lIndex < rIndex) {
+ partitionIndex = partition(arr, lIndex, rIndex);
+ quickSort(arr, lIndex, partitionIndex - 1);
+ quickSort(arr, partitionIndex + 1, rIndex);
+ }
+ return arr;
+}
+
+function partition(arr: NumberArrayMap, left: number, right: number) {
+ let pivot: number = left;
+ let index: number = pivot + 1;
+ for (let i = index; i <= right; i++) {
+ if (arr[i] < arr[pivot]) {
+ [arr[i], arr[index]] = [arr[index], arr[i]];
+ index++;
+ }
+ }
+ [arr[pivot], arr[index - 1]] = [arr[index - 1], arr[pivot]];
+ return index - 1;
+}
+
+// export function quickSort(arr: NumberArrayMap): NumberArrayMap {
+// let len = arr.length;
+// if (len < 2) {
+// return arr;
+// }
+// let pivotIndex: number = Math.floor(len / 2);
+// let pivot: number = arr.splice(pivotIndex, 1)[0];
+// let left: NumberArrayMap = [];
+// let right: NumberArrayMap = [];
+// for (let i = 0; i < len; i++) {
+// if (arr[i] < pivot) {
+// left.push(arr[i]);
+// } else {
+// right.push(arr[i]);
+// }
+// }
+// return quickSort(left).concat([pivot], quickSort(right));
+// }
+
基数排序是一种非比较型整数排序算法, 其原理是将整数按位数切割成不同的数字, 然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数, 所以基数排序也不是只能使用于整数。
基数排序有两种方法:
这三种排序算法都利用了桶的概念, 但对桶的使用方法上有明显差异:
/*
+ * @Author: Rainy
+ * @Date: 2019-11-14 19:25:01
+ * @LastEditors : Rainy
+ * @LastEditTime : 2019-12-29 11:15:28
+ */
+
+import { NumberArrayMap } from 'types';
+import { insertionSort } from '../insertionSort';
+
+export function bucketSort(
+ arr: NumberArrayMap,
+ bucketSize?: number,
+): NumberArrayMap {
+ if (arr.length === 0) {
+ return arr;
+ }
+
+ let i;
+ let minValue = arr[0];
+ let maxValue = arr[0];
+ for (i = 1; i < arr.length; i++) {
+ if (arr[i] < minValue) {
+ minValue = arr[i];
+ } else if (arr[i] > maxValue) {
+ maxValue = arr[i];
+ }
+ }
+
+ let DEFAULT_BUCKET_SIZE: number = 5;
+ bucketSize = bucketSize || DEFAULT_BUCKET_SIZE;
+ let bucketCount: number = Math.floor((maxValue - minValue) / bucketSize) + 1;
+ let buckets = new Array(bucketCount);
+ for (i = 0; i < buckets.length; i++) {
+ buckets[i] = [];
+ }
+
+ for (i = 0; i < arr.length; i++) {
+ buckets[Math.floor((arr[i] - minValue) / bucketSize)].push(arr[i]);
+ }
+
+ arr.length = 0;
+ for (i = 0; i < buckets.length; i++) {
+ insertionSort(buckets[i]);
+ for (let j = 0; j < buckets[i].length; j++) {
+ arr.push(buckets[i][j]);
+ }
+ }
+
+ return arr;
+}
+
选择排序是一种简单直观的排序算法 d
, 无论什么数据进去都是 O(n²)
的时间复杂度。所以用到它的时候d
, 数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。
首先在未排序序列中找到最小(大)元素 d
, 存放到排序序列的起始位置;
再从剩余未排序元素中继续寻找最小(大)元素 d
, 然后放到已排序序列的末尾;
重复第二步d, 直到所有元素均排序完毕。
/*
+ * @Author: Rainy
+ * @Date: 2019-11-14 19:25:01
+ * @LastEditors: Rainy
+ * @LastEditTime: 2019-11-24 17:51:27
+ */
+
+import { BaseArrayMap } from 'types';
+
+export function selectionSort(arr: BaseArrayMap): BaseArrayMap {
+ let len = arr.length;
+ if (len < 2) {
+ return arr;
+ }
+ let minIndex = 0;
+ for (let i = 0; i < len - 1; i++) {
+ minIndex = i;
+ for (let j = i + 1; j < len; j++) {
+ if (arr[j] < arr[minIndex]) {
+ minIndex = j;
+ }
+ }
+ [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
+ }
+ return arr;
+}
+
希尔排序, 也称递减增量排序算法, 是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
插入排序在对几乎已经排好序的数据操作时, 效率高, 即可以达到线性排序的效率;
但插入排序一般来说是低效的, 因为插入排序每次只能将数据移动一位; +希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序, 待整个序列中的记录'基本有序'时, 再对全体记录进行依次直接插入排序。
选择一个增量序列 t1, t2, ……, tk, 其中 ti > tj, tk = 1;
按增量序列个数 k, 对序列进行 k 趟排序;
每趟排序, 根据对应的增量 ti, 将待排序列分割成若干长度为 m 的子序列, 分别对各子表进行直接插入排序。仅增量因子为 1 时, 整个序列作为一个表来处理, 表长度即为整个序列的长度。
/*
+ * @Author: Rainy
+ * @Date: 2019-11-14 19:25:01
+ * @LastEditors: Rainy
+ * @LastEditTime: 2019-11-24 19:43:47
+ */
+
+import { NumberArrayMap } from 'types';
+
+export function shellSort(
+ arr: NumberArrayMap,
+ gapLen: number = 1
+): NumberArrayMap {
+ let len = arr.length;
+ let gap = 1;
+ if (len < 2) {
+ return arr;
+ }
+ while (gap < len / gapLen) {
+ gap = gap * gapLen + 1;
+ }
+ for (gap; gap > 0; gap = Math.floor(gap / 2)) {
+ for (let i = gap; i < len; i++) {
+ let j = i;
+ let current = arr[i];
+ while (j - gap >= 0 && current < arr[j - gap]) {
+ arr[j] = arr[j - gap];
+ j = j - gap;
+ }
+ arr[j] = current;
+ }
+ }
+ return arr;
+}
+
+ ← + + 选择排序 + + 03. 数组中重复的数字 + + → +
找出数组中重复的数字。
在一个长度为 n
的数组 nums
里的所有数字都在 0~n - 1
的范围内。数组中某些数字是重复的, 但不知道有几个数字重复了, 也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
示例 1:
输入:
[2, 3, 1, 0, 2, 5, 3]
+输出: 2 或 3
+
限制:
2 <= n <= 100000
+
来源: 力扣LeetCode (opens new window)
遍历该数组, 检查是否出现过, 首次出现过就标识, 再次出现就返回该值。
由于题目限定了所有数字都在 0~n - 1
的范围内, 即元素位置 index
取到的值 也会在当前数组的范围内, 所以只需要遍历该数组, 如果遍历的值不等于当前的 index
, 就把它放到对应的值位置上, 如果该位置上存在该值, 结果就是该值。
/*
+ * @Author: Rainy
+ * @Date: 2019-11-14 19:25:01
+ * @LastEditors: Rainy
+ * @LastEditTime: 2020-07-26 10:52:46
+ */
+
+import { ObjectMap } from "types";
+
+/**
+ * @param {number[]} nums
+ * @return {number}
+ */
+export function findRepeatNumber_1(nums: number[]): number | undefined {
+ const map: ObjectMap<boolean> = {};
+ for (const num of nums) {
+ if (!map[num]) {
+ map[num] = true;
+ } else {
+ return num;
+ }
+ }
+}
+
+export function findRepeatNumber_2(nums: number[]): number | undefined {
+ let num: number;
+ for (let index = 0; index < nums.length; index++) {
+ while ((num = nums[index]) !== index) {
+ if (num === nums[num]) {
+ return num;
+ }
+
+ [nums[num], nums[index]] = [nums[index], nums[num]];
+ }
+ }
+}
+
+ ← + + 希尔排序 + + 64 求1 ~ n的和 + + → +
求 1 + 2 + ... + n
, 要求 不能使用 乘除法
, for
, while
, if
, else
, switch
, case
等关键字及条件判断语句 (A ? B : C)
。
示例 1:
输入: n = 3
+输出: 6
+
示例 2:
输入: n = 9
+输出: 45
+
限制:
1 <= n <= 10000
+
来源: 力扣 (LeetCode) (opens new window)
我们小学就学过 1 + 2 + ... + n
的结果就是 $ sum = \frac{(1 + n) * n}{2} $, 但是由于题目限定, 我们不能使用常规方法, 所以我们可以通过 短路递归 和 幂运算移位 来计算出结果。
/*
+ * @Author: Rainy
+ * @Date: 2019-11-14 19:25:01
+ * @LastEditors: Rainy
+ * @LastEditTime: 2020-07-28 15:42:46
+ */
+
+/**
+ * @param {number} n
+ * @return {number}
+ */
+export function sumNums_1(n: number): number {
+ return n && (n + sumNums_1(--n));
+};
+
+export function sumNums_2(n: number): number {
+ // (n + 1) * n / 2 => (n ^ 2 + n) / 2
+ return (n ** 2 + n) >> 1;
+};
+
+
+ ← + + 03. 数组中重复的数字 + + 无重复字符的最长子串 + + → +
提示
主要是对一些常用 &
复杂类型使用 typescript
进行定义
+/*
+ * @Author: Rainy
+ * @Date: 2019-11-14 19:55:36
+ * @LastEditors : Rainy
+ * @LastEditTime : 2019-12-29 11:01:00
+ */
+
+export class ClassType {}
+
+// Base
+export type BaseValueType = number | string;
+
+export type MixedValueType = number | string | boolean;
+
+// Array
+export type BaseArrayMap = BaseValueType[];
+
+export type NumberArrayMap = number[];
+
+export type StringArrayMap = string[];
+
+export type AnyArrayMap = unknown[];
+
+export type ArrayMap<T> = T[];
+
+// Object
+
+export type ObjectMap<V> = {
+ [name: string]: V;
+};
+
+// Function
+export type NullFunction = () => null;
+
+export type AnyFunction = () => any;
+
+export type WithParamsReturnAnyFunction = (...arg: any) => any;
+
+export type BaseFunction = () => BaseValueType;
+
+export type MixedFunction = () => MixedValueType;
+
+export type WithParamsFunction = (...arg: any) => MixedValueType;
+
提示
主要是对一些常用 &
复杂通用工具函数进行定义
/*
+ * @Author: Rainy
+ * @Date: 2019-11-21 19:02:37
+ * @LastEditors: Rainy
+ * @LastEditTime: 2019-11-21 20:12:23
+ */
+
+export function isAbsType(target: any): string {
+ // [object Object] -> object
+ return Object.prototype.toString
+ .call(target)
+ .split(' ')[1]
+ .slice(0, -1)
+ .toLowerCase();
+}
+
我们知道目前所有的浏览器都是遵循类似上面图片所绘制出的工作流, 仅在细节处略有不同。
创建DOM树
一旦浏览器接收到一个HTML
文件, 渲染引擎 (render engine)
就开始解析它, 并根据 HTML 元素 (elements)一一对应地生成DOM节点 (nodes)
, 组成一棵DOM树
。
创建渲染树
同时, 浏览器也会解析来自外部CSS文件
和元素上的inline
样式。通常浏览器会为这些样式信息, 连同包含样式信息的DOM树
上的节点, 再创建另外一个树, 一般被称作渲染树 (render tree)
创建渲染树背后的故事
WebKit
内核的浏览器上, 处理一个节点的样式的过程称为attachment
。DOM树
上的每个节点都有一个attach
方法, 它接收计算好的样式信息, 返回一个render
对象 (又名renderer
)
Attachment
的过程是同步的, 新节点插入 DOM树
时, 会调用新节点的attach
方法。
+构建渲染树时, 由于包含了这些 render 对象, 每个 render 对象都需要计算视觉属性 (visual properties)
; 这个过程通过计算每个元素的样式属性来完成。
布局 Layout
+又被简称为Reflow
+构造了渲染树以后, 浏览器引擎开始着手布局 (layout)
。布局时, 渲染树上的每个节点根据其在屏幕上应该出现的精确位置, 分配一组屏幕坐标值。
绘制 Painting
接着, 浏览器将会通过遍历渲染树, 调用每个节点的 paint
方法来绘制这些 render
对象。paint
方法根据浏览器平台, 使用不同的 UI后端API
(agnostic UI backend API)
。 通过绘制, 最终将在屏幕上展示内容。
DOM操作
真正的问题在于每次操作都会触发布局的改变、DOM树
的修改和渲染。Virtual DOM
把管理 DOM碎片
这件事情自动化、抽象化了, 使得你无需再去手动处理。
Virtual DOM
是对DOM
的抽象, 本质上是JavaScript
对象, 这个对象就是更加轻量级的对DOM
的描述.
传统 diff
算法的复杂度为O(n^3)
React diff
算法的复杂度为O(n)
先中后序遍历 -> 遍历根节点的顺序
先序 -> 根, 左, 右
中序 -> 左, 根, 右
后序 -> 左, 右, 根
深度(DFS)遍历 -> 从根节点出发, 沿着左子树方向进行纵向遍历, 直到找到叶子节点为止。然后回溯到前一个节点, 进行右子树节点的遍历, 直到遍历完所有可达节点为止。
广度(BFS)遍历 -> 从根节点出发, 在横向遍历二叉树层段节点的基础上纵向遍历二叉树的层次。
替换
移除
index.d.ts
export interface Node {
+ type: string;
+ props: AnyObject;
+ children: Array<Node>;
+}
+
+export type AnyObject = {
+ [propName: string]: any;
+}
+
+export type AnyArray = unknown[];
+
utils.ts
const CREATE = 'CREATE';
+const REMOVE = 'REMOVE';
+const REPLACE = 'REPLACE';
+const UPDATE = 'UPDATE';
+const SET_PROP = 'SET_PROP';
+const REMOVE_PROP = 'REMOVE_PROP';
+
+function isString(str: string): boolean {
+ return Object.prototype.toString.call(str) === '[object String]';
+}
+
+export { CREATE, REMOVE, REPLACE, UPDATE, SET_PROP, REMOVE_PROP, isString };
+
render.js
import { diff } from './diff';
+import { createElement, patch } from './patch';
+
+function flatten(arr) {
+ return [].concat.apply([], arr);
+}
+
+function h(type, props, ...children) {
+ props = props || {};
+ return { type, props, children: flatten(children) };
+}
+
+
+function view(count) {
+ const r = [...Array(count).keys()];
+ return (
+ <ul id="cool" className={`my-class-${count}`}>
+ {r.map(n => (
+ <li>item {(count * n).toString()}</li>
+ ))}
+ </ul>
+ );
+}
+
+function tick(el, count) {
+ const patches = diff(view(count + 1), view(count));
+ patch(el, patches);
+ if (count > 20) {
+ return;
+ }
+ setTimeout(() => tick(el, count + 1), 500);
+}
+
+function render(el) {
+ el.appendChild(createElement(view(0)));
+ setTimeout(() => tick(el, 0), 500);
+}
+
+export { render };
+
diff.ts
import {
+ CREATE,
+ REMOVE,
+ REPLACE,
+ UPDATE,
+ SET_PROP,
+ REMOVE_PROP
+} from './utils';
+
+import { Node, AnyObject, AnyArray } from './index';
+
+function changed(node1: Node | any, node2: Node | any): boolean {
+ return (
+ typeof node1 !== typeof node2 ||
+ node1.type !== node2.type ||
+ (typeof node1 !== 'string' && node1 !== node2)
+ );
+}
+
+function diffProps(oldProps: AnyObject, newProps: AnyObject): AnyArray {
+ const patches: AnyArray = [];
+ const props: AnyObject = Object.assign({}, oldProps, newProps);
+ Object.keys(props).forEach(key => {
+ const oldValue: any = oldProps[key];
+ const newValue: any = newProps[key];
+
+ if (!newValue) {
+ patches.push({
+ type: REMOVE_PROP,
+ name,
+ value: oldValue
+ });
+ } else if (!oldValue || oldValue !== newValue) {
+ patches.push({
+ type: SET_PROP,
+ name,
+ value: newValue
+ });
+ }
+ });
+ return patches;
+}
+
+function diffChildren(oldChildren: AnyArray, newChildren: AnyArray): AnyArray {
+ const patches: AnyArray = [];
+ const patchesLength: number = Math.max(newChildren.length, oldChildren.length);
+
+ for (let i = 0; i < patchesLength; i++) {
+ patches[i] = diff(oldChildren, newChildren);
+ }
+ return patches;
+}
+
+function diff(oldNode: Node | any, newNode: Node | any): AnyObject | undefined {
+ if (!oldNode) {
+ return { type: CREATE, newNode };
+ }
+
+ if (!newNode) {
+ return { type: REMOVE };
+ }
+
+ if (changed(oldNode, newNode)) {
+ return { type: REPLACE, newNode };
+ }
+
+ if (oldNode.type !== newNode.type) {
+ return {
+ type: UPDATE,
+ props: diffProps(oldNode.props, newNode.props),
+ children: diffChildren(oldNode.children, newNode.children)
+ };
+ }
+}
+
+export { changed, diffProps, diffChildren, diff };
+
patch.ts
import {
+ CREATE,
+ REMOVE,
+ REPLACE,
+ UPDATE,
+ SET_PROP,
+ REMOVE_PROP,
+ isString
+} from './utils';
+
+import { Node, AnyObject, AnyArray } from './index';
+
+function createElement(node: Node | any) {
+ if (isString(node)) {
+ return document.createTextNode(node + '');
+ }
+ const el = document.createElement(node.type);
+ setProps(el, node.props || {});
+ node.children &&
+ node.children.map(createElement).forEach(el.appendChild.bind(el));
+
+ return el;
+}
+
+function setProp(target, name: string, value: AnyObject): void {
+ if (name.toLowerCase() === 'classname') {
+ name = 'class';
+ }
+ target.setAttribute(name, value);
+}
+
+function setProps(target, props: AnyObject) {
+ Object.keys(props).forEach(name => {
+ setProp(target, name, props[name]);
+ });
+}
+
+function removeProp(target, name: string, value: AnyObject): void {
+ if (name.toLowerCase() === 'classname') {
+ name = 'class';
+ }
+ target.removeAttribute(name);
+}
+
+function patchProps(parent, patches: AnyArray): void {
+ patches.forEach((patch: AnyObject) => {
+ const {type, name, value} = patch;
+ if (type === SET_PROP) {
+ setProp(parent, name, value)
+ }
+ if (type === REMOVE_PROP) {
+ removeProp(parent, name, value)
+ }
+ });
+}
+
+function patch(parent, patches: AnyObject, index: number = 0) {
+ if (!patches) {
+ return;
+ }
+ const el = parent.children[index];
+
+ switch (patches.type) {
+ case CREATE: {
+ const { newNode } = patches;
+ const newEl = document.createElement(newNode);
+ return parent.appendChild(newEl);
+ }
+ case REMOVE: {
+ return parent.removeChild(el);
+ }
+ case REPLACE: {
+ const { newNode } = patches;
+ const newEl = createElement(newNode);
+ return parent.replaceChild(newEl, el);
+ }
+ case UPDATE: {
+ const { props, children } = patches;
+ patchProps(el, props);
+ children.forEach((child, idx) => {
+ patch(el, child, idx);
+ });
+ }
+ }
+}
+
+export { createElement, setProp, setProps, removeProp, patchProps, patch };
+
Diff Strategies (opens new window)
React Diffing 算法 (opens new window)
React's diff algorithm - Christopher Chedeau (opens new window)
React Dom Diff (opens new window)
Under-the-hood-ReactJS (opens new window)
babel-plugin-transform-react-jsx (opens new window)
diff 算法原理概述 (opens new window)
React 源码剖析系列 - 不可思议的 react diff (opens new window)
How to write your own Virtual DOM (opens new window)
Mini Virtual DOM 实现 (opens new window)
virtual-dom (opens new window)
解析 snabbdom 源码 (opens new window)
为什么要使用 Virtual DOM (opens new window) -> 中文版 (opens new window)
+ ← + + 节流 + + 手写Promise + + → +