├── .gitignore
├── 1.广度优先遍历和深度优先遍历
└── 广度优先遍历和深度优先遍历.html
├── 10.Vue和React路由原理
├── hash.html
└── history.html
├── 11.webpack原理
├── .vscode
│ └── settings.json
├── babel
│ ├── bundle.js
│ ├── dist
│ │ └── bundle.js
│ ├── index.html
│ └── src
│ │ ├── add.js
│ │ ├── index.js
│ │ └── minus.js
├── demo
│ ├── .vscode
│ │ └── settings.json
│ ├── bundle.js
│ ├── index.html
│ ├── package.json
│ └── src
│ │ ├── add.js
│ │ ├── index.js
│ │ └── minus.js
└── loader
│ ├── asyncLoader.js
│ ├── demo-webpack-plugin.js
│ ├── dist
│ ├── index.md
│ └── main.js
│ ├── package.json
│ ├── src
│ └── index.js
│ ├── syncLoader.js
│ └── webpack.config.js
├── 13.Vue2.0和Vue3.0的响应式原理
├── 01.defineProperty.js
├── 02.defineProperty监听数据的变化.js
├── 03.defineProperty监听数据的变化.js
├── 04.Proxy.js
├── 05.Proxy支持监控数组.js
├── 06.Proxy嵌套的支持.js
└── 07.优劣势.js
├── 14.手写async函数
├── 1.js
├── 2.js
├── 3.js
├── 4.js
├── async.md
├── async的副本.md
└── demo
│ ├── 1.js
│ └── 2.js
├── 15.使用Es6提供的构造函数Proxy实现数据绑定
└── 1.html
├── 16.最大公共前缀
└── index.js
├── 17.手写redux
├── index.js
├── index1.js
├── index2.js
├── index3.js
├── index4.js
├── readme.md
└── redux.js
├── 18.使用setTimeout实现setInterval
└── readme.md
├── 19.对象扁平化
└── index.js
├── 2.发布订阅者和观察者
├── 发布订阅者.js
└── 观察者模式.js
├── 20.反转二叉树
└── index.js
├── 21.字符串的全排列
└── index.js
├── 22.ES5实现B继承A
└── index.js
├── 23.虚拟DOM转为真实DOM
└── 1.html
├── 25.合并数组
└── 1.index.js
├── 26.控制最大并发数
└── 1.js
├── 27.数组转为树
└── 1.js
├── 28.手写call函数
├── 1.html
├── 1.js
└── reade.md
├── 29.字符串相加
└── 1.js
├── 3.防抖和节流
└── 防抖和截流.html
├── 30.最长连续递增子序列
└── 1.js
├── 4.深拷贝和浅拷贝
├── 1.js
├── 10.js
├── 2.js
├── 3.js
├── 4.js
├── 5.js
├── 6.js
├── 7.js
├── 8.js
├── 9.js
└── 浅拷贝
│ ├── 1.js
│ ├── 2.js
│ └── 3.js
├── 6.Promise、Async
├── age.txt
├── name.txt
├── promise._all.js
└── test.js
├── 7.Vue自定义事件原理
├── 1.自定义事件的基本用法.html
├── 2.事件总线.html
├── 3.原理分析.js
└── vue.js
├── 8.React和Vue中key的问题
├── 1.性能对比.html
└── vue.js
├── README.md
└── img
├── 12.1.png
├── 12.png
├── BFS.png
├── DFS.png
└── ss.png
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | .DS_Store
3 |
4 | node_modules/
5 |
--------------------------------------------------------------------------------
/1.广度优先遍历和深度优先遍历/广度优先遍历和深度优先遍历.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
18 |
23 |
24 | c
25 |
26 |
27 |
28 |
33 |
38 |
39 | f
40 |
41 |
42 |
43 |
48 |
53 |
54 | r
55 |
56 |
57 |
58 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/10.Vue和React路由原理/hash.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 | - home
14 | - about
15 |
16 |
17 |
18 |
19 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/10.Vue和React路由原理/history.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Document
6 |
7 |
8 |
9 |
16 |
17 |
52 |
53 |
--------------------------------------------------------------------------------
/11.webpack原理/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "git.ignoreLimitWarning": true
3 | }
--------------------------------------------------------------------------------
/11.webpack原理/babel/bundle.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const path = require('path')
3 | const parser = require('@babel/parser')
4 | const traverse = require('@babel/traverse').default
5 | const babel = require('@babel/core')
6 | const getModuleInfo = file => {
7 | const body = fs.readFileSync(file, 'utf-8')
8 | const ast = parser.parse(body, {
9 | // 表示我们要解析的是es6模块
10 | sourceType: 'module'
11 | })
12 | const deps = {}
13 | traverse(ast, {
14 | ImportDeclaration({
15 | node
16 | }) {
17 | const dirname = path.dirname(file);
18 | const absPath = './' + path.join(dirname, node.source.value)
19 | deps[node.source.value] = absPath
20 | }
21 | })
22 | const {
23 | code
24 | } = babel.transformFromAst(ast, null, {
25 | presets: ["@babel/preset-env"]
26 | })
27 | const moduleInfo = {
28 | file,
29 | deps,
30 | code
31 | }
32 | console.log(moduleInfo)
33 | return moduleInfo
34 |
35 | }
36 | const parseModules = file => {
37 | // 定义依赖图
38 | const depsGraph = {}
39 | // 首先获取入口的信息
40 | const entry = getModuleInfo(file)
41 | const temp = [entry]
42 | for (let i = 0; i < temp.length; i++) {
43 | const item = temp[i]
44 | const deps = item.deps
45 | if (deps) {
46 | // 遍历模块的依赖,递归获取模块信息
47 | for (const key in deps) {
48 | if (deps.hasOwnProperty(key)) {
49 | temp.push(getModuleInfo(deps[key]))
50 | }
51 | }
52 | }
53 | }
54 | temp.forEach(moduleInfo => {
55 | depsGraph[moduleInfo.file] = {
56 | deps: moduleInfo.deps,
57 | code: moduleInfo.code
58 | }
59 | })
60 | console.log(depsGraph)
61 | return depsGraph
62 | }
63 | const bundle = file => {
64 | const depsGraph = JSON.stringify(parseModules(file))
65 | return `(function(graph){
66 | function require(file) {
67 | var exports = {};
68 | function absRequire(relPath){
69 | return require(graph[file].deps[relPath])
70 | }
71 | (function(require, exports, code){
72 | eval(code)
73 | })(absRequire, exports, graph[file].code)
74 | return exports
75 | }
76 | require('${file}')
77 | })(${depsGraph})`
78 | }
79 | const content = bundle('./src/index.js')
80 | // 写入到dist/bundle.js
81 | fs.mkdirSync('./dist')
82 | fs.writeFileSync('./dist/bundle.js', content)
--------------------------------------------------------------------------------
/11.webpack原理/babel/dist/bundle.js:
--------------------------------------------------------------------------------
1 | (function(graph){
2 | function require(file) {
3 | var exports = {};
4 | function absRequire(relPath){
5 | return require(graph[file].deps[relPath])
6 | }
7 | (function(require, exports, code){
8 | eval(code)
9 | })(absRequire, exports, graph[file].code)
10 | return exports
11 | }
12 | require('./src/index.js')
13 | })({"./src/index.js":{"deps":{"./add.js":"./src/add.js","./minus.js":"./src/minus.js"},"code":"\"use strict\";\n\nvar _add = _interopRequireDefault(require(\"./add.js\"));\n\nvar _minus = require(\"./minus.js\");\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { \"default\": obj }; }\n\nvar sum = (0, _add[\"default\"])(1, 2);\nvar division = (0, _minus.minus)(2, 1);\nconsole.log('sum>>>>>', sum);\nconsole.log('division>>>>>', division);"},"./src/add.js":{"deps":{},"code":"\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports[\"default\"] = void 0;\n\nvar _default = function _default(a, b) {\n return a + b;\n};\n\nexports[\"default\"] = _default;"},"./src/minus.js":{"deps":{},"code":"\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.minus = void 0;\n\nvar minus = function minus(a, b) {\n return a - b;\n};\n\nexports.minus = minus;"}})
--------------------------------------------------------------------------------
/11.webpack原理/babel/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | demo
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/11.webpack原理/babel/src/add.js:
--------------------------------------------------------------------------------
1 | export default (a, b) => {
2 | return a + b
3 | }
--------------------------------------------------------------------------------
/11.webpack原理/babel/src/index.js:
--------------------------------------------------------------------------------
1 | import add from './add.js'
2 | import {
3 | minus
4 | } from './minus.js'
5 |
6 | const sum = add(1, 2)
7 | const division = minus(2, 1)
8 | console.log('sum>>>>>', sum)
9 | console.log('division>>>>>', division)
--------------------------------------------------------------------------------
/11.webpack原理/babel/src/minus.js:
--------------------------------------------------------------------------------
1 | export const minus = (a, b) => {
2 | return a - b
3 | }
--------------------------------------------------------------------------------
/11.webpack原理/demo/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "git.ignoreLimitWarning": true
3 | }
--------------------------------------------------------------------------------
/11.webpack原理/demo/bundle.js:
--------------------------------------------------------------------------------
1 | // 1. 获取模块内容
2 | const fs = require("fs");
3 | const parser = require("@babel/parser");
4 | const path = require("path");
5 | const traverse = require("@babel/traverse").default;
6 | // 获取文件内容的函数
7 | const getModuleInfo = (file) => {
8 | const body = fs.readFileSync(file, "utf-8");
9 | const ast = parser.parse(body, {
10 | sourceType: "module", // 表示解析Es6模块
11 | });
12 | const deps = {}; // 收集依赖路径
13 |
14 | traverse(ast, {
15 | ImportDeclaration({ node }) {
16 | const dirname = path.dirname(file);
17 | const abspath = "./" + path.join(dirname, node.source.value);
18 | deps[node.source.value] = abspath;
19 | },
20 | });
21 | console.log(deps);
22 | };
23 | getModuleInfo("./src/index.js");
24 |
--------------------------------------------------------------------------------
/11.webpack原理/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/11.webpack原理/demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "demo",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "webpack": "^4.43.0",
14 | "webpack-cli": "^3.3.11"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/11.webpack原理/demo/src/add.js:
--------------------------------------------------------------------------------
1 | export default (a, b) => {
2 | return a + b
3 | }
--------------------------------------------------------------------------------
/11.webpack原理/demo/src/index.js:
--------------------------------------------------------------------------------
1 | import add from './add.js'
2 | import {
3 | minus
4 | } from './minus.js'
5 | const sum = add(1, 2)
6 | const divison = minus(2, 1)
7 | console.log(sum)
8 | console.log(divison)
--------------------------------------------------------------------------------
/11.webpack原理/demo/src/minus.js:
--------------------------------------------------------------------------------
1 | export const minus = (a, b) => {
2 | return a - b
3 | }
--------------------------------------------------------------------------------
/11.webpack原理/loader/asyncLoader.js:
--------------------------------------------------------------------------------
1 | const loaderUtils = require('loader-utils')
2 | module.exports = function (source) {
3 | const options = loaderUtils.getOptions(this)
4 | const asyncfunc = this.async()
5 | setTimeout(() => {
6 | console.log(source)
7 | asyncfunc(null, source)
8 | }, 200)
9 | }
--------------------------------------------------------------------------------
/11.webpack原理/loader/demo-webpack-plugin.js:
--------------------------------------------------------------------------------
1 | class DemoWebpackPlugin {
2 | constructor() {
3 | console.log('plugin init')
4 | }
5 | // compiler是webpack实例
6 | apply(compiler) {
7 | // 一个新的编译(compilation)创建之后(同步)
8 | // compilation代表每一次执行打包,独立的编译
9 | compiler.hooks.compile.tap('DemoWebpackPlugin', compilation => {
10 | console.log('123')
11 | })
12 | // 生成资源到 output 目录之前(异步)
13 | compiler.hooks.emit.tapAsync('DemoWebpackPlugin', (compilation, fn) => {
14 | console.log('456')
15 | compilation.assets['index.md'] = {
16 | // 文件内容
17 | source: function () {
18 | return 'this is a demo for plugin'
19 | },
20 | // 文件尺寸
21 | size: function () {
22 | return 25
23 | }
24 | }
25 | fn()
26 | })
27 | }
28 | }
29 |
30 | module.exports = DemoWebpackPlugin
--------------------------------------------------------------------------------
/11.webpack原理/loader/dist/index.md:
--------------------------------------------------------------------------------
1 | this is a demo for plugin
--------------------------------------------------------------------------------
/11.webpack原理/loader/dist/main.js:
--------------------------------------------------------------------------------
1 | /******/ (function(modules) { // webpackBootstrap
2 | /******/ // The module cache
3 | /******/ var installedModules = {};
4 | /******/
5 | /******/ // The require function
6 | /******/ function __webpack_require__(moduleId) {
7 | /******/
8 | /******/ // Check if module is in cache
9 | /******/ if(installedModules[moduleId]) {
10 | /******/ return installedModules[moduleId].exports;
11 | /******/ }
12 | /******/ // Create a new module (and put it into the cache)
13 | /******/ var module = installedModules[moduleId] = {
14 | /******/ i: moduleId,
15 | /******/ l: false,
16 | /******/ exports: {}
17 | /******/ };
18 | /******/
19 | /******/ // Execute the module function
20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
21 | /******/
22 | /******/ // Flag the module as loaded
23 | /******/ module.l = true;
24 | /******/
25 | /******/ // Return the exports of the module
26 | /******/ return module.exports;
27 | /******/ }
28 | /******/
29 | /******/
30 | /******/ // expose the modules object (__webpack_modules__)
31 | /******/ __webpack_require__.m = modules;
32 | /******/
33 | /******/ // expose the module cache
34 | /******/ __webpack_require__.c = installedModules;
35 | /******/
36 | /******/ // define getter function for harmony exports
37 | /******/ __webpack_require__.d = function(exports, name, getter) {
38 | /******/ if(!__webpack_require__.o(exports, name)) {
39 | /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
40 | /******/ }
41 | /******/ };
42 | /******/
43 | /******/ // define __esModule on exports
44 | /******/ __webpack_require__.r = function(exports) {
45 | /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
46 | /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
47 | /******/ }
48 | /******/ Object.defineProperty(exports, '__esModule', { value: true });
49 | /******/ };
50 | /******/
51 | /******/ // create a fake namespace object
52 | /******/ // mode & 1: value is a module id, require it
53 | /******/ // mode & 2: merge all properties of value into the ns
54 | /******/ // mode & 4: return value when already ns object
55 | /******/ // mode & 8|1: behave like require
56 | /******/ __webpack_require__.t = function(value, mode) {
57 | /******/ if(mode & 1) value = __webpack_require__(value);
58 | /******/ if(mode & 8) return value;
59 | /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
60 | /******/ var ns = Object.create(null);
61 | /******/ __webpack_require__.r(ns);
62 | /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
63 | /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
64 | /******/ return ns;
65 | /******/ };
66 | /******/
67 | /******/ // getDefaultExport function for compatibility with non-harmony modules
68 | /******/ __webpack_require__.n = function(module) {
69 | /******/ var getter = module && module.__esModule ?
70 | /******/ function getDefault() { return module['default']; } :
71 | /******/ function getModuleExports() { return module; };
72 | /******/ __webpack_require__.d(getter, 'a', getter);
73 | /******/ return getter;
74 | /******/ };
75 | /******/
76 | /******/ // Object.prototype.hasOwnProperty.call
77 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
78 | /******/
79 | /******/ // __webpack_public_path__
80 | /******/ __webpack_require__.p = "";
81 | /******/
82 | /******/
83 | /******/ // Load entry module and return exports
84 | /******/ return __webpack_require__(__webpack_require__.s = "./src/index.js");
85 | /******/ })
86 | /************************************************************************/
87 | /******/ ({
88 |
89 | /***/ "./src/index.js":
90 | /*!**********************!*\
91 | !*** ./src/index.js ***!
92 | \**********************/
93 | /*! no static exports found */
94 | /***/ (function(module, exports) {
95 |
96 | eval("// index.js\nconsole.log('我要学好前端,因为学好前端可以: ')\n\n//# sourceURL=webpack:///./src/index.js?");
97 |
98 | /***/ })
99 |
100 | /******/ });
--------------------------------------------------------------------------------
/11.webpack原理/loader/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "loader",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC"
12 | }
13 |
--------------------------------------------------------------------------------
/11.webpack原理/loader/src/index.js:
--------------------------------------------------------------------------------
1 | // index.js
2 | console.log('我要学好前端,因为学好前端可以: ')
--------------------------------------------------------------------------------
/11.webpack原理/loader/syncLoader.js:
--------------------------------------------------------------------------------
1 | // module.exports = function (source) {
2 | // console.log('source>>>>', source)
3 | // return source
4 | // }
5 |
6 | // syncLoader.js
7 | const loaderUtils = require('loader-utils')
8 | module.exports = function (source) {
9 | const options = loaderUtils.getOptions(this)
10 | console.log(options.message)
11 | // 可以传递更详细的信息
12 | this.callback(null, source) // 或者retrun都行
13 | }
--------------------------------------------------------------------------------
/11.webpack原理/loader/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const DemoWebpackPlugin = require('./demo-webpack-plugin')
3 | module.exports = {
4 | mode: 'development',
5 | plugins: [
6 | new DemoWebpackPlugin()
7 | ],
8 | entry: {
9 | main: './src/index.js'
10 | },
11 | output: {
12 | path: path.resolve(__dirname, 'dist'),
13 | filename: '[name].js'
14 | },
15 | resolveLoader: {
16 | // loader路径查找顺序从左往右
17 | modules: ['node_modules', './']
18 | },
19 | module: {
20 | rules: [{
21 | test: /\.js$/,
22 | use: [{
23 | loader: 'syncLoader',
24 | options: {
25 | message: '走上人生巅峰'
26 | }
27 | },
28 | {
29 | loader: 'asyncLoader'
30 | }
31 | ]
32 | }]
33 | }
34 | }
--------------------------------------------------------------------------------
/13.Vue2.0和Vue3.0的响应式原理/01.defineProperty.js:
--------------------------------------------------------------------------------
1 | /**
2 | Object.defineProperty(obj, prop, descriptor)
3 | 参数1:obj: 要在其上定义属性的对象。
4 | 参数2:prop: 要定义或修改的属性的名称。
5 | 参数3:descriptor: 将被定义或修改的属性的描述符(包含数据描述符和存取描述符)。
6 | **/
7 | //数据描述符
8 | let obj1 = {};
9 | Object.defineProperty(obj1, "key", {
10 | //该属性对应的值,默认为 undefined。
11 | value: 1,
12 | //属性的 writable 为 true 时,该属性才能被赋值运算符改变。默认为 false。
13 | writable: true,
14 | //属性的 enumerable 为 true 时,
15 | //该属性才能够出现在对象的枚举属性中。默认为 false。
16 | enumerable: true,
17 | //属性的configurable 为 true 时,
18 | //该属性描述符才能够被改变,也能够被删除。默认为 false。
19 | configurable: true,
20 | });
21 |
22 | console.log(obj1);
23 |
24 | // 存取描述符
25 | let obj2 = {};
26 | let value;
27 | Object.defineProperty(obj2, "key", {
28 | // 数据描述符.....
29 | get: function () {
30 | console.log("执行了获取操作");
31 |
32 | return value;
33 | },
34 | set: function (newValue) {
35 | console.log("执行了设置操作");
36 |
37 | value = newValue + "真帅!!!!";
38 | },
39 | });
40 | //执行get
41 | console.log(obj2.key);
42 | //执行set
43 | obj2.key = "铁蛋儿";
--------------------------------------------------------------------------------
/13.Vue2.0和Vue3.0的响应式原理/02.defineProperty监听数据的变化.js:
--------------------------------------------------------------------------------
1 | // 监听的数据
2 | let obj = {
3 | name: '铁蛋儿',
4 | age: 1,
5 | arr: ['张三', '李四', '王五'],
6 | job: {
7 | code: 'FE'
8 | }
9 | }
10 |
11 | // 封装监听数据变化的函数
12 | function defineProperty(obj, key, val) {
13 | observer(val)
14 | Object.defineProperty(obj, key, {
15 | get() {
16 | // 读取方法
17 | console.log('读取', key, '成功')
18 | return val
19 | },
20 | set(newval) {
21 | // 赋值监听方法
22 | if (newval === val) return
23 | // 遍历监听数据的每一项
24 | observer(newval)
25 | console.log('监听赋值成功', newval)
26 | val = newval
27 | // 可以执行渲染操作
28 | }
29 | })
30 | }
31 |
32 | function observer(obj) {
33 | if (typeof obj !== 'object' || obj == null) {
34 | return
35 | }
36 | for (const key in obj) {
37 | // 给对象中的每一项都设置响应式
38 | defineProperty(obj, key, obj[key])
39 | }
40 | }
41 |
42 | observer(obj)
43 |
44 |
45 | // obj.name = '小白龙'
46 |
47 | // obj.job.code = 'server'
48 |
49 | // obj.name = {
50 | // sname: '欧巴'
51 | // }
52 | // obj.name.sname = '欧巴铁蛋儿'
53 |
54 | obj.arr[3] = 1
55 | // obj.arr.push([1,[2,[3]]])
--------------------------------------------------------------------------------
/13.Vue2.0和Vue3.0的响应式原理/03.defineProperty监听数据的变化.js:
--------------------------------------------------------------------------------
1 | // Object.defineProperty 是对象的方法监听不到数组的变更的 Vue2.x的做法是重写数组的7个方法
2 | // 封装监听数据变化的函数
3 | let obj = {
4 | name: '铁蛋儿',
5 | age: 1,
6 | arr: ['张三', '李四', '王五'],
7 | job: {
8 | code: 'FE'
9 | }
10 | }
11 | const arrayMethods = Array.prototype;
12 | // 先克隆一份Array的原型出来
13 | const arrayProto = Object.create(arrayMethods);
14 | const methodsToPatch = [
15 | 'push',
16 | 'pop',
17 | 'shift',
18 | 'unshift',
19 | 'splice',
20 | 'sort',
21 | 'reverse'
22 | ]
23 | methodsToPatch.forEach(method => {
24 | arrayProto[method] = function () {
25 | // 执行原始操作
26 | arrayMethods[method].apply(this, arguments)
27 | console.log('监听赋值成功', method)
28 | }
29 | })
30 |
31 | function defineProperty(obj, key, val) {
32 | observer(val)
33 | Object.defineProperty(obj, key, {
34 | get() {
35 | // 读取方法
36 | console.log('读取', key, '成功')
37 | return val
38 | },
39 | set(newval) {
40 | // 赋值监听方法
41 | if (newval === val) return
42 | // 遍历监听数据的每一项
43 | observer(newval)
44 | console.log('监听赋值成功', newval)
45 | val = newval
46 | // 可以执行渲染操作
47 | }
48 | })
49 | }
50 |
51 | function observer(obj) {
52 | if (typeof obj !== 'object' || obj == null) {
53 | return
54 | }
55 | if (Array.isArray(obj)) {
56 | // 如果是数组, 重写原型
57 | obj.__proto__ = arrayProto
58 | // 传入的数据可能是多维度的,也需要执行响应式
59 | for (let i = 0; i < obj.length; i++) {
60 | observer(obj[i])
61 | }
62 | } else {
63 | for (const key in obj) {
64 | // 给对象中的每一个方法都设置响应式
65 | defineProperty(obj, key, obj[key])
66 | }
67 | }
68 | }
69 |
70 | observer(obj)
71 |
72 | obj.arr.push(4)
--------------------------------------------------------------------------------
/13.Vue2.0和Vue3.0的响应式原理/04.Proxy.js:
--------------------------------------------------------------------------------
1 | // let pro = new Proxy(target, handler);
2 | // new Proxy相当于创建了一个Proxy实例,
3 | // target为所要拦截的目标对象,
4 | // handler也是一个对象, 里面定义的是对拦截对象所要进行的拦截方法。
5 |
6 | // 针对对象: 针对整个对象,而不是对象的某个属性
7 | // 支持数组: 不需要对数组的方法进行重载,省去了重写数组的方法
8 | // 嵌套支持: get里面递归调用Proxy并返回
9 | // 不需要对keys 进行遍历。这解决Object.defineProperty() 的第二个问题.Proxy 是针对整个 obj 的。
10 | // 所以 obj 内部包含的所有的 key ,都可以走进 set。(省了一个 Object.keys() 的遍历)
11 |
12 |
13 | // 另外 Reflect.get 和 Reflect.set 可以理解为类继承里的super,即调用原来的方法
14 |
15 |
16 | // Reflect.get():获取对象身上某个属性的值,类似于 target[name]。
17 | // Reflect.set():将值分配给属性的函数,返回一个Boolean,如果更新成功,则返回true
18 | let obj = {
19 | name: '铁蛋儿',
20 | age: 30
21 | }
22 | let handler = {
23 | get(target, key, receiver) {
24 | console.log('get', key)
25 | return Reflect.get(target, key, receiver)
26 | },
27 | set(target, key, value, receiver) {
28 | console.log('set', key, value)
29 | return Reflect.set(target, key, value, receiver)
30 | }
31 | }
32 | let proxy = new Proxy(obj, handler)
33 | proxy.name = '小白龙' // set name 小白龙
34 | proxy.age = 18 // set age 18
--------------------------------------------------------------------------------
/13.Vue2.0和Vue3.0的响应式原理/05.Proxy支持监控数组.js:
--------------------------------------------------------------------------------
1 | let arr = [1, 2, 3]
2 | let proxy = new Proxy(arr, {
3 | get(target, key, receiver) {
4 | console.log('get', key)
5 | return Reflect.get(target, key, receiver)
6 | },
7 | set(target, key, value, receiver) {
8 | console.log('set', key, value)
9 | return Reflect.set(target, key, value, receiver)
10 | }
11 | })
12 | proxy.push(4)
13 | // 能够打印出很多内容
14 | // get push (寻找 proxy.push 方法)
15 | // get length (获取当前的 length)
16 | // set 3 4 (设置 proxy[3] = 4)
17 | // set length 4 (设置 proxy.length = 4)
--------------------------------------------------------------------------------
/13.Vue2.0和Vue3.0的响应式原理/06.Proxy嵌套的支持.js:
--------------------------------------------------------------------------------
1 | // Proxy 也是不支持嵌套的, 这点和 Object.defineProperty() 是一样的。
2 | // 因此也需要通过逐层遍历来解决。 Proxy 的写法是在 get 里面递归调用 Proxy 并返回
3 | let obj = {
4 | info: {
5 | name: '铁蛋儿',
6 | blogs: ['webpack', 'gulp', 'babel']
7 | }
8 | }
9 | let handler = {
10 | get(target, key, receiver) {
11 | console.log('get', key)
12 | // 递归创建并返回
13 | if (typeof target[key] === 'object' && target[key] !== null) {
14 | return new Proxy(target[key], handler)
15 | }
16 | return Reflect.get(target, key, receiver)
17 | },
18 | set(target, key, value, receiver) {
19 | console.log('set', key, value)
20 | return Reflect.set(target, key, value, receiver)
21 | }
22 | }
23 | let proxy = new Proxy(obj, handler)
24 | // 以下两句都能够进入 set
25 | proxy.info.name = '小白龙'
--------------------------------------------------------------------------------
/13.Vue2.0和Vue3.0的响应式原理/07.优劣势.js:
--------------------------------------------------------------------------------
1 | // 优势:
2 | // Proxy 的第二个参数可以有 13 种拦截方法,
3 | // 比 Object.defineProperty() 要更加丰富,
4 | // Proxy 作为新标准受到浏览器厂商的重点关注和性能优化,
5 | // 相比之下 Object.defineProperty() 是一个已有的老方法。
6 | // Proxy返回的是一个新对象,我们可以只操作新的对象达到目的,
7 | // 而Object.defineProperty只能遍历对象属性直接修改。
8 | // 劣势:
9 | // Proxy 的兼容性不如 Object.defineProperty() 可是使用 polyfill 来处理兼容性
--------------------------------------------------------------------------------
/14.手写async函数/1.js:
--------------------------------------------------------------------------------
1 | const getData = () => new Promise(resolve =>
2 | setTimeout(() => resolve("data"), 1000)
3 | )
4 |
5 | async function test() {
6 | const data = await getData()
7 | console.log('data: ', data);
8 | const data2 = await getData()
9 | console.log('data2: ', data2);
10 | return 'success'
11 | }
12 |
13 |
14 | // 这样的一个函数 应该再1秒后打印data 再过一秒打印data2 最后打印success
15 | test().then(res => console.log(res))
--------------------------------------------------------------------------------
/14.手写async函数/2.js:
--------------------------------------------------------------------------------
1 | const getData = () => new Promise(resolve =>
2 | setTimeout(() => resolve("data"), 1000)
3 | )
4 |
5 | async function testG() {
6 | const data = await getData()
7 | console.log('data: ', data);
8 | const data2 = await getData()
9 | console.log('data2: ', data2);
10 | return 'success'
11 | }
12 |
13 | function asyncToGenerator(generatorFunc) {
14 | // 返回的是一个新的函数
15 | return function () {
16 |
17 | // 先调用generator函数 生成迭代器
18 | // 对应 let gen = testG()
19 | const gen = generatorFunc.apply(this, arguments)
20 |
21 | // 返回一个promise 因为外部是用.then的方式 或者await的方式去使用这个函数的返回值的
22 | // let test = asyncToGenerator(testG)
23 | // test().then(res => console.log(res))
24 | return new Promise((resolve, reject) => {
25 |
26 | // 内部定义一个step函数 用来一步一步的跨过yield的阻碍
27 | // key有next和throw两种取值,分别对应了gen的next和throw方法
28 | // arg参数则是用来把promise resolve出来的值交给下一个yield
29 | function step(key, arg) {
30 | let generatorResult
31 |
32 | // 这个方法需要包裹在try catch中
33 | // 如果报错了 就把promise给reject掉 外部通过.catch可以获取到错误
34 | try {
35 | generatorResult = gen[key](arg)
36 | } catch (error) {
37 | return reject(error)
38 | }
39 |
40 | // gen.next() 得到的结果是一个 { value, done } 的结构
41 | const { value, done } = generatorResult
42 |
43 | if (done) {
44 | // 如果已经完成了 就直接resolve这个promise
45 | // 这个done是在最后一次调用next后才会为true
46 | // 以本文的例子来说 此时的结果是 { done: true, value: 'success' }
47 | // 这个value也就是generator函数最后的返回值
48 | return resolve(value)
49 | } else {
50 | // 除了最后结束的时候外,每次调用gen.next()
51 | // 其实是返回 { value: Promise, done: false } 的结构,
52 | // 这里要注意的是Promise.resolve可以接受一个promise为参数
53 | // 并且这个promise参数被resolve的时候,这个then才会被调用
54 | return Promise.resolve(
55 | // 这个value对应的是yield后面的promise
56 | value
57 | ).then(
58 | // value这个promise被resove的时候,就会执行next
59 | // 并且只要done不是true的时候 就会递归的往下解开promise
60 | // 对应gen.next().value.then(value => {
61 | // gen.next(value).value.then(value2 => {
62 | // gen.next()
63 | //
64 | // // 此时done为true了 整个promise被resolve了
65 | // // 最外部的test().then(res => console.log(res))的then就开始执行了
66 | // })
67 | // })
68 | function onResolve(val) {
69 | step("next", val)
70 | },
71 | // 如果promise被reject了 就再次进入step函数
72 | // 不同的是,这次的try catch中调用的是gen.throw(err)
73 | // 那么自然就被catch到 然后把promise给reject掉啦
74 | function onReject(err) {
75 | step("throw", err)
76 | },
77 | )
78 | }
79 | }
80 | step("next")
81 | })
82 | }
83 | }
84 | asyncToGenerator(testG)
--------------------------------------------------------------------------------
/14.手写async函数/3.js:
--------------------------------------------------------------------------------
1 | const getData = () => new Promise(resolve => setTimeout(() => resolve('data'), 1000));
2 |
3 | function* testG() {
4 | const data = yield getData();
5 | console.log('data: ', data);
6 | const data2 = yield getData();
7 | console.log('data2: ', data2);
8 | return 'success';
9 | }
10 |
11 |
12 |
13 | function co(generator) {
14 | return new Promise((resolve, reject) => {
15 | const gen = generator();
16 |
17 | function next(...param) {
18 | let tmp = gen.next(...param);
19 | if (tmp.done) {
20 | resolve(tmp.value);
21 | return;
22 | }
23 | tmp.value.then((...ret) => {
24 | next(...ret);
25 | })
26 | }
27 | next();
28 | })
29 | }
30 |
31 | co(testG).then((res) => {
32 | console.log(res);
33 | })
--------------------------------------------------------------------------------
/14.手写async函数/4.js:
--------------------------------------------------------------------------------
1 | const getData = () => new Promise(resolve => setTimeout(() => resolve('data'), 1000));
2 |
3 | function* testG() {
4 | const data = yield getData();
5 | console.log('data: ', data);
6 | const data2 = yield getData();
7 | console.log('data2: ', data2);
8 | return 'success';
9 | }
10 |
11 | function asyncToGenerator(generatorFunc) {
12 | const gen = generatorFunc.apply(this, arguments)
13 | return new Promise((resolve, reject) => {
14 | function step(key, arg) {
15 | let generatorResult
16 | try {
17 | generatorResult = gen[key](arg)
18 | } catch (error) {
19 | return reject(error)
20 | }
21 | const {
22 | value,
23 | done
24 | } = generatorResult
25 |
26 | if (done) {
27 | return resolve(value)
28 | } else {
29 | return Promise.resolve(
30 | value
31 | ).then(
32 | function onResolve(val) {
33 | step("next", val)
34 | },
35 | function onReject(err) {
36 | step("throw", err)
37 | },
38 | )
39 | }
40 | }
41 | step("next")
42 | })
43 |
44 | }
45 |
46 | asyncToGenerator(testG).then(res => {
47 | console.log(res)
48 | })
--------------------------------------------------------------------------------
/14.手写async函数/async.md:
--------------------------------------------------------------------------------
1 | ## 14. 手写async函数
2 |
3 | 经常有人说async函数是generator函数的语法糖,那么到底是怎么样一个糖呢?让我们来一层层的剥开它的糖衣。
4 |
5 | 有的同学想说,既然用了generator函数何必还要实现async呢?
6 |
7 | 这篇文章的目的就是带大家理解清楚async和generator之间到底是如何相互协作,管理异步的。
8 |
9 | ## 示例
10 |
11 | ```javascript
12 | const getData = () => new Promise(resolve => setTimeout(() => resolve("data"), 1000))
13 |
14 | async function test() {
15 | const data = await getData()
16 | console.log('data: ', data);
17 | const data2 = await getData()
18 | console.log('data2: ', data2);
19 | return 'success'
20 | }
21 |
22 | // 这样的一个函数 应该再1秒后打印data 再过一秒打印data2 最后打印success
23 | test().then(res => console.log(res))
24 | ```
25 |
26 | ## 思路
27 |
28 | 对于这个简单的案例来说,如果我们把它用generator函数表达,会是怎么样的呢?
29 |
30 | ```javascript
31 | function* testG() {
32 | // await被编译成了yield
33 | const data = yield getData()
34 | console.log('data: ', data);
35 | const data2 = yield getData()
36 | console.log('data2: ', data2);
37 | return 'success'
38 | }
39 | ```
40 |
41 | 我们知道,generator函数是不会自动执行的,每一次调用它的next方法,会停留在下一个yield的位置。
42 |
43 | 利用这个特性,我们只要编写一个自动执行的函数,就可以让这个generator函数完全实现async函数的功能。
44 |
45 | ```javascript
46 | const getData = () => new Promise(resolve => setTimeout(() => resolve("data"), 1000))
47 |
48 | let test = asyncToGenerator(
49 | function* testG() {
50 | // await被编译成了yield
51 | const data = yield getData()
52 | console.log('data: ', data);
53 | const data2 = yield getData()
54 | console.log('data2: ', data2);
55 | return 'success'
56 | }
57 | )
58 |
59 | test().then(res => console.log(res))
60 | ```
61 |
62 | 那么大体上的思路已经确定了,
63 |
64 | `asyncToGenerator`接受一个`generator`函数,返回一个`promise`,
65 |
66 | 关键就在于,里面用`yield`来划分的异步流程,应该如何自动执行。
67 |
68 | ## 如果是手动执行
69 |
70 | 在编写这个函数之前,我们先模拟手动去调用这个`generator`函数去一步步的把流程走完,有助于后面的思考。
71 |
72 | ```javascript
73 | function* testG() {
74 | // await被编译成了yield
75 | const data = yield getData()
76 | console.log('data: ', data);
77 | const data2 = yield getData()
78 | console.log('data2: ', data2);
79 | return 'success'
80 | }
81 | ```
82 |
83 | 我们先调用`testG`生成一个迭代器
84 |
85 | ```javascript
86 | // 返回了一个迭代器
87 | let gen = testG()
88 | ```
89 |
90 | 然后开始执行第一次`next`
91 |
92 | ```javascript
93 | // 第一次调用next 停留在第一个yield的位置
94 | // 返回的promise里 包含了data需要的数据
95 | let dataPromise = gen.next()
96 | ```
97 |
98 | 这里返回了一个`promise`,就是第一次`getData()`所返回的`promise`,注意
99 |
100 | ```javascript
101 | const data = yield getData()
102 | ```
103 |
104 | 这段代码要切割成左右两部分来看,第一次调用`next`,其实只是停留在了`yield getData()`这里,
105 |
106 | `data`的值并没有被确定。
107 |
108 | 那么什么时候data的值会被确定呢?
109 |
110 | **下一次调用next的时候,传的参数会被作为上一个yield前面接受的值**
111 |
112 | 也就是说,我们再次调用`gen.next('这个参数才会被赋给data变量')`的时候
113 |
114 | `data`的值才会被确定为``'这个参数才会被赋给data变量'``
115 |
116 | ```js
117 | gen.next('这个参数才会被赋给data变量')
118 | // 然后这里的data才有值
119 | const data = yield getData()
120 |
121 | // 然后打印出data
122 | console.log('data: ', data);
123 |
124 | // 然后继续走到下一个yield
125 | const data2 = yield getData()
126 | ```
127 |
128 | 然后往下执行,直到遇到下一个`yield`,继续这样的流程...
129 |
130 | 这是generator函数设计的一个比较难理解的点,但是为了实现我们的目标,还是得去学习它~
131 |
132 | 借助这个特性,如果我们这样去控制yield的流程,是不是就能实现异步串行了?
133 |
134 | ```javascript
135 | function* testG() {
136 | // await被编译成了yield
137 | const data = yield getData()
138 | console.log('data: ', data);
139 | const data2 = yield getData()
140 | console.log('data2: ', data2);
141 | return 'success'
142 | }
143 |
144 | let gen = testG()
145 |
146 | let dataPromise = gen.next()
147 |
148 | dataPromise.then((value1) => {
149 | // data1的value被拿到了 继续调用next并且传递给data
150 | let data2Promise = gen.next(value1)
151 |
152 | // console.log('data: ', data);
153 | // 此时就会打印出data
154 |
155 | data2Promise.then((value2) => {
156 | // data2的value拿到了 继续调用next并且传递value2
157 | gen.next(value2)
158 |
159 | // console.log('data2: ', data2);
160 | // 此时就会打印出data2
161 | })
162 | })
163 | ```
164 |
165 | 这样的一个看着像`callback hell`的调用,就可以让我们的generator函数把异步安排的明明白白。
166 |
167 | ## 实现
168 |
169 | 有了这样的思路,实现这个高阶函数就变得很简单了。
170 |
171 | 先整体看一下结构,有个印象,然后我们逐行注释讲解。
172 |
173 | ```javascript
174 | function asyncToGenerator(generatorFunc) {
175 | return function() {
176 | const gen = generatorFunc.apply(this, arguments)
177 | return new Promise((resolve, reject) => {
178 | function step(key, arg) {
179 | let generatorResult
180 | try {
181 | generatorResult = gen[key](arg)
182 | } catch (error) {
183 | return reject(error)
184 | }
185 | const { value, done } = generatorResult
186 | if (done) {
187 | return resolve(value)
188 | } else {
189 | return Promise.resolve(value).then(val => step('next', val), err => step('throw', err))
190 | }
191 | }
192 | step("next")
193 | })
194 | }
195 | }
196 | ```
197 |
198 | 不多不少,22行。
199 |
200 | 接下来逐行讲解。
201 |
202 | ```javascript
203 | function asyncToGenerator(generatorFunc) {
204 | // 先调用generator函数 生成迭代器
205 | // 对应 let gen = testG()
206 | const gen = generatorFunc.apply(this, arguments)
207 |
208 | // 返回一个promise 因为外部是用.then的方式 或者await的方式去使用这个函数的返回值的
209 | // let test = asyncToGenerator(testG)
210 | // test().then(res => console.log(res))
211 | return new Promise((resolve, reject) => {
212 |
213 | // 内部定义一个step函数 用来一步一步的跨过yield的阻碍
214 | // key有next和throw两种取值,分别对应了gen的next和throw方法
215 | // arg参数则是用来把promise resolve出来的值交给下一个yield
216 | function step(key, arg) {
217 | let generatorResult
218 |
219 | // 这个方法需要包裹在try catch中
220 | // 如果报错了 就把promise给reject掉 外部通过.catch可以获取到错误
221 | try {
222 | generatorResult = gen[key](arg)
223 | } catch (error) {
224 | return reject(error)
225 | }
226 |
227 | // gen.next() 得到的结果是一个 { value, done } 的结构
228 | const { value, done } = generatorResult
229 |
230 | if (done) {
231 | // 如果已经完成了 就直接resolve这个promise
232 | // 这个done是在最后一次调用next后才会为true
233 | // 以本文的例子来说 此时的结果是 { done: true, value: 'success' }
234 | // 这个value也就是generator函数最后的返回值
235 | return resolve(value)
236 | } else {
237 | // 除了最后结束的时候外,每次调用gen.next()
238 | // 其实是返回 { value: Promise, done: false } 的结构,
239 | // 这里要注意的是Promise.resolve可以接受一个promise为参数
240 | // 并且这个promise参数被resolve的时候,这个then才会被调用
241 | return Promise.resolve(
242 | // 这个value对应的是yield后面的promise
243 | value
244 | ).then(
245 | // value这个promise被resove的时候,就会执行next
246 | // 并且只要done不是true的时候 就会递归的往下解开promise
247 | // 对应gen.next().value.then(value => {
248 | // gen.next(value).value.then(value2 => {
249 | // gen.next()
250 | //
251 | // // 此时done为true了 整个promise被resolve了
252 | // // 最外部的test().then(res => console.log(res))的then就开始执行了
253 | // })
254 | // })
255 | function onResolve(val) {
256 | step("next", val)
257 | },
258 | // 如果promise被reject了 就再次进入step函数
259 | // 不同的是,这次的try catch中调用的是gen.throw(err)
260 | // 那么自然就被catch到 然后把promise给reject掉啦
261 | function onReject(err) {
262 | step("throw", err)
263 | },
264 | )
265 | }
266 | }
267 | step("next")
268 | })
269 | }
270 | }
271 | asyncToGenerator(testData).then(res=>console.log(res))
272 | ```
273 |
274 | ## 总结
275 |
276 | 本文用最简单的方式实现了asyncToGenerator这个函数,这是babel编译async函数的核心,当然在babel中,generator函数也被编译成了一个很原始的形式,本文我们直接以generator替代。
277 |
278 |
--------------------------------------------------------------------------------
/14.手写async函数/async的副本.md:
--------------------------------------------------------------------------------
1 | ## 前言
2 |
3 | 如果让你手写async函数的实现,带你用20行搞定它的核心。
4 |
5 | 经常有人说async函数是generator函数的语法糖,那么到底是怎么样一个糖呢?让我们来一层层的剥开它的糖衣。
6 |
7 | 有的同学想说,既然用了generator函数何必还要实现async呢?
8 |
9 | 这篇文章的目的就是带大家理解清楚async和generator之间到底是如何相互协作,管理异步的。
10 |
11 | ## 示例
12 |
13 | ```javascript
14 | const getData = () => new Promise(resolve => setTimeout(() => resolve("data"), 1000))
15 |
16 | async function test() {
17 | const data = await getData()
18 | console.log('data: ', data);
19 | const data2 = await getData()
20 | console.log('data2: ', data2);
21 | return 'success'
22 | }
23 |
24 | // 这样的一个函数 应该再1秒后打印data 再过一秒打印data2 最后打印success
25 | test().then(res => console.log(res))
26 | ```
27 |
28 | ## 思路
29 |
30 | 对于这个简单的案例来说,如果我们把它用generator函数表达,会是怎么样的呢?
31 |
32 | ```javascript
33 | function* testG() {
34 | // await被编译成了yield
35 | const data = yield getData()
36 | console.log('data: ', data);
37 | const data2 = yield getData()
38 | console.log('data2: ', data2);
39 | return 'success'
40 | }
41 | ```
42 |
43 | 我们知道,generator函数是不会自动执行的,每一次调用它的next方法,会停留在下一个yield的位置。
44 |
45 | 利用这个特性,我们只要编写一个自动执行的函数,就可以让这个generator函数完全实现async函数的功能。
46 |
47 | ```javascript
48 | const getData = () => new Promise(resolve => setTimeout(() => resolve("data"), 1000))
49 |
50 | let test = asyncToGenerator(
51 | function* testG() {
52 | // await被编译成了yield
53 | const data = yield getData()
54 | console.log('data: ', data);
55 | const data2 = yield getData()
56 | console.log('data2: ', data2);
57 | return 'success'
58 | }
59 | )
60 |
61 | test().then(res => console.log(res))
62 | ```
63 |
64 | 那么大体上的思路已经确定了,
65 |
66 | `asyncToGenerator`接受一个`generator`函数,返回一个`promise`,
67 |
68 | 关键就在于,里面用`yield`来划分的异步流程,应该如何自动执行。
69 |
70 | ## 如果是手动执行
71 |
72 | 在编写这个函数之前,我们先模拟手动去调用这个`generator`函数去一步步的把流程走完,有助于后面的思考。
73 |
74 | ```javascript
75 | function* testG() {
76 | // await被编译成了yield
77 | const data = yield getData()
78 | console.log('data: ', data);
79 | const data2 = yield getData()
80 | console.log('data2: ', data2);
81 | return 'success'
82 | }
83 | ```
84 |
85 | 我们先调用`testG`生成一个迭代器
86 |
87 | ```javascript
88 | // 返回了一个迭代器
89 | let gen = testG()
90 | ```
91 |
92 | 然后开始执行第一次`next`
93 |
94 | ```javascript
95 | // 第一次调用next 停留在第一个yield的位置
96 | // 返回的promise里 包含了data需要的数据
97 | let dataPromise = gen.next()
98 | ```
99 |
100 | 这里返回了一个`promise`,就是第一次`getData()`所返回的`promise`,注意
101 |
102 | ```javascript
103 | const data = yield getData()
104 | ```
105 |
106 | 这段代码要切割成左右两部分来看,第一次调用`next`,其实只是停留在了`yield getData()`这里,
107 |
108 | `data`的值并没有被确定。
109 |
110 | 那么什么时候data的值会被确定呢?
111 |
112 | **下一次调用next的时候,传的参数会被作为上一个yield前面接受的值**
113 |
114 | 也就是说,我们再次调用`gen.next('这个参数才会被赋给data变量')`的时候
115 |
116 | `data`的值才会被确定为``'这个参数才会被赋给data变量'``
117 |
118 | ```js
119 | gen.next('这个参数才会被赋给data变量')
120 | // 然后这里的data才有值
121 | const data = yield getData()
122 |
123 | // 然后打印出data
124 | console.log('data: ', data);
125 |
126 | // 然后继续走到下一个yield
127 | const data2 = yield getData()
128 | ```
129 |
130 | 然后往下执行,直到遇到下一个`yield`,继续这样的流程...
131 |
132 | 这是generator函数设计的一个比较难理解的点,但是为了实现我们的目标,还是得去学习它~
133 |
134 | 借助这个特性,如果我们这样去控制yield的流程,是不是就能实现异步串行了?
135 |
136 | ```javascript
137 | function* testG() {
138 | // await被编译成了yield
139 | const data = yield getData()
140 | console.log('data: ', data);
141 | const data2 = yield getData()
142 | console.log('data2: ', data2);
143 | return 'success'
144 | }
145 |
146 | let gen = testG()
147 |
148 | let dataPromise = gen.next()
149 |
150 | dataPromise.then((value1) => {
151 | // data1的value被拿到了 继续调用next并且传递给data
152 | let data2Promise = gen.next(value1)
153 |
154 | // console.log('data: ', data);
155 | // 此时就会打印出data
156 |
157 | data2Promise.then((value2) => {
158 | // data2的value拿到了 继续调用next并且传递value2
159 | gen.next(value2)
160 |
161 | // console.log('data2: ', data2);
162 | // 此时就会打印出data2
163 | })
164 | })
165 | ```
166 |
167 | 这样的一个看着像`callback hell`的调用,就可以让我们的generator函数把异步安排的明明白白。
168 |
169 | ## 实现
170 |
171 | 有了这样的思路,实现这个高阶函数就变得很简单了。
172 |
173 | 先整体看一下结构,有个印象,然后我们逐行注释讲解。
174 |
175 | ```javascript
176 | function asyncToGenerator(generatorFunc) {
177 | return function() {
178 | const gen = generatorFunc.apply(this, arguments)
179 | return new Promise((resolve, reject) => {
180 | function step(key, arg) {
181 | let generatorResult
182 | try {
183 | generatorResult = gen[key](arg)
184 | } catch (error) {
185 | return reject(error)
186 | }
187 | const { value, done } = generatorResult
188 | if (done) {
189 | return resolve(value)
190 | } else {
191 | return Promise.resolve(value).then(val => step('next', val), err => step('throw', err))
192 | }
193 | }
194 | step("next")
195 | })
196 | }
197 | }
198 | ```
199 |
200 | 不多不少,22行。
201 |
202 | 接下来逐行讲解。
203 |
204 | ```javascript
205 | function asyncToGenerator(generatorFunc) {
206 | // 先调用generator函数 生成迭代器
207 | // 对应 let gen = testG()
208 | const gen = generatorFunc.apply(this, arguments)
209 | // 返回一个promise 因为外部是用.then的方式 或者await的方式去使用这个函数的返回值的
210 | // let test = asyncToGenerator(testG)
211 | // test().then(res => console.log(res))
212 | return new Promise((resolve, reject) => {
213 | // 内部定义一个step函数 用来一步一步的跨过yield的阻碍
214 | // key有next和throw两种取值,分别对应了gen的next和throw方法
215 | // arg参数则是用来把promise resolve出来的值交给下一个yield
216 | function step(key, arg) {
217 | let generatorResult
218 | // 这个方法需要包裹在try catch中
219 | // 如果报错了 就把promise给reject掉 外部通过.catch可以获取到错误
220 | try {
221 | generatorResult = gen[key](arg)
222 | } catch (error) {
223 | return reject(error)
224 | }
225 | // gen.next() 得到的结果是一个 { value, done } 的结构
226 | const { value, done } = generatorResult
227 | if (done) {
228 | // 如果已经完成了 就直接resolve这个promise
229 | // 这个done是在最后一次调用next后才会为true
230 | // 以本文的例子来说 此时的结果是 { done: true, value: 'success' }
231 | // 这个value也就是generator函数最后的返回值
232 | return resolve(value)
233 | } else {
234 | // 除了最后结束的时候外,每次调用gen.next()
235 | // 其实是返回 { value: Promise, done: false } 的结构,
236 | // 这里要注意的是Promise.resolve可以接受一个promise为参数
237 | // 并且这个promise参数被resolve的时候,这个then才会被调用
238 | return Promise.resolve(
239 | // 这个value对应的是yield后面的promise
240 | value
241 | ).then(
242 | // value这个promise被resove的时候,就会执行next
243 | // 并且只要done不是true的时候 就会递归的往下解开promise
244 | // 对应gen.next().value.then(value => {
245 | // gen.next(value).value.then(value2 => {
246 | // gen.next()
247 | //
248 | // // 此时done为true了 整个promise被resolve了
249 | // // 最外部的test().then(res => console.log(res))的then就开始执行了
250 | // })
251 | // })
252 | function onResolve(val) {
253 | step("next", val)
254 | },
255 | // 如果promise被reject了 就再次进入step函数
256 | // 不同的是,这次的try catch中调用的是gen.throw(err)
257 | // 那么自然就被catch到 然后把promise给reject掉啦
258 | function onReject(err) {
259 | step("throw", err)
260 | },
261 | )
262 | }
263 | }
264 | step("next")
265 | })
266 | }
267 | asyncToGenerator(testG).then(res => {
268 | console.log(res)
269 | })
270 | ```
271 |
272 | ## 总结
273 |
274 | 本文用最简单的方式实现了asyncToGenerator这个函数,这是babel编译async函数的核心,当然在babel中,generator函数也被编译成了一个很原始的形式,本文我们直接以generator替代。
275 |
276 |
--------------------------------------------------------------------------------
/14.手写async函数/demo/1.js:
--------------------------------------------------------------------------------
1 | const getData = () =>
2 | new Promise(resolve => setTimeout(() => resolve("data"), 1000))
3 |
4 | async function test() {
5 | const data = await getData()
6 | console.log('data: ', data);
7 | const data2 = await getData()
8 | console.log('data2: ', data2);
9 | return 'success'
10 | }
11 |
12 | // 这样的一个函数 应该再1秒后打印data 再过一秒打印data2 最后打印success
13 | test().then(res => console.log(res))
--------------------------------------------------------------------------------
/14.手写async函数/demo/2.js:
--------------------------------------------------------------------------------
1 | const getData = () =>
2 | new Promise(resolve => setTimeout(() => resolve("data"), 1000))
3 |
4 | function* testG() {
5 | // await被编译成了yield
6 | const data = yield getData()
7 | console.log('data: ', data);
8 | const data2 = yield getData()
9 | console.log('data2: ', data2);
10 | return 'success'
11 | }
12 | function asyncToGenerator(generatorFunc) {
13 | // 先调用generator函数 生成迭代器
14 | // 对应 let gen = testG()
15 | const gen = generatorFunc.apply(this, arguments)
16 |
17 | // 返回一个promise 因为外部是用.then的方式 或者await的方式去使用这个函数的返回值的
18 | // let test = asyncToGenerator(testG)
19 | // test().then(res => console.log(res))
20 | return new Promise((resolve, reject) => {
21 |
22 | // 内部定义一个step函数 用来一步一步的跨过yield的阻碍
23 | // key有next和throw两种取值,分别对应了gen的next和throw方法
24 | // arg参数则是用来把promise resolve出来的值交给下一个yield
25 | function step(key, arg) {
26 | let generatorResult
27 |
28 | // 这个方法需要包裹在try catch中
29 | // 如果报错了 就把promise给reject掉 外部通过.catch可以获取到错误
30 | try {
31 | generatorResult = gen[key](arg)
32 | } catch (error) {
33 | return reject(error)
34 | }
35 |
36 | // gen.next() 得到的结果是一个 { value, done } 的结构
37 | const { value, done } = generatorResult
38 |
39 | if (done) {
40 | // 如果已经完成了 就直接resolve这个promise
41 | // 这个done是在最后一次调用next后才会为true
42 | // 以本文的例子来说 此时的结果是 { done: true, value: 'success' }
43 | // 这个value也就是generator函数最后的返回值
44 | return resolve(value)
45 | } else {
46 | // 除了最后结束的时候外,每次调用gen.next()
47 | // 其实是返回 { value: Promise, done: false } 的结构,
48 | // 这里要注意的是Promise.resolve可以接受一个promise为参数
49 | // 并且这个promise参数被resolve的时候,这个then才会被调用
50 | return Promise.resolve(
51 | // 这个value对应的是yield后面的promise
52 | value
53 | ).then(
54 | // value这个promise被resove的时候,就会执行next
55 | // 并且只要done不是true的时候 就会递归的往下解开promise
56 | // 对应gen.next().value.then(value => {
57 | // gen.next(value).value.then(value2 => {
58 | // gen.next()
59 | //
60 | // // 此时done为true了 整个promise被resolve了
61 | // // 最外部的test().then(res => console.log(res))的then就开始执行了
62 | // })
63 | // })
64 | function onResolve(val) {
65 | step("next", val)
66 | },
67 | // 如果promise被reject了 就再次进入step函数
68 | // 不同的是,这次的try catch中调用的是gen.throw(err)
69 | // 那么自然就被catch到 然后把promise给reject掉啦
70 | function onReject(err) {
71 | step("throw", err)
72 | },
73 | )
74 | }
75 | }
76 | step("next")
77 | })
78 | }
79 | asyncToGenerator(testG).then(res => console.log(res))
--------------------------------------------------------------------------------
/15.使用Es6提供的构造函数Proxy实现数据绑定/1.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 | hello,world
12 |
13 | 我是铁蛋儿
14 |
38 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/16.最大公共前缀/index.js:
--------------------------------------------------------------------------------
1 | function longestCommonPrefix(strs) {
2 | // write code here
3 | if (strs.length === 0 || strs === null) {
4 | return ""
5 | }
6 | let maxid = strs[0].length - 1;
7 |
8 | for (let i = 1; i < strs.length; i++) {
9 |
10 | indx = -1; //下标flag
11 |
12 | while (indx < maxid && indx < strs[i].length - 1) {
13 | if (strs[0].charAt(indx + 1) === strs[i].charAt(indx + 1)) {
14 | indx++
15 | } else {
16 | break;
17 | }
18 | }
19 |
20 | if (indx === -1) {
21 | return ""
22 | }
23 | maxid = indx;
24 | }
25 | return strs[0].substring(0, maxid + 1);
26 | }
27 |
28 | console.log(longestCommonPrefix(["abca", "abc", "abca", "abc", "abcc"]))
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/17.手写redux/index.js:
--------------------------------------------------------------------------------
1 | function createStore() {
2 | // 需要监听的值
3 | let data = 0
4 |
5 | // 收集依赖
6 | let listenters = []
7 |
8 | // 收集依赖的方法
9 | function subscribe(listenter) {
10 | listenters.push(listenter)
11 | }
12 |
13 | // 发生改变通知左右依赖项
14 | function dispatch(action) {
15 | data = reducer(data, action)
16 | listenters.forEach((v, i) => {
17 | v()
18 | })
19 | }
20 | // 获取状态
21 | function getState() {
22 | return data
23 | }
24 | return {
25 | subscribe,
26 | dispatch,
27 | getState
28 | }
29 | }
30 | function reducer(state, action) {
31 | switch (action.type) {
32 | case "TIEDAN":
33 | return ++state
34 | default:
35 | return state
36 | }
37 | }
38 |
39 | const store = createStore()
40 | store.subscribe(() => {
41 | console.log('监听的数据发生变化了')
42 | })
43 | // console.log('改变前' + store.getState())
44 | // // 改变监听的值
45 | // store.dispatch(1)
46 |
47 | // console.log('改变后' + store.getState())
48 |
49 | // setInterval(() => {
50 | // let count = store.getState()
51 | // count++
52 | // console.log(store.getState())
53 | // store.dispatch(count)
54 | // }, 1000)
55 | // store.dispatch('tiedan')
56 | let action = {
57 | type: "TIEDAN"
58 | }
59 |
60 |
61 | store.dispatch(action)
62 |
63 | console.log(store.getState())
--------------------------------------------------------------------------------
/17.手写redux/index1.js:
--------------------------------------------------------------------------------
1 | //需要监听的值
2 | let data = 0;
3 | //所有依赖的对列
4 | let listenters = [];
5 | //收集依赖方法
6 | function subscribe(listener) {
7 | listenters.push(listener);
8 | }
9 | //用来重写数据的赋值操作,添加通知所有依赖的操作
10 | function changeData(val) {
11 | data = val;
12 | listenters.forEach((v, i) => {
13 | v();
14 | })
15 | }
16 | //我们主动收集一下需要通知的依赖
17 | subscribe(() => {
18 | console.log("监听到数据变化");
19 | })
20 | //改变监听的值
21 | changeData(1)
22 |
--------------------------------------------------------------------------------
/17.手写redux/index2.js:
--------------------------------------------------------------------------------
1 | function createStore() {
2 | //需要监听的值
3 | let data = 0;
4 | //所有依赖的对列
5 | let listenters = [];
6 | //收集依赖方法
7 | function subscribe(listener) {
8 | listenters.push(listener);
9 | }
10 | //用来重写数据的赋值操作,添加通知所有依赖的操作
11 | function dispatch(val) {
12 | data = val;
13 | listenters.forEach((v, i) => {
14 | v();
15 | })
16 | }
17 | function getState() {
18 | return data
19 | }
20 | return {
21 | subscribe,
22 | dispatch,
23 | getState
24 | }
25 |
26 | }
27 | let store = createStore()
28 | //我们主动收集一下需要通知的依赖
29 | store.subscribe(() => {
30 | console.log("监听到数据变化");
31 | })
32 | console.log("改变前----" + store.getState());//改变前----0
33 | store.dispatch(1);
34 | console.log("改变后----" + store.getState());//改变后----1
35 |
36 |
--------------------------------------------------------------------------------
/17.手写redux/index3.js:
--------------------------------------------------------------------------------
1 | function createStore() {
2 | //需要监听的值
3 | let data = 0;
4 | //所有依赖的对列
5 | let listenters = [];
6 | //收集依赖方法
7 | function subscribe(listener) {
8 | listenters.push(listener);
9 | }
10 | //用来重写数据的赋值操作,添加通知所有依赖的操作
11 | function dispatch(val) {
12 | data = val;
13 | listenters.forEach((v, i) => {
14 | v();
15 | })
16 | }
17 | function getState() {
18 | return data
19 | }
20 | return {
21 | subscribe,
22 | dispatch,
23 | getState
24 | }
25 |
26 | }
27 | let store = createStore()
28 | //我们主动收集一下需要通知的依赖
29 | store.subscribe(() => {
30 | console.log("监听到数据变化");
31 | })
32 | console.log("改变前----" + store.getState());//改变前----0
33 | store.dispatch(1);
34 | console.log("改变后----" + store.getState());//改变后----1
35 |
36 | //状态会自增
37 | // setInterval(() => {
38 | // let count = store.getState();
39 | // count++
40 | // console.log(store.getState());
41 | // store.dispatch(count);
42 | // }, 1000)
43 |
44 | // console.log(store.getState());//0
45 | setInterval(() => {
46 | let count = store.getState();
47 | count++
48 | console.log(store.getState());
49 | store.dispatch(count);
50 | }, 1000)
51 | store.dispatch("abc");
52 |
--------------------------------------------------------------------------------
/17.手写redux/index4.js:
--------------------------------------------------------------------------------
1 | function createStore(reducer) {
2 | let data = 0;
3 | let listenters = [];
4 |
5 | function subscribe(listener) {
6 | listenters.push(listener);
7 | }
8 | //修改 store. dispatch方法,告诉它修改 state 的时候,按照我们的计划修改
9 | function dispatch(action) {
10 | data = reducer(data, action);
11 | listenters.forEach((v, i) => {
12 | v();
13 | });
14 | }
15 | function getState(params) {
16 | return data;
17 | }
18 | return {
19 | subscribe,
20 | dispatch,
21 | getState
22 | };
23 | }
24 |
25 | function reducer(state, action) {
26 | switch (action.type) {
27 | case 'INCREMENT':
28 | return ++state
29 | default:
30 | return state;
31 | }
32 | }
33 | //告诉 store,我的修改计划是什么
34 | let store = createStore(reducer);
35 | store.subscribe(() => {
36 | // console.log("监听到数据变化");
37 | });
38 | let action = {
39 | type: "INCREMENT"
40 | }
41 | store.dispatch(action);
42 | console.log(store.getState());//1
43 |
--------------------------------------------------------------------------------
/17.手写redux/readme.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Tie-Dan/Algo/bd1d6192e3966a43c8a90bb363ce44c8ed96e728/17.手写redux/readme.md
--------------------------------------------------------------------------------
/17.手写redux/redux.js:
--------------------------------------------------------------------------------
1 | function createStore() {
2 | let state; // state记录所有状态
3 | let listeners = []; // 保存所有注册的回调
4 |
5 | function subscribe(callback) {
6 | listeners.push(callback); // subscribe就是将回调保存下来
7 | }
8 |
9 | // dispatch就是将所有的回调拿出来依次执行就行
10 | function dispatch(action) {
11 | state = reducer(state, action);
12 | for (let i = 0; i < listeners.length; i++) {
13 | const listener = listeners[i];
14 | listener();
15 | }
16 | }
17 |
18 | // getState直接返回state
19 | function getState() {
20 | return state;
21 | }
22 |
23 | // store包装一下前面的方法直接返回
24 | const store = {
25 | subscribe,
26 | dispatch,
27 | getState
28 | }
29 |
30 | return store;
31 | }
--------------------------------------------------------------------------------
/18.使用setTimeout实现setInterval/readme.md:
--------------------------------------------------------------------------------
1 | ## 18. 使用setTimeout实现setInterval
2 |
3 | 前面在JS 事件循环之宏任务和微任务中讲到过,setInterval 是一个宏任务。
4 |
5 | 用多了你就会发现它并不是准确无误,极端情况下还会出现一些令人费解的问题。
6 |
7 | ```js
8 | let startTime = new Date().getTime();
9 | let count = 0;
10 | //耗时任务
11 | setInterval(function() {
12 | let i = 0;
13 | while (i++ < 1000000000);
14 | }, 0);
15 | setInterval(function() {
16 | count++;
17 | console.log(
18 | "与原设定的间隔时差了:",
19 | new Date().getTime() - (startTime + count * 1000),
20 | "毫秒"
21 | );
22 | }, 1000);
23 | // 输出:
24 | // 与原设定的间隔时差了: 699 毫秒
25 | // 与原设定的间隔时差了: 771 毫秒
26 | // 与原设定的间隔时差了: 887 毫秒
27 | // 与原设定的间隔时差了: 981 毫秒
28 | // 与原设定的间隔时差了: 1142 毫秒
29 | // 与原设定的间隔时差了: 1822 毫秒
30 | // 与原设定的间隔时差了: 1891 毫秒
31 | // 与原设定的间隔时差了: 2001 毫秒
32 | // 与原设定的间隔时差了: 2748 毫秒
33 | // ...
34 |
35 | ```
36 |
37 | 可以看出来,相差的时间是越来越大的,越来越不准确。
38 |
39 | ## 函数操作耗时过长导致的不准确
40 |
41 | 考虑极端情况,假如定时器里面的代码需要进行大量的计算(耗费时间较长),或者是 DOM 操作。这样一来,花的时间就比较长,有可能前一次代码还没有执行完,后一次代码就被添加到队列了。也会到时定时器变得不准确,甚至出现同一时间执行两次的情况。
42 |
43 | 最常见的出现的就是,当我们需要使用 ajax 轮询服务器是否有新数据时,必定会有一些人会使用 setInterval,然而无论网络状况如何,它都会去一遍又一遍的发送请求,最后的间隔时间可能和原定的时间有很大的出入。
44 |
45 | ```js
46 | // 做一个网络轮询,每一秒查询一次数据。
47 | let startTime = new Date().getTime();
48 | let count = 0;
49 |
50 | setInterval(() => {
51 | let i = 0;
52 | while (i++ < 10000000); // 假设的网络延迟
53 | count++;
54 | console.log(
55 | "与原设定的间隔时差了:",
56 | new Date().getTime() - (startTime + count * 1000),
57 | "毫秒"
58 | );
59 | }, 1000)
60 | 输出:
61 | // 与原设定的间隔时差了: 567 毫秒
62 | // 与原设定的间隔时差了: 552 毫秒
63 | // 与原设定的间隔时差了: 563 毫秒
64 | // 与原设定的间隔时差了: 554 毫秒(2次)
65 | // 与原设定的间隔时差了: 564 毫秒
66 | // 与原设定的间隔时差了: 602 毫秒
67 | // 与原设定的间隔时差了: 573 毫秒
68 | // 与原设定的间隔时差了: 633 毫秒
69 |
70 |
71 | ```
72 |
73 | ## setInterval 缺点 与 setTimeout 的不同
74 |
75 | > 再次强调,定时器指定的时间间隔,表示的是何时将定时器的代码添加到消息队列,而不是何时执行代码。所以真正何时执行代码的时间是不能保证的,取决于何时被主线程的事件循环取到,并执行。
76 |
77 | **每个 setTimeout 产生的任务会直接 push 到任务队列中;**
78 |
79 | **而 setInterval 在每次把任务 push 到任务队列前,都要进行一下判断(看上次的任务是否仍在队列中,如果有则不添加,没有则添加)。**
80 |
81 | 在某些情况下,setInterval 缺点是很明显的,为了解决这些弊端,可以使用 settTimeout() 代替。
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/19.对象扁平化/index.js:
--------------------------------------------------------------------------------
1 | let obj = {
2 | a: 'a',
3 | b: [1, { c: true }, [3]],
4 | d: { e: undefined, f: 3 },
5 | g: null,
6 | }
7 | // {
8 | // a: "a",
9 | // b[0]: 1,
10 | // b[1].c: true,
11 | // b[2][0]: 3,
12 | // d.f: 3
13 |
14 | // }
15 |
16 | function test(obj) {
17 | const res = {}
18 | function _test(obj, prev = null) {
19 | // 数组
20 | if (Array.isArray(obj)) {
21 | for (const index in obj) {
22 | const val = obj[index]
23 | if (val instanceof Object) {
24 | _test(val, `${prev ? prev : +''}[${index}]`)
25 | } else {
26 | res[`${prev ? prev : +''}[${index}]`] = val
27 | }
28 | }
29 | return
30 | }
31 |
32 | // 对象
33 | for (const key in obj) {
34 | if (typeof obj[key] === 'object') {
35 | // null
36 | if (obj[key] !== null) {
37 | _test(obj[key], `${prev ? prev + '.' : ''}${key} `)
38 | }
39 | } else {
40 | // undefined
41 | if (obj[key] !== undefined) {
42 | res[`${prev ? prev + '.' : ''}${key} `] = obj[key]
43 | }
44 | }
45 | }
46 | }
47 | _test(obj)
48 | return res
49 | }
50 |
51 |
52 | console.log(test(obj))
--------------------------------------------------------------------------------
/2.发布订阅者和观察者/发布订阅者.js:
--------------------------------------------------------------------------------
1 | // on是订阅 emit是发布
2 | let e = {
3 | _callback: [],
4 | on(callback) {
5 | // 订阅一件事 当这件事发生的时候 触发对应的函数
6 | // 订阅 就是将函数放到数组中
7 | this._callback.push(callback);
8 | },
9 | emit(value) {
10 | this._callback.forEach(method => {
11 | method(value);
12 | });
13 | }
14 | };
15 | // 订阅
16 | e.on(function (value) {
17 | console.log(value + ":张三的订阅");
18 | });
19 | // 订阅
20 | e.on(function (value) {
21 | console.log(value + ":李四的订阅");
22 | });
23 | // 订阅
24 | e.on(function (value) {
25 | console.log(value + ":王五的订阅");
26 | });
27 | // 发布
28 | e.emit('发布')
--------------------------------------------------------------------------------
/2.发布订阅者和观察者/观察者模式.js:
--------------------------------------------------------------------------------
1 | // 被观察者 (小宝宝)
2 | class Subject {
3 | constructor(name) {
4 | this.name = name;
5 | this.state = "开心"; // 被观察者的状态
6 | this.observers = []; // 存放观察者
7 | }
8 | // 需要将观察者放到自己的身上
9 | attach(ther) {
10 | this.observers.push(ther);
11 | }
12 | // 更新被观察者的状态
13 | setState(state) {
14 | this.state = state;
15 | this.observers.forEach(ther => {
16 | ther.update(this);
17 | });
18 | }
19 | }
20 | // 观察者
21 | class Observer {
22 | constructor(name) {
23 | this.name = name;
24 | }
25 | // 等会被观察者的状态发生变化会调用这个方法
26 | update(subject) {
27 | console.log(this.name + ":" + subject.name + "当前状态是" + subject.state);
28 | }
29 | }
30 | let bady = new Subject("小宝宝");
31 | let father = new Observer("爸爸");
32 | let mother = new Observer("妈妈");
33 | bady.attach(father);
34 | bady.attach(mother);
35 | bady.setState("不开心");
36 | bady.setState("饿了");
--------------------------------------------------------------------------------
/20.反转二叉树/index.js:
--------------------------------------------------------------------------------
1 | const obj = {
2 | 'id': '4',
3 | 'left': {
4 | 'id': '2',
5 | 'left': {
6 | 'id': '1',
7 | 'left': null,
8 | 'right': null
9 | },
10 | 'right': {
11 | 'id': '3',
12 | 'left': null,
13 | 'right': null
14 | }
15 | },
16 | 'right': {
17 | 'id': '7',
18 | 'left': {
19 | 'id': '6',
20 | 'left': null,
21 | 'right': null
22 | },
23 | 'right': {
24 | 'id': '9',
25 | 'left': null,
26 | 'right': null
27 | }
28 | }
29 | }
30 |
31 |
32 | function invertTree(root) {
33 | if (root !== null) {
34 | let temp = root.left
35 | root.left = root.right
36 | root.right = temp
37 | invertTree(root.left)
38 | invertTree(root.right)
39 | }
40 | return root
41 | }
42 |
43 | console.log(invertTree(obj))
--------------------------------------------------------------------------------
/21.字符串的全排列/index.js:
--------------------------------------------------------------------------------
1 | // > 'abc'
2 |
3 | // > ['abc', 'acb', 'bac', 'bca', 'cab', 'cba']
4 | // [][abc]
5 | // [a][bc]
6 | // [ab][c]
7 | // [abc][]
8 | // [ac][b]
9 | // [acb][]
10 |
11 | function pre(s) {
12 | let res = []
13 | s = s.split('').sort((a, b) => {
14 | return a > b ? 1 : -1
15 | }).join('')
16 | const dfs = (curr, store) => {
17 | // 1. 是否满足条件添加
18 | if (!store.length) {
19 | return res.push(curr)
20 | }
21 | for (let i = 0; i < store.length; i++) {
22 | if (i > 0 && store[i] === store[i - 1]) continue
23 | // 3. 递归 dfs
24 | console.log(curr + store[i], store.slice(0, i) + store.slice(i + 1))
25 | dfs(curr + store[i], store.slice(0, i) + store.slice(i + 1))
26 | }
27 |
28 | }
29 | dfs('', s)
30 | return res
31 | }
32 |
33 | console.log(pre('abc'))
--------------------------------------------------------------------------------
/22.ES5实现B继承A/index.js:
--------------------------------------------------------------------------------
1 | // 1. 原型链继承、
2 |
3 | function Parent() {
4 | this.isShow = true
5 | this.info = {
6 | name: "tiedan",
7 | age: 18,
8 | };
9 | }
10 |
11 | Parent.prototype.getInfo = function () {
12 | console.log(this.info);
13 | console.log(this.isShow); // true
14 | }
15 |
16 | function Child() { };
17 |
18 | Child.prototype = new Parent();
19 |
20 | let Child1 = new Child();
21 | Child1.info.gender = "男";
22 | Child1.getInfo(); // {name: "tiedan", age: 18, gender: "男"}
23 |
24 | let child2 = new Child();
25 | child2.getInfo(); // {name: "tiedan", age: 18, gender: "男"}
26 |
27 | child2.isShow = false
28 |
29 | console.log(child2.isShow); // false
30 |
31 | // 2.构造函数继承、
32 | function Parent() {
33 | this.info = {
34 | name: "yhd",
35 | age: 19,
36 | }
37 | }
38 |
39 | function Child() {
40 | Parent.call(this)
41 | }
42 |
43 | let child1 = new Child();
44 | child1.info.gender = "男";
45 | console.log(child1.info); // {name: "tiedan", age: 19, gender: "男"};
46 |
47 | let child2 = new Child();
48 | console.log(child2.info); // {name: "tiedan", age: 19}
49 |
50 | // 3. 组合继承、
51 | function Parent(name) {
52 | this.name = name
53 | this.colors = ["red", "blue", "yellow"]
54 | }
55 | Parent.prototype.sayName = function () {
56 | console.log(this.name);
57 | }
58 |
59 | function Child(name, age) {
60 | // 继承父类属性
61 | Parent.call(this, name)
62 | this.age = age;
63 | }
64 | // 继承父类方法
65 | Child.prototype = new Parent();
66 |
67 | Child.prototype.sayAge = function () {
68 | console.log(this.age);
69 | }
70 |
71 | let child1 = new Child("tiedan", 19);
72 | child1.colors.push("pink");
73 | console.log(child1.colors); // ["red", "blue", "yellow", "pink"]
74 | child1.sayAge(); // 19
75 | child1.sayName(); // "tiedan"
76 |
77 | let child2 = new Child("xbl", 30);
78 | console.log(child2.colors); // ["red", "blue", "yellow"]
79 | child2.sayAge(); // 30
80 | child2.sayName(); // "xbl"
81 |
82 | // 4. 原型继承、
83 | function objectCopy(obj) {
84 | function Fun() { };
85 | Fun.prototype = obj;
86 | return new Fun()
87 | }
88 |
89 | let person = {
90 | name: "yhd",
91 | age: 18,
92 | friends: ["jack", "tom", "rose"],
93 | sayName: function () {
94 | console.log(this.name);
95 | }
96 | }
97 |
98 | let person1 = objectCopy(person);
99 | person1.name = "wxb";
100 | person1.friends.push("lily");
101 | person1.sayName(); // wxb
102 |
103 | let person2 = objectCopy(person);
104 | person2.name = "gsr";
105 | person2.friends.push("kobe");
106 | person2.sayName(); // "gsr"
107 |
108 | console.log(person.friends); // ["jack", "tom", "rose", "lily", "kobe"]
109 |
110 | // 5. 寄生式继承、
111 | function objectCopy(obj) {
112 | function Fun() { };
113 | Fun.prototype = obj;
114 | return new Fun();
115 | }
116 |
117 | function createAnother(original) {
118 | let clone = objectCopy(original);
119 | clone.getName = function () {
120 | console.log(this.name);
121 | };
122 | return clone;
123 | }
124 |
125 | let person = {
126 | name: "yhd",
127 | friends: ["rose", "tom", "jack"]
128 | }
129 |
130 | let person1 = createAnother(person);
131 | person1.friends.push("lily");
132 | console.log(person1.friends);
133 | person1.getName(); // yhd
134 |
135 | let person2 = createAnother(person);
136 | console.log(person2.friends); // ["rose", "tom", "jack", "lily"]
137 |
138 | // 6. 寄生组合继承
139 | function objectCopy(obj) {
140 | function Fun() { };
141 | Fun.prototype = obj;
142 | return new Fun();
143 | }
144 |
145 | function inheritPrototype(child, parent) {
146 | let prototype = objectCopy(parent.prototype); // 创建对象
147 | prototype.constructor = child; // 增强对象
148 | Child.prototype = prototype; // 赋值对象
149 | }
150 |
151 | function Parent(name) {
152 | this.name = name;
153 | this.friends = ["rose", "lily", "tom"]
154 | }
155 |
156 | Parent.prototype.sayName = function () {
157 | console.log(this.name);
158 | }
159 |
160 | function Child(name, age) {
161 | Parent.call(this, name);
162 | this.age = age;
163 | }
164 |
165 | inheritPrototype(Child, Parent);
166 | Child.prototype.sayAge = function () {
167 | console.log(this.age);
168 | }
169 |
170 | let child1 = new Child("yhd", 23);
171 | child1.sayAge(); // 23
172 | child1.sayName(); // yhd
173 | child1.friends.push("jack");
174 | console.log(child1.friends); // ["rose", "lily", "tom", "jack"]
175 |
176 | let child2 = new Child("yl", 22)
177 | child2.sayAge(); // 22
178 | child2.sayName(); // yl
179 | console.log(child2.friends); // ["rose", "lily", "tom"]
180 |
--------------------------------------------------------------------------------
/23.虚拟DOM转为真实DOM/1.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
10 |
11 |
12 |
13 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/25.合并数组/1.index.js:
--------------------------------------------------------------------------------
1 | let ad = [1, 3, 5, 6, 7, 13, 14, 15, 16, 20, 33]
2 | let ap = [3, 4, 6, 8, 9, 13, 17, 18, 19]
3 |
4 | // 合并后的数组
5 |
6 | let between = []
7 |
8 | function transforNew() {
9 | // 直接循环次数为两个数组的总长度
10 | while (ad.length + ap.length) {
11 | // 如果有一个数组添加完 就break
12 | if (ad.length < 1 || ap.length < 1) {
13 | // 则剩余的数组合并到between
14 | between = between.concat(ad.length < 1 ? ap : ad)
15 | break
16 | }
17 | between.push(ad[0] >= ap[0] ? ap.shift() : ad.shift())
18 | }
19 | return between
20 | }
21 |
22 | console.log(transforNew(ad, ap))
--------------------------------------------------------------------------------
/26.控制最大并发数/1.js:
--------------------------------------------------------------------------------
1 | const URLs = [
2 | 'bytedance.com',
3 | 'tencent.com',
4 | 'alibaba.com',
5 | 'microsoft.com',
6 | 'apple.com',
7 | 'hulu.com',
8 | 'amazon.com'
9 | ];
10 |
11 |
12 | class PromisePool {
13 | constructor(max, fn) {
14 | this.max = max // 最大并发数
15 | this.fn = fn // 自定义的请求函数
16 | this.pool = [] // 并发池
17 | this.urls = [] // 剩余的请求地址
18 | }
19 | start(urls) {
20 | this.urls = urls
21 | // 循环把并发池塞满
22 | while (this.pool.length < this.max) {
23 | let url = this.urls.shift()
24 | this.setTask(url)
25 | }
26 | // 利用Promise.race 方法来获得并发池中某个任务完成的信号
27 | let race = Promise.race(this.pool)
28 | this.run(race)
29 | }
30 | setTask(url) {
31 | if (!url) return
32 | let task = this.fn(url)
33 | this.pool.push(task) // 将任务推入pool并发池中
34 | console.log(`${url}开始,当前的并发数:${this.pool.length}`)
35 | task.then(res => {
36 | // 请求结束将该promise任务从并发池中移除
37 | this.pool.splice(this.pool.indexOf(task), 1)
38 | console.log(`${url}结束,当前的并发数:${this.pool.length}`)
39 | })
40 | }
41 |
42 | run(race) {
43 | race.then(res => {
44 | // 每当并发池中完成一个任务,就在塞入一个任务
45 | let url = this.urls.shift()
46 | this.setTask(url)
47 | this.run(Promise.race(this.pool))
48 | })
49 | }
50 |
51 | }
52 | // 模拟异步请求函数
53 | let n = 0
54 | let requestFn = (url) => {
55 | return new Promise(resolve => {
56 | setTimeout(() => {
57 | resolve(`任务${url}完成`)
58 | }, 1000 * n++)
59 | }).then(res => {
60 | console.log('外部逻辑', res)
61 | })
62 | }
63 |
64 |
65 |
66 | // 并发数为3
67 | const pool = new PromisePool(3, requestFn)
68 |
69 | pool.start(URLs)
70 |
71 |
72 |
--------------------------------------------------------------------------------
/27.数组转为树/1.js:
--------------------------------------------------------------------------------
1 | const data = [
2 | { id: 1, parentId: null },
3 | { id: 2, parentId: 1 },
4 | { id: 3, parentId: 1 },
5 | { id: 4, parentId: 2 },
6 | { id: 5, parentId: 2 },
7 | { id: 6, parentId: 3 }
8 | ]
9 |
10 | function createNode(id, data) {
11 | // data中去查找根节点下有哪些子节点
12 | const childData = data.filter(({ parentId }) => parentId === id)
13 |
14 | // 重写节点
15 |
16 | const node = {
17 | id,
18 | children: childData.reduce((acc, cur) => {
19 | acc.push(createNode(cur.id, data))
20 | return acc;
21 | }, [])
22 | }
23 | return node
24 | }
25 |
26 | function getTree(data) {
27 | // 获取到哪个是根节点
28 | const rootNodeData = data.find(({ parentId }) => parentId === null)
29 |
30 | if (!rootNodeData) {
31 | throw new Error('数据中找不到根的节点')
32 | }
33 |
34 | // 从根节点开始创建,构建树形结构
35 | return createNode(rootNodeData.id, data)
36 | }
37 |
38 | console.log(JSON.stringify(getTree(data)))
--------------------------------------------------------------------------------
/28.手写call函数/1.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
10 |
11 |
12 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/28.手写call函数/1.js:
--------------------------------------------------------------------------------
1 | let arr = []
2 | arr.length = 0
3 |
4 | console.log(arr[0])
--------------------------------------------------------------------------------
/28.手写call函数/reade.md:
--------------------------------------------------------------------------------
1 | ## 28. 手写call函数(百度)
2 |
3 | ### 1. call方法简介
4 |
5 | - call() 方法使用一个指定的this值和单独给出的一个或多个参数来调用一个函数。
6 | - call() 允许为不同的对象分配和调用属于一个对象的函数/方法。
7 | - call() 提供新的this值给当前调用的函数/方法。
8 |
9 | - 可以使用call来实现继承,让另外一个新的对象来继承它(而不是在新对象中再写一次这个方法)。
10 |
11 | **语法:**
12 |
13 | > function.call(thisArg, arg1, arg2, ...)
14 |
15 | **参数:**
16 |
17 | - thisArg
18 | 可选的。在function函数运行时使用的this值。请注意,this可能不是该方法看到的实际值:如果这个函数处于==非严格模式==下,则指定为null或undefined时会自动替换为指向全局对象,原始值会被包装。
19 |
20 | - arg1, arg2, ...
21 | 指定的参数列表。
22 |
23 | - 返回值
24 | 使用调用者提供的this值和参数调用该函数的返回值。若该方法没有返回值,则返回undefined。
25 |
26 | ### 2. 实现call方法
27 |
28 | **思路如下:**
29 |
30 | 所有函数都可以调用,所以应该将该方法添加到Function的原型对象上,该方法传入两个参数:
31 |
32 | ```js
33 | Function.prototype.mycall = function (thisArg, ...args) {
34 | // ...
35 | }
36 | ```
37 |
38 | 第一个参数thisArg是在function函数运行时使用的this值,如果thisArg为null或undefined时,则thisArg指向window:
39 |
40 | ```js
41 | Function.prototype.mycall = function (thisArg, ...args) {
42 | // thisArg为null或undefined时默认指向window
43 | thisArg = thisArg || window;
44 | }
45 | ```
46 |
47 | 当然你也可以这样写:
48 |
49 | ```js
50 | // thisArg为null或undefined时默认指向window
51 | Function.prototype.mycall = function (thisArg = window, ...args) {
52 | // ...
53 | }
54 | ```
55 | 如果想把函数中的this变成我们指定的thisArg,实现方法是将该函数作为属性添加到thisArg上,然后调用它。
56 |
57 | ```js
58 | // thisArg为null或undefined时默认指向window
59 | Function.prototype.mycall = function (thisArg = window, ...args) {
60 | // 将函数作为属性添加到thisArg上
61 | thisArg.fn = this;
62 | }
63 | ```
64 |
65 | call的返回值是使用调用者提供的this值和参数调用该函数的返回值,因此我们需要返回该值:
66 |
67 | ```js
68 | // thisArg为null或undefined时默认指向window
69 | Function.prototype.mycall = function (thisArg = window, ...args) {
70 | // 将函数作为属性添加到thisArg上
71 | thisArg.fn = this;
72 | // 执行thisArg.fn, 并返回返回值
73 | return thisArg.fn(...args);
74 | }
75 | ```
76 |
77 | 最后,我们需要清除thisArg上的该属性以避免污染thisArg。需要注意的是,return必须在delete之后(否则不会执行delete),这样一来,我们需要先将返回值存储起来,然后再在delete执行完后将该值返回出去:
78 |
79 | ```js
80 | // thisArg为null或undefined时默认指向window
81 | Function.prototype.mycall = function (thisArg = window, ...args) {
82 | // 将函数作为属性添加到thisArg上
83 | thisArg.fn = this;
84 | // 执行thisArg.fn, 并储存返回值
85 | let res = thisArg.fn(...args);
86 | // 删除该方法以避免对传入对象造成污染
87 | delete thisArg.fn;
88 | // 返回函数执行的返回值
89 | return res;
90 | }
91 | ```
92 |
93 | ### 3.使用mycall
94 |
95 | **情景1:**普通函数
96 |
97 | ```js
98 | let tiedan = {
99 | name: 'tiedan'
100 | }
101 |
102 | let xiaobailong = {
103 | name: 'xiaobailong',
104 | intr(...args) {
105 | console.log('hello, myname is ' + this.name);
106 | return Array.from(args).reduce((total, item) => total + item, 0);
107 | }
108 | }
109 |
110 | let res = xiaobailong.intr.mycall(tiedan, 1, 2, 3, 4, 5); // 'hello, myname is tiedan'
111 | console.log(res); // 15
112 | ```
113 | **情景2:** 构造函数
114 |
115 |
116 | ```js
117 | let Animal = function(name) {
118 | this.name = name;
119 | }
120 |
121 | let Cat = function(name, color) {
122 | Animal.mycall(this, name);
123 | this.color = color;
124 | }
125 |
126 | let cat = new Cat('tom', 'red');
127 |
128 | console.log(cat); // Cat {name: "tom", color: "red"}
129 | ```
130 | 这里实现了自己的mycall方法,它接受和call相同的参数,具备改变普通函数和构造函数那中this指向的功能。
131 |
132 | 但这其中有个小问题,如果thisArg本身就有个fn方法,那就可能造成**覆盖**了。那该如何来解决这个问题呢?
133 |
134 | 换言之,有没有某种操作,具有独一无二的特性,可以避免和任何属性重名?
135 |
136 | 有,那就是ES6中新添加的基础数据类型:**Symbol**。
137 |
138 | **使用Symbol解决冲突:**
139 |
140 | *ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值,最大的用法是用来定义对象的唯一属性名。*
141 |
142 | 在上面的基础上,我们执行如下代码,发现程序报错了:
143 |
144 | ```js
145 | let tiedan = {
146 | name: 'tiedan',
147 | // 声明了一个_fn方法
148 | _fn() {
149 | console.log('tiedan._fn', 1);
150 | }
151 | }
152 |
153 | tiedan._fn(); // tiedan._fn, 1
154 | xiaobailong.intr.mycall(tiedan, 1, 2, 3, 4, 5); // 'hello, myname is tiedan'
155 | // 发生了属性覆盖,污染了源对象。
156 | tiedan._fn(); // TypeError: xiaohua._fn is not a function
157 | ```
158 | 这是因为我们实现的mycall是使用_fn字面量来命名的,所以在对于原来就有_fn属性的
159 | 方法来说,会修改和删除这个属性,从而导致报错。我们也不能强迫开发者不使用_fn来作为属性名,因此,可以考虑采用symbol类型来创造一个唯一的属性值。
160 |
161 | ```js
162 | Function.prototype.mycall = function (thisArg = window, ...args) {
163 | // 创建一个独一无二的symbol:fn
164 | let fn = Symbol('thisFn');
165 | // 将fn作为属性添加到thisArg上
166 | // 请注意,只能使用[]来添加和读取变量属性
167 | thisArg[fn] = this;
168 | // 执行thisArg[fn], 并储存返回值
169 | let res = thisArg[fn](...args);
170 | // 删除该方法以避免对传入对象造成污染
171 | delete thisArg[fn];
172 | // 返回函数执行的返回值
173 | return res;
174 | }
175 | ```
176 | 现在,我们创建了一个不会和任何属性重名的属性,我们来试试效果:
177 |
178 | ```js
179 | let tiedan = {
180 | name: 'tiedan',
181 | fn() {
182 | console.log('tiedan.fn', 1);
183 | }
184 | }
185 |
186 | tiedan.fn(); // xiaohua.fn, 1
187 | let res = xiaobailong.intr.mycall(tiedan, 1, 2, 3, 4, 5); // 'hello, myname is tiedan'
188 | console.log(res); // 15
189 | // symbol不和任何属性重名,不会污染源对象
190 | tiedan.fn(); // tiedan.fn, 1
191 | ```
192 | ### 4.总结
193 |
194 | 1. 获取所有参数,使用展开语法(ES6),当然还有一种实现思路——arguments 对象;
195 |
196 | 2. 在thisArg为null或undefined时将其指向window,这里可以使用逻辑或,也可以使用默认参数(ES6);
197 |
198 | 3. 使函数中的this指向thisArg,实现思路是将其作为属性添加到thisArg上,为了保证不污染thisArg,在调用完后需要将其删除——使用delete操作符;
199 |
200 | 4. 返回函数执行后的返回值,但返回只能后于delete执行,因此,需要先将返回值存储起来;
201 |
202 | 5. 使用symbol变量以避免变量重名,这里要用到计算属性名;
203 |
204 | 6. 当函数为构造函数时,手动实现的call方法不会出现问题,因此不需要考虑这个因素。但bind时就需要考虑这种特殊情况了。
205 |
206 | **全部代码:**
207 |
208 | ```html
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 | 手写call
217 |
218 |
219 |
269 |
270 |
271 |
272 |
273 | ```
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
--------------------------------------------------------------------------------
/29.字符串相加/1.js:
--------------------------------------------------------------------------------
1 | let addStrings = function (num1, num2) {
2 | let res = ''
3 | let carry = 0
4 | let i1 = num1.length - 1
5 | let i2 = num2.length - 1
6 | while (i1 >= 0 || i1 >= 0) {
7 | const x = i1 >= 0 ? num1[i1] - '0' : 0
8 | const y = i2 >= 0 ? num2[i2] - '0' : 0
9 | const sum = x + y + carry
10 | res += (sum % 10)
11 | carry = Math.floor(sum / 10)
12 | i1--
13 | i2--
14 | }
15 | if (carry) res += carry
16 | return res.split("").reverse().join("")
17 |
18 | }
19 |
20 | console.log(addStrings('199', '188'))
--------------------------------------------------------------------------------
/3.防抖和节流/防抖和截流.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
42 |
43 |
44 |
45 |
46 |