├── .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 |
14 |
15 | a 16 |
17 |
18 |
19 |
20 | b 21 |
22 |
23 |
24 | c 25 |
26 |
27 |
28 |
29 |
30 | d 31 |
32 |
33 |
34 |
35 | e 36 |
37 |
38 |
39 | f 40 |
41 |
42 |
43 |
44 |
45 | g 46 |
47 |
48 |
49 |
50 | h 51 |
52 |
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 | 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 | 47 | 48 | -------------------------------------------------------------------------------- /30.最长连续递增子序列/1.js: -------------------------------------------------------------------------------- 1 | let findLength = function (nums) { 2 | let res = 0 // 最大子序列的长度 3 | let len = nums.length 4 | let count = 1 5 | if (len === 1) return 1// 只有一个返回1 6 | for (let i = 1; i < len; i++) { 7 | // 如果当前数小于等于它前面的数,说明不是连续递增序列,重新计算 8 | if (nums[i] <= nums[i - 1]) { 9 | count = 1 10 | } else { 11 | count++ // 如果满足递增 那就继续增加 12 | } 13 | res = Math.max(count, res) // 每次重新赋值 选最大那个 14 | } 15 | return res 16 | } 17 | 18 | console.log(findLength([1, 3, 3, 5, 6, 4, 7])) -------------------------------------------------------------------------------- /4.深拷贝和浅拷贝/1.js: -------------------------------------------------------------------------------- 1 | // 循环递归法1 2 | function isObject(obj) { 3 | return (typeof obj === 'object' || typeof obj === 'function') && obj !== null 4 | } 5 | // 迭代递归法:深拷贝对象与数组 6 | function deepClone(obj) { 7 | if (!isObject(obj)) { 8 | throw new Error('obj 不是一个对象!') 9 | } 10 | 11 | let isArray = Array.isArray(obj) 12 | 13 | let cloneObj = isArray ? [] : {} 14 | 15 | for (let key in obj) { 16 | cloneObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key] 17 | } 18 | 19 | return cloneObj 20 | } 21 | let sym = Symbol('我是一个Symbol') 22 | let obj1 = { 23 | a: { 24 | b: 1, 25 | 26 | }, 27 | } 28 | obj1[sym] = 111 29 | let obj2 = deepClone(obj1) 30 | console.log(obj1) 31 | console.log(obj2) 32 | /* 33 | 深拷贝是针对引用类型的, 在进行深拷贝之前, 我们应该先知道js中有哪些引用类型, 34 | js中引用类型目前有六种: object, array, date, regexp, 35 | function, err。 下面的两种方法只能实现object, array的深拷贝。 36 | 循环递归法 function、Date、RegExp 和Error无法复制 因为它们有特殊的构造函数。 37 | */ -------------------------------------------------------------------------------- /4.深拷贝和浅拷贝/10.js: -------------------------------------------------------------------------------- 1 | /* 2 | 1. 日常深拷贝,建议序列化反序列化方法。 3 | 2. 面试时遇见面试官搞事情,写一个能拷贝自身可枚举、自身不可枚举、自身 Symbol 类型键、原型上可枚举、原型上不可枚举、原型上的 Symol 类型键,循环引用也可以拷的深拷贝函数: 4 | 3. 有特殊需求的深拷贝,建议使用 lodash 的 copyDeep 或 copyDeepWith 方法。 5 | */ 6 | // 将之前写的 deepClone 函数封装一下 7 | function cloneDeep(obj) { 8 | let family = {} 9 | let parent = Object.getPrototypeOf(obj) 10 | 11 | while (parent != null) { 12 | family = completeAssign(deepClone(family), parent) 13 | parent = Object.getPrototypeOf(parent) 14 | } 15 | 16 | // 下面这个函数会拷贝所有自有属性的属性描述符,来自于 MDN 17 | // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/assign 18 | function completeAssign(target, ...sources) { 19 | sources.forEach(source => { 20 | let descriptors = Object.keys(source).reduce((descriptors, key) => { 21 | descriptors[key] = Object.getOwnPropertyDescriptor(source, key) 22 | return descriptors 23 | }, {}) 24 | 25 | // Object.assign 默认也会拷贝可枚举的Symbols 26 | Object.getOwnPropertySymbols(source).forEach(sym => { 27 | let descriptor = Object.getOwnPropertyDescriptor(source, sym) 28 | if (descriptor.enumerable) { 29 | descriptors[sym] = descriptor 30 | } 31 | }) 32 | Object.defineProperties(target, descriptors) 33 | }) 34 | return target 35 | } 36 | 37 | return completeAssign(deepClone(obj), family) 38 | } -------------------------------------------------------------------------------- /4.深拷贝和浅拷贝/2.js: -------------------------------------------------------------------------------- 1 | // 循环递归法2 2 | function isObject(o) { 3 | return (typeof o === 'object' || typeof o === 'function') && o !== null 4 | } 5 | 6 | function deepClone(obj) { 7 | if (!isObject(obj)) { 8 | throw new Error('obj 不是一个对象!') 9 | } 10 | 11 | let isArray = Array.isArray(obj) 12 | let cloneObj = isArray ? [...obj] : { 13 | ...obj 14 | } 15 | // Reflect.ownKeys 返回对象的所有属性 16 | Reflect.ownKeys(cloneObj).forEach(key => { 17 | cloneObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key] 18 | }) 19 | 20 | return cloneObj 21 | } 22 | 23 | let sym = Symbol('我是一个Symbol') 24 | let obj1 = { 25 | a: { 26 | b: 1, 27 | 28 | }, 29 | } 30 | obj1[sym] = 111 31 | let obj2 = deepClone(obj1) 32 | console.log(obj1) 33 | console.log(obj2) -------------------------------------------------------------------------------- /4.深拷贝和浅拷贝/3.js: -------------------------------------------------------------------------------- 1 | // 序列化反序列化法 使用JSON对象的parse和stringify方法来实现深拷贝 2 | function deepClone(obj) { 3 | let _obj = JSON.stringify(obj), 4 | objClone = JSON.parse(_obj); 5 | return objClone 6 | } 7 | let obj1 = { 8 | a: { 9 | b: 1 10 | } 11 | }; 12 | Object.defineProperty(obj1, 'innumerable', { 13 | value: '不可枚举属性', 14 | enumerable: false 15 | }); 16 | let obj2 = deepClone(obj1) 17 | console.log(obj1) 18 | console.log(obj2) 19 | // 它也只能深拷贝对象和数组,对于其他种类的对象,会失真。 20 | // 这种方法比较适合平常开发中使用,因为通常不需要考虑对象和数组之外的类型。 21 | 22 | // 拷贝的对象的值中如果有函数,undefined,symbol则经过JSON.stringify()序列化后的JSON字符串中这个键值对会消失 23 | // 无法拷贝不可枚举的属性, 无法拷贝对象的原型链 24 | // 拷贝Date引用类型会变成字符串 25 | // 拷贝RegExp引用类型会变成空对象 26 | // 对象中含有NaN、 Infinity和 - Infinity, 则序列化的结果会变成null 27 | // 无法拷贝对象的循环应用(即obj[key] = obj) -------------------------------------------------------------------------------- /4.深拷贝和浅拷贝/4.js: -------------------------------------------------------------------------------- 1 | // 3. lodash中深拷贝的实现 2 | // Lodash是一个轻量级的JavaScript工具函数库, 它方便了日常开发中对数据的操作, 提高了开发效率。 3 | // 著名的 lodash 中的 cloneDeep 方法同样是使用 Reflect 法 实现的, 4 | // 只不过它支持的对象种类更多, 具体的实现过程读者可以参考 lodash 的 baseClone 方法。 5 | // lodash可以完成array、 object、 date、 regexp的深拷贝, 但 6 | 7 | // function 和 error 仍然不可拷贝 8 | 9 | // https://github.com/lodash/lodash/blob/master/.internal/baseClone.js 10 | // ​ 日常开发中,通常会对数据,特别是数组和对象进行各种读写等操作: 11 | // 比如去重,拷贝,合并,过滤,求交集,求和等等。根据平时开发中对数据的操作, 12 | const _ = require('lodash') 13 | const obj = { 14 | a: 1 15 | } 16 | obj.loopObj = obj 17 | const obj1 = _.cloneDeep(obj) 18 | console.log(obj1 === obj) -------------------------------------------------------------------------------- /4.深拷贝和浅拷贝/5.js: -------------------------------------------------------------------------------- 1 | /* 2 | 对象成环怎么办? 3 | 我们给 test 加一个 loopObj 键,值指向自身: 4 | test.loopObj = test 5 | 这时我们使用第一种方法中的 for..in 实现和 Reflect 实现都会栈溢出: 6 | 7 | 环对象深拷贝报错 8 | 而使用第二种方法也会报错: 9 | 10 | 11 | 但 lodash 却可以得到正确结果: 12 | 13 | 14 | 因为 lodash 使用的是栈把对象存储起来了,如果有环对象,就会从栈里检测到, 15 | 从而直接返回结果,悬崖勒马。这种算法思想来源于 HTML5 规范定义的结构化克隆算法, 16 | 它同时也解释了为什么 lodash 不对 Error 和 Function 类型进行拷贝。 17 | 18 | 19 | 当然,设置一个哈希表存储已拷贝过的对象同样可以达到同样的目的: 20 | */ 21 | 22 | 23 | function deepClone(obj, hash = new WeakMap()) { 24 | if (!isObject(obj)) { 25 | return obj 26 | } 27 | // 查表 28 | if (hash.has(obj)) return hash.get(obj) 29 | 30 | let isArray = Array.isArray(obj) 31 | let cloneObj = isArray ? [] : {} 32 | // 哈希表设值 33 | hash.set(obj, cloneObj) 34 | 35 | let result = Object.keys(obj).map(key => { 36 | return { 37 | [key]: deepClone(obj[key], hash) 38 | } 39 | }) 40 | return Object.assign(cloneObj, ...result) 41 | } 42 | 43 | //这里我们使用 WeakMap 作为哈希表,因为它的键是弱引用的,而我们这个场景里键恰好是对象,需要弱引用。 -------------------------------------------------------------------------------- /4.深拷贝和浅拷贝/6.js: -------------------------------------------------------------------------------- 1 | // 键值不是字符串而是 Symbol 2 | 3 | var test = {} 4 | let sym = Symbol('我是一个Symbol') 5 | test[sym] = 'symbol' 6 | 7 | let result = deepClone(test) 8 | console.log(result) 9 | console.log(result[sym] === test[sym]) 10 | // 拷贝失败了, 为什么? 11 | 12 | // 因为 Symbol 是一种特殊的数据类型, 它最大的特点便是独一无二, 所以它的深拷贝就是浅拷贝 -------------------------------------------------------------------------------- /4.深拷贝和浅拷贝/7.js: -------------------------------------------------------------------------------- 1 | /* 2 | 但如果这时我们使用 Reflect 实现的版本: 3 | 成功了,因为 for...in 无法获得 Symbol 类型的键,而 Reflect 是可以获取的。 4 | 5 | 当然,我们改造一下 for...in 实现也可以: 6 | 7 | */ 8 | function deepClone(obj) { 9 | if (!isObject(obj)) { 10 | throw new Error('obj 不是一个对象!') 11 | } 12 | 13 | let isArray = Array.isArray(obj) 14 | let cloneObj = isArray ? [] : {} 15 | let symKeys = Object.getOwnPropertySymbols(obj) 16 | // console.log(symKey) 17 | if (symKeys.length > 0) { 18 | symKeys.forEach(symKey => { 19 | cloneObj[symKey] = isObject(obj[symKey]) ? deepClone(obj[symKey]) : obj[symKey] 20 | }) 21 | } 22 | for (let key in obj) { 23 | cloneObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key] 24 | } 25 | 26 | return cloneObj 27 | } -------------------------------------------------------------------------------- /4.深拷贝和浅拷贝/8.js: -------------------------------------------------------------------------------- 1 | // for...in 会追踪原型链上的属性, 2 | // 而其它三种方法(Object.keys、Reflect.ownKeys 和 JSON 方法)都不会追踪原型链上的属性: -------------------------------------------------------------------------------- /4.深拷贝和浅拷贝/9.js: -------------------------------------------------------------------------------- 1 | // 需要拷贝不可枚举的属性 2 | // 第四种情况, 就是我们需要拷贝类似属性描述符, setters 以及 getters 这样不可枚举的属性, 一般来说, 这就需要一个额外的不可枚举的属性集合来存储它们。 类似在第二种情况使用 3 | // for... in 拷贝 Symbol 类型键时: 4 | // 我们给 test 变量里的 obj 和 arr 属性定义一下属性描述符: 5 | Object.defineProperties(test, { 6 | 'obj': { 7 | writable: false, 8 | enumerable: false, 9 | configurable: false 10 | }, 11 | 'arr': { 12 | get() { 13 | console.log('调用了get') 14 | return [1, 2, 3] 15 | }, 16 | set(val) { 17 | console.log('调用了set') 18 | } 19 | } 20 | }) 21 | // 然后实现我们的拷贝不可枚举属性的版本: 22 | function deepClone(obj, hash = new WeakMap()) { 23 | if (!isObject(obj)) { 24 | return obj 25 | } 26 | // 查表,防止循环拷贝 27 | if (hash.has(obj)) return hash.get(obj) 28 | 29 | let isArray = Array.isArray(obj) 30 | // 初始化拷贝对象 31 | let cloneObj = isArray ? [] : {} 32 | // 哈希表设值 33 | hash.set(obj, cloneObj) 34 | // 获取源对象所有属性描述符 35 | let allDesc = Object.getOwnPropertyDescriptors(obj) 36 | // 获取源对象所有的 Symbol 类型键 37 | let symKeys = Object.getOwnPropertySymbols(obj) 38 | // 拷贝 Symbol 类型键对应的属性 39 | if (symKeys.length > 0) { 40 | symKeys.forEach(symKey => { 41 | cloneObj[symKey] = isObject(obj[symKey]) ? deepClone(obj[symKey], hash) : obj[symKey] 42 | }) 43 | } 44 | 45 | // 拷贝不可枚举属性,因为 allDesc 的 value 是浅拷贝,所以要放在前面 46 | cloneObj = Object.create( 47 | Object.getPrototypeOf(cloneObj), 48 | allDesc 49 | ) 50 | // 拷贝可枚举属性(包括原型链上的) 51 | for (let key in obj) { 52 | cloneObj[key] = isObject(obj[key]) ? deepClone(obj[key], hash) : obj[key]; 53 | } 54 | 55 | return cloneObj 56 | } -------------------------------------------------------------------------------- /4.深拷贝和浅拷贝/浅拷贝/1.js: -------------------------------------------------------------------------------- 1 | let target = {} 2 | let obj = { 3 | a: { 4 | b: 3 5 | } 6 | }; 7 | Object.assign(target, obj); 8 | console.log(target); //{a:1} 9 | 10 | obj.a.b = 1; 11 | 12 | console.log(obj); //{a:2} 13 | console.log(target); //{a:1} -------------------------------------------------------------------------------- /4.深拷贝和浅拷贝/浅拷贝/2.js: -------------------------------------------------------------------------------- 1 | let obj1 = { 2 | a: { 3 | b: 1 4 | }, 5 | sym: Symbol(1) 6 | }; 7 | Object.defineProperty(obj1, 'innumerable', { 8 | value: '不可枚举属性', 9 | enumerable: false 10 | }); 11 | let obj2 = {}; 12 | 13 | Object.assign(obj2, obj1) 14 | 15 | obj1.a.b = 2; 16 | console.log('obj1', obj1); 17 | console.log('obj2', obj2); -------------------------------------------------------------------------------- /4.深拷贝和浅拷贝/浅拷贝/3.js: -------------------------------------------------------------------------------- 1 | let obj = { 2 | a: 1, 3 | b: { 4 | c: 1 5 | } 6 | } 7 | let obj2 = { 8 | ...obj 9 | }; 10 | obj.a = 2; 11 | console.log(obj); //{a:2,b:{c:1}} 12 | console.log(obj2); //{a:1,b:{c:1}} 13 | 14 | obj.b.c = 2; 15 | console.log(obj); //{a:2,b:{c:2}} 16 | console.log(obj2); //{a:1,b:{c:2}} -------------------------------------------------------------------------------- /6.Promise、Async/age.txt: -------------------------------------------------------------------------------- 1 | 18 -------------------------------------------------------------------------------- /6.Promise、Async/name.txt: -------------------------------------------------------------------------------- 1 | 铁蛋儿 -------------------------------------------------------------------------------- /6.Promise、Async/promise._all.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const util = require('util') 3 | let readFile = util.promisify(fs.readFile) 4 | 5 | let isPromise = (x) => { 6 | if ((typeof x === 'object' && x != null) || typeof x === 'function') { 7 | if (typeof x.then === 'function') { 8 | 9 | return true 10 | } 11 | } 12 | return false; 13 | } 14 | 15 | Promise.all = (promises) => { 16 | console.log('all') 17 | return new Promise((resolve, reject) => { 18 | let arr = [] 19 | let idx = 0 20 | let promisesData = (value, index) => { 21 | arr[index] = value 22 | if (++idx === promises.length) { 23 | resolve(arr) 24 | } 25 | } 26 | for (let i = 0; i < promises.length; i++) { 27 | let x = promises[i] 28 | if (isPromise(x)) { 29 | x.then(y => { 30 | promisesData(y, i) 31 | }, reject) 32 | } else { 33 | promisesData(x, i) 34 | } 35 | } 36 | }) 37 | } 38 | 39 | Promise.all([1, readFile('./name.txt', 'utf-8'), readFile('./age.txt', 'utf-8'), 3]) 40 | .then(data => { 41 | console.log(data) 42 | }) -------------------------------------------------------------------------------- /6.Promise、Async/test.js: -------------------------------------------------------------------------------- 1 | new Promise(function (resolve, reject) { 2 | setTimeout(() => { 3 | throw new Error("Whoops!"); 4 | }, 1000); 5 | }).catch(alert); 6 | // 正如本章所讲, 函数代码周围有个“ 隐式的 7 | // try..catch”。 所以, 所有同步错误都会得到处理。 8 | 9 | // 但是这里的错误并不是在 executor 运行时生成的, 而是在稍后生成的。 因此, promise 无法处理它。 -------------------------------------------------------------------------------- /7.Vue自定义事件原理/1.自定义事件的基本用法.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 |
12 | 13 |
14 | 21 | 27 | 28 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /7.Vue自定义事件原理/2.事件总线.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 |
12 | 13 | 14 |
15 | 21 | 27 | 28 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /7.Vue自定义事件原理/3.原理分析.js: -------------------------------------------------------------------------------- 1 | // 事件中心 2 | vm._events = { 3 | a: [fn], 4 | b: [fn, fn] 5 | } 6 | Vue.prototype.$on = function (event, fn) { 7 | var vm = this; 8 | if (Array.isArray(event)) { 9 | for (var i = 0, l = event.length; i < l; i++) { 10 | vm.$on(event[i], fn); 11 | } 12 | } else { 13 | (vm._events[event] || (vm._events[event] = [])).push(fn); 14 | } 15 | return vm 16 | }; 17 | 18 | Vue.prototype.$emit = function (event) { 19 | var vm = this; 20 | var cbs = vm._events[event]; // 根据事件名找对应数组也是回调集合 21 | if (cbs) { 22 | var args = toArray(arguments, 1); 23 | for (var i = 0, l = cbs.length; i < l; i++) { 24 | cbs[i].apply(vm, args) 25 | } 26 | } 27 | return vm 28 | }; 29 | // 监听事件 30 | vm.$on(['a', 'b'], function (data) { 31 | // data是传过来的数据 32 | }) 33 | vm.$on('b', function (data) { 34 | // data是传过来的数据 35 | }) 36 | // 触发事件 37 | vm.$emit('b', this.data) -------------------------------------------------------------------------------- /8.React和Vue中key的问题/1.性能对比.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tie-Dan/Algo/bd1d6192e3966a43c8a90bb363ce44c8ed96e728/8.React和Vue中key的问题/1.性能对比.html -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 01. 深度优先遍历(DFS) 和 广度优先遍历(BFS) 2 | 3 | > 面试题: 4 | > 5 | > ​ 给定一个二叉树,使用 DFS 和 BFS 遍历返回所有节点。 6 | 7 | #### 深度优先遍历(DFS) 8 | 9 | ![DFS](https://tva1.sinaimg.cn/large/e6c9d24ely1h0we46up81j20dm085jrs.jpg) 10 | 11 | - DFS 的思想是从上至下,对每一个分支一直往下一层遍历直到这个分支结束,然后返回上一层,对上一层的右子树这个分支继续深搜,直到一整棵树完全遍历,因此符合栈**后进先出**的特点 12 | 13 | - 深度优先遍历常用的数据结构是**栈** 14 | 15 | - DFS 特性 : 不全部保留结点,占用空间少;有回溯操作(即有入栈、出栈操作),运行速度慢。 16 | 17 | ```js 18 | // 广度优先遍历 队列 19 | let bfs = (node) => { 20 | let stack = [] // 队列 21 | let nodes = [] // 返回的节点 22 | if (node) { 23 | stack.push(node) 24 | while (stack.length) { 25 | let item = stack.shift() // 从前面去 26 | nodes.push(item) 27 | let children = item.children 28 | for (let i = 0; i < children.length; i++) { 29 | stack.push(children[i]) 30 | } 31 | } 32 | } 33 | return nodes 34 | } 35 | console.log(bfs(node_prent)) 36 | ``` 37 | 38 | 39 | 40 | #### 广度优先遍历(BFS) 41 | 42 | ![BFS](https://tva1.sinaimg.cn/large/e6c9d24ely1h1f5do85i6j20cj07v74l.jpg) 43 | 44 | - BFS 的思想是从左至右,对树的每一层所有结点依次遍历,当一层的结点遍历完全后,对下一层开始遍历,而下一层结点又恰好是上一层的子结点。因此符合队**列先进先出**的特点 45 | - 深度优先遍历常用的数据结构为栈 46 | - BFS 的特性 : 保留全部结点,占用空间大;无回溯操作(即无入栈、出栈操作),运行速度快。 47 | 48 | ## 02. 发布订阅者和观察者 49 | 50 | ![ss](https://tva1.sinaimg.cn/large/e6c9d24ely1h1f7fk0a6jj20fe0acjs7.jpg) 51 | 52 | **观察者模式:**观察者(Observer)直接订阅(Subscribe)主题(Subject),而当主题被激活的时候,会触发(Fire Event)观察者里的事件。 53 | 54 | **发布订阅模式:**订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Topic),当发布者(Publisher)发布该事件(Publish topic)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。 55 | 56 | ## 03. 防抖和截流 57 | 58 | 在开发的过程中,我们经常会需要绑定一些持续触发的事件,如 resize、scroll、mousemove 等等,但有些时候我们并不希望在事件持续触发的过程中那么频繁地去执行函数浪费性能。 59 | 60 | #### 防抖(debounce) 61 | 62 | > 指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间 63 | 64 | 一个频繁触发的函数,在规定时间内,只让最后一次生效,前面的不生效。 65 | 66 | 原理:在第一次调用函数的时候,创建一个定时器,在指定的时间间隔之后运行代码;如果代码还没运行时,又触发了该函数,则清除旧的定时器,重新创建新的定时器;如果超过延时执行的时间,代码执行了,则此时已经是第二次触发; 67 | 68 | - earch搜索联想,用户在不断输入值时,用防抖来节约请求资源。 69 | - window触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次 70 | 71 | #### 节流(throttle) 72 | 73 | > 指连续触发事件但是在 n 秒中只执行一次函数 74 | 75 | 一个频繁触发的函数,在规定时间内,函数执行一次后,只有大于设定的执行周期后才会执行第二次。 76 | 77 | 原理:第一次执行函数的时候,记录函数执行的时间,当下一次执行的时候,比较时间是否还在间隔时间内,如果是则不执行,否则继续执行; 78 | 79 | - 鼠标不断点击触发,mousedown(单位时间内只触发一次) 80 | - 监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断 81 | 82 | **函数防抖和节流,都是控制事件触发频率的方法。应用场景有很多,输入框持续输入,将输入内容远程校验、多次触发点击事件、onScroll等等。** 83 | 84 | ## 04. 浅拷贝和深拷贝 85 | 86 | **浅拷贝:**仅仅是指向被复制的内存地址,如果原地址发生改变,那么浅复制出来的对象也会相应的改变。 87 | 88 | > 创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。 89 | 90 | **深拷贝:**在计算机中开辟一块**新的内存地址**用于存放复制的对象。 91 | 92 | > 将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象 93 | 94 | **深拷贝和浅拷贝最根本的区别在于是否是真正获取了一个对象的复制实体,而不是引用。** 95 | 96 | **浅拷贝实现方法:** 97 | 98 | 1. Object.assign 99 | 100 | - Object.assign是一个浅拷贝,它只是在**根属性**(对象的第一层级)创建了一个新的对象,但是对于属性的值是仍是对象的话依然是浅拷贝 101 | 102 | - 不会拷贝对象继承的属性 103 | 104 | - 不可枚举的属性 105 | 106 | - 可以拷贝Symbol类型 107 | 108 | 2. 扩展运算符、slice、concat 109 | 110 | - 和assgin一样只拷贝一层 111 | 112 | **深拷贝实现方法:** 113 | 114 | 1. 循环+递归 115 | - 只能实现object、array的深拷贝 116 | - for...in 无法获得 Symbol 类型的键,而 Reflect 可以获取 117 | 118 | 2. JSON.stringify 119 | 120 | - 拷贝的对象的值中如果有函数,undefined,symbol则经过JSON.stringify()序列化后的JSON字符串中这个键值对会消失 121 | 122 | - 无法拷贝不可枚举的属性, 无法拷贝对象的原型链 123 | 124 | - 拷贝Date引用类型会变成字符串 125 | 126 | - 拷贝RegExp引用类型会变成空对象 127 | 128 | - 对象中含有NaN、 Infinity和 - Infinity, 则序列化的结果会变成null 129 | 130 | - 无法拷贝对象的循环应用(即obj[key] = obj) 131 | 132 | 3. lodash([第三方库](https://github.com/lodash/lodash/blob/master/.internal/baseClone.js)) 133 | 134 | ## 05. 介绍下重绘和回流(Repaint & Reflow),以及如何优化? 135 | 136 | #### 1. 浏览器渲染机制 137 | 138 | - 浏览器采用流式布局模型(`Flow Based Layout`) 139 | - 浏览器会把`HTML`解析成`DOM Tree`,解析`css`构建`render`树(将css代码解析成树形的数据结构,然后结合DOM合并成render树) 140 | - 有了`RenderTree`,我们就知道了所有节点的样式,然后计算他们在页面上的大小和位置,最后把节点绘制到页面上。 141 | - 由于浏览器使用流式布局,对`Render Tree`的计算通常只需要遍历一次就可以完成,**但`table`及其内部元素除外,他们可能需要多次计算,通常要花3倍于同等元素的时间,这也是为什么要避免使用`table`布局的原因之一**。 142 | 143 | #### 2. 重绘 144 | 145 | ​ 由于节点的几何属性发生改变或者由于样式发生改变而不会影响布局的,称为重绘,例如`outline`, `visibility`, `color`、`background-color`等,重绘的代价是高昂的,因为浏览器必须验证DOM树上其他节点元素的可见性。 146 | 147 | #### 3. 回流 148 | 149 | 回流是布局或者几何属性需要改变就称为回流。回流是影响浏览器性能的关键因素,因为其变化涉及到部分页面(或是整个页面)的布局更新。一个元素的回流可能会导致了其所有子元素以及DOM中紧随其后的节点、祖先节点元素的随后的回流。因为其在DOM中在回流元素之后,**大部分的回流将导致页面的重新渲染,回流必定会发生重绘,重绘不一定会引发回流。** 150 | 151 | #### 4. 浏览器优化 152 | 153 | 现代浏览器大多都是通过队列机制来批量更新布局,浏览器会把修改操作放在队列中,至少一个浏览器刷新(即16.6ms)才会清空队列,但是当你**获取布局信息的时候 比如`改变元素的宽高`,`元素的位置`,导致浏览器不得不重新计算元素的几何属性,队列中可能有会影响这些属性或方法返回值的操作,即使没有,浏览器也会强制清空队列,触发回流与重绘来确保返回正确的值并重新构建渲染树** 154 | 155 | 主要包括以下属性或方法: 156 | 157 | - `offsetTop`、`offsetLeft`、`offsetWidth`、`offsetHeight` 158 | - `scrollTop`、`scrollLeft`、`scrollWidth`、`scrollHeight` 159 | - `clientTop`、`clientLeft`、`clientWidth`、`clientHeight` 160 | - `width`、`height` 161 | - `getComputedStyle()` 162 | - `getBoundingClientRect()` 163 | 164 | 所以,我们应该避免频繁的使用上述的属性,它们都会强制渲染刷新队列。 165 | 166 | #### 5. 减少重绘与回流 167 | 168 | 1. CSS 169 | 170 | - **使用 `transform` 替代 `top`** 171 | 172 | - **使用 `visibility` 替换 `display: none`** ,因为前者只会引起重绘,后者会引发回流(改变了布局) 173 | 174 | - **避免使用`table`布局**,可能很小的一个小改动会造成整个 `table` 的重新布局。 175 | 176 | - **尽可能在`DOM`树的最末端改变`class`**,回流是不可避免的,但可以减少其影响。尽可能在DOM树的最末端改变class,可以限制了回流的范围,使其影响尽可能少的节点。 177 | 178 | - **避免设置多层内联样式**,css 选择符**从右往左**匹配查找,避免节点层级过多,保证**层级扁平**。 179 | 180 | - **将动画效果应用到`position`属性为`absolute`或`fixed`的元素上**,避免影响其他元素的布局,这样只是一个重绘,而不是回流,同时,控制动画速度可以选择 `requestAnimationFrame`。 181 | 182 | - **避免使用`CSS`表达式**,可能会引发回流。 183 | 184 | - **CSS3 硬件加速**可以让`transform`、`opacity`、`filters`这些动画不会引起回流重绘 。但是对于动画的其它属性,比如`background-color`这些,还是会引起回流重绘的,不过它还是可以提升这些动画的性能。 185 | 186 | 2. JS 187 | 188 | - **避免频繁操作样式**,最好一次性重写`style`属性,或者将样式列表定义为`class`并一次性更改`class`属性。 189 | - **避免频繁操作`DOM`**,创建一个`documentFragment`,在它上面应用所有`DOM操作`,最后再把它添加到文档中。 190 | - **避免频繁读取会引发回流/重绘的属性**,如果确实需要多次使用,就用一个变量缓存起来。 191 | 192 | - **对具有复杂动画的元素使用绝对定位**,使它脱离文档流,否则会引起父元素及后续元素频繁回流。 193 | 194 | ## 06. Promise 和 Async 195 | 196 | 1. Promise和Callback有什么区别 197 | 198 | - 深度和宽度的区别都能解决异步 199 | - Promise是ES6标准提出异步编程解决方案 200 | 201 | 2. Promise有几个状态 202 | 203 | - pending 等待、fulfilled 成功、rejected 失败 204 | 205 | 3. Promise构造函数是同步还是异步执行,then那 206 | 207 | - Promise构造函数是同步,then是异步 208 | 209 | 4. Promise如何实现 210 | 211 | 5. Promise的优缺点 212 | 213 | **优点** 214 | promise对象,可以将 **异步操作** 以 **同步操作的流程** 表达出来,避免层层嵌套 215 | 216 | **缺点** 217 | 218 | 1. 无法取消 Promise,一旦新建它就会立即执行,无法中途取消。 219 | 2. 如果不设置回调函数,Promise 内部抛出的错误,不会反映到外部。 220 | 3. 处于pending状态时,是不能知道目前进展到哪个阶段的 ( 刚开始?,即将结束?) 221 | 222 | 6. 如何设计Promise.all() 223 | 224 | 7. Promise怎么异常捕获 225 | 226 | 1. reject 227 | 228 | 2. catch(推荐) 229 | 230 | > 因为catch可以捕获执行中的错误,也更接近同步的写法(try/catch) 231 | 232 | 3. 捕获不了异步错误 因为 try catch只能捕获同步错误 233 | 234 | 8. Async/Await和Promise的区别 235 | 236 | 1. 简洁不用写匿名参数调用 237 | 2. Async/Await让try/catch可以同时处理同步和异步错误 238 | 3. Async/Await可以让程序中断 239 | 4. 条件语句 240 | 5. 错误栈 241 | 242 | 9. Async/Await内部实现原理 243 | 244 | ​ Generator+CO模块 245 | 246 | 1. 内置执行器,不需要使用next()手动执行。 247 | 2. await命令后面可以是Promise对象或原始类型的值,yield命令后面只能是Thunk函数或Promise对象。 248 | 3. 返回值是Promise。返回非Promise时,async函数会把它包装成Promise返回。(Promise.resolve(value)) 249 | 250 | ## 07. Vue自定事件的原理 251 | 252 | 1. 基本用法 253 | 254 | ```js 255 | vm.$emit('自定义事件的名称',this.data) // 触发事件 256 | vm.$on('自定义事件的名称',function(data){}) // 监听事件 257 | ``` 258 | 259 | **注意**: 260 | 261 | ​ 如果是子到父通信 不能用 $on 侦听子组件抛出的事件,而**必须**在模板里直接用 v-on 绑定。 262 | ​ 就是父组件可以在使用子组件的地方直接用 v-on 来监听子组件触发的事件。 263 | 264 | 2. 实现原理 265 | 266 | - 子父通信原理 (观察者模式) 267 | 268 | - 事件总线原理 (发布订阅者模式) 269 | 270 | > on只能监听同一个Vue实例上emit出来的事件,事件总线就是靠这个机制实现数据传递的。 271 | > 272 | > 但是父子组件是独立的Vue实例, 所以emit的事件,如果不在使用它的时候监听这个自定义事件, 273 | > 274 | > 在父组件里面如果用on是监听不到的, 但是可以在一个组件里面通信(至今没发现有什么用) 275 | 276 | ## 08. Vue如何优化首页加载 277 | 278 | 1. 首页白屏原因 279 | 280 | > 主要原因是单页应用,加载资源过慢, 需要将所有需要的资源都下载到浏览器端并解析。单页面应用的html 是靠 js 生成,因为首屏需要加载很大的js文件(`app.js` `vendor.js`),所以当网速差的时候会产生一定程度的白屏。 281 | 282 | 2. 首屏优化方法 283 | 284 | 1. 在路由返回内容前添加loading(骨架屏) 285 | - [vue-server-renderer](https://links.jianshu.com/go?to=https%3A%2F%2Fgithub.com%2Fvuejs%2Fvue%2Ftree%2Fdev%2Fpackages%2Fvue-server-renderer%23readme) 286 | - [vue-skeleton-webpack-plugin](https://links.jianshu.com/go?to=https%3A%2F%2Fgithub.com%2Flavas-project%2Fvue-skeleton-webpack-plugin) 287 | - [page-skeleton-webpack-plugin](https://links.jianshu.com/go?to=https%3A%2F%2Fgithub.com%2FElemeFE%2Fpage-skeleton-webpack-plugin) 288 | 2. 使用首屏SSR + 跳转SPA方式来优化 289 | 3. 改单页应用为多页应用,使用quicklink(单页面配合路由)技术 290 | 4. 协议优化(**B站前端铁蛋**儿) 291 | 5. 使用web worker 292 | 6. 第三方资源使用cdn 293 | 7. 优化webpackp配置 294 | - webpack的code-split结合vue-router做懒加载 295 | - wepack的contenthash模式,针对文件级别更改做缓存 296 | 297 | 8. 图片使用webp、小图采用base64编码、雪碧图等 298 | 299 | ## 09. React 和 Vue 循环为什么加key? 300 | 301 | > 基于没有key的情况diff速度会更快, 没有绑定key的情况下遍历节点的时候,虚拟DOM的新旧节点会复用。 302 | 303 | ```html 304 |
305 |
{{ i }}
306 |
307 | ``` 308 | 309 | ```js 310 | let vm = new Vue({ 311 | el: '#app', 312 | data: { 313 | dataList: [1, 2, 3, 4, 5] 314 | } 315 | }) 316 | ``` 317 | 318 | 以上的例子,v-for的内容会生成以下的dom节点数组,我们给每一个节点标记一个身份id: 319 | 320 | ```js 321 | [ 322 | '
1
', // id: A 323 | '
2
', // id: B 324 | '
3
', // id: C 325 | '
4
', // id: D 326 | '
5
' // id: E 327 | ] 328 | ``` 329 | 330 | **1. 改变dataList数据** 331 | 332 | ```js 333 | vm.dataList = [5, 4, 3, 1, 2] 334 | // 没有key的情况, 节点位置不变,但是节点innerText内容更新了 335 | [ 336 | '
5
', // id: A 337 | '
4
', // id: B 338 | '
3
', // id: C 339 | '
1
', // id: D 340 | '
2
' // id: E 341 | ] 342 | 343 | // 有key的情况,dom节点位置进行了交换,但是内容没有更新 344 | //
{{ i }}
345 | [ 346 | '
5
', // id: D 347 | '
4
', // id: A 348 | '
3
', // id: C 349 | '
1
', // id: E 350 | '
2
' // id: B 351 | ] 352 | ``` 353 | 354 | **2. 增删dataList数据** 355 | 356 | ```js 357 | vm.dataList = [3, 4, 5, 6, 7] // 数据进行增删 358 | // 没有key的情况, 节点位置不变,内容也更新了 359 | [ 360 | '
3
', // id: A 361 | '
4
', // id: B 362 | '
5
', // id: C 363 | '
6
', // id: D 364 | '
7
' // id: E 365 | ] 366 | 367 | // 有key的情况, 节点删除了 A, B 节点,新增了 F, G 节点 368 | //
{{ i }}
369 | [ 370 | '
3
', // id: C 371 | '
4
', // id: D 372 | '
5
', // id: E 373 | '
6
', // id: F 374 | '
7
' // id: G 375 | ] 376 | ``` 377 | 378 | ​ 从以上来看,不带有key,**并且使用简单的模板,**基于这个前提下,可以更有效的复用节点,diff对比也是不带key的快,因为带key在增删节点上有耗时。这就是Vue文档所说的**默认模式**。但是这个并不是key作用,而是没有key的情况下可以对节点就地复用,提高性能。 379 | 380 | ​ 这种模式会带来一些隐藏的副作用,比如可能不会产生过渡效果,或者在某些节点有绑定数据(表单)状态,会出现状态错位。Vue文档也说明了这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态 的列表渲染输出(例如:表单输入值)。 381 | 382 | ​ 为什么还要建议带key呢?因为这种不带key只适用于渲染简单的无状态组件。对于大多数场景来说,列表组件都有自己的状态。 383 | 384 | ## 10.Vue和React路由实现的原理 385 | 386 | > vue-router和react-router实现原理大同小异,更新视图但不重新请求页面是前端路由原理的核心之一,目前在浏览器环境中的实现有2种方式 387 | 388 | 1. hash模式(老版浏览器支持) 389 | 390 | 原理是 onhashchage 事件可以在window对象上监听这个事件 391 | 392 | 2. History模式(高版本浏览器支持) 393 | 394 | 原理是利用History 在 HTML5中新增的方法 395 | 396 | **两种方式对比:** 397 | 398 | - hash模式 399 | - 通过路径中的hash值来控制路由跳转,不存在兼容问题 400 | 401 | - history 402 | - 利用了HTML5 History 中新增的 pushState() 方法。 403 | - 需要后台配置支持。如果刷新时,服务器没有响应响应的资源,会刷出404, 404 | 405 | **路由面试题:** 406 | 407 | 1. Vue路由的实现原理 408 | 2. SPA路由history模式上线后刷新404 409 | - 在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面。 410 | 3. $(route/router)的区别 411 | - $route是路由信息对象 412 | - $router是路由实例对象 413 | 414 | 4. 路由里的 标签和 标签有什么区别 415 | 416 | ```js 417 | Link 的本质也是a 标签。只不过在Link 中禁用了 a 标签的默认事件,改用了history对象提供的方法进行跳转。 418 | ``` 419 | 420 | **原生实现hash和history两种路由模式:** 421 | 422 | ​ 参照code 423 | 424 | ## 11. webpack的打包原理 425 | 426 | 1. 简单需求 427 | 428 | - 浏览器不支持ES6的模块 429 | 430 | 2. 核心打包功能 431 | 432 | ```js 433 | // 打包工作的基本流程如下: 434 | 1. 需要读到入口文件里的内容 435 | 2. 分析入口文件,递归的去读取模块所依赖的文件内容,生成AST语法树 436 | 3. 根据AST语法树,生成浏览器能够运行的最终代码 437 | ``` 438 | 439 | 1. 获取模块内容 440 | 2. 分析模块内容 441 | - 安装@babel/parser包(转AST) 442 | 3. 对模块内容处理 443 | - 安装@babel/traverse包(遍历AST) 444 | - 安装@babel/core和@babel/preset-env包(Es6转Es5) 445 | 4. 递归所有模块 446 | 5. 生成最终代码 447 | 448 | 3. 手动loader、plugin 449 | 450 | 1. 实现一个同步的loader 451 | 2. 实现一个异步的loader 452 | 3. 实现一个plugin 453 | 454 | ## 12.webpack配置Vue实现热更新 455 | 456 | ## 13.Vue 2.0和Vue3.0 响应式原理的区别? 457 | 458 | #### Object.defineProperty和Proxy区别 459 | 460 | 1. Object.defineProperty无法监控到数组方法,导致通过数组添加元素,不能实时响应; 461 | 462 | 2. Object.defineProperty只能劫持对象的属性,从而需要对每个对象,每个属性进行遍历, 463 | 464 | 如果,属性值是对象,还需要深度遍历。Proxy可以劫持整个对象,**并返回一个新的对象**。 465 | 466 | 3. Proxy不仅可以代理对象,还可以代理数组。还可以代理动态增加的属性。 467 | 468 | ## 14. 手写 async/await 的实现 469 | 470 | > async函数本职上就是generator函数的语法糖,说白了就是co库。 471 | > 472 | > babel编译中generator函数也被编译成了一个很原始的形式,现在用generator替代。 473 | 474 | ## 15. 使用ES6提供的构造函数Proxy实现数据绑定 475 | 476 | - 详见代码 477 | 478 | ## 16. 求最大公共前缀 479 | 480 | 编程题:求最大公共前缀,如`['aaafsd', 'aawwewer', 'aaddfff'] => 'aa'` 481 | 482 | ## 17. 手写一个redux 483 | 484 | - 订阅发布者模式 485 | 486 | ## 18. 使用setTimeout实现setInterval 487 | 488 | ## 19. 对象扁平化(2022阿里伯乐) 489 | 490 | **输入**: 491 | 492 | ```JavaScript 493 | { 494 | a: 'a', 495 | b: [1, { c: true }, [3]], 496 | d: { e: undefined, f: 3 }, 497 | g: null, 498 | } 499 | ``` 500 | 501 | **输出**: (null和undefined直接舍去) 502 | 503 | ```JavaScript 504 | { 505 | a: "a", 506 | b[0]: 1, 507 | b[1].c: true, 508 | b[2][0]: 3, 509 | d.f: 3 510 | 511 | } 512 | ``` 513 | 514 | 代码实现 515 | 516 | ```JavaScript 517 | function flatten(obj) { 518 | const res = {}; 519 | const _flatten = function (o, prev = null) { 520 | if (Array.isArray(o)) { 521 | for (const index in o) { 522 | const ele = o[index]; 523 | if (ele instanceof Object) { 524 | _flatten(ele, `${prev ? prev : ''}[${index}]`); 525 | } else { 526 | if (ele) { 527 | res[`${prev ? prev : ''}[${index}]`] = ele; 528 | } 529 | } 530 | } 531 | return; 532 | } 533 | for (const key in o) { 534 | if (typeof o[key] === 'object') { 535 | if (o[key] !== null) { 536 | _flatten(o[key], `${prev ? prev + '.' : ''}${key}`); 537 | } 538 | } else { 539 | if (o[key] !== undefined) { 540 | res[`${prev ? prev + '.' : ''}${key}`] = o[key]; 541 | } 542 | } 543 | } 544 | }; 545 | _flatten(obj); 546 | return res; 547 | } 548 | ``` 549 | 550 | ## 20. 反转二叉树(知乎) 551 | 552 | ![img](https://secure2.wostatic.cn/static/9w8Tx657R6QWHXgGdovmTK/image.png) 553 | 554 | ```JavaScript 555 | const obj = { 556 | 'id': '4', 557 | 'left': { 558 | 'id': '2', 559 | 'left': { 560 | 'id': '1', 561 | 'left': null, 562 | 'right': null 563 | }, 564 | 'right': { 565 | 'id': '3', 566 | 'left': null, 567 | 'right': null 568 | } 569 | }, 570 | 'right': { 571 | 'id': '7', 572 | 'left': { 573 | 'id': '6', 574 | 'left': null, 575 | 'right': null 576 | }, 577 | 'right': { 578 | 'id': '9', 579 | 'left': null, 580 | 'right': null 581 | } 582 | } 583 | } 584 | 585 | 586 | function invertTree(root) { 587 | if (root !== null) { 588 | let temp = root.left; 589 | root.left = root.right; 590 | root.right = temp; 591 | invertTree(root.left); 592 | invertTree(root.right); 593 | } 594 | return root 595 | }; 596 | console.log(invertTree(obj)) 597 | ``` 598 | 599 | ## 21. 输出一个字符串全排列(快手) 600 | 601 | > 'abc' 602 | 603 | > [ 'abc', 'acb', 'bac', 'bca', 'cab', 'cba' ] 604 | 605 | ```JavaScript 606 | // 不能有重复元素 607 | let pre = function (s) { 608 | let res = [] 609 | s = s.split('').sort((a, b) => { 610 | return a > b ? 1 : -1 611 | }).join('') 612 | const dfs = (curr, store) => { 613 | if (!store.length) { 614 | return res.push(curr) 615 | } 616 | for (let i = 0; i < store.length; i++) { 617 | if (i > 0 && store[i] === store[i - 1]) continue 618 | dfs(curr + store[i], store.slice(0, i) + store.slice(i + 1)) 619 | } 620 | } 621 | dfs('', s) 622 | return res 623 | } 624 | ``` 625 | 626 | ## 22. ES5实现B继承A (58同城) 627 | 628 | 1. 原型链继承 629 | 630 | - 将父类的实例作为子类的原型 631 | - 优点: 632 | 1. 父类方法可以复用 633 | - 缺点: 634 | 1. 父类的所有`引用属性`会被所有子类共享,更改一个子类的引用属性,其他子类也会受影响 635 | 2. 子类型实例不能给父类型构造函数传参 636 | 637 | 2. 构造函数继承 638 | 639 | - 在子类构造函数中调用父类构造函数,可以在子类构造函数中使用 640 | 641 | ``` 642 | call() 643 | ``` 644 | 645 | 和 646 | 647 | ``` 648 | apply() 649 | ``` 650 | 651 | 方法 652 | 653 | - 优点: 654 | 1. 可以在子类构造函数中向父类传参数 655 | 2. 父类的引用属性不会被共享 656 | - 缺点: 657 | 1. 子类不能访问父类原型上定义的方法(即不能访问Parent.prototype上定义的方法),因此所有方法属性都写在构造函数中,每次创建实例都会初始化 658 | 659 | 3. 组合继承 660 | 661 | - 组合继承综合了`原型链继承`和`构造函数继承`,将两者的优点结合了起来,基本的思路就是使用原型链继承原型上的属性和方法,而通过构造函数继承实例属性,这样既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性。 662 | 663 | 优点: 664 | 665 | ``` 666 | 1. 父类的方法可以复用 667 | ``` 668 | 669 | 4. 可以在Child构造函数中向Parent构造函数中传参 670 | 671 | 5. 父类构造函数中的引用属性不会被共享 672 | 673 | 6. 原型式继承 674 | 675 | - 对参数对象的一种浅复制 676 | 677 | 优点: 678 | 679 | 1. 父类方法可复用 680 | 681 | 缺点: 682 | 683 | 1. 父类的引用会被所有子类所共享 684 | 2. 子类实例不能向父类传参 685 | 686 | 7. 寄生式继承 687 | 688 | - 使用原型式继承对一个目标对象进行浅复制,增强这个浅复制的能力 689 | 690 | 8. 寄生式组合继承 691 | 692 | 优点: 693 | 694 | 1. 只调用一次父类构造函数 695 | 696 | 9. Child可以向Parent传参 697 | 698 | 10. 父类方法可以复用 699 | 700 | 11. 父类的引用属性不会被共享 701 | 702 | > 寄生式组合继承可以算是引用类型继承的最佳模式 703 | 704 | ## 23. 手写虚拟 Dom 转化为真实 Dom(滴滴) 705 | 706 | ```JavaScript 707 | { 708 | tag: 'DIV', 709 | attrs:{ 710 | id:'app' 711 | }, 712 | children: [ 713 | { 714 | tag: 'SPAN', 715 | children: [ 716 | { tag: 'A', children: [] } 717 | ] 718 | }, 719 | { 720 | tag: 'SPAN', 721 | children: [ 722 | { tag: 'A', children: [] }, 723 | { tag: 'A', children: [] } 724 | ] 725 | } 726 | ] 727 | } 728 | // 把上诉虚拟Dom转化成下方真实Dom 729 |
730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 |
738 | ``` 739 | 740 | ## 24. Vue.nextTick的原理和用途 741 | 742 | **原理** 743 | 744 | 1. 异步说明 745 | 746 | > Vue 实现响应式并**不是数据发生变化之后 DOM 立即变化**,而是按一定的策略进行 DOM 的更新。 747 | 748 | 2. 事件循环说明 749 | 750 | 简单来说,Vue在修改数据后,视图不会立刻更新,而是等**同一事件循环**中的所有数据变化完成之后,再统一进行视图更新。 751 | 752 | ![img](https:////upload-images.jianshu.io/upload_images/6879762-4e28f4ec6177e461.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/866/format/webp) 753 | 754 | ![img](https:////upload-images.jianshu.io/upload_images/6879762-d362dda15d4cec74.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/423/format/webp) 755 | 756 | **应用场景** 757 | 758 | 1. 在Vue生命周期的created()钩子函数进行DOM操作一定要放到Vue.nextTick()的回调函数中。 759 | 760 | 在created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,所以此处一定要将DOM操作的js代码放进Vue.nextTick()的回调函数中。 761 | 762 | 与之对应的就是mounted()钩子函数,因为该钩子函数执行时所有的DOM挂载和渲染都已完成,此时在该钩子函数中进行任何DOM操作都不会有问题。 763 | 764 | 2. 在数据变化后要执行的某个操作,而这个操作需要使用随数据改变而改变的DOM结构的时候,这个操作都应该放进Vue.nextTick()的回调函数中。 765 | 766 | 具体原因在Vue的官方文档中详细解释: 767 | 768 | > Vue 异步执行 DOM 更新。只要观察到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作上非常重要。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部尝试对异步队列使用原生的Promise.then和MessageChannel,如果执行环境不支持,会采用setTimeout(fn, 0)代替。 769 | 770 | > 例如,当你设置vm.someData = 'new value',该组件不会立即重新渲染。当刷新队列时,组件会在事件循环队列清空时的下一个“tick”更新。多数情况我们不需要关心这个过程,但是如果你想在 DOM 状态更新后做点什么,这就可能会有些棘手。虽然 Vue.js 通常鼓励开发人员沿着“数据驱动”的方式思考,避免直接接触 DOM,但是有时我们确实要这么做。为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用Vue.nextTick(callback)。这样回调函数在 DOM 更新完成后就会调用。 771 | 772 | **深入理解** 773 | 774 | https://github.com/vuejs/vue/blob/dev/src/core/util/next-tick.js(源码地址) 775 | 776 | ```JavaScript 777 | var nextTick = (function () { 778 | // 这里存放的是回调函数的队列 779 | var callbacks = []; 780 | var pending = false; 781 | var timerFunc; 782 | //这个函数就是DOM更新后需要执行的 783 | function nextTickHandler () { 784 | pending = false; 785 | //这里将回调函数copy给copies 786 | var copies = callbacks.slice(0); 787 | callbacks.length = 0; 788 | //进行循环执行回调函数的队列 789 | for (var i = 0; i < copies.length; i++) { 790 | copies[i](); 791 | } 792 | } 793 | })() 794 | ``` 795 | 796 | Vue用了三个方法来执行`nextTickHandler`函数,分别是: 797 | 798 | - `Promise` 799 | 800 | ```JavaScript 801 | //当浏览器支持Promise的时候就是用Promise 802 | p.then(nextTickHandler).catch(logError); 803 | ``` 804 | 805 | - `MutationObserver` 806 | 807 | ```JavaScript 808 | //当浏览器支持MutationObserver的时候就是用MutationObserver 809 | var observer = new MutationObserver(nextTickHandler); 810 | var textNode = document.createTextNode(String(counter)); 811 | observer.observe(textNode, { 812 | characterData: true 813 | }); 814 | timerFunc = function () { 815 | counter = (counter + 1) % 2; 816 | textNode.data = String(counter); 817 | }; 818 | ``` 819 | 820 | - `setTimeout` 821 | 822 | ```JavaScript 823 | //当以上都不支持的时候就用setTimeout 824 | setTimeout(nextTickHandler, 0); 825 | ``` 826 | 827 | 那么Vue.nextTick([callback, context])的第二个参数是什么呢?来看下面的代码。 828 | 829 | ```JavaScript 830 | return function queueNextTick (cb, ctx) { 831 | var _resolve; 832 | callbacks.push(function () { 833 | //看这里,其实是可以给cb指定一个对象环境,来改变cb中this的指向 834 | if (cb) { cb.call(ctx); } 835 | if (_resolve) { _resolve(ctx); } 836 | }); 837 | if (!pending) { 838 | pending = true; 839 | timerFunc(); 840 | } 841 | if (!cb && typeof Promise !== 'undefined') { 842 | return new Promise(function (resolve) { 843 | _resolve = resolve; 844 | }) 845 | } 846 | } 847 | ``` 848 | 849 | 这样写就报错了: 850 | 851 | ```JavaScript 852 | Vue.nextTick(()=>{ 853 | this.text() 854 | }, { text(){ 855 | console.log('铁蛋儿') 856 | } 857 | }) 858 | ``` 859 | 860 | 源码中使用的是`if (cb) { cb.call(ctx) }` 所以不能使用箭头函数,箭头函数的`this`是固定的,是不可用`apply`,`call`,`bind`来改变的。改成这样: 861 | 862 | ```JavaScript 863 | Vue.nextTick(function () { 864 | this.text() 865 | }, { 866 | text(){ 867 | console.log('铁蛋儿') 868 | } 869 | }) 870 | ``` 871 | 872 | ## 25. 两个长度不等的有序数组合并成一个有序数组(ACM、字节、美团) 873 | 874 | > 不能使用sort 875 | 876 | ```JavaScript 877 | let ad = [1,3,5,6,7,13,14,15,16,20,33] 878 | let ap = [3,4,6,8,9,13,17,18,19] 879 | // 合并后的数组有序 880 | let between = [] 881 | const transForNew = (ad,ap) => { 882 | while(ad.length + ap.length > 0){ 883 | if(ad.length < 1 || ap.length < 1){ 884 | between = between.concat(ad.length < 1 ? ap : ad) 885 | break 886 | } 887 | between.push(ad[0] >= ap[0] ? ap.shift() : ad.shift()) 888 | } 889 | return between 890 | } 891 | console.log(transForNew(ad,ap)); 892 | ``` 893 | 894 | ## 26. 控制最大并发数(字节) 895 | 896 | ```JavaScript 897 | class PromisePool { 898 | constructor(max, fn) { 899 | this.max = max; // 最大并发数 900 | this.fn = fn; // 自定义的请求函数 901 | this.pool = []; // 并发池 902 | this.urls = []; // 剩余的请求地址 903 | } 904 | start(urls) { 905 | this.urls = urls; 906 | // 先循环把并发池塞满 907 | while (this.pool.length < this.max) { 908 | let url = this.urls.shift(); 909 | this.setTask(url); 910 | } 911 | // 利用Promise.race 方法来获得并发池中某任务完成的信号 912 | let race = Promise.race(this.pool); 913 | return this.run(race); 914 | } 915 | run(race) { 916 | race 917 | .then(res => { 918 | // 每当并发池跑完一个任务,就再塞入一个任务 919 | let url = this.urls.shift(); 920 | this.setTask(url); 921 | return this.run(Promise.race(this.pool)); 922 | }); 923 | } 924 | setTask(url) { 925 | if (!url) return; 926 | let task = this.fn(url); 927 | this.pool.push(task); // 将该任务推入pool并发池中 928 | console.log(`\x1B[43m ${url} 开始,当前并发数:${this.pool.length}`); 929 | task.then(res => { 930 | // 请求结束后将该Promise任务从并发池中移除 931 | this.pool.splice(this.pool.indexOf(task), 1); 932 | console.log(`\x1B[43m ${url} 结束,当前并发数:${this.pool.length}`); 933 | }); 934 | } 935 | } 936 | // test 937 | const URLs = [ 938 | 'bytedance.com', 939 | 'tencent.com', 940 | 'alibaba.com', 941 | 'microsoft.com', 942 | 'apple.com', 943 | 'hulu.com', 944 | 'amazon.com' 945 | ]; 946 | let dur = 0; 947 | // 自定义请求函数 948 | let requestFn = url => { 949 | return new Promise(resolve => { 950 | setTimeout(_ => { 951 | resolve(`任务 ${url} 完成`); 952 | }, 1000 * dur++) 953 | }).then(res => { 954 | console.log('外部逻辑 ', res); 955 | }) 956 | } 957 | 958 | const pool = new PromisePool(3, requestFn); // 并发数为3 959 | pool.start(URLs); 960 | ``` 961 | 962 | ## 27. 数组怎么转成tree (字节) 963 | 964 | ```JavaScript 965 | // 传入一个这样的数据结构 966 | [ 967 | { id: 1, parentId: null }, 968 | { id: 2, parentId: 1 }, 969 | { id: 3, parentId: 1 }, 970 | { id: 4, parentId: 2 }, 971 | { id: 5, parentId: 2 }, 972 | { id: 6, parentId: 3 } 973 | ] 974 | 975 | // 转化成如下 976 | 977 | { 978 | id: 1, 979 | children: [ 980 | { 981 | id: 2, 982 | children: [ 983 | { id: 4, children: [] }, 984 | { id: 5, children: [] } 985 | ] 986 | }, 987 | { 988 | id: 3, 989 | children: [ 990 | { id: 6, children: [] } 991 | ] 992 | } 993 | ] 994 | } 995 | ``` 996 | 997 | 998 | 999 | -------------------------------------------------------------------------------- /img/12.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tie-Dan/Algo/bd1d6192e3966a43c8a90bb363ce44c8ed96e728/img/12.1.png -------------------------------------------------------------------------------- /img/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tie-Dan/Algo/bd1d6192e3966a43c8a90bb363ce44c8ed96e728/img/12.png -------------------------------------------------------------------------------- /img/BFS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tie-Dan/Algo/bd1d6192e3966a43c8a90bb363ce44c8ed96e728/img/BFS.png -------------------------------------------------------------------------------- /img/DFS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tie-Dan/Algo/bd1d6192e3966a43c8a90bb363ce44c8ed96e728/img/DFS.png -------------------------------------------------------------------------------- /img/ss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tie-Dan/Algo/bd1d6192e3966a43c8a90bb363ce44c8ed96e728/img/ss.png --------------------------------------------------------------------------------