├── .gitignore ├── 1 JavaScript高级程序设计 ├── 01 语言基础.js ├── 02 值、作用域与内存.js ├── 03 基本引用类型.js ├── 04 集合引用类型.js ├── 05 迭代器与生成器.js ├── 06 面向对象.js ├── 07 代理与反射.js ├── 08 函数.js ├── 09 异步编程.js ├── 10 BOM.js ├── 11 客户端检测.js ├── 12 DOM基础.js ├── 13 DOM扩展.js ├── 14 DOM2和DOM3.js └── 15 事件.js ├── 2 手撕JavaScript ├── 01 跨浏览器事件工具.js ├── 02 尾递归(斐波那契数列).js ├── 03 节流防抖.js ├── 04 手写Promise.js ├── 05 手写Promise方法合集.js ├── 06 Promise周边.js ├── 07 手写函数方法.js ├── 08 实现深拷贝.js ├── 09 实现delay函数.js ├── 10 解析URL.js ├── 11 柯里化的add函数.js ├── 12 调用计数器(支持重置).js ├── 13 手写数组方法.js ├── 14 实现内存函数缓存函数调用结果.js ├── 15 手写new操作.js ├── 16 实现sleep函数.js ├── 17 手写isNaN函数.js ├── 19 读写object路径上的值.js ├── 20 手写字符串方法.js ├── 21 数组扁平化.js ├── 22 数组去重.js ├── 23 手写instanceof.js ├── 24 手写Object静态方法.js ├── 25 对象扁平化.js ├── 26 使用Promise封装Ajax请求.js ├── 27 事件委托.js ├── 28 移除空属性.js ├── 29 实现compose函数.js ├── 30 遍历DOM树.js ├── 31 实现repeat包装函数.js ├── 32 实现每隔一定时间间隔轮询数据.js ├── 33 实现Jsonp跨域请求.js ├── 34 对象展开.js ├── 35 使用ES5语法实现const变量声明.js ├── 36 实现通用函数柯里化.js ├── 37 带并发限制的请求数据.js ├── 38 使用setTimeout实现setInterval.js ├── 39 Promise串行与并行.js └── 40 异步任务调度器.js ├── 3 手撕万物 ├── 1 设计模式相关 │ ├── 01 单例模式实现请求缓存.ts │ ├── 02 发布订阅模式实现上线提醒.ts │ ├── 03 代理模式实现用户上线订阅.ts │ ├── 04 使用迭代器模式模拟DOM树.ts │ ├── 05 组合模式模拟文件结构.ts │ └── 06 EventBus.js └── 2 JavaScript应用 │ ├── 01 LRU缓存置换算法.js │ ├── 02 对象的key驼峰式转下划线.js │ ├── 03 千分位数值分隔.js │ ├── 04 数据格式处理合集.js │ ├── 05 阿拉伯数字转汉字数字.js │ ├── 06 各种排序.js │ └── 07 支持过期时间的LocalStorage.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | test.html 3 | temp -------------------------------------------------------------------------------- /1 JavaScript高级程序设计/01 语言基础.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * JavaScript高级程序设计 - 代码笔记 3 | * 01 语言基础 4 | * Dasen Sun 5 | * 2021-12-04 6 | **************************************************/ 7 | 8 | 9 | // ==================== 变量 ==================== 10 | 11 | // 1 变量声明 12 | 13 | // var声明 14 | console.log(a); // undefined 15 | var a = "aaa"; 16 | console.log(a); // aaa 17 | var a = "bbb" 18 | console.log(a); // bbb 19 | console.log(this.a); // bbb 20 | 21 | // let声明作用域为块作用域 22 | if(true) { 23 | var a = "aaa"; 24 | let b = "bbb"; 25 | console.log(a); // aaa 26 | console.log(b); // bbb 27 | } 28 | console.log(a); // aaa 29 | console.log(b); // ReferenceError: b is not defined 30 | 31 | // let声明不会被提升 32 | console.log(a); // undefined 33 | console.log(b); // ReferenceError: b is not defined 34 | var a; 35 | let b; 36 | 37 | // for循环中的临时变量 38 | for (var i = 0; i < 5; ++i) { 39 | setTimeout(() => console.log(i), 0) 40 | } // 5 5 5 5 5 41 | for (let j = 0; j < 5; ++j) { 42 | setTimeout(() => console.log(j), 0) 43 | } // 0 1 2 3 4 44 | 45 | 46 | // ==================== 数据类型 ==================== 47 | 48 | // 1 undefined 49 | 50 | let a; 51 | console.log(typeof a); // undefined 52 | console.log(typeof b); // undefined 53 | console.log(a); // undefined 54 | console.log(b); // ReferenceError: b is not defined 55 | 56 | 57 | // 2 字符串 58 | 59 | // 标签函数 60 | function x2Tag(strings, ...expressions) { 61 | console.log(strings); // ['', ' + ', ' = ', ''] 62 | console.log(expressions); // [1, 2, 3] 63 | let e = expressions.map((val) => val*2); 64 | let r = strings[0]; 65 | for(let i=0; i1) { 153 | // j>1时,直接break最外层循环,循环结束 154 | break aloop; 155 | } 156 | for (let k = 0; k < 3; k++) { 157 | if(k>1) { 158 | // k>1时,结束第三层循环,continue到第二层继续 159 | continue bloop; 160 | } 161 | console.log(i, j, k); 162 | } 163 | } 164 | } 165 | 166 | 167 | // 2 with语句 168 | 169 | let obj = { name: "obj", a: "aaa" }; 170 | with(obj) { 171 | let aaa = a; 172 | console.log(aaa); // aaa 173 | console.log(name); // obj 174 | name = "ooo"; 175 | console.log(name); // ooo 176 | } 177 | -------------------------------------------------------------------------------- /1 JavaScript高级程序设计/02 值、作用域与内存.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * JavaScript高级程序设计 - 代码笔记 3 | * 02 值、作用域与内存 4 | * Dasen Sun 5 | * 2021-12-06 6 | **************************************************/ 7 | 8 | 9 | // ==================== 值 ==================== 10 | 11 | // 1 判断值的类型 12 | 13 | // 原始值 14 | let name = "Dasen"; 15 | console.log(typeof name === "number"); // false 16 | console.log(typeof name === "string"); // true 17 | 18 | // 引用值 19 | let arr = []; 20 | console.log(typeof arr === "object"); // true 21 | console.log(arr instanceof Array); // true 22 | 23 | 24 | // ==================== 垃圾回收 ==================== 25 | 26 | // 1 内存泄漏 27 | 28 | // 意外声明全局变量 29 | function setName() { 30 | name = "Dasen"; 31 | } 32 | 33 | // 定时器内存泄漏 34 | let name = "Dasen"; 35 | setInterval(() => { 36 | console.log(name); 37 | }, 1000); 38 | 39 | // 闭包造成的内存泄漏 40 | let outer = function () { 41 | let name = "Dasen"; 42 | return function () { 43 | return name; 44 | }; 45 | }; 46 | -------------------------------------------------------------------------------- /1 JavaScript高级程序设计/03 基本引用类型.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * JavaScript高级程序设计 - 代码笔记 3 | * 03 基本引用类型 4 | * Dasen Sun 5 | * 2021-12-07 6 | **************************************************/ 7 | 8 | 9 | // ==================== Date ==================== 10 | 11 | // 1 时间日期 12 | 13 | // 创建时间日期 14 | let dt1 = new Date(); 15 | let dt2 = new Date(Date.now()); 16 | let dt3 = new Date(Date.parse("5/1/2020")); 17 | let dt4 = new Date(Date.parse("May 1, 2020")); 18 | let dt5 = new Date(Date.parse("我不是日期")); 19 | let dt6 = new Date(Date.UTC(2020,4,1,13,14,5,21)); 20 | let dt7 = new Date("5/1/2020"); 21 | let dt8 = new Date(2020,4,1,13,14,5,21); 22 | console.log(Number(dt1)); // 1638845562036 23 | console.log(Number(dt2)); // 1638845562036 24 | console.log(Number(dt3)); // 1588262400000 25 | console.log(Number(dt4)); // 1588262400000 26 | console.log(Number(dt5)); // NaN 27 | console.log(Number(dt6)); // 1588338845021 28 | console.log(Number(dt7)); // 1588262400000 29 | console.log(Number(dt8)); // 1588338845021 30 | // 日期基本方法 31 | console.log(dt1); // Tue Dec 07 2021 10:56:26 GMT+0800 (中国标准时间) 32 | console.log(dt1.toLocaleString()); // 2021/12/7 上午10:56:26 33 | console.log(dt1.valueOf()); // 1638845786774 34 | 35 | // 日期格式化方法 36 | let dt = new Date(); 37 | console.log(dt.toDateString()); // Tue Dec 07 2021 38 | console.log(dt.toTimeString()); // 11:02:35 GMT+0800 (中国标准时间) 39 | console.log(dt.toLocaleDateString()); // 2021/12/7 40 | console.log(dt.toLocaleTimeString()); // 上午11:02:35 41 | console.log(dt.toUTCString()); // Tue, 07 Dec 2021 03:02:35 GMT 42 | console.log(dt.toJSON()); // 2021-12-07T03:02:35.844Z 43 | 44 | 45 | // ==================== RegExp ==================== 46 | 47 | // 1 使用正则表达式 48 | 49 | // 创建正则表达式 50 | let p1 = /at/g; // 匹配字符串中所有的"at" 51 | let p2 = /[bc]at/i; // 匹配第一个"bat"或"cat",忽略大小写 52 | let p3 = /.at/gi; // 将全局模式和忽略大小写结合 53 | let p4 = new RegExp(".at", "gi"); // 使用构造函数创建,等价于p3 54 | 55 | 56 | // 2 正则表达式方法 57 | 58 | // exec 59 | let text = "mom and dad and baby"; 60 | let pattern = /mom( and dad( and baby)?)?/gi; 61 | let matches = pattern.exec(text); 62 | console.log(matches.index); // 0 63 | console.log(matches.input); // "mom and dad and baby" 64 | console.log(matches[0]); // "mom and dad and baby" 65 | console.log(matches[1]); // " and dad and baby" 66 | console.log(matches[2]); // " and baby" 67 | 68 | 69 | // ==================== 原始值包装类型 ==================== 70 | 71 | // 1 Object包装 72 | 73 | let s = new Object("Dasen"); 74 | let n = new Object(2); 75 | let b = new Object(true); 76 | console.log(typeof s); // object 77 | console.log(typeof n); // object 78 | console.log(typeof b); // object 79 | console.log(s instanceof String); // true 80 | console.log(n instanceof Number); // true 81 | console.log(b instanceof Boolean); // true 82 | 83 | 84 | // 2 Boolean 85 | 86 | let b1 = new Boolean(true); 87 | let b2 = new Boolean(false); 88 | if (b1) { 89 | console.log("b1解释为真"); 90 | } 91 | if (b2) { 92 | console.log("b2解释为真"); 93 | } 94 | // b1解释为真 95 | // b2解释为真 96 | 97 | 98 | // 3 Number 99 | 100 | let n = new Number(3); 101 | let f = new Number(3.1415926535); 102 | console.log(n.toString(2)); // 11 103 | console.log(f.toFixed(2)); // 3.14 104 | console.log(f.toExponential()); // 3.1415926535e+0 105 | console.log(f.toPrecision(5)); // 3.1416 106 | 107 | 108 | // 4 String 109 | 110 | // UTF16编码 111 | let s = "我是大森"; 112 | console.log(s.charAt(1)); // 是 113 | console.log(s.charCodeAt(2)); // 22823 114 | console.log(s.charCodeAt(3).toString(16)); // 68ee 115 | console.log(String.fromCharCode(22823,0x68EE)); // 大森 116 | 117 | // UTF32编码 118 | let s = "我是大森😊"; 119 | console.log(s.charAt(4)); // � 120 | console.log(s.charAt(5)); // � 121 | console.log(s.charCodeAt(4)); // 55357 122 | console.log(s.charCodeAt(5)); // 56842 123 | console.log(s.codePointAt(4)); // 128522 124 | console.log(s.codePointAt(5)); // 56842 125 | console.log(String.fromCharCode(55357,56842)); // 😊 126 | console.log(String.fromCodePoint(128522)); // 😊 127 | 128 | // 字符串迭代器 129 | let s = "我是大森😊"; 130 | for(const c of s) { 131 | console.log(c); 132 | } 133 | // 我 134 | // 是 135 | // 大 136 | // 森 137 | // 😊 138 | 139 | // 字符串操作方法 140 | let s = "Hello"; 141 | console.log(s.concat(" world", "!")); // Hello world! 142 | console.log(s.slice(1,4)); // ell 143 | console.log(s.slice(1)); // ello 144 | console.log(s.slice(-4,-1)); // ell 145 | console.log(s.substring(1,4)); // ell 146 | console.log(s.substring(1)); // ello 147 | console.log(s.substring(-4,4)); // Hell 148 | console.log("输出空串:", s.substring(-4,-1)); // 输出空串: 149 | console.log(s.substr(1,3)); // ell 150 | console.log(s.substr(-4,3)); // ell 151 | console.log("输出空串:", s.substr(-4,-1)); // 输出空串: 152 | 153 | // 字符串位置方法 154 | let s = "Hello world!"; 155 | console.log(s.indexOf("o")); // 4 156 | console.log(s.lastIndexOf("o")); // 7 157 | console.log(s.indexOf("o",5)); // 7 158 | console.log(s.lastIndexOf("o",6)); // 4 159 | 160 | // 字符串包含方法 161 | let s = "hello"; 162 | console.log(s.startsWith("he")); // true 163 | console.log(s.startsWith("lo")); // false 164 | console.log(s.endsWith("he")); // false 165 | console.log(s.endsWith("lo")); // true 166 | console.log(s.includes("ell")); // true 167 | 168 | // 其他字符串方法 169 | console.log(" hello \n ".trim()); // hello 170 | console.log("呐呐".repeat(5)); // 呐呐呐呐呐呐呐呐呐呐 171 | console.log("Dasen".padEnd(10,"-").padStart(15,"-")); // -----Dasen----- 172 | -------------------------------------------------------------------------------- /1 JavaScript高级程序设计/04 集合引用类型.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * JavaScript高级程序设计 - 代码笔记 3 | * 04 集合引用类型 4 | * Dasen Sun 5 | * 2021-12-10 6 | **************************************************/ 7 | 8 | 9 | // ==================== 数组 ==================== 10 | 11 | // 1 创建数组 12 | 13 | let arr = []; // 数组字面量 14 | let arr = new Array(); // 创建空数组 15 | let arr = new Array(5); // 创建有五个元素的数组,元素默认值undefined 16 | let arr = new Array(1,2,3); // 创建数组[1,2,3] 17 | 18 | 19 | // 2 数组静态方法 20 | 21 | let arr1 = Array.from("string"); 22 | console.log(arr1); // ['s', 't', 'r', 'i', 'n', 'g'] 23 | let arr2 = Array.from({0:"Dasen", 1:22, length:2}); 24 | console.log(arr2); // ['Dasen', 22] 25 | let arr3 = Array.of(1,2,3,4,5); 26 | console.log(arr3); // [1, 2, 3, 4, 5] 27 | console.log(Array.isArray(arr2)); // true 28 | console.log(Array.isArray({0:"Dasen", 1:22, length:2})); // false 29 | 30 | 31 | // 3 迭代器方法 32 | 33 | let arr = ["red", "blue", "yellow"]; 34 | let iter = arr.values(); 35 | console.log(iter.next()); // {value: 'red', done: false} 36 | console.log(iter.next()); // {value: 'blue', done: false} 37 | console.log(iter.next()); // {value: 'yellow', done: false} 38 | console.log(iter.next()); // {value: undefined, done: true} 39 | console.log(arr.keys()); // Array Iterator 40 | for (const [index, value] of arr.entries()) { 41 | console.log(index, value); 42 | } 43 | // 0 red 44 | // 1 blue 45 | // 2 yellow 46 | 47 | 48 | // 4 赋值和填充方法 49 | 50 | // copyWithin 51 | let arr = [1,2,3,4,5,6,7,8,9]; 52 | arr.copyWithin(3); 53 | console.log(arr); // [1, 2, 3, 1, 2, 3, 4, 5, 6] 54 | arr = [1,2,3,4,5,6,7,8,9]; 55 | arr.copyWithin(2,6); 56 | console.log(arr); // [1, 2, 7, 8, 9, 6, 7, 8, 9] 57 | arr = [1,2,3,4,5,6,7,8,9]; 58 | arr.copyWithin(2,6,8); 59 | console.log(arr); // [1, 2, 7, 8, 5, 6, 7, 8, 9] 60 | arr = [1,2,3,4,5,6,7,8,9]; 61 | arr.copyWithin(-7,-3,-1); 62 | console.log(arr); // [1, 2, 7, 8, 5, 6, 7, 8, 9] 63 | // fill 64 | arr.fill(0,5,8); 65 | console.log(arr); // [1, 2, 7, 8, 5, 0, 0, 0, 9] 66 | arr.fill(0,5); 67 | console.log(arr); // [1, 2, 7, 8, 5, 0, 0, 0, 0] 68 | arr.fill(0); 69 | console.log(arr); // [0, 0, 0, 0, 0, 0, 0, 0, 0] 70 | 71 | 72 | // 5 转换方法 73 | 74 | let arr = [1,2,3,4,5,6,7,8,9]; 75 | console.log(arr.toString()); // 1,2,3,4,5,6,7,8,9 76 | console.log(arr.join(",")); // 1,2,3,4,5,6,7,8,9 77 | console.log(arr.join(" ")); // 1 2 3 4 5 6 7 8 9 78 | 79 | 80 | // 6 排序方法 81 | 82 | let arr = ["1", "2", "11", "12"]; 83 | arr.reverse(); 84 | console.log(arr); // ['12', '11', '2', '1'] 85 | arr.sort(); 86 | console.log(arr); // ['1', '11', '12', '2'] 87 | arr.sort((a,b) => parseInt(a)-parseInt(b)); 88 | console.log(arr); // ['1', '2', '11', '12'] 89 | 90 | 91 | // 7 搜索和位置方法 92 | 93 | let arr = [1, 2, 3, 4, 5, 4, 3, 2, 1]; 94 | console.log(arr.indexOf(3)); // 2 95 | console.log(arr.lastIndexOf(3)); // 6 96 | arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]; 97 | function find1(val) { 98 | // 寻找第一个能被 3 整除的数 99 | return !(val % 3); 100 | } 101 | function find2(val, index, array) { 102 | // 寻找能被 3 整除并位于数组后半部分的数 103 | if (val % 3) { 104 | return false; 105 | } else { 106 | if (index >= (array.length - 1) / 2) { 107 | return true; 108 | } else { 109 | return false; 110 | } 111 | } 112 | } 113 | console.log(arr.find(find1)); // 3 114 | console.log(arr.find(find2)); // 6 115 | console.log(arr.findIndex(find1)); // 2 116 | console.log(arr.findIndex(find2)); // 5 117 | 118 | 119 | // 8 迭代方法 120 | 121 | let arr1 = [1, "a", 2, "b", 3, "c"]; 122 | let arr2 = [1, 2, 3, 4, 5, 6]; 123 | console.log(arr1.every((val) => typeof val === "number")); // false 124 | console.log(arr2.every((val) => typeof val === "number")); // true 125 | console.log(arr1.some((val) => typeof val === "string")); // true 126 | console.log(arr2.some((val) => typeof val === "string")); // false 127 | console.log(arr1.filter((val) => typeof val === "number")); // [1, 2, 3] 128 | console.log(arr1.filter((val) => typeof val === "string")); // ['a', 'b', 'c'] 129 | let sum = 0; 130 | arr2.forEach((val) => sum+=val) 131 | console.log(sum); // 21 132 | console.log(arr2.map((val) => val.toString())); // ['1', '2', '3', '4', '5', '6'] 133 | 134 | 135 | // 9 归并方法 136 | 137 | // 数组求和 138 | let arr = [1, 2, 3, 4, 5, 6]; 139 | let sum = arr.reduce((presum, cur) => presum+cur); 140 | console.log(sum); // 21 141 | 142 | // 统计 2 在数组中出现了几次 143 | let arr = [1, 2, 3, 2, 5, 6, 2, 8, 9]; 144 | let count = arr.reduce((precount, cur, index) => { 145 | if(index === 1) { 146 | let c = 0; 147 | if(precount === 2) c++; 148 | if(cur === 2) c++; 149 | return c; 150 | } else { 151 | return precount + (cur === 2); 152 | } 153 | }); 154 | console.log(count); // 3 155 | 156 | // 不可能实现的求斐波那契数列 157 | let arr = [0, 1]; 158 | let n = 10; 159 | let res = arr.reduce((pre, cur, index, array) => { 160 | if(index <= n) { 161 | array.push(pre+cur); 162 | } 163 | return cur; 164 | }); 165 | console.log(res); // 1 166 | console.log(arr); // [0, 1, 1] 167 | 168 | 169 | // 10 其他方法 170 | 171 | // concat 172 | let colors = ["red", "green", "blue"]; 173 | let colors2 = colors.concat("yellow", ["black", "brown"]); 174 | console.log(colors); // ["red", "green", "blue"] 175 | console.log(colors2); // ["red", "green", "blue", "yellow", "black", "brown"] 176 | 177 | // 阻止concat打平数组 178 | let colors = ["red", "green", "blue"]; 179 | let colors2 = ["black", "brown"]; 180 | let colors3 = ["black", "brown"]; 181 | colors3[Symbol.isConcatSpreadable] = false; 182 | console.log(colors.concat(colors2)); // ['red', 'green', 'blue', 'black', 'brown'] 183 | console.log(colors.concat(colors3)); // ['red', 'green', 'blue', Array(2)] 184 | 185 | // slice 186 | let arr = [1,2,3,4,5,6,7,8,9]; 187 | console.log(arr.slice(5)); // [6, 7, 8, 9] 188 | console.log(arr.slice(5,7)); // [6, 7] 189 | 190 | // splice 191 | let arr = [1,2,3,4,5,6,7,8,9]; 192 | let returnVal; 193 | returnVal = arr.splice(5,4,4,3,2,1); // 把 5 6 7 8 替换为 4 3 2 1 194 | console.log(arr); // [1, 2, 3, 4, 5, 4, 3, 2, 1] 195 | console.log(returnVal); // [6, 7, 8, 9] 196 | returnVal = arr.splice(4,arr.length-4); // 只保留前四个元素,删除后面所有元素 197 | console.log(arr); // [1, 2, 3, 4] 198 | console.log(returnVal); // [5, 4, 3, 2, 1] 199 | returnVal = arr.splice(0,0,4,3,2); // 在索引0位置上插入 4 3 2 200 | console.log(arr); // [4, 3, 2, 1, 2, 3, 4] 201 | console.log(returnVal); // [] 202 | 203 | 204 | // ==================== 映射 ==================== 205 | 206 | // 1 创建映射 207 | 208 | // 创建一个空映射 209 | let m = new Map(); 210 | 211 | // 创建一个映射,由键值对的数组给出初始值 212 | let m = new Map([ 213 | ["key1", "val1"], 214 | ["key2", "val2"], 215 | ["key3", "val3"] 216 | ]); 217 | 218 | // 创建一个映射,由可迭代对象创建 219 | let m = new Map({ 220 | * [Symbol.iterator]() { 221 | yield ["key1", "val1"]; 222 | yield ["key2", "val2"]; 223 | yield ["key3", "val3"]; 224 | } 225 | }); 226 | 227 | 228 | // 2 键值方法 229 | 230 | let m = new Map(); 231 | console.log(m); // Map(0) 232 | m.set("name","Dasen").set("age",22); // 连缀使用set 233 | console.log(m); // Map(2) {name => Dasen, age => 22} 234 | console.log(m.get("name")); // Dasen 235 | console.log(m.has("age")); // true 236 | m.delete("age"); 237 | console.log(m.has("age")); // false 238 | console.log(m.size); // 1 239 | m.clear(); 240 | console.log(m); // Map(0) 241 | 242 | 243 | // 3 迭代器方法 244 | 245 | let m = new Map([ 246 | ["key1", "val1"], 247 | ["key2", "val2"], 248 | ["key3", "val3"] 249 | ]); 250 | let valIter = m.values(); 251 | console.log(valIter.next()); // {value: 'val1', done: false} 252 | console.log(valIter.next()); // {value: 'val2', done: false} 253 | console.log(valIter.next()); // {value: 'val3', done: false} 254 | console.log(valIter.next()); // {value: undefined, done: true} 255 | for (const key of m.keys()) { 256 | console.log(key); 257 | } 258 | // key1 259 | // key2 260 | // key3 261 | for (const [key, val] of m) { 262 | console.log(key,":",val); 263 | } 264 | // key1 : val1 265 | // key2 : val2 266 | // key3 : val3 267 | m.forEach((val,key) => console.log(key,":",val)); 268 | // key1 : val1 269 | // key2 : val2 270 | // key3 : val3 271 | 272 | 273 | // ==================== 弱映射 ==================== 274 | 275 | // 1 弱映射管理私有变量 276 | 277 | let User = (() => { 278 | const wm = new WeakMap(); 279 | class User { 280 | constructor(id) { 281 | this.setId(id); 282 | } 283 | setPrivate(property, value) { 284 | const privateProperty = wm.get(this) || {}; 285 | privateProperty[property] = value; 286 | wm.set(this, privateProperty); 287 | } 288 | getPrivate(property) { 289 | const privateProperty = wm.get(this) || {}; 290 | return privateProperty[property]; 291 | } 292 | setId(id) { 293 | this.setPrivate("id",id); 294 | } 295 | getId() { 296 | return this.getPrivate("id"); 297 | } 298 | } 299 | return User; 300 | })(); 301 | let user = new User(2021); 302 | console.log(user.getId()); // 2021 303 | 304 | 305 | // ==================== 集合 ==================== 306 | 307 | // 1 创建集合 308 | 309 | // 创建空集合 310 | let s = Set(); 311 | // 从可迭代对象创建集合 312 | let s = Set([1,2,3]); 313 | -------------------------------------------------------------------------------- /1 JavaScript高级程序设计/05 迭代器与生成器.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * JavaScript高级程序设计 - 代码笔记 3 | * 05 迭代器与生成器 4 | * Dasen Sun 5 | * 2021-12-12 6 | **************************************************/ 7 | 8 | 9 | // ==================== 迭代器 ==================== 10 | 11 | // 1 可迭代对象 12 | 13 | // 可迭代接口 14 | let arr = []; 15 | let obj = {}; 16 | console.log(arr[Symbol.iterator]); // ƒ values() 17 | console.log(obj[Symbol.iterator]); // undefined 18 | 19 | // 使用迭代结构 20 | let nums = [1,2,3]; 21 | for (const num of nums) { 22 | console.log(num); 23 | } 24 | // 1 25 | // 2 26 | // 3 27 | 28 | 29 | // 2 直接使用迭代器 30 | 31 | let arr = [1,2,3]; 32 | let iter = arr[Symbol.iterator](); // 取得arr的迭代器对象 33 | console.log(iter); // Array Iterator 34 | console.log(iter.next()); // {value: 1, done: false} 35 | console.log(iter.next()); // {value: 2, done: false} 36 | console.log(iter.next()); // {value: 3, done: false} 37 | console.log(iter.next()); // {value: undefined, done: true} 38 | console.log(iter.next()); // {value: undefined, done: true} 39 | 40 | 41 | // 3 自定义迭代器 42 | 43 | // 实现next方法的对象 44 | class Counter { 45 | constructor(end) { 46 | this.count = 0; 47 | this.end = end; 48 | } 49 | next() { 50 | if(this.count < this.end) { 51 | return {value: this.count++, done: false}; 52 | } else { 53 | return {value: undefined, done: true}; 54 | } 55 | } 56 | [Symbol.iterator]() { 57 | return this; 58 | } 59 | } 60 | let counter = new Counter(3); 61 | for (const c of counter) { 62 | console.log(c); 63 | } 64 | // 0 65 | // 1 66 | // 2 67 | for (const c of counter) { 68 | console.log(c); 69 | } 70 | // 无输出 71 | 72 | // 闭包改进迭代器 73 | class Counter { 74 | constructor(end) { 75 | this.count = 0; 76 | this.end = end; 77 | } 78 | [Symbol.iterator]() { 79 | let count = 0; 80 | let end = this.end; 81 | return { 82 | next() { 83 | if(count < end) { 84 | return {value: count++, done: false}; 85 | } else { 86 | return {value: undefined, done: true}; 87 | } 88 | } 89 | }; 90 | } 91 | } 92 | let counter = new Counter(3); 93 | for (const c of counter) { 94 | console.log(c); 95 | } 96 | // 0 97 | // 1 98 | // 2 99 | for (const c of counter) { 100 | console.log(c); 101 | } 102 | // 0 103 | // 1 104 | // 2 105 | 106 | 107 | // 4 提前关闭迭代器 108 | 109 | // 迭代器的return方法 110 | class Counter { 111 | constructor(end) { 112 | this.count = 0; 113 | this.end = end; 114 | } 115 | [Symbol.iterator]() { 116 | let count = 0; 117 | let end = this.end; 118 | return { 119 | next() { 120 | if(count < end) { 121 | return {value: count++, done: false}; 122 | } else { 123 | return {value: undefined, done: true}; 124 | } 125 | }, 126 | return() { 127 | console.log("迭代器提前关闭啦!"); 128 | return { done: true }; 129 | } 130 | }; 131 | } 132 | } 133 | let counter = new Counter(3); 134 | for (const c of counter) { 135 | console.log(c); 136 | if(c>0) break; 137 | } 138 | // 0 139 | // 1 140 | // 迭代器提前关闭啦! 141 | for (const c of counter) { 142 | console.log(c); 143 | if(c>1) break; 144 | } 145 | // 0 146 | // 1 147 | // 2 148 | // 迭代器提前关闭啦! 149 | 150 | // 不可关闭的迭代器 151 | let arr = [1,2,3,4,5,6]; 152 | let iter = arr[Symbol.iterator](); 153 | for (const i of iter) { 154 | console.log(i); 155 | if(i>2) break; 156 | } 157 | console.log("继续迭代"); 158 | for (const i of iter) { 159 | console.log(i); 160 | } 161 | // 1 162 | // 2 163 | // 3 164 | // 继续迭代 165 | // 4 166 | // 5 167 | // 6 168 | 169 | 170 | // 5 扩展操作符 171 | 172 | let arr1 = [1, 2, 3]; 173 | let arr2 = [...arr1]; 174 | let arr3 = [0, ...arr2, 4, 5, 6]; 175 | console.log(arr1); // [1, 2, 3] 176 | console.log(arr2); // [1, 2, 3] 177 | console.log(arr3); // [0, 1, 2, 3, 4, 5, 6] 178 | 179 | 180 | // ==================== 生成器 ==================== 181 | 182 | // 1 生成器状态 183 | 184 | function * generator() { 185 | console.log("生成器开始执行了!"); 186 | yield "第1次返回值"; 187 | console.log("又开始执行了!"); 188 | yield "第2次返回值"; 189 | console.log("又又开始执行了!"); 190 | yield "第3次返回值"; 191 | console.log("要结束了!"); 192 | return "Bye!" 193 | } 194 | let g = generator(); // 未开始执行 195 | let r = g.next(); // 生成器开始执行了! 196 | console.log(r); // {value: '第1次返回值', done: false} 197 | r = g.next(); // 又开始执行了! 198 | console.log(r); // {value: '第2次返回值', done: false} 199 | r = g.next(); // 又又开始执行了! 200 | console.log(r); // {value: '第3次返回值', done: false} 201 | r = g.next(); // 要结束了! 202 | console.log(r); // {value: 'Bye!', done: true} 203 | r = g.next(); 204 | console.log(r); // {value: undefined, done: true} 205 | 206 | 207 | // 2 迭代生成器 208 | 209 | // 每次迭代新的生成器 210 | function * generator() { 211 | yield "val1"; 212 | yield "val2"; 213 | yield "val3"; 214 | return "Bye" 215 | } 216 | for (const i of generator()) { 217 | console.log(i); 218 | } 219 | // val1 220 | // val2 221 | // val3 222 | console.log("再来一次"); // 再来一次 223 | for (const i of generator()) { 224 | console.log(i); 225 | } 226 | // val1 227 | // val2 228 | // val3 229 | 230 | // 迭代同一个生成器 231 | function * generator() { 232 | yield "val1"; 233 | yield "val2"; 234 | yield "val3"; 235 | return "Bye" 236 | } 237 | let g = generator(); 238 | for (const i of g) { 239 | console.log(i); 240 | } 241 | // val1 242 | // val2 243 | // val3 244 | for (const i of g) { 245 | console.log(i); 246 | } 247 | // 没有输出 248 | 249 | 250 | // 3 yield关键字输入输出 251 | 252 | function * generator() { 253 | let a = 1; 254 | a *= yield a; // (1) 255 | a *= yield a; // (2) 256 | a *= yield a; // (3) 257 | return a; 258 | } 259 | let g = generator(); // 创建生成器 260 | console.log(g.next()); // 第一次next仅仅是为了启动生成器,获得了(1)中yield的值,但不会向yield传值 261 | console.log(g.next(2)); // 向(1)中yield传入2,生成器使a乘以2,同时获得了(2)中yield的值 262 | console.log(g.next(3)); // 向(2)中yield传入3,生成器使a乘以3,同时获得了(3)中yield的值 263 | console.log(g.next(4)); // 向(3)中yield传入4,生成器使a乘以4,同时获得了返回值 264 | 265 | 266 | // 4 yield * 267 | 268 | function * generator() { 269 | yield * [1,2,3]; 270 | } 271 | let g = generator(); 272 | console.log(g.next()); // {value: 1, done: false} 273 | console.log(g.next()); // {value: 2, done: false} 274 | console.log(g.next()); // {value: 3, done: false} 275 | console.log(g.next()); // {value: undefined, done: true} 276 | 277 | 278 | // 5 使用生成器函数作为默认迭代器 279 | 280 | let obj = new class { 281 | constructor() { 282 | this.values = [1,2,3]; 283 | } 284 | * [Symbol.iterator]() { 285 | yield * this.values; 286 | } 287 | }(); 288 | for (const i of obj) { 289 | console.log(i); 290 | } 291 | // 1 292 | // 2 293 | // 3 294 | 295 | 296 | // 6 提前关闭生成器 297 | 298 | // return方法 299 | function * generator() { 300 | yield * [1,2,3,4,5,6]; 301 | } 302 | let g = generator(); 303 | for (const i of g) { 304 | console.log(i); 305 | if(i>2) break; 306 | } 307 | // 1 308 | // 2 309 | // 3 310 | console.log(g.next()); // {value: undefined, done: true} 311 | for (const i of g) { 312 | console.log(i); 313 | } 314 | 315 | // throw方法 316 | function * generator() { 317 | for (const i of [1,2,3,4]) { 318 | try { 319 | yield i; 320 | } catch (e) { 321 | console.log(e); 322 | } 323 | } 324 | } 325 | let g = generator(); 326 | console.log(g.next()); // {value: 1, done: false} 327 | g.throw("啊呀呀!出错了呢!"); 328 | let r = g.next(); // 啊呀呀!出错了呢! 329 | console.log(r); // {value: 3, done: false} 330 | console.log(g.next()); // {value: 4, done: false} 331 | console.log(g.next()); // {value: undefined, done: true} 332 | -------------------------------------------------------------------------------- /1 JavaScript高级程序设计/06 面向对象.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * JavaScript高级程序设计 - 代码笔记 3 | * 06 面向对象 4 | * Dasen Sun 5 | * 2021-11-29 6 | **************************************************/ 7 | 8 | 9 | // ==================== 对象与属性 ==================== 10 | 11 | // 1 创建对象和配置属性 12 | 13 | // 创建一个Object的实例,为它添加属性 14 | let person = new Object(); 15 | person.name = "Dasen Sun"; 16 | person.age = 22; 17 | person.sayName = function() { 18 | console.log(this.name); 19 | }; 20 | 21 | // 对象字面量 22 | let person = { 23 | name: "Dasen Sun", 24 | age: 22, 25 | sayName() { 26 | console.log(this.name); 27 | } 28 | }; 29 | 30 | // 定义带配置项的属性 31 | let person = {}; 32 | Object.defineProperty(person, "name", { 33 | writable: false, 34 | value: "Dasen" 35 | }); 36 | 37 | // 修改已有属性 38 | let person = { 39 | name: "Dasen" 40 | }; 41 | person.name = "Dasen Sun"; // 修改成功 42 | Object.defineProperty(person, "name", { 43 | writable: false 44 | }); 45 | person.name = "dasen"; // 静默失败 46 | console.log(person); // {name: 'Dasen Sun'} 47 | 48 | // 定义访问器属性 49 | let person = { 50 | name: "大森" 51 | }; 52 | Object.defineProperty(person, "nickname", { 53 | get() { 54 | return "可爱的" + this.name; 55 | }, 56 | set(newVal) { 57 | if(newVal.slice(0,3) === "可爱的") { 58 | this.name = newVal.slice(3); 59 | } else { 60 | this.name = newVal; 61 | } 62 | } 63 | }); 64 | console.log(person.name); // 大森 65 | console.log(person.nickname); // 可爱的大森 66 | person.nickname = "可爱的Dasen"; 67 | console.log(person.name); // Dasen 68 | console.log(person.nickname); // 可爱的Dasen 69 | person.nickname = "Dasen Sun"; 70 | console.log(person.name); // Dasen Sun 71 | console.log(person.nickname); // 可爱的Dasen Sun 72 | 73 | 74 | // 2 合并对象 75 | 76 | dest = { 77 | set a(val) { 78 | console.log(`Invoked dest setter with param ${val}`); 79 | } 80 | }; 81 | src = { 82 | get a() { 83 | console.log('Invoked src getter'); 84 | return 'foo'; 85 | } 86 | }; 87 | Object.assign(dest, src); 88 | console.log(dest); // { set a(val) {...} } 89 | dest2 = { a:'aaa' }; 90 | Object.assign(dest2, src); 91 | console.log(dest2); // {a: 'foo'} 92 | 93 | 94 | // 3 相等比较 95 | 96 | // ES6之前 97 | console.log(+0 === -0); // true 98 | console.log(+0 === 0); // true 99 | console.log(-0 === 0); // true 100 | console.log(NaN === NaN); // false 101 | console.log(isNaN(NaN)); // true,要确定NaN的相等性,必须使用isNaN() 102 | 103 | // ES6新方法 104 | console.log(Object.is(+0, -0)); // false 105 | console.log(Object.is(+0, 0)); // true 106 | console.log(Object.is(-0, 0)); // false 107 | console.log(Object.is(NaN, NaN)); // true 108 | console.log(Object.is({}, {})); // false 109 | 110 | 111 | // 4 对象与属性语法糖 112 | 113 | // 属性值简写 114 | let name = "Dasen"; 115 | let age = 22; 116 | let person = { 117 | name, 118 | age 119 | }; 120 | 121 | // 可计算属性名 122 | let nameKey = "name"; 123 | let ageKey = "age"; 124 | let person = { 125 | [nameKey]: "Dasen", 126 | [ageKey]: 22 127 | }; 128 | 129 | // 简写方法名 130 | let person = { 131 | name: "Dasen", 132 | sayName() { 133 | console.log(this.name); 134 | } 135 | }; 136 | 137 | 138 | // 5 对象解构 139 | 140 | // 不使用对象解构 141 | let person = { 142 | name: "Dasen", 143 | age: 22 144 | }; 145 | let personName = person.name, personAge = person.age; 146 | 147 | // 使用对象解构 148 | let person = { 149 | name: "Dasen", 150 | age: 22 151 | }; 152 | let { name: personName, age: personAge } = person; 153 | 154 | // 使用属性简写 155 | let person = { 156 | name: "Dasen", 157 | age: 22 158 | }; 159 | let { name, age } = person; 160 | 161 | // 尝试提取不存在的属性 162 | let person = { 163 | name: "Dasen", 164 | age: 22 165 | }; 166 | let { name, job } = person; 167 | let { name, job="Software engineer" } = person; 168 | let { name: personName, job: personJob="Software engineer" } = person; 169 | 170 | // 对原始值的结构 171 | let { length } = "Dasen"; 172 | console.log(length); // 5 173 | let { constructor: c } = 2; 174 | console.log(c === Number); // true 175 | 176 | // 使用事先声明的变量解构 177 | let personName, personAge; 178 | let person = { 179 | name: "Dasen", 180 | age: 22 181 | }; 182 | ({ name: personName, age: personAge } = person); 183 | 184 | // 嵌套解构 185 | let person = { 186 | name: "Dasen", 187 | age: 22, 188 | job: { 189 | title: "Software engineer" 190 | } 191 | }; 192 | let { job: { title } } = person; 193 | 194 | // 嵌套解构复制对象 195 | let person = { 196 | name: "Dasen", 197 | age: 22, 198 | job: { 199 | title: "Software engineer" 200 | } 201 | }; 202 | let personCopy = {}; 203 | ({ name: personCopy.name, age: personCopy.age, job: personCopy.job } = person); 204 | 205 | // 参数匹配 206 | function printPerson(info, {name, age}) { 207 | console.log(info, name, age); 208 | } 209 | let person = { 210 | name: "Dasen", 211 | age: 22 212 | }; 213 | printPerson("1st", {name:"Dasen", age:22}); // 1st Dasen 22 214 | printPerson("2nd", person); // 2nd Dasen 22 215 | 216 | 217 | // ==================== 创建对象 ==================== 218 | 219 | // 1 工厂模式 220 | 221 | function createPerson(name, age) { 222 | let o = new Object(); 223 | o.name = name; 224 | o.age = age; 225 | o.sayName = function () { 226 | console.log(this.name); 227 | }; 228 | return o; 229 | } 230 | person = createPerson("Dasen", 22); 231 | console.log(person); // {name: 'Dasen', age: 22, sayName: ƒ} 232 | 233 | 234 | // 2 构造函数模式 235 | 236 | // 普通构造函数 237 | function Person(name, age) { 238 | this.name = name; 239 | this.age = age; 240 | this.sayName = function () { 241 | console.log(this.name); 242 | }; 243 | } 244 | // 使用new操作符调用——构造函数 245 | let person = new Person("Dasen", 22); 246 | console.log(person); // {name: 'Dasen', age: 22, sayName: ƒ} 247 | // 不使用new操作符调用——普通函数 248 | let person = Person("Dasen", 22); 249 | console.log(person); // undefined 250 | console.log(window.name, window.age); // Dasen 22 251 | // 判断类型 252 | console.log(person.constructor === Person); // true 253 | console.log(person instanceof Person); // true 254 | 255 | // 改造的共享方法的构造函数 256 | function Person(name, age) { 257 | this.name = name; 258 | this.age = age; 259 | this.sayName = sayName; 260 | } 261 | function sayName() { 262 | console.log(this.name); 263 | }; 264 | let person = new Person("Dasen", 22); 265 | person.sayName(); 266 | 267 | 268 | // 3 原型模式 269 | 270 | function Person(name, age) { 271 | this.name = name; 272 | this.age = age; 273 | } 274 | Person.prototype.sayName = function () { 275 | console.log(this.name); 276 | }; 277 | let person1 = new Person("Dasen", 22); 278 | let person2 = new Person("Three Zhang", 18); 279 | person1.sayName(); 280 | person2.sayName(); 281 | 282 | 283 | // ==================== 原型 ==================== 284 | 285 | // 1 字面值重写原型 286 | 287 | // 没有添加constructor属性 288 | function Person(name, age) { 289 | this.name = name; 290 | this.age = age; 291 | } 292 | Person.prototype = { 293 | sayName() { 294 | console.log("我的名字叫", this.name); 295 | }, 296 | sayAge() { 297 | console.log("我今年", this.age, "岁了"); 298 | } 299 | }; 300 | let person = new Person("Dasen", 22); 301 | console.log(person instanceof Person); // true 302 | console.log(person.constructor === Person); // false 303 | 304 | // 添加了constructor属性 305 | function Person(name, age) { 306 | this.name = name; 307 | this.age = age; 308 | } 309 | Person.prototype = { 310 | constructor: Person, 311 | sayName() { 312 | console.log("我的名字叫", this.name); 313 | }, 314 | sayAge() { 315 | console.log("我今年", this.age, "岁了"); 316 | } 317 | }; 318 | let person = new Person("Dasen", 22); 319 | console.log(person instanceof Person); // true 320 | console.log(person.constructor === Person); // true 321 | 322 | 323 | // ==================== 继承 ==================== 324 | 325 | // 1 原型链继承 326 | 327 | function SuperType() { 328 | this.property = "SuperType"; 329 | } 330 | SuperType.prototype.getSuperValue = function () { 331 | return this.property 332 | }; 333 | function SubType() { 334 | this.subproperty = "SubType"; 335 | } 336 | SubType.prototype = new SuperType(); 337 | SubType.prototype.getSubValue = function () { 338 | return this.subproperty; 339 | }; 340 | let sub = new SubType(); 341 | console.log(sub.getSuperValue(), sub.getSubValue()); // SuperType SubType 342 | console.log(sub.property, sub.subproperty); // SuperType SubType 343 | console.log(sub instanceof SuperType); // true 344 | console.log(sub instanceof SubType); // true 345 | console.log(sub instanceof Object); // true 346 | console.log(SuperType.prototype.isPrototypeOf(sub)); // true 347 | console.log(SubType.prototype.isPrototypeOf(sub)); // true 348 | console.log(Object.prototype.isPrototypeOf(sub)); // true 349 | 350 | 351 | // 2 盗用构造函数 352 | 353 | function SuperType() { 354 | this.names = ["Dasen", "Dasen Sun"]; 355 | } 356 | function SubType() { 357 | SuperType.call(this); 358 | } 359 | let names1 = new SubType(); 360 | let names2 = new SubType(); 361 | console.log(names1.names, names2.names); // ['Dasen', 'Dasen Sun'] ['Dasen', 'Dasen Sun'] 362 | names1.names.push("Sadose"); 363 | console.log(names1.names, names2.names); // ['Dasen', 'Dasen Sun', 'Sadose'] ['Dasen', 'Dasen Sun'] 364 | 365 | 366 | // 3 组合继承 367 | 368 | function SuperType(name) { 369 | this.name = name; 370 | if(arguments.length > 0) { 371 | this.friends = [...arguments]; 372 | this.friends.shift(); 373 | } else { 374 | this.friends = []; 375 | } 376 | } 377 | SuperType.prototype.getFriends = function () { 378 | console.log(this.friends.join(", ")); 379 | }; 380 | function SubType(name, age) { 381 | let friends = Array.prototype.slice.call(arguments, 2); 382 | SuperType.call(this, name, ...friends); 383 | this.age = age; 384 | } 385 | SubType.prototype = new SuperType(); 386 | SubType.prototype.getAge = function () { 387 | console.log(this.age); 388 | }; 389 | let dasen = new SubType("Dasen", 22, "Xiaoyu", "Three"); 390 | let three = new SubType("Three", 18, "Dasen"); 391 | console.log(dasen); // SubType {name: 'Dasen', friends: Array(2), age: 22} 392 | console.log(three); // SubType {name: 'Three', friends: Array(1), age: 18} 393 | dasen.getAge(); // 22 394 | dasen.getFriends(); // Xiaoyu, Three 395 | console.log(dasen instanceof SuperType); // true 396 | console.log(dasen instanceof SubType); // true 397 | 398 | 399 | // 4 原型式继承 400 | 401 | let person = { 402 | name: "Dasen", 403 | friends: ["Xiaoyu", "Three"] 404 | }; 405 | let anotherPerson = Object.create(person); 406 | anotherPerson.name = "Xiaoyu"; 407 | anotherPerson.friends.push("Tom"); 408 | let yetAnotherPerson = Object.create(person, { 409 | name: { 410 | value: "Three" 411 | } 412 | }); 413 | yetAnotherPerson.friends.push("Jack"); 414 | console.log(person.name, anotherPerson.name, yetAnotherPerson.name); // Dasen Xiaoyu Three 415 | console.log(person.friends); // ['Xiaoyu', 'Three', 'Tom', 'Jack'] 416 | console.log(anotherPerson.__proto__ === person); // true 417 | console.log(yetAnotherPerson.__proto__ === person); // true 418 | 419 | 420 | // 5 寄生式继承 421 | 422 | let person = { 423 | name: "Dasen", 424 | age: 22 425 | }; 426 | function factoryFun(person) { 427 | let clone = Object.create(person); 428 | clone.sayHi = function () { 429 | console.log("Hi. My name is " + this.name + "."); 430 | console.log("I'm " + this.age + " years old."); 431 | }; 432 | return clone; 433 | } 434 | let confidentPerson = factoryFun(person); 435 | confidentPerson.sayHi(); 436 | 437 | 438 | // 6 寄生式组合继承 439 | 440 | function inheritPrototype(subType, supperType) { 441 | let prototype = Object.create(supperType.prototype); 442 | prototype.constructor = subType; 443 | subType.prototype = prototype; 444 | } 445 | function SuperType(name) { 446 | this.name = name; 447 | if(arguments.length > 0) { 448 | this.friends = [...arguments]; 449 | this.friends.shift(); 450 | } else { 451 | this.friends = []; 452 | } 453 | } 454 | SuperType.prototype.getFriends = function () { 455 | console.log(this.friends.join(", ")); 456 | }; 457 | function SubType(name, age) { 458 | let friends = Array.prototype.slice.call(arguments, 2); 459 | SuperType.call(this, name, ...friends); 460 | this.age = age; 461 | } 462 | inheritPrototype(SubType, SuperType); 463 | SubType.prototype.getAge = function () { 464 | console.log(this.age); 465 | }; 466 | let dasen = new SubType("Dasen", 22, "Xiaoyu", "Three"); 467 | let three = new SubType("Three", 18, "Dasen"); 468 | console.log(dasen); // SubType {name: 'Dasen', friends: Array(2), age: 22} 469 | console.log(three); // SubType {name: 'Three', friends: Array(1), age: 18} 470 | dasen.getAge(); // 22 471 | dasen.getFriends(); // Xiaoyu, Three 472 | console.log(dasen instanceof SuperType); // true 473 | console.log(dasen instanceof SubType); // true 474 | 475 | 476 | // ==================== 类 ==================== 477 | 478 | // 1 生成器方法 479 | 480 | // 生成器方法和静态生成器方法 481 | class Person { 482 | *createNameIterator() { 483 | yield "Dasen"; 484 | yield "Dasen Sun"; 485 | yield "Sadose"; 486 | } 487 | static *createHobbyIterator() { 488 | yield "Code"; 489 | yield "Girl"; 490 | yield "Cube"; 491 | } 492 | } 493 | let person = new Person(); 494 | let names = person.createNameIterator(); 495 | let hobbies = Person.createHobbyIterator(); 496 | console.log(names.next().value); // Dasen 497 | console.log(names.next().value); // Dasen Sun 498 | console.log(names.next().value); // Sadose 499 | console.log(hobbies.next().value); // Code 500 | console.log(hobbies.next().value); // Girl 501 | console.log(hobbies.next().value); // Cube 502 | 503 | // 可迭代对象 504 | class Person { 505 | *[Symbol.iterator]() { 506 | yield "Dasen"; 507 | yield "Dasen Sun"; 508 | yield "Sadose"; 509 | } 510 | } 511 | let p = new Person(); 512 | for(let name of p) { 513 | console.log(name); 514 | } 515 | 516 | 517 | // 2 继承 518 | 519 | // 抽象基类 520 | class Vehicle { 521 | constructor() { 522 | console.log(new.target); 523 | if(new.target === Vehicle) { 524 | throw new Error("Vehicle 是一个抽象基类,不可以实例化!"); 525 | } 526 | } 527 | } 528 | class Bus extends Vehicle {} 529 | let v; 530 | try { 531 | v = new Vehicle(); // class Vehicle 532 | } catch (error) { 533 | console.log(error); // Error: Vehicle 是一个抽象基类,不可以实例化! 534 | } 535 | let b = new Bus(); // class Bus extends Vehicle 536 | console.log(v, b); // undefined Bus 537 | 538 | // 抽象基类要求派生类必须拥有某个方法 539 | class Vehicle { 540 | constructor() { 541 | console.log(this); 542 | if(new.target === Vehicle) { 543 | throw new Error("Vehicle 是一个抽象基类,不可以实例化!"); 544 | } 545 | if(!this.drive) { 546 | throw new Error("车必须能开!"); 547 | } 548 | console.log("成功!"); 549 | } 550 | } 551 | class Bus extends Vehicle { 552 | drive() {} 553 | } 554 | class BadVehicle extends Vehicle {} 555 | try { 556 | new Vehicle(); 557 | } catch (error) { 558 | console.log(error); // Error: Vehicle 是一个抽象基类,不可以实例化! 559 | } 560 | try { 561 | new BadVehicle(); 562 | } catch (error) { 563 | console.log(error); // Error: 车必须能开! 564 | } 565 | new Bus(); // 成功! 566 | 567 | // 继承内置类型 568 | class SuperArray extends Array { 569 | shuffle() { 570 | for(let i=this.length-1; i>0; --i) { 571 | const j = Math.floor(Math.random()*(i+1)); 572 | [this[i], this[j]] = [this[j], this[i]]; 573 | } 574 | } 575 | } 576 | let a = new SuperArray(1, 2, 3, 4, 5, 6, 7, 8, 9); 577 | console.log(a); // SuperArray(9) [1, 2, 3, 4, 5, 6, 7, 8, 9] 578 | a.shuffle(); 579 | console.log(a); // SuperArray(9) [8, 4, 7, 9, 5, 6, 3, 1, 2] 580 | console.log(a instanceof SuperArray); // true 581 | console.log(a instanceof Array); // true 582 | 583 | // 类混入 584 | class Baseclass {} 585 | let AMixin = (Superclass) => class extends Superclass { 586 | funA() { 587 | console.log("A"); 588 | } 589 | } 590 | let BMixin = (Superclass) => class extends Superclass { 591 | funB() { 592 | console.log("B"); 593 | } 594 | } 595 | let CMixin = (Superclass) => class extends Superclass { 596 | funC() { 597 | console.log("C"); 598 | } 599 | } 600 | function mix(baseclass, ...mixins) { 601 | return mixins.reduce((pre, cur) => cur(pre), baseclass); 602 | } 603 | class Subclass extends mix(Baseclass, AMixin, BMixin, CMixin) {} 604 | let o = new Subclass(); 605 | o.funA(); // A 606 | o.funB(); // B 607 | o.funC(); // C 608 | -------------------------------------------------------------------------------- /1 JavaScript高级程序设计/07 代理与反射.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * JavaScript高级程序设计 - 代码笔记 3 | * 07 代理与反射 4 | * Dasen Sun 5 | * 2021-12-17 6 | **************************************************/ 7 | 8 | 9 | // ==================== 代理 ==================== 10 | 11 | // 1 定义并使用代理 12 | 13 | // 创建一个对象 obj 和它的代理 proxy 14 | let obj = { 15 | name: "Dasen", 16 | age: 22, 17 | girlfriend: null 18 | }; 19 | let proxy = new Proxy(obj, {}); 20 | // 访问属性没有差别 21 | console.log(obj.name); // Dasen 22 | console.log(obj.name); // Dasen 23 | // 操作属性没有差别 24 | proxy.age = 21; 25 | console.log(obj.age); // 21 26 | proxy.age--; 27 | console.log(obj.age); // 20 28 | obj.age += 2; 29 | console.log(proxy.age); // 22 30 | // 甚至方法的调用都没有差别 31 | console.log(obj.hasOwnProperty("girlfriend")); // true 32 | console.log(proxy.hasOwnProperty("girlfriend")); // true 33 | console.log(proxy.girlfriend); // null 34 | // 相等判断可以区分代理和原对象 35 | console.log(proxy == obj); // false 36 | console.log(proxy === obj); // false 37 | // 代理构造函数没有原型因此不能使用 instanceof 操作符判断对象是不是代理 38 | console.log(typeof obj); // object 39 | console.log(typeof proxy); // object 40 | console.log(obj instanceof Object); // true 41 | console.log(proxy instanceof Object); // true 42 | console.log(proxy instanceof Proxy); // TypeError: Function has non-object prototype 'undefined' in instanceof check 43 | 44 | 45 | // 2 捕获器 46 | 47 | // 使用捕获器 48 | let obj = { 49 | name: "Dasen", 50 | age: 22, 51 | girlfriend: null 52 | }; 53 | let handler = { 54 | get(tar, property, rec) { 55 | if(property==="girlfriend" && tar[property]===null) { 56 | return "无可奉告!"; 57 | } else { 58 | return Reflect.get(...arguments); 59 | } 60 | } 61 | } 62 | let proxy = new Proxy(obj, handler); 63 | console.log(proxy.name); // Dasen 64 | console.log(proxy.age); // 22 65 | console.log(obj.girlfriend); // null 66 | console.log(proxy.girlfriend); // 无可奉告! 67 | obj.girlfriend = "someone"; 68 | console.log(proxy.girlfriend); // someone 69 | 70 | // 捕获器不变式 71 | let obj = {}; 72 | Object.defineProperty(obj,"a",{ 73 | value: "aaa", 74 | configurable: false, 75 | writable: false 76 | }); 77 | let handler = { 78 | get() { 79 | return "bbb"; 80 | } 81 | }; 82 | let proxy = new Proxy(obj, handler); 83 | console.log(proxy.a); // TypeError: 'get' on proxy: ... 84 | 85 | 86 | // 3 可撤销代理 87 | 88 | let obj = { a: "aaa" }; 89 | let handler = { 90 | get() { 91 | return "hhh"; 92 | } 93 | }; 94 | let { proxy, revoke } = Proxy.revocable(obj,handler); 95 | console.log(proxy.a); // hhh 96 | revoke(); // 撤销代理 97 | console.log(proxy.a); // TypeError: Cannot perform 'get' on a proxy that has been revoked 98 | 99 | 100 | // 4 反射 101 | 102 | // 与操作对象有关的反射 103 | let obj = {}; 104 | if(Reflect.defineProperty(obj,"a",{value:"aaa"})) { 105 | console.log("成功了!"); 106 | } else { 107 | console.log("失败了……"); 108 | } 109 | 110 | 111 | // 5 多层代理 112 | 113 | let obj = { name: "" }; 114 | let p1 = new Proxy(obj, { 115 | // 拦截 set 操作,将写入的值转换为小写 116 | set(target, property, value, rec) { 117 | if(property !== "name") { 118 | return Reflect.set(...arguments); 119 | } 120 | return Reflect.set(target,property,value.toLowerCase(),rec); 121 | } 122 | }); 123 | let p2 = new Proxy(p1, { 124 | // 拦截 set 操作,去掉写入的值的前后空白符 125 | set(target, property, value, rec) { 126 | if(property !== "name") { 127 | return Reflect.set(...arguments); 128 | } 129 | return Reflect.set(target,property,value.trim(),rec); 130 | } 131 | }); 132 | p2.name = " Dasen "; 133 | console.log(p2.name); // dasen 134 | 135 | 136 | // 6 代理类 137 | 138 | // 代理对象 139 | const wm = new WeakMap(); 140 | class User { 141 | constructor(userid) { 142 | wm.set(this, userid); 143 | } 144 | set id(userid) { 145 | wm.set(this, userid); 146 | } 147 | get id() { 148 | return wm.get(this); 149 | } 150 | } 151 | const user = new User(555); 152 | console.log(user.id); // 555 153 | const proxy = new Proxy(user,{}); 154 | console.log(proxy.id); // undefined 155 | // 代理类 156 | const ProxyUser = new Proxy(User,{}); 157 | const proxyUser = new ProxyUser(666); 158 | console.log(proxyUser.id); // 666 159 | 160 | 161 | // ==================== 代理模式 ==================== 162 | 163 | // 1 监听属性 164 | 165 | // get捕获器监听属性何时被访问 166 | const obj = { name: "Dasen" }; 167 | const proxy = new Proxy(obj, { 168 | get(target, p) { 169 | console.log("obj 的属性",p,"被访问了!"); 170 | return Reflect.get(...arguments); 171 | } 172 | }); 173 | console.log(proxy.name); 174 | // obj 的属性 name 被访问了! 175 | // Dasen 176 | 177 | // set捕获器实现简单的数据双向绑定 178 | const obj1 = { val: "" }; 179 | const obj2 = { val: "" }; 180 | const proxy1 = new Proxy(obj1, { 181 | set(target, p, v) { 182 | if(p !== "val") { 183 | return Reflect.set(...arguments); 184 | } 185 | obj2[p] = v; 186 | return Reflect.set(...arguments); 187 | } 188 | }); 189 | const proxy2 = new Proxy(obj2, { 190 | set(target, p, v) { 191 | if(p !== "val") { 192 | return Reflect.set(...arguments); 193 | } 194 | obj1[p] = v; 195 | return Reflect.set(...arguments); 196 | } 197 | }); 198 | proxy1.val = "val1"; 199 | console.log(proxy1.val); // val1 200 | console.log(proxy2.val); // val1 201 | proxy2.val = "val2"; 202 | console.log(proxy1.val); // val2 203 | console.log(proxy2.val); // val2 204 | 205 | 206 | // 2 隐藏属性 207 | 208 | const hiddenProperties = ["age", "weight"]; 209 | const obj = { 210 | name: "Dasen", 211 | age: 22, 212 | height: 180, 213 | weight: 135 214 | }; 215 | const proxy = new Proxy(obj, { 216 | get(t,p) { 217 | if(hiddenProperties.includes(p)) { 218 | return undefined; 219 | } 220 | return Reflect.get(...arguments); 221 | }, 222 | has(t,p) { 223 | if(hiddenProperties.includes(p)) { 224 | return false; 225 | } 226 | return Reflect.has(...arguments); 227 | } 228 | }); 229 | console.log(proxy.name); // Dasen 230 | console.log(proxy.age); // undefined 231 | console.log(proxy.height); // 180 232 | console.log(proxy.weight); // undefined 233 | 234 | 235 | // 3 对象属性验证 236 | 237 | const obj = { 238 | name: "Dasen", 239 | age: 22, 240 | height: 180, 241 | weight: 135 242 | }; 243 | const proxy = new Proxy(obj, { 244 | set(o,p,v) { 245 | if(p==="weight") { 246 | if(typeof v !== "number" || v <= 0) { 247 | console.log("体重只能是大于0的数字!"); 248 | return false; 249 | } else if(v > 180) { 250 | console.log("大森是不可能那么胖的!"); 251 | return false; 252 | } 253 | } 254 | return Reflect.set(...arguments); 255 | } 256 | }); 257 | proxy.weight = 130; 258 | console.log(proxy.weight); // 130 259 | proxy.weight = 185; // 大森是不可能那么胖的! 260 | console.log(proxy.weight); // 130 261 | proxy.weight = "135"; // 体重只能是大于0的数字! 262 | console.log(proxy.weight); // 130 263 | proxy.weight = -1; // 体重只能是大于0的数字! 264 | console.log(proxy.weight); // 130 265 | 266 | 267 | // 4 参数验证 268 | 269 | // 函数参数验证 270 | function sum(arr) { 271 | // 求数组中所有数的和 272 | return arr.reduce((r,c) => r+c); 273 | } 274 | const proxy = new Proxy(sum, { 275 | apply(t,thisArg,arg) { 276 | let arrArg = arg[0]; 277 | if(!Array.isArray(arrArg)) { 278 | console.log("要传入一个数组作为参数!"); 279 | return undefined; 280 | } 281 | for (const i of arrArg) { 282 | if(typeof i !== "number") { 283 | console.log("数组中的每一个值都应该是数值!"); 284 | return undefined; 285 | } 286 | } 287 | return Reflect.apply(...arguments); 288 | } 289 | }); 290 | console.log(proxy([1,2,3,4,5])); // 15 291 | console.log(proxy(1)); 292 | // 要传入一个数组作为参数! 293 | // undefined 294 | console.log(proxy(["1",2,"3"])); 295 | // 数组中的每一个值都应该是数值! 296 | // undefined 297 | 298 | // 构造函数参数验证 299 | class User { 300 | constructor(userid) { 301 | this.id = userid; 302 | } 303 | } 304 | const proxy = new Proxy(User, { 305 | construct(o,argArr) { 306 | if(argArr[0] === undefined) { 307 | throw "必须要传入UserID!"; 308 | } 309 | return Reflect.construct(...arguments); 310 | } 311 | }); 312 | new proxy(1); 313 | new proxy(); // Error: 必须要传入UserID! 314 | 315 | 316 | // 5 观察者模式 317 | 318 | const userList = []; 319 | const watcher = new Proxy(userList, { 320 | set(target, p, value) { 321 | const res = Reflect.set(...arguments); 322 | if(p !== "length" && res) { 323 | console.log("欢迎",value.name,"的加入!"); 324 | } 325 | return res; 326 | } 327 | }) 328 | class User { 329 | constructor(username) { 330 | this.name = username; 331 | } 332 | } 333 | const userProxy = new Proxy(User, { 334 | construct() { 335 | const newUser = Reflect.construct(...arguments); 336 | watcher.push(newUser); 337 | return newUser; 338 | } 339 | }); 340 | new userProxy("Dasen"); // 欢迎 Dasen 的加入! 341 | new userProxy("Three"); // 欢迎 Three 的加入! 342 | new userProxy("Jack"); // 欢迎 Jack 的加入! 343 | -------------------------------------------------------------------------------- /1 JavaScript高级程序设计/08 函数.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * JavaScript高级程序设计 - 代码笔记 3 | * 08 函数 4 | * Dasen Sun 5 | * 2021-12-22 6 | **************************************************/ 7 | 8 | 9 | // ==================== 函数基础 ==================== 10 | 11 | // 1 定义函数 12 | 13 | // 函数声明 14 | function sum(a, b) { 15 | return a + b; 16 | } 17 | console.log(sum(2,3)); // 5 18 | 19 | // 函数表达式 20 | let sum = function (a, b) { 21 | return a+b; 22 | }; 23 | console.log(sum(2,3)); // 5 24 | 25 | // 箭头函数 26 | let sum = (a, b) => a + b; 27 | console.log(sum(2,3)); // 5 28 | 29 | // 函数对象 30 | let sum = new Function("a","b","return a+b;"); 31 | console.log(sum(2,3)); // 5 32 | 33 | 34 | // 2 函数表达式 35 | 36 | // 条件定义函数 37 | let char = "a"; 38 | let process; 39 | if(char >= "a" && char <= "z") { 40 | process = function (c) { 41 | return c.toUpperCase(); 42 | } 43 | } else if(char >= "A" && char <= "Z") { 44 | process = function (c) { 45 | return c.toLowerCase(); 46 | } 47 | } 48 | console.log(process(char)); // A 49 | 50 | // 立即调用的函数表达式 51 | for (var i = 0; i < 5; ++i) { 52 | setTimeout((function (j) { 53 | return () => console.log(j); 54 | })(i)); 55 | } // 0 1 2 3 4 56 | 57 | 58 | // ==================== 函数结构 ==================== 59 | 60 | // 1 函数名 61 | 62 | // 函数具有多个名称 63 | function sum(a, b) { 64 | return a + b; 65 | } 66 | let add = sum; 67 | console.log(sum(2,3)); // 5 68 | console.log(add(2,3)); // 5 69 | console.log(sum === add); // true 70 | 71 | // 函数名 72 | function sum1(a, b) { 73 | return a + b; 74 | } 75 | let sum2 = function (a, b) { 76 | return a + b; 77 | }; 78 | let sum3 = (a, b) => a + b; 79 | let sum4 = new Function("a", "b", "return a + b;"); 80 | let sum5 = sum1; 81 | let sum6 = sum2; 82 | let sum7 = sum3; 83 | let sum8 = sum4; 84 | console.log(sum1.name, sum5.name); // sum1 sum1 85 | console.log(sum2.name, sum6.name); // sum2 sum2 86 | console.log(sum3.name, sum7.name); // sum3 sum3 87 | console.log(sum4.name, sum8.name); // anonymous anonymous 88 | let funArr = [ 89 | function (a, b) { 90 | return a + b; 91 | }, 92 | (a, b) => a + b 93 | ]; 94 | console.log(funArr[0].name === ""); // true 95 | console.log(funArr[1].name === ""); // true 96 | let sum9 = funArr[0]; 97 | let sum10 = funArr[1]; 98 | console.log(funArr[0].name === ""); // true 99 | console.log(funArr[1].name === ""); // true 100 | 101 | // 带前缀的函数名 102 | let obj = { 103 | name: "Three", 104 | age_: 18, 105 | get age() { 106 | return this.age_; 107 | } 108 | }; 109 | let ageFun = Object.getOwnPropertyDescriptor(obj, "age"); 110 | console.log(ageFun.get.name); // get age 111 | function fun() {} 112 | console.log(fun.bind(null).name); // bound fun 113 | 114 | 115 | // 2 参数 116 | 117 | // arguments 118 | function fun(a, b) { 119 | console.log(a, b); 120 | arguments[1] = 99; 121 | console.log(a, b); 122 | b = 66; 123 | console.log(arguments[1]); 124 | } 125 | fun(1, 2); 126 | // 1 2 127 | // 1 99 128 | // 66 129 | fun(1); 130 | // 1 undefined 131 | // 1 undefined 132 | // 99 133 | 134 | // 严格模式 135 | function fun(a, b) { 136 | "use strict" 137 | console.log(a, b); 138 | arguments[1] = 99; 139 | console.log(a, b); 140 | b = 66; 141 | console.log(arguments[1]); 142 | } 143 | fun(1, 2); 144 | // 1 2 145 | // 1 2 146 | // 99 147 | 148 | // 默认参数 149 | function sum(a, b=2) { 150 | return a + b; 151 | } 152 | console.log(sum(5,5)); // 10 153 | console.log(sum(5)); // 7 154 | 155 | // 默认参数的求值顺序 156 | function f() { 157 | console.log("f this :", this); 158 | return 1; 159 | } 160 | function s() { 161 | console.log("second"); 162 | return 2; 163 | } 164 | function fun(first=f(), second=s(), third=second*2, t=this) { 165 | console.log("fun this :", t); 166 | return `${first} ${second} ${third}`; 167 | } 168 | let obj = { fun }; 169 | console.log(obj.fun()); 170 | // f this : window 171 | // second 172 | // fun this : obj 173 | // 1 2 4 174 | 175 | // 参数的收集和扩展 176 | function print(name="unknow", age, ...others) { 177 | console.log(`name: ${name}, age: ${age}, others: ${others}`); 178 | } 179 | print(); // name: unknow, age: undefined, others: 180 | let p = ["Dasen", 22]; 181 | print(...p, "other1", "other2"); // name: Dasen, age: 22, others: other1,other2 182 | 183 | 184 | // 3 函数内部对象 185 | 186 | // 未使用arguments.callee的例子 187 | let factorial = function (num) { 188 | if (num <= 1) { 189 | return 1; 190 | } else { 191 | return num * factorial(num - 1); 192 | } 193 | }; 194 | console.log(factorial(5)); // 120 195 | let anotherFun = factorial; // 改变函数名 196 | factorial = null; // 废弃掉原来的函数名 197 | console.log(anotherFun(5)); // TypeError: factorial is not a function 198 | 199 | // 使用arguments.callee改进 200 | let factorial = function (num) { 201 | if (num <= 1) { 202 | return 1; 203 | } else { 204 | return num * arguments.callee(num - 1); 205 | } 206 | }; 207 | console.log(factorial(5)); // 120 208 | let anotherFun = factorial; // 改变函数名 209 | factorial = null; // 废弃掉原来的函数名 210 | console.log(anotherFun(5)); // 120 211 | 212 | // 带有默认名称的函数表达式 213 | "use strict" 214 | let factorial = (function f(num) { 215 | if (num <= 1) { 216 | return 1; 217 | } else { 218 | return num * f(num - 1); 219 | } 220 | }); 221 | console.log(factorial(5)); // 120 222 | 223 | // caller 224 | function inner() { 225 | console.log(inner.caller); 226 | } 227 | function outer() { 228 | inner(); 229 | } 230 | inner(); // 结果不一定 231 | outer(); // ƒ outer() 232 | 233 | 234 | // 4 函数的属性与方法 235 | 236 | // length属性 237 | function sum(a, b) { 238 | return a + b; 239 | } 240 | let add2 = (a) => a + 2; 241 | console.log(sum.length); // 2 242 | console.log(add2.length); // 1 243 | 244 | // apply方法、call方法和bind方法 245 | function addAll(a, b, c, d) { 246 | console.log(this); 247 | return a + b + c + d; 248 | } 249 | let obj = {}; 250 | console.log(addAll.apply(obj, [1,2,3,4])); 251 | // obj: {} 252 | // 10 253 | console.log(addAll.call(obj, 1, 2, 3, 4)); 254 | // obj: {} 255 | // 10 256 | let fun = addAll.bind(obj); 257 | console.log(fun(1, 2, 3, 4)); 258 | // obj: {} 259 | // 10 260 | 261 | 262 | // ==================== 闭包 ==================== 263 | 264 | // 1 this 265 | 266 | function outer() { 267 | console.log(this); 268 | } 269 | let obj = { 270 | inner() { 271 | console.log(this); 272 | outer(); // 在全局定义,内部调用 273 | let fun = function () { 274 | console.log(this); 275 | } 276 | fun(); // 在内部定义,内部调用 277 | return [ 278 | function () { 279 | console.log(this); 280 | }, 281 | () => console.log(this), 282 | fun 283 | ]; 284 | } 285 | }; 286 | outer(); // window - 在全局定义,全局调用 287 | let [fun, afun, innerfun] = obj.inner(); 288 | // obj - obj.inner()的this 289 | // window - 在全局定义,内部调用 290 | // window - 在内部定义,内部调用 291 | fun(); // window - (返回的匿名函数) 292 | afun(); // obj - (返回的箭头函数) 293 | innerfun(); // window - 在内部定义,全局调用 294 | 295 | 296 | // 2 闭包模式的私有属性 297 | 298 | // 简单闭包模式 299 | function Person(name) { 300 | this.getName = function () { 301 | return name; 302 | }; 303 | this.setName = function (personName) { 304 | name = personName; 305 | }; 306 | } 307 | let person = new Person("Dasen"); 308 | console.log(person.getName()); // Dasen 309 | person.setName("name"); 310 | console.log(person.getName()); // name 311 | 312 | // 类私有成员 313 | let User = (function () { 314 | // 类(静态)私有成员 315 | let userCounter = 0; 316 | // 构造函数 317 | let User = function (username) { 318 | this.name = username; 319 | userCounter++; 320 | }; 321 | // 类(静态)公共方法 322 | User.prototype.countUser = function () { 323 | return userCounter; 324 | } 325 | return User; 326 | })(); 327 | let user1 = new User("Dasen"); 328 | console.log(user1.countUser()); // 1 329 | console.log(user1.name); // Dasen 330 | let user2 = new User("Three"); 331 | console.log(user2.countUser()); // 2 332 | console.log(user2.name); // Three 333 | 334 | // 模块模式 335 | let rooms = function () { 336 | // 私有成员 337 | let roomList = []; 338 | // 初始化私有成员 339 | roomList.push("Dasen's room"); 340 | // 公共接口 341 | return { 342 | getRooms() { 343 | return roomList.join(", "); 344 | }, 345 | getRoomNumber() { 346 | return roomList.length; 347 | }, 348 | createRoom(roomName) { 349 | roomList.push(roomName); 350 | } 351 | } 352 | }(); 353 | console.log(rooms.getRoomNumber()); // 1 354 | console.log(rooms.getRooms()); // Dasen's room 355 | rooms.createRoom("New room"); 356 | console.log(rooms.getRoomNumber()); // 2 357 | console.log(rooms.getRooms()); // Dasen's room, New room 358 | -------------------------------------------------------------------------------- /1 JavaScript高级程序设计/09 异步编程.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * JavaScript高级程序设计 - 代码笔记 3 | * 09 异步编程 4 | * Dasen Sun 5 | * 2021-12-01 6 | **************************************************/ 7 | 8 | 9 | // ==================== 异步 ==================== 10 | 11 | // 异步 12 | function double(value) { 13 | setTimeout(() => { 14 | setTimeout(console.log, 0, value*2); 15 | }, 1000); 16 | } 17 | double(2); // 大约1000毫秒后输出 4 18 | 19 | // 回调函数处理异步返回值 20 | function double(value, callback) { 21 | setTimeout(() => { 22 | callback(value*2); // 调用回调函数处理异步返回值 23 | }, 1000); 24 | } 25 | function callback(value) { 26 | console.log(value); 27 | } 28 | double(2, callback); 29 | 30 | // 添加失败回调和嵌套异步回调 31 | function double(value, successCallback, failureCallback) { 32 | setTimeout(() => { 33 | try { 34 | if(typeof value !== "number") { 35 | throw new Error("I need a number."); 36 | } 37 | successCallback(value*2); // 调用回调函数处理异步返回值 38 | } catch (error) { 39 | failureCallback(error); 40 | } 41 | }, 1000); 42 | } 43 | function successCallback(value) { // 简单的成功处理程序 44 | console.log(value); 45 | } 46 | function successCallback(value) { // 再次请求异步结果的处理程序 47 | double(value, (x) => console.log("Success:", x)); 48 | } 49 | function failureCallback(error) { 50 | console.log(error); 51 | } 52 | double(2, successCallback, failureCallback); 53 | double("2", successCallback, failureCallback); // Error: I need a number. 54 | 55 | 56 | // ==================== 期约 ==================== 57 | 58 | // 1 期约状态 59 | 60 | // 执行器函数 61 | let p = new Promise((resolve, reject) => { 62 | setTimeout(resolve, 1000); 63 | setTimeout(reject, 5000); 64 | }); 65 | 66 | 67 | // 2 期约方法 68 | 69 | // finally方法返回一个待定期约的例子 70 | let p1 = Promise.resolve('foo'); 71 | let p2 = p1.finally( 72 | () => { 73 | new Promise((resolve, reject) => { 74 | setTimeout(() => { 75 | console.log("解决了!"); 76 | resolve('bar'); 77 | }, 100); 78 | }) 79 | }); 80 | console.log(p2); // 立即输出: Promise 81 | setTimeout(console.log, 0, p2); // 立即输出: Promise {: 'foo'} 82 | // 100 毫秒后输出: 解决了! 83 | setTimeout(() => setTimeout(console.log, 0, p2), 200); // 200 毫秒后输出: Promise : foo 84 | 85 | 86 | // 3 期约连锁与合成 87 | 88 | // 期约连锁 89 | let p = new Promise((resolve, reject) => { 90 | console.log("期约 1 开始执行"); 91 | setTimeout(() => { 92 | console.log("期约 1 解决为 1"); 93 | resolve(1); 94 | }, 1000); 95 | }); 96 | p.then((val) => { 97 | return new Promise((resolve, reject) => { 98 | console.log("期约 2 开始执行"); 99 | setTimeout((val) => { 100 | console.log("期约 2 解决为", val*2); 101 | resolve(val*2); 102 | }, 1000, val); 103 | }); 104 | }).then((val) => { 105 | return new Promise((resolve, reject) => { 106 | console.log("期约 3 开始执行"); 107 | setTimeout((val) => { 108 | console.log("期约 3 解决为", val*3); 109 | resolve(val*3); 110 | }, 1000, val); 111 | }); 112 | }).then((val) => { 113 | console.log("所有异步任务解决完毕,最终的值为", val); 114 | }); 115 | // 立即输出:期约 1 开始执行 116 | // 约 1 秒后:期约 1 解决为 1 117 | // 约 1 秒后:期约 2 开始执行 118 | // 约 2 秒后:期约 2 解决为 2 119 | // 约 2 秒后:期约 3 开始执行 120 | // 约 3 秒后:期约 3 解决为 6 121 | // 约 3 秒后:所有异步任务解决完毕,最终的值为 6 122 | 123 | // 期约合成 all 124 | let p1 = Promise.all([ 125 | Promise.resolve(3), 126 | Promise.resolve(1), 127 | Promise.resolve(2) 128 | ]); 129 | p1.then((val) => console.log(val)); // [3, 1, 2] 130 | let p2 = Promise.all([ 131 | new Promise(() => {}), 132 | Promise.reject(new Error("error!")), 133 | Promise.reject(new Error("ignore")), 134 | Promise.resolve(0) 135 | ]); 136 | p2.catch((val) => console.log(val)); // Error: error! 137 | 138 | // 期约合成 race 139 | let p1 = Promise.race([ 140 | Promise.resolve(3), 141 | Promise.resolve(1), 142 | Promise.resolve(2) 143 | ]); 144 | p1.then((val) => console.log(val)); // 3 145 | let p2 = Promise.race([ 146 | new Promise(() => {}), 147 | Promise.resolve(0), 148 | Promise.reject(new Error("error!")), 149 | Promise.resolve(1) 150 | ]); 151 | p2.then((val) => console.log(val)); // 0 152 | let p3 = Promise.race([ 153 | Promise.reject(new Error("error!")), 154 | Promise.resolve(0), 155 | Promise.resolve(1) 156 | ]); 157 | p3.catch((val) => console.log(val)); // Error: error! 158 | 159 | 160 | // 4 期约扩展 161 | 162 | // 期约取消 163 | class CancelToken { 164 | constructor(cancelFn) { 165 | this.promise = new Promise((resolve, reject) => { 166 | cancelFn(() => { 167 | setTimeout(console.log, 0, "delay cancelled"); 168 | resolve(); 169 | }); 170 | }); 171 | } 172 | } 173 | const startButton = document.querySelector('#start'); 174 | const cancelButton = document.querySelector('#cancel'); 175 | function cancellableDelayedResolve(delay) { 176 | setTimeout(console.log, 0, "set delay"); 177 | return new Promise((resolve, reject) => { 178 | const id = setTimeout((() => { 179 | setTimeout(console.log, 0, "delayed resolve"); 180 | resolve(); 181 | }), delay); 182 | const cancelToken = new CancelToken((cancelCallback) => 183 | cancelButton.addEventListener("click", cancelCallback)); 184 | cancelToken.promise.then(() => clearTimeout(id)); 185 | }); 186 | } 187 | startButton.addEventListener("click", () => cancellableDelayedResolve(1000)); 188 | 189 | // 期约进度通知 190 | class TrackablePromise extends Promise { 191 | constructor(executor) { 192 | const notifyHandlers = []; 193 | super((resolve, reject) => { 194 | return executor(resolve, reject, (status) => { 195 | notifyHandlers.map((handler) => handler(status)); 196 | }); 197 | }); 198 | this.notifyHandlers = notifyHandlers; 199 | } 200 | notify(notifyHandler) { 201 | this.notifyHandlers.push(notifyHandler); 202 | return this; 203 | } 204 | } 205 | let p = new TrackablePromise((resolve, reject, notify) => { 206 | function countdown(x) { 207 | if (x > 0) { 208 | notify(`${20*x}% remaining`); 209 | setTimeout(() => countdown(x - 1), 1000); 210 | } else { 211 | resolve(); 212 | } 213 | } 214 | countdown(5); 215 | }); 216 | p.notify((x) => setTimeout(console.log, 0, 'progress:', x)); 217 | p.then(() => setTimeout(console.log, 0, 'completed')); 218 | 219 | 220 | // ==================== 异步函数 ==================== 221 | 222 | // 1 异步函数策略 223 | 224 | // 模拟sleep函数 225 | async function sleep(delay) { 226 | return new Promise((resolve) => setTimeout(resolve, delay)); 227 | } 228 | async function func() { 229 | const t0 = Date.now(); 230 | await sleep(2000); 231 | console.log(Date.now() - t0); // 2014 232 | } 233 | func(); 234 | 235 | // 没有平行执行 236 | async function randomDelay(id) { 237 | // 随机延迟0~1000 毫秒 238 | const delay = Math.random() * 1000; 239 | return new Promise((resolve) => setTimeout(() => { 240 | console.log(`${id} finished`); 241 | resolve(); 242 | }, delay)); 243 | } 244 | async function foo() { 245 | const t0 = Date.now(); 246 | await randomDelay(0); 247 | await randomDelay(1); 248 | await randomDelay(2); 249 | await randomDelay(3); 250 | await randomDelay(4); 251 | console.log(`${Date.now() - t0}ms elapsed`); 252 | } 253 | foo(); 254 | // 0 finished 255 | // 1 finished 256 | // 2 finished 257 | // 3 finished 258 | // 4 finished 259 | // 1733ms elapsed 260 | 261 | // 平行执行 262 | async function randomDelay(id) { 263 | const delay = Math.random() * 1000; 264 | return new Promise((resolve) => setTimeout(() => { 265 | setTimeout(console.log, 0, `${id} finished`); 266 | resolve(); 267 | }, delay)); 268 | } 269 | async function foo() { 270 | const t0 = Date.now(); 271 | const p0 = randomDelay(0); 272 | const p1 = randomDelay(1); 273 | const p2 = randomDelay(2); 274 | const p3 = randomDelay(3); 275 | const p4 = randomDelay(4); 276 | await p0; 277 | await p1; 278 | await p2; 279 | await p3; 280 | await p4; 281 | setTimeout(console.log, 0, `${Date.now() - t0}ms elapsed`); 282 | } 283 | foo(); 284 | // 3 finished 285 | // 4 finished 286 | // 0 finished 287 | // 1 finished 288 | // 2 finished 289 | // 893ms elapsed 290 | 291 | // 平行执行且随机返回 292 | async function randomDelay(id) { 293 | const delay = Math.random() * 1000; 294 | return new Promise((resolve) => setTimeout(() => { 295 | resolve(id); 296 | console.log(`${id} finished`); 297 | }, delay)); 298 | } 299 | function onResolved(id) { 300 | console.log(`${id} returned`); 301 | } 302 | const t0 = Date.now(); 303 | const p0 = randomDelay(0).then(onResolved); 304 | const p1 = randomDelay(1).then(onResolved); 305 | const p2 = randomDelay(2).then(onResolved); 306 | const p3 = randomDelay(3).then(onResolved); 307 | const p4 = randomDelay(4).then(onResolved); 308 | const p = Promise.all([p0,p1,p2,p3,p4]).then(((t0) => { 309 | return () => { 310 | setTimeout(console.log, 0, `${Date.now() - t0}ms elapsed`); 311 | } 312 | })(t0)); 313 | // 1 finished 314 | // 1 returned 315 | // 4 finished 316 | // 4 returned 317 | // 0 finished 318 | // 0 returned 319 | // 2 finished 320 | // 2 returned 321 | // 3 finished 322 | // 3 returned 323 | // 754ms elapsed 324 | 325 | // 异步任务串联 326 | async function fun1(x) { 327 | console.log("异步任务 1 开始执行"); 328 | console.log("等待任务 1 的返回值"); 329 | const r = await 1; 330 | console.log("异步任务 1 返回了", r); 331 | console.log("返回加工的值为", r*x); 332 | return r*x; 333 | } 334 | async function fun2(x) { 335 | console.log("异步任务 2 开始执行"); 336 | console.log("等待任务 2 的返回值"); 337 | const r = await 2; 338 | console.log("异步任务 2 返回了", r); 339 | console.log("返回加工的值为", r*x); 340 | return r*x; 341 | } 342 | async function fun3(x) { 343 | console.log("异步任务 3 开始执行"); 344 | console.log("等待任务 3 的返回值"); 345 | const r = await 3; 346 | console.log("异步任务 3 返回了", r); 347 | console.log("返回加工的值为", r*x); 348 | return r*x; 349 | } 350 | async function fun(x) { 351 | for (const fn of [fun1,fun2,fun3]) { 352 | x = await fn(x); 353 | } 354 | return x; 355 | } 356 | fun(1).then(console.log); 357 | // 异步任务 1 开始执行 358 | // 等待任务 1 的返回值 359 | // 异步任务 1 返回了 1 360 | // 返回加工的值为 1 361 | // 异步任务 2 开始执行 362 | // 等待任务 2 的返回值 363 | // 异步任务 2 返回了 2 364 | // 返回加工的值为 2 365 | // 异步任务 3 开始执行 366 | // 等待任务 3 的返回值 367 | // 异步任务 3 返回了 3 368 | // 返回加工的值为 6 369 | // 6 370 | -------------------------------------------------------------------------------- /1 JavaScript高级程序设计/10 BOM.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * JavaScript高级程序设计 - 代码笔记 3 | * 10 BOM 4 | * Dasen Sun 5 | * 2022-01-16 6 | **************************************************/ 7 | 8 | 9 | // ==================== window对象 ==================== 10 | 11 | // 1 显示当前窗口大小 12 | 13 | document.getElementById("view1").innerText = window.screenLeft.toString(); 14 | document.getElementById("view2").innerText = window.screenTop.toString(); 15 | 16 | 17 | // 2 显示当前浏览器像素比 18 | 19 | document.getElementById("view").innerText = window.devicePixelRatio.toString(); 20 | 21 | 22 | // 3 获取视口尺寸 23 | 24 | // 常规做法 25 | let pageWidth = window.innerWidth, 26 | pageHeight = window.innerHeight; 27 | if (typeof pageWidth != "number") { 28 | if (document.compatMode == "CSS1Compat") { 29 | pageWidth = document.documentElement.clientWidth; 30 | pageHeight = document.documentElement.clientHeight; 31 | } else { 32 | pageWidth = document.body.clientWidth; 33 | pageHeight = document.body.clientHeight; 34 | } 35 | } 36 | 37 | // 更简单的做法 38 | var pageWidth = window.innerWidth || 39 | document.documentElement.clientWidth || 40 | document.body.clientWidth; 41 | var pageHeight = window.innerHeight || 42 | document.documentElement.clientHeight || 43 | document.body.clientHeight; 44 | 45 | 46 | // 4 滚动视口 47 | 48 | // 正常滚动 49 | window.scrollTo({ 50 | left: 100, 51 | top: 100, 52 | behavior: 'auto' 53 | }); 54 | 55 | // 平滑滚动 56 | window.scrollTo({ 57 | left: 100, 58 | top: 100, 59 | behavior: 'smooth' 60 | }); 61 | 62 | 63 | // 5 跳转与打开 64 | 65 | // 打开链接 66 | window.open("https://sadose.github.io/", "myFrame"); 67 | 68 | // 新窗口打开 69 | window.open("https://sadose.github.io/", 70 | "blogWindow", 71 | "height=800, width=800, top=10, left=10, resizable=yes"); 72 | 73 | 74 | // 6 定时器 75 | 76 | // 使用 setInterval 77 | let num = 0; 78 | const id = setInterval(() => { 79 | num++; 80 | if (num >= 10) clearInterval(id); 81 | }, 500); 82 | 83 | // 使用 setTimeout 代替 setInterval 84 | let num = 0; 85 | (function increace() { 86 | setTimeout(() => { 87 | num++; 88 | if (num < 10) increace(); 89 | }, 500); 90 | })(); 91 | 92 | 93 | // ==================== navigator对象 ==================== 94 | 95 | // 1 检测插件 96 | 97 | let hasPlugin = function (name) { 98 | name = name.toLowerCase(); 99 | for (let plugin of window.navigator.plugins) { 100 | if (plugin.name.toLowerCase().indexOf(name) > -1) { 101 | return true; 102 | } 103 | } 104 | return false; 105 | } 106 | 107 | 108 | // ==================== history对象 ==================== 109 | 110 | // 1 go()方法 111 | 112 | // 后退一页 113 | history.go(-1); 114 | // 前进一页 115 | history.go(1); 116 | // 前进两页 117 | history.go(2); 118 | // 后退一页 119 | history.back(); 120 | // 前进一页 121 | history.forward(); 122 | 123 | 124 | // 2 状态管理 125 | 126 | // push状态 127 | let stateObject = { description: "这是一个描述" }; 128 | history.pushState(stateObject, "Title", "baz.html"); 129 | 130 | // 状态后退 131 | window.addEventListener("popstate", (event) => { 132 | let state = event.state; 133 | if (state) { 134 | processState(state); 135 | } 136 | }); 137 | -------------------------------------------------------------------------------- /1 JavaScript高级程序设计/11 客户端检测.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * JavaScript高级程序设计 - 代码笔记 3 | * 11 客户端检测 4 | * Dasen Sun 5 | * 2022-01-22 6 | **************************************************/ 7 | 8 | 9 | // ==================== 能力检测 ==================== 10 | 11 | // 1 简单的能力检测 12 | 13 | function getElement(id) { 14 | if (document.getElementById) { 15 | return document.getElementById(id); 16 | } else if (document.all) { 17 | return document.all[id]; 18 | } else { 19 | throw new Error("No way to retrieve element! "); 20 | } 21 | } 22 | 23 | 24 | // 2 基于能力检测的浏览器分析 25 | 26 | class BrowserDetector { 27 | constructor() { 28 | // 测试条件编译 29 | // IE6~10 支持 30 | this.isIE_Gte6Lte10 = /*@cc_on! @*/false; 31 | // 测试documentMode 32 | // IE7~11 支持 33 | this.isIE_Gte7Lte11 = !!document.documentMode; 34 | // 测试StyleMedia构造函数 35 | // Edge 20 及以上版本支持 36 | this.isEdge_Gte20 = !!window.StyleMedia; 37 | // 测试Firefox专有扩展安装API 38 | // 所有版本的Firefox都支持 39 | this.isFirefox_Gte1 = typeof InstallTrigger !== 'undefined'; 40 | // 测试chrome对象及其webstore属性 41 | // Opera的某些版本有window.chrome,但没有window.chrome.webstore 42 | // 所有版本的Chrome都支持 43 | this.isChrome_Gte1 = !!window.chrome && !!window.chrome.webstore; 44 | // Safari早期版本会给构造函数的标签符追加"Constructor"字样,如: 45 | // window.Element.toString(); // [object ElementConstructor] 46 | // Safari 3~9.1 支持 47 | this.isSafari_Gte3Lte9_1 = /constructor/i.test(window.Element); 48 | // 推送通知API暴露在window对象上 49 | // 使用默认参数值以避免对undefined调用toString() 50 | // Safari 7.1 及以上版本支持 51 | this.isSafari_Gte7_1 = 52 | (({ 53 | pushNotification = {} 54 | } = {}) => 55 | pushNotification.toString() == '[object SafariRemoteNotification]' 56 | )(window.safari); 57 | // 测试addons属性 58 | // Opera 20 及以上版本支持 59 | this.isOpera_Gte20 = !!window.opr && !!window.opr.addons; 60 | } 61 | isIE() { 62 | return this.isIE_Gte6Lte10 || this.isIE_Gte7Lte11; 63 | } 64 | isEdge() { 65 | return this.isEdge_Gte20 && !this.isIE(); 66 | } 67 | isFirefox() { 68 | return this.isFirefox_Gte1; 69 | } 70 | isChrome() { 71 | return this.isChrome_Gte1; 72 | } 73 | isSafari() { 74 | return this.isSafari_Gte3Lte9_1 || this.isSafari_Gte7_1; 75 | } 76 | isOpera() { 77 | return this.isOpera_Gte20; 78 | } 79 | } 80 | 81 | 82 | // ==================== 软硬件检测 ==================== 83 | 84 | // 1 获取地理位置 85 | 86 | let p; 87 | navigator.geolocation.getCurrentPosition((position) => p = position); 88 | p.timestamp // 时间戳 89 | p.coords.latitude // 纬度 90 | p.coords.longitude // 经度 91 | p.coords.accuracy // 精度(单位:米) 92 | p.coords.altitude // 高度(海拔) 93 | p.coords.altitudeAccuracy // 海拔精度(单位:米) 94 | p.coords.speed // 设备移动速度 95 | p.coords.heading // 朝向(以正北方为0°的角度) 96 | 97 | // 2 获取电池状态 98 | 99 | let b; 100 | navigator.getBattery().then((bs) => b = bs); 101 | -------------------------------------------------------------------------------- /1 JavaScript高级程序设计/12 DOM基础.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * JavaScript高级程序设计 - 代码笔记 3 | * 12 DOM基础 4 | * Dasen Sun 5 | * 2022-01-26 6 | **************************************************/ 7 | 8 | 9 | // ==================== DOM 编程 ==================== 10 | 11 | // 1 动态脚本 12 | 13 | // 动态添加外部JS文件 14 | let script = document.createElement("script"); 15 | script.src = "foo.js"; 16 | document.body.appendChild(script); 17 | 18 | // 动态添加外部JS文件 - 函数 19 | function loadScript(url) { 20 | let script = document.createElement("script"); 21 | script.src = url; 22 | document.body.appendChild(script); 23 | } 24 | 25 | // 添加内联JS代码 - 方法一 26 | let script = document.createElement("script"); 27 | script.appendChild(document.createTextNode("function sayHi(){alert('hi'); }")); 28 | document.body.appendChild(script); 29 | 30 | // 添加内联JS代码 - 方法二 31 | var script = document.createElement("script"); 32 | script.text = "functionsayHi(){alert('hi');}"; 33 | document.body.appendChild(script); 34 | 35 | // 添加内联JS代码 - 综合 36 | function loadScriptString(code) { 37 | var script = document.createElement("script"); 38 | script.type = "text/javascript"; 39 | try { 40 | script.appendChild(document.createTextNode(code)); 41 | } catch (ex) { 42 | script.text = code; 43 | } 44 | document.body.appendChild(script); 45 | } 46 | 47 | 48 | // ==================== MutationObserver 接口 ==================== 49 | 50 | // 1 基本用法 51 | 52 | // 创建 MutationObserver 实例 53 | let observer = new MutationObserver(() => console.log('DOM was mutated! ')); 54 | 55 | // 绑定观察 56 | let observer = new MutationObserver(() => console.log(' attributes changed')); 57 | observer.observe(document.body, { 58 | attributes: true 59 | }); 60 | 61 | // 终止观察 62 | let observer = new MutationObserver(() => console.log(' attributes changed')); 63 | observer.observe(document.body, { 64 | attributes: true 65 | }); 66 | observer.disconnect(); 67 | -------------------------------------------------------------------------------- /1 JavaScript高级程序设计/13 DOM扩展.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * JavaScript高级程序设计 - 代码笔记 3 | * 13 DOM扩展 4 | * Dasen Sun 5 | * 2022-01-29 6 | **************************************************/ 7 | 8 | 9 | // 这一节没有代码内容 QAQ 10 | -------------------------------------------------------------------------------- /1 JavaScript高级程序设计/14 DOM2和DOM3.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * JavaScript高级程序设计 - 代码笔记 3 | * 14 DOM2和DOM3 4 | * Dasen Sun 5 | * 2022-03-06 6 | **************************************************/ 7 | 8 | 9 | // ==================== 核心、视图和HTML ==================== 10 | 11 | // 1 给节点追加数据 12 | 13 | // 追加数据 14 | document.body.setUserData("name", "Nicholas", function () {}); 15 | // 获取数据 16 | let value = document.body.getUserData("name"); 17 | 18 | 19 | // ==================== 样式 ==================== 20 | 21 | // 1 操作元素内联样式 22 | 23 | // 获取元素 DOM 24 | let div = document.getElementById("myDiv"); 25 | // 设置背景颜色 26 | div.style.backgroundColor = "red"; 27 | // 修改大小 28 | div.style.width = "100px"; 29 | div.style.height = "200px"; 30 | // 设置边框 31 | div.style.border = "1px solid black"; 32 | 33 | 34 | // 2 操作的外联样式表 35 | 36 | // 读取文档中的样式表 37 | let sheet = null; 38 | for (let i = 0, len = document.styleSheets.length; i < len; i++) { 39 | sheet = document.styleSheets[i]; 40 | console.log(sheet.href); 41 | } 42 | 43 | // 操作样式表 44 | let sheet = document.styleSheets[0]; 45 | let rules = sheet.cssRules || sheet.rules; // 取得规则集合 46 | let rule = rules[0]; // 取得第一条规则 47 | rule.style.backgroundColor = "red"; // 修改它 48 | 49 | 50 | // 3 元素尺寸 51 | 52 | // 计算元素在页面上的左偏移 53 | function getElementLeft(element) { 54 | let actualLeft = element.offsetLeft; 55 | let current = element.offsetParent; 56 | while (current !== null) { 57 | actualLeft += current.offsetLeft; 58 | current = current.offsetParent; 59 | } 60 | return actualLeft; 61 | } 62 | 63 | // 计算元素在页面上的上偏移 64 | function getElementTop(element) { 65 | let actualTop = element.offsetTop; 66 | let current = element.offsetParent; 67 | while (current !== null) { 68 | actualTop += current.offsetTop; 69 | current = current.offsetParent; 70 | } 71 | return actualTop; 72 | } 73 | -------------------------------------------------------------------------------- /1 JavaScript高级程序设计/15 事件.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * JavaScript高级程序设计 - 代码笔记 3 | * 15 事件 4 | * Dasen Sun 5 | * 2022-03-10 6 | **************************************************/ 7 | 8 | 9 | // ==================== 事件处理程序 ==================== 10 | 11 | // 1 DOM0 事件处理程序 12 | 13 | let btn = document.getElementById("myBtn"); 14 | btn.onclick = function () { 15 | console.log("Clicked"); 16 | }; 17 | 18 | 19 | // 2 DOM2 事件处理程序 20 | 21 | let btn = document.getElementById("myBtn"); 22 | btn.addEventListener("click", () => { 23 | console.log(this.id); 24 | }, false); 25 | 26 | 27 | // 3 使用 DOM2 方法更换绑定的事件处理程序 28 | 29 | let fun = null; 30 | 31 | function setEventListener(dom, event, fn) { 32 | if (fun) dom.removeEventListener(event, fn); 33 | dom.addEventListener(event, fn); 34 | fun = fn; 35 | } 36 | 37 | 38 | // 4 跨浏览器事件处理程序 39 | 40 | const EventUtil = { 41 | addHandler(element, type, handler) { 42 | if (element.addEventListener) { 43 | element.addEventListener(type, handler, false); 44 | } else if (element.attachEvent) { 45 | element.attachEvent("on" + type, handler); 46 | } else { 47 | element["on" + type] = handler; 48 | } 49 | }, 50 | removeHandler(element, type, handler) { 51 | if (element.removeEventListener) { 52 | element.removeEventListener(type, handler, false); 53 | } else if (element.detachEvent) { 54 | element.detachEvent("on" + type, handler); 55 | } else { 56 | element["on" + type] = null; 57 | } 58 | } 59 | }; 60 | 61 | 62 | // ==================== 事件对象 ==================== 63 | 64 | // 1 阻止 DOM 事件继续冒泡或捕获 65 | 66 | let btn = document.getElementById("myBtn"); 67 | btn.onclick = function (event) { 68 | console.log("Clicked"); 69 | event.stopPropagation(); 70 | }; 71 | document.body.onclick = function (event) { 72 | console.log("Body clicked"); 73 | }; 74 | 75 | 76 | // 2 IE 事件对象 77 | 78 | // DOM0 方式的事件对象 79 | const btn = document.getElementById("myBtn"); 80 | btn.onclick = function () { 81 | let event = window.event; 82 | console.log(event.type); // "click" 83 | }; 84 | 85 | // IE 事件处理程序的事件对象 86 | const btn = document.getElementById("myBtn"); 87 | btn.attachEvent("onclick", function (event) { 88 | console.log(event.type); // "click" 89 | }); 90 | 91 | 92 | // 3 跨浏览器事件对象 93 | 94 | const EventUtil = { 95 | addHandler(element, type, handler) { 96 | // 代码略 97 | }, 98 | getEvent(event) { 99 | returnevent ? event : window.event; 100 | }, 101 | getTarget(event) { 102 | returnevent.target || event.srcElement; 103 | }, 104 | preventDefault(event) { 105 | if (event.preventDefault) { 106 | event.preventDefault(); 107 | } else { 108 | event.returnValue = false; 109 | } 110 | }, 111 | removeHandler(element, type, handler) { 112 | // 代码略 113 | }, 114 | stopPropagation(event) { 115 | if (event.stopPropagation) { 116 | event.stopPropagation(); 117 | } else { 118 | event.cancelBubble = true; 119 | } 120 | } 121 | }; -------------------------------------------------------------------------------- /2 手撕JavaScript/01 跨浏览器事件工具.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 01 跨浏览器事件工具 3 | * Dasen Sun 4 | * 2022-03-13 5 | **************************************************/ 6 | 7 | 8 | const EventUtil = { 9 | // 添加事件处理程序 10 | addHandler(element, type, handler) { 11 | if (element.addEventListener) { 12 | element.addEventListener(type, handler, false); 13 | } else if (element.attachEvent) { 14 | element.attachEvent("on" + type, handler); 15 | } else { 16 | element["on" + type] = handler; 17 | } 18 | }, 19 | // 移除事件处理程序 20 | removeHandler(element, type, handler) { 21 | if (element.removeEventListener) { 22 | element.removeEventListener(type, handler, false); 23 | } else if (element.detachEvent) { 24 | element.detachEvent("on" + type, handler); 25 | } else { 26 | element["on" + type] = null; 27 | } 28 | }, 29 | // 获取事件对象 30 | getEvent(event) { 31 | returnevent ? event : window.event; 32 | }, 33 | // 获取 target 34 | getTarget(event) { 35 | returnevent.target || event.srcElement; 36 | }, 37 | // 阻止事件默认行为 38 | preventDefault(event) { 39 | if (event.preventDefault) { 40 | event.preventDefault(); 41 | } else { 42 | event.returnValue = false; 43 | } 44 | }, 45 | // 阻止事件继续传播 46 | stopPropagation(event) { 47 | if (event.stopPropagation) { 48 | event.stopPropagation(); 49 | } else { 50 | event.cancelBubble = true; 51 | } 52 | } 53 | }; -------------------------------------------------------------------------------- /2 手撕JavaScript/02 尾递归(斐波那契数列).js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 02 尾递归(斐波那契数列) 3 | * Dasen Sun 4 | * 2022-03-13 5 | **************************************************/ 6 | 7 | 8 | function fibonacci(n) { 9 | return (function (n1, n2, i) { 10 | return (i < n) ? arguments.callee(n2, n1 + n2, i + 1) : n1; 11 | })(1, 1, 1); 12 | } -------------------------------------------------------------------------------- /2 手撕JavaScript/03 节流防抖.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 03 节流防抖 3 | * Dasen Sun 4 | * 2022-03-13 5 | **************************************************/ 6 | 7 | 8 | // 1 函数防抖 debounce 9 | 10 | // 在事件被触发n秒后再执行回调函数,如果在这n秒内又被触发,则重新计时。 11 | // debounce 将是一个高阶函数,它接收一个函数和防抖的时间,返回一个包装后的函数,这个函数用于响应事件。 12 | 13 | // 应用场景举例: 14 | // search搜索联想,用户在不断输入值时,用防抖来节约请求资源。 15 | // window触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次. 16 | 17 | function debounce(fun, delay) { 18 | // 闭包封装一个变量保存定时器id 19 | let timer = 0; 20 | // 返回一个包装后的函数 21 | return function (...args) { 22 | // 保存上下文 23 | const that = this; // 保存 this 是为了可以使包装函数成为对象的方法(如使用DOM0方式来绑定事件处理程序) 24 | const _args = args; 25 | // 清除上一个计时器 26 | clearTimeout(timer); 27 | // 设置新的计时器 28 | setTimeout(() => { 29 | // 以保存的上下文来调用 30 | fun.call(that, ..._args); 31 | }, delay); 32 | }; 33 | } 34 | 35 | 36 | // 2 函数节流 throttle 37 | 38 | // 规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。 39 | // 换句话说,就是经过节流包装的函数,触发一次后有一定的冷却时间,冷却时间结束后才能再次触发,防止频繁触发。 40 | // throttle 同样是一个高阶函数,将传入的函数进行包装。 41 | 42 | // 应用场景举例: 43 | // 鼠标不断点击,但让点击事件单位时间内最多触发一次。 44 | // 监听滚动事件,比如是否滑到底部加载更多,单位时间内只判断一次。 45 | 46 | function throttle(fun, delay) { 47 | // 闭包封装上次触发时的时间戳和计时器id 48 | let last, timer; 49 | // 返回一个包装后的函数 50 | return function (...args) { 51 | // 保存上下文 52 | let that = this; 53 | let _args = args; 54 | // 记录当前时间戳 55 | let now = +new Date(); 56 | if (last && now < last + delay) { 57 | // 如果有上次触发的记录并且还没有超过延时,那就推迟到delay之后执行函数(延迟执行) 58 | clearTimeout(timer); 59 | timer = setTimeout(function () { 60 | // 注意:凡执行函数的地方,务必要记住保存执行时间戳 61 | last = now; 62 | fun.apply(that, _args); 63 | }, delay); 64 | } else { 65 | // 如果已经超过延时了,就可以直接执行函数了 66 | last = now; 67 | fun.apply(that, ..._args); 68 | } 69 | } 70 | } 71 | 72 | 73 | // 3 节流防抖的区别 74 | 75 | // 防抖:频繁触发间隔较近的重复操作,只希望触发一次,即前面的全都取消,只保留最后一次触发。 76 | // 节流:频繁触发的重复操作,允许多次触发,但是要限制一下触发的频率不能太快。 -------------------------------------------------------------------------------- /2 手撕JavaScript/04 手写Promise.js: -------------------------------------------------------------------------------- 1 | // Promise/A+ 规范的三种状态 2 | const PENDING = "pending"; 3 | const FULFILLED = "fulfilled"; 4 | const REJECTED = "rejected"; 5 | 6 | class MyPromise { 7 | constructor(executor) { 8 | this._status = PENDING; // Promise状态 9 | this._resolveQueue = []; // 成功队列 10 | this._rejectQueue = []; // 失败队列 11 | 12 | // 由于 resolve/reject 是在 executor 内部被调用,因此需要使用箭头函数固定this指向 13 | let _resolve = (val) => { 14 | if (this._status !== PENDING) return; // 状态只能由 pending 到 fulfilled 或 rejected 15 | this._status = FULFILLED; // 变更状态 16 | while (this._resolveQueue.length) { 17 | const callback = this._resolveQueue.shift(); 18 | callback(val); 19 | } 20 | } 21 | 22 | // reject 的实现同理 23 | let _reject = (val) => { 24 | if (this._status !== PENDING) return; 25 | this._status = REJECTED; 26 | while (this._rejectQueue.length) { 27 | const callback = this._rejectQueue.shift(); 28 | callback(val); 29 | } 30 | } 31 | 32 | // 立即执行 executor ,并传入 resolve 和 reject 33 | executor(_resolve, _reject) 34 | } 35 | 36 | then(resolveFn, rejectFn) { 37 | // return 一个新的 promise ,支持链式调用 38 | return new MyPromise((resolve, reject) => { 39 | // 把 resolveFn 重新包装一下再 push 进 resolve 执行队列,为了变更返回的 promise 的状态 40 | const fulfilledFn = value => { 41 | try { 42 | // 执行 promise1 的成功回调,并获取返回值 43 | let x = resolveFn(value); 44 | // 返回值如果是 Promise ,那么它落定则 promise2 落定,直接用 promise2 的 resolve/reject 作为其落定回调 45 | // 返回值不是 Promise ,promise2 直接解决为返回的值 46 | x instanceof MyPromise ? x.then(resolve, reject) : resolve(x); 47 | } catch (error) { 48 | reject(error); 49 | } 50 | }; 51 | // 把包装好的 fulfilledFn 收集到成功回调队列中 52 | this._resolveQueue.push(fulfilledFn); 53 | 54 | // reject 的实现同理 55 | const rejectedFn = error => { 56 | try { 57 | let x = rejectFn(error); 58 | x instanceof MyPromise ? x.then(resolve, reject) : resolve(x); 59 | } catch (error) { 60 | reject(error); 61 | } 62 | } 63 | this._rejectQueue.push(rejectedFn); 64 | }); 65 | } 66 | } -------------------------------------------------------------------------------- /2 手撕JavaScript/05 手写Promise方法合集.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 05 手写Promise方法合集 3 | * Dasen Sun 4 | * 2022-03-23 5 | **************************************************/ 6 | 7 | 8 | // 1 手写 Promise.all() 9 | 10 | Promise.myAll = function (iterable) { 11 | return new Promise((resolve, reject) => { 12 | let index = 0; // 由于传入的是一个可迭代对象,并不一定是数组,可能没有 length 属性,因此使用变量手动计数 13 | let elementCount = 0; // 已解决的 promise 数量 14 | let anErrorOccurred = false; // 是否有一个错误发生了 15 | for (const promise of iterable) { 16 | const currentIndex = index; // 对每个 promise 要封闭一下 index 作用域! 17 | promise.then( 18 | (value) => { 19 | if (anErrorOccurred) return; // 如果已经有错误发生,就什么也不做了 20 | result[currentIndex] = value; // 将解决结果存入结果数组 21 | elementCount++; // 统计解决个数 22 | if (elementCount === result.length) { 23 | resolve(result); // 全部解决了,最终的 promise 解决 24 | } 25 | }, 26 | (err) => { 27 | if (anErrorOccurred) return; // 如果已经有错误发生,就什么也不做了 28 | anErrorOccurred = true; // 发生了第一个错误,置标志 29 | reject(err); // 最终的 promise 拒绝 30 | }); 31 | index++; 32 | } 33 | if (index === 0) { 34 | resolve([]); // 如果是一个空迭代器,不包含 promise ,解决为空数组 35 | return; 36 | } 37 | const result = new Array(index); // 结果数组,要放在最后,因为 index 要在循环后才能统计出来 38 | }); 39 | }; 40 | 41 | 42 | // 2 手写 Promise.race() 43 | 44 | Promise.myRace = function (iterable) { 45 | return new Promise((resolve, reject) => { 46 | let settlementOccurred = false; // 是否已经有一个解决了的 promise 47 | for (const promise of iterable) { 48 | promise.then( // 给每个 promise 添加解决和拒绝回调 49 | (value) => { 50 | if (settlementOccurred) return; // 如果已经有一个落定了的 promise,就什么也不做了 51 | settlementOccurred = true; // 发生了一个解决,置标志 52 | resolve(value); // 最终的 promise 解决 53 | }, 54 | (err) => { 55 | if (settlementOccurred) return; // 如果已经有一个落定了的 promise,就什么也不做了 56 | settlementOccurred = true; // 发生了一个拒绝,置标志 57 | reject(err); // 最终的 promise 拒绝 58 | }); 59 | } 60 | }); 61 | }; 62 | 63 | 64 | // 3 手写 Promise.allSettled() 65 | 66 | Promise.allSettled = function (promisesArg) { 67 | if (!promisesArg.length) return Promise.resolve([]); // 如果是个空数组 68 | const promises = promisesArg.map((p) => p instanceof Promise ? p : Promise.resolve(p)); // 包装一下不是 promise 的项 69 | return new Promise((resolve, reject) => { 70 | const result = []; 71 | let unSettledPromiseCount = promises.length; 72 | promises.forEach((p, index) => { 73 | p.then((reason) => { 74 | result[index] = { 75 | status: "resolve", 76 | reason, 77 | }; 78 | }, (reason) => { 79 | result[index] = { 80 | status: "reject", 81 | reason, 82 | }; 83 | }).finally(() => { 84 | --unSettledPromiseCount; 85 | if (!unSettledPromiseCount) resolve(result); 86 | }); 87 | }); 88 | }); 89 | }; 90 | 91 | 92 | // 4 手写 Promise.retry() 超时重新请求,并在重试一定次数依然失败时输出缓存内容。 93 | 94 | // 简易版 - 不使用缓存,仅重试 95 | Promise.retrySimple = function (fn, maxRetry, timeout) { 96 | // 参数检查 97 | if (typeof fn !== 'function') { 98 | throw new TypeError('Expected a function'); 99 | } 100 | maxRetry = maxRetry || 3; 101 | timeout = timeout || 1000; 102 | // 默认参数 103 | let retryCount = 0; // 已重试次数 104 | return new Promise((resolve, reject) => { 105 | // 内部函数,进行一次尝试 106 | const run = () => { 107 | fn().then( 108 | (value) => { 109 | // 封装的 promise 解决 110 | resolve(value); 111 | }, 112 | (err) => { 113 | // 超过了最大重试次数,拒绝 114 | if (retryCount >= options.maxRetry) { 115 | reject(err); 116 | return; 117 | } 118 | // 没有超过最大重试次数,则重试 119 | setTimeout(run, options.retryDelay); 120 | retryCount++; 121 | }); 122 | }; 123 | run(); 124 | }); 125 | }; 126 | 127 | // 完整版 - 有缓存 128 | Promise.retry = function (fn, options) { 129 | // 参数检查 130 | if (typeof fn !== 'function') { 131 | throw new TypeError('Expected a function'); 132 | } 133 | options = options || {}; 134 | // 默认参数 135 | options = Object.assign({ 136 | maxRetry: 3, // 默认重试次数 137 | retryDelay: 1000, // 默认重试时间间隔 138 | cache: false, // 是否缓存结果 139 | cacheKey: "", // 缓存 key 140 | cacheExpire: 0, // 缓存过期时间,单位:毫秒 141 | cacheMax: 0 // 缓存最大值,超过后清空缓存 142 | }, options); 143 | let retryCount = 0; // 已重试次数 144 | return new Promise((resolve, reject) => { 145 | // 内部函数,进行一次尝试 146 | const run = () => { 147 | fn().then( 148 | (value) => { 149 | // 成功收到响应,如果需要缓存,则缓存结果,同时设置缓存过期时间 150 | if (options.cache) { 151 | localStorage.setItem(options.cacheKey, JSON.stringify({ 152 | value, 153 | expire: Date.now() + options.cacheExpire 154 | })); 155 | } 156 | // 封装的 promise 解决 157 | resolve(value); 158 | }, 159 | (err) => { 160 | // 超过了最大重试次数,拒绝 161 | if (retryCount >= options.maxRetry) { 162 | reject(err); 163 | return; 164 | } 165 | // 没有超过重试次数,如果有缓存,则读取缓存 166 | if (options.cache) { 167 | const cache = localStorage.getItem(options.cacheKey); 168 | if (cache) { 169 | const cacheObj = JSON.parse(cache); 170 | if (cacheObj.expire > Date.now()) { 171 | resolve(cacheObj.value); 172 | return; 173 | } 174 | } 175 | } 176 | // 重试 177 | setTimeout(run, options.retryDelay); 178 | retryCount++; 179 | }); 180 | }; 181 | run(); 182 | }); 183 | }; 184 | 185 | 186 | // 5 手写 Promise.any() 187 | 188 | Promise.myAny = function (promises) { 189 | return new Promise((resolve, reject) => { 190 | promises = Array.isArray(promises) ? promises : Array.from(promises); 191 | let count = promises.length; 192 | let errs = []; // 用于收集所有 reject 193 | promises.forEach((promise) => { 194 | // 对每个 promise 绑定:如果解决了,则合成的 promise 解决,如果失败了,计数减一,全部失败返回失败结果 195 | promise.then(value => { 196 | resolve(value); 197 | }, err => { 198 | count--; 199 | errs.push(err); 200 | if (count === 0) { 201 | reject(new AggregateError(errs)); 202 | } 203 | }); 204 | }); 205 | }); 206 | }; -------------------------------------------------------------------------------- /2 手撕JavaScript/06 Promise周边.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 06 Promise周边 3 | * Dasen Sun 4 | * 2022-03-21 5 | **************************************************/ 6 | 7 | 8 | // 1 使用 Promise 改写回调地狱 9 | 10 | let t = setTimeout(() => { 11 | console.log(111); 12 | let t1 = setTimeout(() => { 13 | console.log(222); 14 | let t2 = setTimeout(() => { 15 | console.log(333); 16 | }, 3000); 17 | }, 2000); 18 | }, 1000); 19 | 20 | let p = new Promise((resolve) => setTimeout(resolve, 1000)).then(() => { 21 | console.log(111); 22 | return new Promise((resolve) => setTimeout(resolve, 2000)); 23 | }).then(() => { 24 | console.log(222); 25 | return new Promise((resolve) => setTimeout(resolve, 3000)); 26 | }).then(() => { 27 | console.log(333); 28 | }); 29 | 30 | 31 | // 2 使用 Promise 实现每隔三秒输出时间 32 | 33 | function outputTime() { 34 | console.log(new Date()); 35 | return new Promise(resolve => setTimeout(resolve,3000)).then(outputTime); 36 | } 37 | outputTime(); 38 | // Thu Mar 17 2022 22:19:04 GMT+0800 (中国标准时间) 39 | // Thu Mar 17 2022 22:19:07 GMT+0800 (中国标准时间) 40 | // Thu Mar 17 2022 22:19:10 GMT+0800 (中国标准时间) 41 | // ... -------------------------------------------------------------------------------- /2 手撕JavaScript/07 手写函数方法.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 07 手写函数方法 3 | * Dasen Sun 4 | * 2022-03-14 5 | **************************************************/ 6 | 7 | 8 | // 1 apply 方法 9 | 10 | Function.prototype.myApply = function (thisArg, argArray) { 11 | // 容错处理 12 | if (typeof thisArg === "undefined" || thisArg === null) { 13 | thisArg = window; 14 | } else if (typeof thisArg !== "function" && typeof thisArg !== "object") { 15 | thisArg = Object(thisArg); 16 | } 17 | argArray = argArray ? argArray : []; 18 | // 创建一个独一无二的键,防止原有的键被覆盖 19 | const key = Symbol(); 20 | thisArg[key] = this; 21 | // 调用函数 22 | const result = thisArg[key](...argArray); 23 | // 删除临时添加的键 24 | delete thisArg[key]; 25 | // 返回结果 26 | return result; 27 | }; 28 | 29 | 30 | // 2 call 方法 31 | 32 | Function.prototype.myCall = function (thisArg, ...args) { 33 | // 容错处理 34 | if (typeof thisArg === "undefined" || thisArg === null) { 35 | thisArg = window; 36 | } else if (typeof thisArg !== "function" && typeof thisArg !== "object") { 37 | thisArg = Object(thisArg); 38 | } 39 | // 创建一个独一无二的键,防止原有的键被覆盖 40 | const key = Symbol(); 41 | thisArg[key] = this; 42 | // 调用函数 43 | const result = thisArg[key](...args); 44 | // 删除临时添加的键 45 | delete thisArg[key]; 46 | // 返回结果 47 | return result; 48 | }; 49 | 50 | 51 | // 3 bind 方法 52 | 53 | Function.prototype.myBind = function (thisArg, ...args) { 54 | const fn = this; 55 | return function newFn(...newFnArray) { 56 | if (this instanceof newFn) { 57 | return new fn(...args, ...newFnArray) 58 | } 59 | return fn.myApply(thisArg, [...args, ...newFnArray]) 60 | } 61 | }; 62 | 63 | 64 | // 4 测试 65 | 66 | function test(a, b, c) { 67 | console.log(this, c, b, a); 68 | } 69 | 70 | const obj = { 71 | name: "obj", 72 | }; 73 | 74 | test.apply(obj, [1, 2, 3]); // obj 3 2 1 75 | test.myApply(obj, [1, 2, 3]); // obj 3 2 1 76 | test.apply("Dasen", [4, 5, 6]); // String ('Dasen') 6 5 4 77 | test.myApply("Dasen", [4, 5, 6]); // String ('Dasen') 6 5 4 78 | 79 | test.call(obj, 1, 2, 3); // obj 3 2 1 80 | test.myCall(obj, 1, 2, 3); // obj 3 2 1 81 | test.call("Dasen", 4, 5, 6); // String ('Dasen') 6 5 4 82 | test.myCall("Dasen", 4, 5, 6); // String ('Dasen') 6 5 4 83 | 84 | const newTest1 = test.bind(obj, 1, 2); 85 | const newTest2 = test.myBind(obj, 1, 2); 86 | const newTest3 = test.bind("Dasen", 4, 5); 87 | const newTest4 = test.myBind("Dasen", 4, 5); 88 | newTest1(3); // obj 3 2 1 89 | newTest2(3); // obj 3 2 1 90 | newTest3(6); // String ('Dasen') 6 5 4 91 | newTest4(6); // String ('Dasen') 6 5 4 -------------------------------------------------------------------------------- /2 手撕JavaScript/08 实现深拷贝.js: -------------------------------------------------------------------------------- 1 | // 1 递归实现简单深拷贝 2 | 3 | // 如果是原始类型,无需继续拷贝,直接返回 4 | // 如果是引用类型,创建一个新的对象,遍历需要克隆的对象,将需要克隆对象的属性执行深拷贝后依次添加到新对象上 5 | 6 | // 缺陷: 7 | // - 没有考虑数组、函数、null等特殊对象 8 | // - 没有考虑循环引用 9 | 10 | function clone1(target) { 11 | if (typeof target !== "object") return target; 12 | const obj = {}; 13 | for (const key in target) { 14 | obj[key] = clone1(target[key]); 15 | } 16 | return obj; 17 | } 18 | 19 | console.log("---------- test1 ----------"); 20 | const test1 = { 21 | name: "Dasen", 22 | age: 23, 23 | girlfriend: { 24 | name: "???", 25 | }, 26 | }; 27 | const test1Clone = clone1(test1); 28 | console.log(test1Clone); // {name: 'Dasen', age: 23, girlfriend: {…}} 29 | console.log(test1 === test1Clone); // false 30 | console.log(test1.girlfriend === test1Clone.girlfriend); // false 31 | 32 | 33 | // 2 考虑数组、函数、null,函数不拷贝 34 | 35 | function clone2(target) { 36 | if (typeof target === "function") return target; // 函数不进行拷贝 37 | else if (typeof target === "object") { 38 | // 拷贝对象 39 | if (target === null) return null; // null 40 | else if (Array.isArray(target)) { 41 | // 数组 42 | const arr = []; 43 | for (const key in target) { 44 | arr[key] = clone2(target[key]); 45 | } 46 | return arr; 47 | } else { 48 | // 普通对象 49 | const obj = {}; 50 | for (const key in target) { 51 | obj[key] = clone2(target[key]); 52 | } 53 | return obj; 54 | } 55 | } else return target; // 原始值类型直接返回 56 | } 57 | 58 | console.log("---------- test2 ----------"); 59 | const test2 = { 60 | name: "Dasen", 61 | age: 23, 62 | girlfriend: { 63 | name: "???", 64 | }, 65 | hobbies: ["coding", "cube", "sleep"], 66 | sayHello() { 67 | console.log("Hello, I am", this.name + "."); 68 | }, 69 | }; 70 | const test2Clone = clone2(test2); 71 | console.log(test2Clone); // {name: 'Dasen', age: 23, girlfriend: {…}} 72 | console.log(test2 === test2Clone); // false 73 | console.log(test2.girlfriend === test2Clone.girlfriend); // false 74 | console.log(test2.hobbies === test2Clone.hobbies); // false 75 | console.log(test2.sayHello === test2Clone.sayHello); // true 76 | test2.sayHello(); 77 | test2Clone.sayHello(); 78 | 79 | 80 | // 3 考虑循环引用 81 | 82 | function clone3(target, map = new Map()) { 83 | if (typeof target === "function") return target; // 函数不进行拷贝 84 | else if (typeof target === "object") { 85 | // 拷贝对象 86 | if (target === null) return null; // null 87 | else if (map.has(target)) { 88 | // 拷贝过这个对象,直接返回 89 | return map.get(target); 90 | } else if (Array.isArray(target)) { 91 | // 数组 92 | const arr = []; 93 | map.set(target, arr); // 存储一下拷贝的对象 94 | for (const key in target) { 95 | arr[key] = clone3(target[key], map); 96 | } 97 | return arr; 98 | } else { 99 | // 拷贝对象 100 | const obj = {}; 101 | map.set(target, obj); // 存储一下拷贝的对象 102 | for (const key in target) { 103 | obj[key] = clone3(target[key], map); 104 | } 105 | return obj; 106 | } 107 | } else return target; // 原始值类型直接返回 108 | } 109 | 110 | console.log("---------- test3 ----------"); 111 | const test3 = { 112 | name: "Dasen", 113 | age: 23, 114 | girlfriend: { 115 | name: "???", 116 | }, 117 | }; 118 | test3.myself = test3; 119 | const test3Clone = clone3(test3); 120 | console.log(test3Clone); // {name: 'Dasen', age: 23, girlfriend: {…}, myself: {…}} 121 | console.log(test3.girlfriend===test3Clone.girlfriend); // false 122 | console.log(test3.myself===test3Clone.myself); // false 123 | console.log(test3Clone===test3Clone.myself); // true 124 | 125 | 126 | // 4 考虑拷贝函数 127 | 128 | function clone4(target) { 129 | if (typeof target === "function") { 130 | // 拷贝函数 131 | const funcString = target.toString(); 132 | if (target.prototype) { 133 | // 普通函数 134 | const bodyReg = /(?<={)(.|\n)+(?=})/m; // ===================== Todo: 正则表达式有问题,要改 135 | const paramReg = /(?<=\().+(?=\)\s+{)/; 136 | const param = paramReg.exec(funcString); 137 | const body = bodyReg.exec(funcString); 138 | console.log(body, param, funcString); 139 | if (param) { 140 | const paramArr = param[0].split(','); 141 | return new Function(...paramArr, body[0]); 142 | } else { 143 | return new Function(body[0]); 144 | } 145 | } else { 146 | // 箭头函数 147 | return eval(funcString); 148 | } 149 | } else if (typeof target === "object") { 150 | // 拷贝对象 151 | if (target === null) return null; // null 152 | else if (Array.isArray(target)) { 153 | // 数组 154 | const arr = []; 155 | for (const key in target) { 156 | arr[key] = clone2(target[key]); 157 | } 158 | return arr; 159 | } else { 160 | // 普通对象 161 | const obj = {}; 162 | for (const key in target) { 163 | obj[key] = clone2(target[key]); 164 | } 165 | return obj; 166 | } 167 | } 168 | } 169 | 170 | console.log("---------- test2 ----------"); 171 | 172 | 173 | // 5 考虑更多常用类型 174 | 175 | 176 | console.log("---------- test5 ----------"); -------------------------------------------------------------------------------- /2 手撕JavaScript/09 实现delay函数.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 09 实现delay函数 3 | * Dasen Sun 4 | * 2022-03-17 5 | **************************************************/ 6 | 7 | 8 | // 1 普通函数 9 | 10 | function delay(time, fn, ...args) { 11 | if (typeof time !== "number" || time < 0) return; 12 | return new Promise((resolve) => { 13 | setTimeout(resolve, time * 1000); 14 | }).then(() => fn(...args)); 15 | } 16 | 17 | 18 | // 2 异步函数 19 | 20 | async function delayAsync(time, fn, ...args) { 21 | if (typeof time !== "number" || time < 0) return; 22 | await new Promise((resolve) => { 23 | setTimeout(resolve, time * 1000); 24 | }); 25 | return fn(...args); 26 | } 27 | 28 | 29 | // 3 测试 30 | 31 | function test(arg) { 32 | console.log("test", arg); 33 | } 34 | 35 | delay(1, test, 1); // 1秒后输出:test 1 36 | delayAsync(2, test, 2); // 2秒后输出:test 2 -------------------------------------------------------------------------------- /2 手撕JavaScript/10 解析URL.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 10 解析URL 3 | * Dasen Sun 4 | * 2022-03-14 5 | **************************************************/ 6 | 7 | 8 | const UrlUtil = { 9 | stringify(baseUrl, parasObj) { 10 | parasObj = parasObj || {}; 11 | let url = baseUrl; 12 | let first = false; 13 | if (url.indexOf("?") === -1) { 14 | url += "?"; 15 | first = true; 16 | } else if (url.indexOf("?") === url.length - 1) { 17 | first = true; 18 | } 19 | for (const key in parasObj) { 20 | if (!first && url.indexOf("&") !== url.length - 1) url += "&"; 21 | else first = false; 22 | url += `${encodeURIComponent(key)}=${encodeURIComponent(parasObj[key])}`; 23 | } 24 | if (url.indexOf("?") === url.length - 1) return url.substring(0, url.length - 1); 25 | return url; 26 | }, 27 | parse(url) { 28 | if (url.indexOf("?") === -1 || url.indexOf("?") === url.length - 1) return {}; 29 | const url_s = url.split("?"); 30 | if (url_s.length > 2) return {}; 31 | const paras = url_s[1].split("&").map((v) => v.split("=")); 32 | const obj = {}; 33 | for (const [key, val] of paras) { 34 | obj[decodeURIComponent(key)] = decodeURIComponent(val); 35 | } 36 | return obj; 37 | }, 38 | getParaItem(url, key) { 39 | const obj = this.parse(url); 40 | return obj[key]; 41 | }, 42 | }; 43 | 44 | 45 | // 测试 46 | 47 | let url; 48 | 49 | url = UrlUtil.stringify("https://www.baidu.com/s", { 50 | wd: "hello world", 51 | tn: "baiduhome_pg", 52 | }); 53 | console.log(url); // https://www.baidu.com/s?wd=hello%20world&tn=baiduhome_pg 54 | 55 | url = UrlUtil.stringify("https://www.baidu.com/s?wd=hello%20world", { 56 | tn: "baiduhome_pg", 57 | }); 58 | console.log(url); // https://www.baidu.com/s?wd=hello%20world&tn=baiduhome_pg 59 | 60 | url = UrlUtil.stringify("https://www.baidu.com/s?", { 61 | wd: "hello world", 62 | }); 63 | console.log(url); // https://www.baidu.com/s?wd=hello%20world 64 | 65 | url = UrlUtil.stringify("https://www.baidu.com/s?"); 66 | console.log(url); // https://www.baidu.com/s 67 | 68 | let parasObj; 69 | 70 | parasObj = UrlUtil.parse("https://www.baidu.com/s?wd=hello%20world&tn=baiduhome_pg"); 71 | console.log(parasObj); // {wd: 'hello world', tn: 'baiduhome_pg'} 72 | 73 | parasObj = UrlUtil.parse("https://www.baidu.com/s?error?error?"); 74 | console.log(parasObj); // {} 75 | 76 | parasObj = UrlUtil.parse("https://www.baidu.com/s?"); 77 | console.log(parasObj); // {} 78 | 79 | parasObj = UrlUtil.parse("https://www.baidu.com/s"); 80 | console.log(parasObj); // {} 81 | 82 | console.log(UrlUtil.getParaItem("https://www.baidu.com/s?wd=hello%20world&tn=baiduhome_pg", "wd")); // hello world -------------------------------------------------------------------------------- /2 手撕JavaScript/11 柯里化的add函数.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 11 柯里化的add函数 3 | * Dasen Sun 4 | * 2022-03-14 5 | **************************************************/ 6 | 7 | 8 | // 1 一种实现 9 | 10 | function add(...args) { 11 | const outerResult = args.reduce((pre, cur) => pre + cur); 12 | const fn = function (...args) { 13 | if (!args.length) return outerResult; 14 | const innerResult = args.reduce((pre, cur) => pre + cur); 15 | return add(outerResult, innerResult); 16 | }; 17 | return fn; 18 | } 19 | 20 | 21 | // 2 另一种实现 22 | 23 | function add2(...args) { 24 | const nums = [...args]; 25 | const fn = function (...args) { 26 | nums.push(...args); 27 | return arguments.length ? fn : nums.reduce((pre, cur) => pre + cur); 28 | }; 29 | return fn; 30 | } 31 | 32 | 33 | // 3 测试 34 | 35 | console.log(add(1, 2, 3)(4, 5)(6)()); // 21 36 | console.log(add(1, 1, 1)(1, 1)(1, 1, 1, 1)()); // 9 37 | console.log(add2(1, 2, 3)(4, 5)(6)()); // 21 38 | console.log(add2(1, 1, 1)(1, 1)(1, 1, 1, 1)()); // 9 -------------------------------------------------------------------------------- /2 手撕JavaScript/12 调用计数器(支持重置).js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 12 调用计数器(支持重置) 3 | * Dasen Sun 4 | * 2022-03-14 5 | **************************************************/ 6 | 7 | 8 | // 问题说明: 9 | // 设计一个计数器,每次调用都能够使计数加一,计数变量封装在内部,并支持重置计数 10 | 11 | 12 | // 1 类式 13 | 14 | function Counter() { 15 | this.cnt = 0; 16 | this.add = function () { 17 | this.cnt++; 18 | console.log(this.cnt); 19 | }; 20 | this.reset = function () { 21 | this.cnt = 0; 22 | console.log(this.cnt); 23 | }; 24 | } 25 | 26 | const counter = new Counter(); 27 | counter.add(); // 1 28 | counter.add(); // 2 29 | counter.reset(); // 0 30 | counter.add(); // 1 31 | 32 | 33 | // 2 函数式 34 | 35 | function createCounter() { 36 | let cnt = 0; 37 | return [ 38 | function add() { 39 | cnt++; 40 | console.log(cnt); 41 | }, 42 | function reset() { 43 | cnt = 0; 44 | console.log(cnt); 45 | } 46 | ]; 47 | } 48 | 49 | const [count,reset] = createCounter(); 50 | count(); // 1 51 | count(); // 2 52 | reset(); // 0 53 | count(); // 1 -------------------------------------------------------------------------------- /2 手撕JavaScript/13 手写数组方法.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 13 手写数组方法 3 | * Dasen Sun 4 | * 2022-03-21 5 | **************************************************/ 6 | 7 | 8 | // 1 手写 Array.prototype.filter() 9 | 10 | Array.prototype.myFilter = function (callback, thisArg) { 11 | // 判断合法性 12 | if (this == undefined) { 13 | throw new TypeError('this is null or not undefined'); 14 | } 15 | if (typeof callback !== 'function') { 16 | throw new TypeError(callback + 'is not a function'); 17 | } 18 | // 结果数组 19 | const res = []; 20 | // 强制转换对象,为了支持类数组对象 21 | const obj = Object(this); 22 | // >>> 0 保证 len 为 number,且为正整数,防止类数组对象的 length 属性被重写为其他意义 23 | const len = obj.length >>> 0; 24 | // 遍历数组 25 | for (let i = 0; i < len; i++) { 26 | // 检查是否存在索引 i ,存在才去过滤,包括原型链 27 | if (i in obj) { 28 | if (callback.call(thisArg, obj[i], i, obj)) { 29 | res.push(obj[i]); 30 | } 31 | } 32 | } 33 | return res; 34 | }; 35 | 36 | 37 | // 2 手写 Array.prototype.map() 38 | 39 | // forEach 方法和 map 实际上是一样的,区别仅仅是不返回值,只是调用一遍回调函数 40 | 41 | Array.prototype.myMap = function (callback, thisArg) { 42 | // 判断合法性 43 | if (this == undefined) { 44 | throw new TypeError('this is null or not defined'); 45 | } 46 | if (typeof callback !== 'function') { 47 | throw new TypeError(callback + ' is not a function'); 48 | } 49 | // 结果数组 50 | const res = []; 51 | // 支持类数组对象 52 | const obj = Object(this); 53 | const len = obj.length >>> 0; 54 | for (let i = 0; i < len; i++) { 55 | if (i in obj) { 56 | res[i] = callback.call(thisArg, obj[i], i, this); 57 | } 58 | } 59 | return res; 60 | }; 61 | 62 | 63 | // 3 手写 Array.prototype.reduce() 64 | 65 | // 下述“累加”并非实指,操作不一定为加操作,具体由用户的回调函数决定 66 | 67 | Array.prototype.myReduce = function (callback, initialValue) { 68 | // 判断合法性 69 | if (this == undefined) { 70 | throw new TypeError('this is null or not defined'); 71 | } 72 | if (typeof callback !== 'function') { 73 | throw new TypeError(callback + ' is not a function'); 74 | } 75 | // 支持类数组对象 76 | const obj = Object(this); 77 | const len = this.length >>> 0; 78 | let accumulator = initialValue; 79 | let k = 0; 80 | if (accumulator === undefined) { 81 | // 第二个参数为 undefined ,寻找数组的第一个有效值作为累加器的初始值 82 | while (k < len && !(k in obj)) { 83 | k++; 84 | } 85 | // 超出数组界限还没有找到累加器的初始值 86 | if (k >= len) { 87 | throw new TypeError('Reduce of empty array with no initial value'); 88 | } 89 | accumulator = O[k++]; 90 | } 91 | // 累加 92 | while (k < len) { 93 | if (k in obj) { 94 | accumulator = callback.call(undefined, accumulator, O[k], k, O); 95 | } 96 | k++; 97 | } 98 | return accumulator; 99 | }; 100 | 101 | 102 | // 4 手写 Array.prototype.sort() 103 | 104 | Array.prototype.mySort = function (compareFn, start, end) { 105 | if (start === undefined) { 106 | start = 0; 107 | end = this.length - 1 108 | } 109 | let i = start, 110 | j = end, 111 | a = this[i]; 112 | if (i >= j) return this; 113 | while (i < j) { 114 | while (compareFn(this[j], a) >= 0 && i < j) --j; 115 | this[i] = this[j]; 116 | while (compareFn(this[i], a) <= 0 && i < j) ++i; 117 | this[j] = this[i]; 118 | } 119 | this[i] = a; 120 | this.mySort(compareFn, start, i - 1); 121 | this.mySort(compareFn, i + 1, end); 122 | return this; 123 | }; 124 | 125 | 126 | // 5 使用数组原生的 reduce 方法实现 map 方法 127 | 128 | Array.prototype.myMap = function (fn,thisArg) { 129 | return this.reduce((pre,cur,index,arr) => { 130 | pre.push(fn.call(thisArg,cur,index,arr)); 131 | return pre; 132 | },[]); 133 | }; 134 | 135 | 136 | // 6 实现 Array.prototype.nestingDepth() 求数组最大嵌套深度 137 | 138 | Array.prototype.nestingDepth = function () { 139 | let max = 1; 140 | for(const a of this) { 141 | if(a instanceof Array) { 142 | const depth = a.nestingDepth()+1; 143 | if(max=n) result.push(pair[0]); 161 | } 162 | return result; 163 | }; 164 | 165 | 166 | // 8 实现洗牌算法 Array.prototype.shuffle() 随机打乱数组 167 | 168 | Array.prototype.shuffle = function () { 169 | const len = this.length; 170 | for (let i = 0; i < len; i++) { 171 | let idx = Math.floor(Math.random() * (len - i)); 172 | [this[len - 1 - i], this[idx]] = [this[idx], this[len - 1 - i]]; 173 | } 174 | return this; 175 | }; -------------------------------------------------------------------------------- /2 手撕JavaScript/14 实现内存函数缓存函数调用结果.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 14 实现内存函数缓存函数调用结果 3 | * Dasen Sun 4 | * 2022-03-15 5 | **************************************************/ 6 | 7 | 8 | // 问题描述: 9 | // 写一个内存函数 memoize ,给它一个计算函数,它把函数包装并返回 10 | // 包装函数第一次调用进行计算并缓存,以后每次调用函数都返回已缓存的计算结果 11 | 12 | 13 | // 1 单一结果函数的包装函数 - 一个变量记录结果 14 | 15 | // 缺陷:能够包装的函数只是一个单一的结算过程,即不需要参数且返回的结果始终不变 16 | 17 | function memoizeInvariable(fn) { 18 | let result; 19 | return function () { 20 | if (result === undefined) { 21 | result = fn(); 22 | } 23 | return result; 24 | }; 25 | } 26 | 27 | function test1() { 28 | console.log("进行计算..."); 29 | return 1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10; 30 | } 31 | 32 | const memoizeTest1 = memoizeInvariable(test1); 33 | 34 | console.log(memoizeTest1()); 35 | // 进行计算... 36 | // 3628800 37 | console.log(memoizeTest1()); 38 | // 3628800 39 | console.log(memoizeTest1()); 40 | // 3628800 41 | 42 | 43 | // 2 多结果函数的包装函数 - 使用 Map 记录结果 44 | 45 | // 缺陷:缓存的 Map 可能不断增长占用大量内存 46 | 47 | function memoize(fn) { 48 | const map = new Map(); 49 | return function (...args) { 50 | const key = args.map((val) => String(val)).join(","); 51 | if (!map.has(key)) { 52 | map.set(key, fn(...args)); 53 | } 54 | return map.get(key); 55 | }; 56 | } 57 | 58 | function test2(a, b) { 59 | console.log("进行计算..."); 60 | return a * b; 61 | } 62 | 63 | const memoizeTest2 = memoize(test2); 64 | 65 | console.log(memoizeTest2(2, 3)); 66 | // 进行计算... 67 | // 6 68 | console.log(memoizeTest2(2, 3)); 69 | // 6 70 | console.log(memoizeTest2(2, 3)); 71 | // 6 72 | 73 | console.log(memoizeTest2(4, 5)); 74 | // 进行计算... 75 | // 20 76 | console.log(memoizeTest2(4, 5)); 77 | // 20 78 | console.log(memoizeTest2(4, 5)); 79 | // 20 80 | 81 | console.log(memoizeTest2(6, 8)); 82 | // 进行计算... 83 | // 48 84 | console.log(memoizeTest2(6, 8)); 85 | // 48 86 | console.log(memoizeTest2(6, 8)); 87 | // 48 88 | 89 | 90 | // 3 进一步优化 91 | 92 | // 基于 2 中的 memoize 函数进行优化,将 map 更换为使用了 LRU 算法的数据结构 93 | // LRU 算法参见 「 手撕万物 - 2 JavaScript应用 - 01 LRU缓存置换算法.js 」 -------------------------------------------------------------------------------- /2 手撕JavaScript/15 手写new操作.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 15 手写new操作 3 | * Dasen Sun 4 | * 2022-03-16 5 | **************************************************/ 6 | 7 | 8 | // 问题描述: 9 | // 将 new 操作封装为一个函数 newOperator 传入构造函数和参数即可通过该构造函数构造出一个实例 10 | 11 | // new 操作的本质: 12 | // 构造函数具有 prototype 那么 new 出来的对象的原型就是构造函数的 prototype 13 | // 可以通过构造函数的 prototype 来创建一个对象,然后以该对象为 this 来调用构造函数,并将其返回即可 14 | 15 | // 注意: 16 | // 当构造函数显式返回了一个可用的对象的话,该对象会成为最终 new 出来的对象 17 | // 如果没有返回一个可用的对象,就视为该构造函数将传进去的 this 进行了一番改造,最终这个改造后的对象就是 new 出来的对象 18 | 19 | 20 | function newOperator(constructorFn, ...args) { 21 | // 检查合法性 22 | if (typeof constructorFn !== 'function') { 23 | throw new TypeError('Type Error'); 24 | } 25 | // 以 constructor 的 prototype 为原型创造一个对象 26 | const obj = Object.create(constructorFn.prototype); 27 | // 以该对象为 this 调用 constructor 28 | const res = constructorFn.apply(obj, args); 29 | // 返回创建的对象 30 | return typeof res === "object" && res !== null || typeof res === "function" ? res : obj; 31 | } -------------------------------------------------------------------------------- /2 手撕JavaScript/16 实现sleep函数.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 16 实现sleep函数 3 | * Dasen Sun 4 | * 2022-03-16 5 | **************************************************/ 6 | 7 | 8 | // 如果说 sleep 函数是为了推迟执行某个函数,那这就和 delay 函数没什么区别了 9 | // 而 JavaScript 又无法实现真正的阻塞主线程的 sleep 函数,因此只能实现阻塞异步函数的 sleep函数 10 | 11 | 12 | async function sleep(time) { 13 | await new Promise(resolve => setTimeout(resolve, time * 1000)); 14 | } 15 | 16 | async function test() { 17 | console.log("111"); 18 | await sleep(1); // sleep 1秒 19 | console.log("222"); 20 | await sleep(2); // sleep 2秒 21 | console.log("333"); 22 | } 23 | 24 | test(); -------------------------------------------------------------------------------- /2 手撕JavaScript/17 手写isNaN函数.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 17 手写isNaN函数 3 | * Dasen Sun 4 | * 2022-03-16 5 | **************************************************/ 6 | 7 | 8 | // 1 前提 9 | 10 | // 之所以需要 isNaN 函数是因为 NaN 不与自己相等,同样我们可以利用 NaN 不与自身相等的特性来判断 NaN 11 | 12 | console.log(NaN === NaN); // false 13 | console.log(typeof NaN === "number"); // true 14 | 15 | // 来看 JavaScript 的两个 isNaN 函数: 16 | 17 | // isNaN() 纯粹地是判断一个值是否可以转换为数值,对于可以通过转型函数转为数值的值一律返回 false ,反之返回 true 18 | console.log(isNaN(NaN)); // true 19 | console.log(isNaN(1)); // false 20 | console.log(isNaN("1")); // false 21 | console.log(isNaN("a")); // true 22 | // Number.isNaN() 则仅仅是判断一个值是否与 NaN 严格相等 23 | console.log(Number.isNaN(NaN)); // true 24 | console.log(Number.isNaN(1)); // false 25 | console.log(Number.isNaN("1")); // false 26 | console.log(Number.isNaN("a")); // false 27 | 28 | 29 | // 2 实现 isNotANumber() 来代替 isNaN() 30 | 31 | function isNotANumber(val) { 32 | val = Number(val); 33 | return val !== val; 34 | } 35 | 36 | 37 | // 3 实现 Number.myIsNaN() 来代替 Number.isNaN() 38 | 39 | Number.myIsNaN = function (val) { 40 | return val !== val; 41 | } 42 | 43 | 44 | // 4 测试 45 | 46 | console.log(isNotANumber(NaN)); // true 47 | console.log(isNotANumber(1)); // false 48 | console.log(isNotANumber("1")); // false 49 | console.log(isNotANumber("a")); // true 50 | console.log(Number.myIsNaN(NaN)); // true 51 | console.log(Number.myIsNaN(1)); // false 52 | console.log(Number.myIsNaN("1")); // false 53 | console.log(Number.myIsNaN("a")); // false -------------------------------------------------------------------------------- /2 手撕JavaScript/19 读写object路径上的值.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 19 读写object路径上的值 3 | * Dasen Sun 4 | * 2022-03-20 5 | **************************************************/ 6 | 7 | 8 | // 1 实现 getValue 函数来读取对象路径上的值 9 | 10 | const getValue = function (obj, query) { 11 | if (query === "" || query === undefined) return obj; 12 | if (typeof query === "string") { 13 | query = query.split("."); 14 | } 15 | if (query.length === 1) { 16 | return obj[query.shift()]; 17 | } 18 | const key = query.shift(); 19 | if (obj[key] !== undefined) { 20 | return getValue(obj[key], query); 21 | } else { 22 | return undefined; 23 | } 24 | }; 25 | 26 | 27 | // 2 实现 setValue 函数来修改对象路径上的值 28 | 29 | const setValue = function (obj, query, val) { 30 | let t = obj; 31 | const rawp = query.split("."); 32 | const properties = rawp.slice(0, -1); 33 | for (const p of properties) { 34 | if (t === null || t === undefined) return false; 35 | t = obj[p]; 36 | } 37 | if (typeof t !== "object") return false; 38 | t[rawp[rawp.length - 1]] = val; 39 | return true; 40 | }; 41 | 42 | 43 | // 3 测试 44 | 45 | const obj = { 46 | name: "Dasen", 47 | age: 23, 48 | bestFriend: { 49 | name: "Tiantian", 50 | age: 22, 51 | homeTown: ["Hubei", "Huanggang"], 52 | }, 53 | }; 54 | 55 | console.log(getValue(obj)); // {name: 'Dasen', age: 23, bestFriend: {…}} 56 | console.log(getValue(obj, "")); // {name: 'Dasen', age: 23, bestFriend: {…}} 57 | console.log(getValue(obj, "name")); // Dasen 58 | console.log(getValue(obj, "age")); // 23 59 | console.log(getValue(obj, "gender")); // undefined 60 | console.log(getValue(obj, "gender.type")); // undefined 61 | console.log(getValue(obj, "bestFriend.name")); // Tiantian 62 | console.log(getValue(obj, "bestFriend.gender")); // undefined 63 | console.log(getValue(obj, "bestFriend.homeTown")); // ['Hubei', 'Huanggang'] 64 | console.log(getValue(obj, "bestFriend.homeTown.1")); // Huanggang 65 | 66 | setValue(obj, "name", "Dasen Sun"); 67 | setValue(obj, "bestFriend.age", 23); 68 | console.log(obj); // {name: 'Dasen Sun', age: 23, bestFriend: {name: 'Tiantian', age: 23, homeTown: Array(2)}} -------------------------------------------------------------------------------- /2 手撕JavaScript/20 手写字符串方法.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 20 手写字符串方法 3 | * Dasen Sun 4 | * 2022-03-21 5 | **************************************************/ 6 | 7 | 8 | // 1 实现字符串翻转方法 String.prototype.reverse() 9 | 10 | String.prototype.reverse = function () { 11 | let result = ""; 12 | const len = this.length; 13 | for(let i=len-1;i>=0;--i) { 14 | result+=this[i]; 15 | } 16 | return result; 17 | }; 18 | 19 | 20 | // 2 手写 String.prototype.indexOf() 21 | 22 | String.prototype.myIndexOf = function (c) { 23 | let len = this.length; 24 | for(let i=0;i { 33 | return pre.concat(Array.isArray(cur) ? cur.flattenA() : cur); 34 | }, []); 35 | }; 36 | console.log(arr.flattenA()); 37 | 38 | 39 | // 5 递归 40 | 41 | Array.prototype.flattenB = function () { 42 | const result = []; 43 | for (const a of this) { 44 | if (Array.isArray(a)) result.push(...a.flattenB()); 45 | else result.push(a); 46 | } 47 | return result; 48 | }; 49 | console.log(arr.flattenB()); -------------------------------------------------------------------------------- /2 手撕JavaScript/22 数组去重.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 22 数组去重 3 | * Dasen Sun 4 | * 2022-03-16 5 | **************************************************/ 6 | 7 | 8 | // 问题描述: 9 | // 实现数组去重(尽可能多的方法) 10 | 11 | 12 | // 1 测试数据 13 | 14 | // 预期结果(顺序无关):[1, 2, 3, 4, 5, 6, 7, 8, 9] 15 | 16 | const arr = [7, 1, 8, 5, 4, 9, 3, 6, 2, 5, 1, 2, 5, 3, 9]; 17 | 18 | 19 | // 2 使用 Set 20 | 21 | // 使用 Map 也可以,思路差不多 22 | 23 | console.log(Array.from(new Set(arr))); 24 | 25 | 26 | // 3 使用数组的 indexOf 方法 27 | 28 | // 使用 includes 方法和 filter 方法也可以,思路都是把没出现过的筛选出来 29 | 30 | Array.prototype.unique = function () { 31 | const result = []; 32 | for (const a of this) { 33 | if (result.indexOf(a) === -1) result.push(a); 34 | } 35 | return result; 36 | }; 37 | console.log(arr.unique()); -------------------------------------------------------------------------------- /2 手撕JavaScript/23 手写instanceof.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 23 手写instanceof 3 | * Dasen Sun 4 | * 2022-03-16 5 | **************************************************/ 6 | 7 | 8 | const myInstanceof = function (obj, type) { 9 | // 基本数据类型都返回false 10 | if (typeof obj !== "object" && typeof obj !== "function" || obj === null) return false; 11 | let proto = Object.getPrototypeOf(obj); 12 | while (true) { 13 | if (proto === null) return false; 14 | if (proto === type.prototype) return true; 15 | proto = Object.getPrototypeOf(proto); 16 | } 17 | }; 18 | 19 | const arr = []; 20 | console.log(myInstanceof(arr, Array)); // true 21 | const obj = {}; 22 | console.log(myInstanceof(obj, Object)); // true 23 | console.log(myInstanceof(arr, Object)); // true 24 | console.log(myInstanceof(obj, Array)); // false -------------------------------------------------------------------------------- /2 手撕JavaScript/24 手写Object静态方法.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 24 手写Object静态方法 3 | * Dasen Sun 4 | * 2022-03-16 5 | **************************************************/ 6 | 7 | 8 | // 1 手写 Object.is() 9 | 10 | // Object.is() 主要为了解决两个问题: 11 | // +0 === -0 和 0 === -0 和 0 === +0 都为 true ,但希望 0 和 -0 不相等 12 | // NaN === NaN 为 false ,但希望它们相等 13 | 14 | Object.myIs = function (a, b) { 15 | if (a === b) { 16 | // 利用 1/(+0) !== 1/(-0) 解决 +0 === -0 的问题 17 | return a !== 0 || b !== 0 || 1 / a === 1 / b; 18 | } else { 19 | // 解决 NaN === NaN 20 | return a !== a && b !== b; 21 | } 22 | }; 23 | 24 | console.log(Object.is(0, +0)); // true 25 | console.log(Object.myIs(0, +0)); // true 26 | console.log(Object.is(0, -0)); // false 27 | console.log(Object.myIs(0, -0)); // false 28 | console.log(Object.is(NaN, NaN)); // true 29 | console.log(Object.myIs(NaN, NaN)); // true 30 | 31 | 32 | // 2 手写 Object.assign() 33 | 34 | // Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象(浅拷贝) 35 | 36 | Object.myAssign = function (obj, ...args) { 37 | // 检查合法性 38 | if (obj == null) { 39 | return new TypeError('Cannot convert undefined or null to object'); 40 | } 41 | // 将原始值转换为对象 42 | const target = Object(obj); 43 | // 遍历每一个源对象 44 | for (const source of args) { 45 | if (source !== null) { 46 | // 使用 for-in 和 hasOwnProperty 双重判断,确保只拿到本身的属性和方法,不包含原型链上的 47 | for (const key in source) { 48 | if (Object.prototype.hasOwnProperty.call(source, key)) { 49 | target[key] = source[key]; 50 | } 51 | } 52 | } 53 | } 54 | return target; 55 | } 56 | 57 | console.log(Object.myAssign({}, { 58 | name: "Dasen", 59 | age: 23 60 | }, { 61 | secondName: "Sun" 62 | })); // {name: 'Dasen', age: 23, secondName: 'Sun'} -------------------------------------------------------------------------------- /2 手撕JavaScript/25 对象扁平化.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 25 对象扁平化 3 | * Dasen Sun 4 | * 2022-03-17 5 | **************************************************/ 6 | 7 | 8 | const flatObject = function (target, keysPrefix, newObj) { 9 | if (!newObj && typeof target !== "object") return target; 10 | const obj = newObj || {}; 11 | if (typeof target === "object") { 12 | for (const key in target) { 13 | let prefix; 14 | if (keysPrefix) prefix = keysPrefix + "." + key; 15 | else prefix = key; 16 | flatObject(target[key], prefix, obj); 17 | } 18 | } else { 19 | obj[keysPrefix] = target; 20 | } 21 | return obj; 22 | }; 23 | 24 | 25 | // 测试 26 | 27 | const obj = { 28 | name: "Dasen", 29 | age: 23, 30 | bestFriend: { 31 | name: "Tiantian", 32 | age: 22, 33 | homeTown: ["Hubei", "Huanggang"], 34 | }, 35 | }; 36 | console.log(flatObject(obj)); // {name: 'Dasen', age: 23, bestFriend.name: 'Tiantian', bestFriend.age: 22, bestFriend.homeTown.0: 'Hubei', …} -------------------------------------------------------------------------------- /2 手撕JavaScript/26 使用Promise封装Ajax请求.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 26 使用Promise封装Ajax请求 3 | * Dasen Sun 4 | * 2022-04-10 5 | **************************************************/ 6 | 7 | 8 | // 问题描述: 9 | // 用 XMLHttpRequest 实现一个 Ajax 请求函数,类似 fetch 10 | // 要求能够传入 headers 对象实现定制请求头 11 | // 返回一个 Promise ,收到响应则 resolve 12 | 13 | 14 | function ajaxRequest(url, method, body, headers) { 15 | return new Promise((resolve, reject) => { 16 | headers = headers || {}; 17 | body = body || null; 18 | method = method || "get"; 19 | const xhr = new XMLHttpRequest(); 20 | xhr.open(method, url, true); 21 | for (const key in headers) { 22 | xhr.setRequestHeader(key, headers[key]); 23 | } 24 | xhr.onreadystatechange = function () { 25 | if (xhr.readyState !== 4) { 26 | reject(xhr.status); 27 | } 28 | if (xhr.status >= 200 && xhr.status < 300) { 29 | resolve(xhr.responseText); 30 | } 31 | }; 32 | xhr.onerror = function (e) { 33 | reject(e); 34 | }; 35 | xhr.send(body); 36 | }) 37 | } -------------------------------------------------------------------------------- /2 手撕JavaScript/27 事件委托.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 27 事件委托 3 | * Dasen Sun 4 | * 2022-03-18 5 | **************************************************/ 6 | 7 | 8 | function delegate(element, eventType, selector, fn) { 9 | element.addEventListener(eventType, e => { 10 | let el = e.target; 11 | while (!el.matches(selector)) { 12 | if (element === el) { 13 | el = null; 14 | break; 15 | } 16 | el = el.parentNode; 17 | } 18 | el && fn.call(el, e, el); 19 | }); 20 | return element; 21 | } -------------------------------------------------------------------------------- /2 手撕JavaScript/28 移除空属性.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 28 移除空属性 3 | * Dasen Sun 4 | * 2022-03-18 5 | **************************************************/ 6 | 7 | 8 | Object.prototype.deleteEmptyProperty = function () { 9 | const delList = []; 10 | for (const key in this) { 11 | if (this[key] === null || this[key] === undefined) { 12 | delList.push(key); 13 | } else if (typeof this[key] === "object") { 14 | this[key].deleteEmptyProperty(); 15 | } 16 | } 17 | for (const key of delList) { 18 | delete this[key]; 19 | } 20 | }; 21 | 22 | 23 | // 测试 24 | 25 | const obj = { 26 | name: "Dasen", 27 | age: 23, 28 | girlFriend: null, 29 | dog: null, 30 | bestFriend: { 31 | name: "Tiantian", 32 | age: undefined, 33 | boyFriend: null, 34 | }, 35 | }; 36 | obj.deleteEmptyProperty(); 37 | console.log(obj); // {name: 'Dasen', age: 23, bestFriend: {name: 'Tiantian'}} -------------------------------------------------------------------------------- /2 手撕JavaScript/29 实现compose函数.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 29 实现compose函数 3 | * Dasen Sun 4 | * 2022-03-18 5 | **************************************************/ 6 | 7 | 8 | function compose(...fns) { 9 | const firstFn = fns.shift(); 10 | return function (...args) { 11 | return fns.reduce((pre, cur) => cur(pre), firstFn(...args)); 12 | }; 13 | } 14 | 15 | 16 | // 测试 17 | 18 | function fn1(a, b, c) { 19 | return a + b + c; 20 | } 21 | 22 | function fn2(para) { 23 | return para.toString(); 24 | } 25 | 26 | function fn3(para) { 27 | let reverse = ""; 28 | for (const c of para) { 29 | reverse = c + reverse; 30 | } 31 | return para + reverse; 32 | } 33 | 34 | function fn4(para) { 35 | return para + "!!!"; 36 | } 37 | 38 | const finallyFn = compose(fn1, fn2, fn3, fn4); 39 | console.log(finallyFn(100, 20, 3)); // 123321!!! -------------------------------------------------------------------------------- /2 手撕JavaScript/30 遍历DOM树.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 30 遍历DOM树 3 | * Dasen Sun 4 | * 2022-03-21 5 | **************************************************/ 6 | 7 | 8 | // 1 DFS 遍历 DOM 树 9 | 10 | function dfs(node) { 11 | if (!node) return; 12 | console.log(node.nodeName.toLowerCase()); 13 | for (const dom of node.children) { 14 | dfs(dom); 15 | } 16 | } 17 | 18 | 19 | // 2 BFS 遍历 DOM 树 20 | 21 | function bfs(root) { 22 | if (!root) return; 23 | const queue = []; 24 | queue.push(root); 25 | while (queue.length) { 26 | const node = queue.shift(); 27 | console.log(node.nodeName.toLowerCase()); 28 | for (const dom of node.children) { 29 | queue.push(dom); 30 | } 31 | } 32 | } 33 | 34 | 35 | // 3 给定 Root 节点和目标 DOM 节点,实现 find 函数找出一条路径,以数组形式返回路径,无合法路径返回空数组 36 | 37 | function find(root, target) { 38 | const result = []; 39 | const dfs = function (node) { 40 | result.push(node); 41 | if (node === target) return true; 42 | for (const child of node.children) { 43 | if (dfs(child, target)) return true; 44 | } 45 | result.pop(); 46 | return false; 47 | }; 48 | if (dfs(root)) return result; 49 | else return []; 50 | } 51 | 52 | 53 | // 4 DOM Node查找最近公共祖先 54 | 55 | function nearestAncestor(node1,node2) { 56 | const set = new Set(); 57 | let p = node1; 58 | set.add(p); 59 | while(p!==document) { 60 | p=p.parentNode; 61 | set.add(p); 62 | } 63 | p=node2; 64 | if(set.has(p)) return p; 65 | while(p) { 66 | p=p.parentNode; 67 | if(set.has(p)) return p; 68 | } 69 | return null; 70 | } -------------------------------------------------------------------------------- /2 手撕JavaScript/31 实现repeat包装函数.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 31 实现repeat包装函数 3 | * Dasen Sun 4 | * 2022-03-19 5 | **************************************************/ 6 | 7 | 8 | // 实现一个 repeat 函数,它的第一个参数是一个函数,第二个参数为重复执行的次数,第三个参数为重复执行的间隔 9 | // repeat 返回一个包装后的函数,如: 10 | // const repeatFn = repeat(console.log, 4, 3000); 11 | // repeatFn("Dasen"); 12 | // 将会输出四次 Dasen ,每两次之间间隔 3 秒 13 | 14 | 15 | function repeat(fn, cnt, time) { 16 | let count = 0; 17 | return function inner(...args) { 18 | fn(...args) 19 | count++; 20 | new Promise((resolve) => { 21 | setTimeout(resolve, time); 22 | }).then(() => { 23 | if (count <= cnt) inner(...args); 24 | }); 25 | }; 26 | } 27 | 28 | 29 | // 测试 30 | 31 | const repeatFn = repeat(console.log, 4, 3000); 32 | repeatFn("Dasen"); -------------------------------------------------------------------------------- /2 手撕JavaScript/32 实现每隔一定时间间隔轮询数据.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 32 实现每隔一定时间间隔轮询数据 3 | * Dasen Sun 4 | * 2022-03-20 5 | **************************************************/ 6 | 7 | 8 | // 问题描述: 9 | // 实现每隔一定时间间隔轮询数据,封装为函数,并提供暂停轮询和继续轮询的方法 10 | 11 | 12 | // 1 基于 Ajax 封装 13 | 14 | function pollingByAjax(url, time, fn) { 15 | let next = true; 16 | const reqNext = () => { 17 | let xhr = new XMLHttpRequest(); 18 | xhr.open("get", url, true); 19 | xhr.send(null); 20 | xhr.onreadystatechange = function () { 21 | if (xhr.readyState === 4 && xhr.status === 200) { 22 | fn(xhr.responseText); 23 | } 24 | }; 25 | xhr.onerror = function (e) { 26 | throw e; 27 | }; 28 | }; 29 | const setNext = () => { 30 | reqNext(); 31 | if (next) setTimeout(setNext, time); 32 | }; 33 | setNext(); 34 | const suspend = () => { 35 | console.log("轮询暂停"); 36 | next = false; 37 | }; 38 | const continueNext = () => { 39 | console.log("轮询继续"); 40 | next = true; 41 | setNext(); 42 | }; 43 | return { 44 | suspend, 45 | continueNext 46 | }; 47 | } 48 | 49 | 50 | // 2 基于 Fetch 封装 51 | 52 | function pollingByFetch(url, time, fn) { 53 | let next = true; 54 | const reqNext = () => fetch(url).then((res) => { 55 | return res.text(); 56 | }).then((text) => { 57 | fn(text); 58 | }).catch((e => { 59 | throw e; 60 | })); 61 | const setNext = () => { 62 | reqNext(); 63 | if (next) setTimeout(setNext, time); 64 | }; 65 | setNext(); 66 | const suspend = () => { 67 | console.log("轮询暂停"); 68 | next = false; 69 | } 70 | const continueNext = () => { 71 | console.log("轮询继续"); 72 | next = true; 73 | setNext(); 74 | }; 75 | return { 76 | suspend, 77 | continueNext 78 | }; 79 | } 80 | 81 | 82 | // 3 测试 83 | 84 | const outputContent = (json) => console.log(JSON.parse(json)["data"]["content"]); 85 | 86 | const polling = pollingByFetch("https://api.xygeng.cn/one", 3000, outputContent); 87 | 88 | setTimeout(() => { 89 | polling.suspend(); 90 | }, 20000); // 轮询 20 秒后暂停 91 | setTimeout(() => { 92 | polling.continueNext(); 93 | }, 30000); // 暂停 10 秒后再继续轮询 -------------------------------------------------------------------------------- /2 手撕JavaScript/33 实现Jsonp跨域请求.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 33 实现Jsonp跨域请求 3 | * Dasen Sun 4 | * 2022-03-24 5 | **************************************************/ 6 | 7 | 8 | const jsonp = ({ 9 | url, 10 | params, 11 | callbackName 12 | }) => { 13 | const generateURL = () => { 14 | let dataStr = ''; 15 | for (let key in params) { 16 | dataStr += `${key}=${params[key]}&`; 17 | } 18 | dataStr += `callback=${callbackName}`; 19 | return `${url}?${dataStr}`; 20 | }; 21 | return new Promise((resolve, reject) => { 22 | // 初始化回调函数名称 23 | callbackName = callbackName || Math.random().toString.replace(',', ''); 24 | // 创建 script 元素并加入到当前文档中 25 | let scriptEle = document.createElement('script'); 26 | scriptEle.src = generateURL(); 27 | document.body.appendChild(scriptEle); 28 | // 绑定到 window 上,为了后面调用 29 | window[callbackName] = (data) => { 30 | resolve(data); 31 | // script 执行完了,成为无用元素,需要清除 32 | document.body.removeChild(scriptEle); 33 | } 34 | }); 35 | }; -------------------------------------------------------------------------------- /2 手撕JavaScript/34 对象展开.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 34 对象展开 3 | * Dasen Sun 4 | * 2022-04-02 5 | **************************************************/ 6 | 7 | 8 | // 对象扁平化的逆过程 9 | 10 | const unfoldObject = function (obj) { 11 | const result = {}; 12 | for (const properties in obj) { 13 | const value = obj[properties]; 14 | const pList = properties.split("."); 15 | let cur = result; 16 | const len = pList.length; 17 | for (let i = 0; i < len; ++i) { 18 | const p = pList[i]; 19 | if (p in cur) { 20 | if (i === len - 1) cur[p] = value; 21 | } else { 22 | if (i < len - 1) cur[p] = {}; 23 | else cur[p] = value 24 | } 25 | cur = cur[p]; 26 | } 27 | } 28 | return result; 29 | }; 30 | 31 | const obj = { 32 | "name": "Dasen", 33 | "age": 23, 34 | "bestFriend.name": "Tiantian", 35 | "bestFriend.age": 22, 36 | "bestFriend.homeTown.0": "Hubei", 37 | "bestFriend.homeTown.1": "Huanggang", 38 | "bestFriend.homeTown..length": 2, 39 | }; 40 | console.log(unfoldObject(obj)); // {name: 'Dasen', age: 23, bestFriend: {…}} -------------------------------------------------------------------------------- /2 手撕JavaScript/35 使用ES5语法实现const变量声明.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 35 使用ES5语法实现const变量声明 3 | * Dasen Sun 4 | * 2022-04-10 5 | **************************************************/ 6 | 7 | 8 | var __const = function __const(data, value) { 9 | window[data] = value; // 把要定义的data挂载到window下,并赋值 value 10 | Object.defineProperty(window, data, { 11 | // 数据劫持,修改其属性描述符 12 | enumerable: false, // 因为const定义的属性在global下也是不存在的,所以用到了 enumerable: false 来模拟这一功能 13 | configurable: false, 14 | get: function () { 15 | return value; 16 | }, 17 | set: function (data) { 18 | if (data !== value) { 19 | // 当要对当前属性进行赋值时,则抛出错误 20 | throw new TypeError('Assignment to constant variable.'); 21 | } else { 22 | return value; 23 | } 24 | } 25 | }); 26 | } 27 | 28 | __const('a', 10); 29 | console.log(a); 30 | delete a; 31 | console.log(a); 32 | a = 20 // 报错 -------------------------------------------------------------------------------- /2 手撕JavaScript/36 实现通用函数柯里化.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 36 实现通用函数柯里化 3 | * Dasen Sun 4 | * 2022-04-12 5 | **************************************************/ 6 | 7 | 8 | const currify = function (fn) { 9 | let need = fn.length; 10 | const args = []; 11 | return function inner(...innerArgs) { 12 | need -= innerArgs.length; 13 | args.push(...innerArgs); 14 | if (need <= 0) return fn(...args); 15 | return inner; 16 | }; 17 | }; 18 | 19 | 20 | // 测试 21 | 22 | const add = function (a, b, c, d, e) { 23 | return a + b + c + d + e; 24 | }; 25 | 26 | console.log(currify(add)(1, 2, 3, 4, 5)); // 15 27 | console.log(currify(add)(1)(2)(3)(4)(5)); // 15 28 | console.log(currify(add)(1, 2, 3)(4, 5)); // 15 29 | console.log(currify(add)(1)(2, 3)(4)(5)); // 15 -------------------------------------------------------------------------------- /2 手撕JavaScript/37 带并发限制的请求数据.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 37 带并发限制的请求数据 3 | * Dasen Sun 4 | * 2022-04-16 5 | **************************************************/ 6 | 7 | 8 | // 1 获取数据的函数,返回一个 Promise 9 | 10 | // XHR 实现 11 | function getDateByXHR(urls) { 12 | let index = 0; 13 | return function getNext() { 14 | if (index >= urls.length) return null; 15 | const p = new Promise((resolve, reject) => { 16 | const xhr = new XMLHttpRequest() 17 | xhr.onload = function () { 18 | resolve(xhr.responseText); 19 | }; 20 | xhr.onerror = function (e) { 21 | reject(e.type); 22 | }; 23 | xhr.open("GET", urls[index]); 24 | xhr.send(); 25 | }); 26 | ++index; 27 | return p; 28 | }; 29 | } 30 | 31 | // fetch 实现 32 | function getDateByFetch(urls) { 33 | let index = 0; 34 | return function getNext() { 35 | if (index >= urls.length) return null; 36 | return fetch(urls[index++]); 37 | }; 38 | } 39 | 40 | 41 | // 2 并发限制 42 | 43 | const getDate = getDateByFetch; 44 | 45 | function concurrentRequest(urls, resolveFn, rejectFn, concurrency = Infinity) { 46 | let limit = Math.min(concurrency, urls.length); // 实际的并发限制数 47 | const getNext = getDate(urls); // 请求数据的函数 48 | const startARequest = () => { 49 | // 开始一个新的请求的函数 50 | const p = getNext(); 51 | if (p !== null) { 52 | p.then(resolveFn, rejectFn); // 绑定解决和拒绝的处理函数 53 | p.finally(() => startARequest()); // 每完成一个请求,就开始下一个新的请求,保证并发数尽可能跑满 54 | } 55 | }; 56 | // 首次运行,开启 limit 个请求 57 | while (limit--) { 58 | startARequest(); 59 | } 60 | }; 61 | 62 | 63 | // 3 使用并发池的实现 64 | 65 | function concurrentRequestByPool(urls, resolveFn, rejectFn, concurrency = Infinity) { 66 | let limit = Math.min(concurrency, urls.length); // 实际的并发限制数 67 | const getNext = getDate(urls); // 请求数据的函数 68 | const pool = []; // 请求池 69 | const startARequest = () => { 70 | // 开始一个新的请求的函数 71 | const p = getNext(); // 获取一个请求 72 | if (p !== null) { 73 | p.then(resolveFn, rejectFn); // 绑定解决和拒绝的处理函数 74 | p.finally(() => { 75 | pool.splice(pool.indexOf(p), 1); // 落定后从请求池中移除 76 | startARequest(); // 开启下一个请求 77 | }); 78 | pool.push(p); // 加入请求池 79 | } 80 | }; 81 | // 首次运行,开启 limit 个请求 82 | while (limit--) { 83 | startARequest(); 84 | } 85 | }; 86 | 87 | 88 | // 4 使用 Promise.race() 的实现 89 | 90 | function concurrentRequestByRace(urls, resolveFn, rejectFn, concurrency = Infinity) { 91 | let limit = Math.min(concurrency, urls.length); // 实际的并发限制数 92 | const getNext = getDate(urls); // 请求数据的函数 93 | const pool = []; // 请求池 94 | const addTask = () => { 95 | // 开始一个新的任务 96 | const p = getNext(); // 获取一个请求 97 | if (p !== null) { 98 | p.then(resolveFn, rejectFn); // 绑定解决和拒绝的处理函数 99 | p.finally(() => pool.splice(pool.indexOf(p), 1)); // 落定后从请求池中移除 100 | pool.push(p); // 加入请求池 101 | } 102 | }; 103 | const race = () => { 104 | // 请求池中的任务开始赛跑 105 | Promise.race(pool).finally(() => { 106 | addTask(); // 添加一个新的任务 107 | race(); // 重新开始赛跑 108 | }); 109 | } 110 | // 首次运行,开启 limit 个请求并加入请求池 111 | while (limit--) { 112 | addTask(); 113 | } 114 | race(); 115 | }; -------------------------------------------------------------------------------- /2 手撕JavaScript/38 使用setTimeout实现setInterval.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 38 使用setTimeout实现setInterval 3 | * Dasen Sun 4 | * 2022-04-27 5 | **************************************************/ 6 | 7 | 8 | // 1 实现可清除的 setInterval 9 | 10 | // 一种实现:返回清除函数 11 | 12 | function mySetInterval(handler, timeout, ...args) { 13 | let timer; 14 | const next = () => { 15 | timer = setTimeout(() => { 16 | handler(...args); 17 | next(); 18 | }, timeout); 19 | }; 20 | next(); 21 | return () => clearTimeout(timer); 22 | } 23 | 24 | // 另一种实现:使用全局对象,类似于原生 setInterval 接口的实现 25 | 26 | const timerIdMap = { 27 | current: 0 28 | }; 29 | 30 | function setIntervalWithId(handler, timeout, ...args) { 31 | const id = timerIdMap.current++; 32 | const next = () => { 33 | return setTimeout(() => { 34 | handler(...args); 35 | timerIdMap[id] = next(); 36 | }, timeout); 37 | } 38 | timerIdMap[id] = next(); 39 | return id; 40 | } 41 | 42 | function clearIntervalWithId(id) { 43 | clearTimeout(timerIdMap[id]); 44 | delete timerIdMap[id]; 45 | } 46 | 47 | 48 | // 2 实现限制次数的 setInterval 49 | 50 | function setIntervalWithCount(handler, timeout, count, ...args) { 51 | if (count !== 0 && !count || typeof count !== "number") count = 0; 52 | let timer; 53 | let cnt = 0; 54 | const next = () => { 55 | timer = setTimeout(() => { 56 | handler(...args); 57 | ++cnt; 58 | if (cnt < count) next(); 59 | }, timeout); 60 | }; 61 | if (count) next(); 62 | return () => clearTimeout(timer); 63 | } 64 | 65 | 66 | // 3 实现可暂停的 setInterval 67 | 68 | function setIntervalSuspendable(handler, timeout, ...args) { 69 | let timer; 70 | const next = () => { 71 | timer = setTimeout(() => { 72 | handler(...args); 73 | next(); 74 | }, timeout); 75 | }; 76 | next(); 77 | const suspend = () => { 78 | clearTimeout(timer); 79 | }; 80 | return [suspend, next]; 81 | } 82 | 83 | 84 | // 4 实现可自动修正延迟的 setInterval 85 | 86 | function setIntervalAutoCorrect(handler, timeout, ...args) { 87 | let timer; 88 | let cnt = 0; 89 | const begin = new Date().getTime(); 90 | const next = () => { 91 | const now = new Date().getTime(); 92 | let dis = now - begin - timeout * cnt; 93 | while (dis > timeout) { 94 | dis -= timeout; 95 | } 96 | timer = setTimeout(() => { 97 | handler(...args); 98 | ++cnt; 99 | next(); 100 | }, timeout - dis); 101 | }; 102 | next(); 103 | return () => clearTimeout(timer); 104 | } -------------------------------------------------------------------------------- /2 手撕JavaScript/39 Promise串行与并行.js: -------------------------------------------------------------------------------- 1 | // 1 给定 URL 的数组,串行请求数据 2 | 3 | function getData1(urls, callback) { 4 | if(!urls.length) return; 5 | let lastPromise = null; 6 | for(const url of urls) { 7 | if(lastPromise) { 8 | lastPromise = lastPromise.then((data) => { 9 | callback(data); 10 | return fetch(url); 11 | }); 12 | } else { 13 | lastPromise = fetch(url); 14 | } 15 | } 16 | lastPromise.then((data) => callback(data)); 17 | } 18 | 19 | // 2 给定 URL 的数组,并行请求数据 20 | 21 | function getData2(urls, callback) { 22 | for(const url of urls) { 23 | fetch(url).then((data) => callback(data)); 24 | } 25 | } -------------------------------------------------------------------------------- /2 手撕JavaScript/40 异步任务调度器.js: -------------------------------------------------------------------------------- 1 | class Scheduler { 2 | constructor(limit) { 3 | this.queue = []; 4 | this.maxCount = limit; 5 | this.runCounts = 0; 6 | } 7 | 8 | add(promiseCreator) { 9 | promiseCreator = promiseCreator || function () { 10 | return new Promise((resolve, reject) => { 11 | setTimeout(() => { 12 | resolve(); 13 | }, 1000); 14 | }); 15 | }; 16 | 17 | this.queue.push(promiseCreator); 18 | this.request(); 19 | } 20 | 21 | request() { 22 | if (!this.queue.length || this.runCounts >= this.maxCount) { 23 | return; 24 | } 25 | this.runCounts++; 26 | // 拿出第一个任务异步执行 27 | this.queue.shift()().then(() => { 28 | this.runCounts--; 29 | this.request(); // 执行下一个异步任务 30 | }); 31 | } 32 | } -------------------------------------------------------------------------------- /3 手撕万物/1 设计模式相关/01 单例模式实现请求缓存.ts: -------------------------------------------------------------------------------- 1 | // api 为接口函数(异步),接受请求的 url 作为参数,返回收到的响应 2 | import api from "./utils"; 3 | 4 | // 全局唯一的单例缓存 5 | const cache: Record = {}; 6 | 7 | // 请求函数 8 | export const request = async (url: string) => { 9 | if (cache[url]) { 10 | return cache[url]; 11 | } 12 | const response = await api(url); 13 | cache[url] = response; 14 | return response; 15 | }; 16 | -------------------------------------------------------------------------------- /3 手撕万物/1 设计模式相关/02 发布订阅模式实现上线提醒.ts: -------------------------------------------------------------------------------- 1 | // 提醒函数的类型定义,提醒函数用于对用户 user 的动作进行发布(给它的订阅者)。 2 | type Notify = (user: User) => void; 3 | 4 | // 支持订阅发布机制的用户类 5 | export class User { 6 | name: string; 7 | status: "offline" | "online"; 8 | // 订阅实例的数组;订阅实例包含了订阅者的信息以及决定订阅方式的订阅函数 9 | followers: { user: User; notify: Notify }[]; 10 | 11 | // 构造函数,初始化用户信息 12 | constructor(name: string) { 13 | this.name = name; 14 | this.status = "offline"; 15 | this.followers = []; 16 | } 17 | 18 | // 添加订阅者 19 | subscribe(user: User, notify: Notify) { 20 | this.followers.push({ user, notify }); 21 | } 22 | 23 | // 用户上线依次触发各个订阅者的 notify 回调函数 24 | online() { 25 | this.status = "online"; 26 | this.followers.forEach(({ notify }) => { 27 | notify(this); 28 | }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /3 手撕万物/1 设计模式相关/03 代理模式实现用户上线订阅.ts: -------------------------------------------------------------------------------- 1 | // 该实现方案是对发布订阅模式的改进,只在遵循单一功能原则,在 online 方法中只负责用户上线状态更改相关的事宜, 2 | // 而将通知订阅者的操作移到代理中进行 3 | 4 | // 这部分与发布订阅模式的实现除了 online 方法外都一样 5 | type Notify = (user: User) => void; 6 | 7 | class User { 8 | name: string; 9 | status: "offline" | "online"; 10 | followers: { user: User; notify: Notify }[]; 11 | 12 | constructor(name: string) { 13 | this.name = name; 14 | this.status = "offline"; 15 | this.followers = []; 16 | } 17 | 18 | subscribe(user: User, notify: Notify) { 19 | this.followers.push({ user, notify }); 20 | } 21 | 22 | // online 方法只负责用户状态变更 23 | online() { 24 | this.status = "online"; 25 | } 26 | } 27 | 28 | // 额外的内容:设置代理 29 | export const createProxyUser = (name: string) => { 30 | const user = new User(name); 31 | 32 | const notifyStatusHandles = (user: User, status: "offline" | "online") => { 33 | // 判断是否对用户进行通知 34 | if (status === "online") { 35 | user.followers.forEach(({ notify }) => { 36 | notify(user); 37 | }) 38 | } 39 | }; 40 | 41 | const proxyUser = new Proxy(user, { 42 | set(target, property: keyof User, value) { 43 | target[property] = value; 44 | if (property === "status") { 45 | // 改变的属性是 status 时,进入通知流程 46 | notifyStatusHandles(target, value); 47 | // 可以通过定义使用更多 handles 来方便地实现功能的扩展 48 | } 49 | return true; 50 | } 51 | }); 52 | 53 | return proxyUser; 54 | } 55 | -------------------------------------------------------------------------------- /3 手撕万物/1 设计模式相关/04 使用迭代器模式模拟DOM树.ts: -------------------------------------------------------------------------------- 1 | class MyDomElement { 2 | tagName: string; // DOM标签名 3 | children: MyDomElement[]; // 子DOM节点列表 4 | 5 | constructor(tagName: string) { 6 | this.tagName = tagName; 7 | this.children = []; 8 | } 9 | 10 | addChildTag(tagNode: MyDomElement) { 11 | // 添加子DOM节点 12 | this.children.push(tagNode); 13 | } 14 | 15 | [Symbol.iterator]() { 16 | // 实现默认迭代器 17 | 18 | const list = [...this.children]; // 用于先序遍历的队列 19 | let node: MyDomElement; // 当前正在遍历的节点 20 | 21 | return { 22 | next() { 23 | // 从队列中弹出一个节点,如果它非空,进行树的前序遍历 24 | while ((node = list.shift())) { 25 | node.children.length > 0 && list.push(...node.children); 26 | return { value: node, done: false }; 27 | } 28 | return { value: null, done: true }; 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /3 手撕万物/1 设计模式相关/05 组合模式模拟文件结构.ts: -------------------------------------------------------------------------------- 1 | class Folder { 2 | size: number; 3 | children: (Folder | Filee)[]; 4 | parent: Folder; 5 | constructor() { 6 | this.size = 0; 7 | this.children = []; 8 | this.parent = null; 9 | } 10 | addChild(child: Folder | Filee) { 11 | console.log(this); 12 | this.children.push(child); 13 | this.size += child.size; 14 | child.parent = this; 15 | } 16 | } 17 | 18 | class Filee { 19 | size: number; 20 | parent: Folder; 21 | constructor(size: number) { 22 | this.size = size; 23 | this.parent = null; 24 | } 25 | } 26 | 27 | const createProxyFolder = () => { 28 | const folder = new Folder(); 29 | const proxy = new Proxy(folder, { 30 | set(target, p: keyof Folder, value, r) { 31 | if (p === "size") { 32 | if (target.parent) target.parent.size += value - target[p]; 33 | } 34 | return Reflect.set(target, p, value, r); 35 | } 36 | }); 37 | return proxy; 38 | } 39 | 40 | const createProxyFile = (size: number) => { 41 | const file = new Filee(size); 42 | const proxy = new Proxy(file, { 43 | set(target, p: keyof Folder, value, r) { 44 | if (p === "size") { 45 | if (target.parent) target.parent.size += value - target[p]; 46 | } 47 | return Reflect.set(target, p, value, r); 48 | } 49 | }); 50 | return proxy; 51 | } 52 | 53 | // test 54 | const folder1 = createProxyFolder(); 55 | const folder2 = createProxyFolder(); 56 | folder1.addChild(folder2); 57 | console.log(folder1.size); 58 | console.log(folder2.size); 59 | const file1 = createProxyFile(2); 60 | const file2 = createProxyFile(3); 61 | folder2.addChild(file2); 62 | console.log(folder1.size); 63 | console.log(folder2.size); 64 | folder1.addChild(file1); 65 | console.log(folder1.size); 66 | console.log(folder2.size); 67 | -------------------------------------------------------------------------------- /3 手撕万物/1 设计模式相关/06 EventBus.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 06 EventBus 3 | * Dasen Sun 4 | * 2022-03-19 5 | **************************************************/ 6 | 7 | 8 | // 全局事件总线 EventBus - 单例模式 9 | // 观察者模式:定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖它的对象都会得到通知并自动更新 10 | // 某对象注册一个事件,在发生某些变化时发出(emit)该事件,其他监听(on)了该事件的对象提供回调函数,在事件发生时被调用 11 | 12 | const EventBus = { 13 | subs: {}, 14 | on(event, fn) { 15 | if (!this.subs[event]) this.subs[event] = []; 16 | this.subs[event].push(fn); 17 | }, 18 | emit(event, ...args) { 19 | if (this.subs[event]) { 20 | this.subs[event].forEach(fn => fn(...args)); 21 | } 22 | }, 23 | once(event, fn) { 24 | const callback = (...args) => { 25 | fn(...args); 26 | this.off(event, callback); 27 | }; 28 | this.on(event, callback); 29 | }, 30 | off(event, fn) { 31 | if(this.subs[event]) { 32 | const index = this.subs[event].indexOf(fn); 33 | if(index!==-1) this.subs[event].splice(index, 1); 34 | } 35 | }, 36 | }; 37 | 38 | 39 | // 测试 40 | 41 | EventBus.on("hello", (name) => console.log("hello", name)); 42 | EventBus.emit("hello", "dasen"); // hello dasen 43 | 44 | const fn = (name) => console.log("你好", name); 45 | EventBus.on("你好", fn); 46 | EventBus.emit("你好", "大森"); // 你好 大森 47 | EventBus.off("你好", fn) 48 | EventBus.emit("你好", "大森"); // (无事发生) 49 | 50 | EventBus.once("你好", fn); 51 | EventBus.emit("你好", "大森"); // 你好 大森 52 | EventBus.emit("你好", "大森"); // (无事发生) -------------------------------------------------------------------------------- /3 手撕万物/2 JavaScript应用/01 LRU缓存置换算法.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 01 LRU缓存置换算法 3 | * Dasen Sun 4 | * 2022-03-13 5 | **************************************************/ 6 | 7 | 8 | class ListNode { 9 | constructor(key, val) { 10 | this.key = key; 11 | this.val = val; 12 | this.pre = null; 13 | this.next = null; 14 | } 15 | } 16 | 17 | const LRUCache = function (capacity) { 18 | this.size = capacity; 19 | this.map = new Map(); 20 | this.head = new ListNode(null, null); 21 | this.tail = new ListNode(null, null); 22 | this.head.next = this.tail; 23 | this.tail.pre = this.head; 24 | }; 25 | 26 | LRUCache.prototype.get = function (key) { 27 | if (!this.map.has(key)) return -1; 28 | let val = this.map.get(key).val; 29 | this.put(key, val); 30 | return val; 31 | }; 32 | 33 | LRUCache.prototype.put = function (key, value) { 34 | if (!this.map.has(key)) { 35 | if (this.map.size === this.size) { 36 | let node = this.tail.pre; 37 | let k = node.key; 38 | this.tail.pre = node.pre; 39 | node.pre.next = this.tail; 40 | node.pre = null; 41 | node.next = null; 42 | node = null; 43 | this.map.delete(k); 44 | } 45 | let node = new ListNode(key, value); 46 | node.next = this.head.next; 47 | node.pre = this.head; 48 | node.next.pre = node; 49 | node.pre.next = node; 50 | this.map.set(key, node); 51 | } else { 52 | let node = this.map.get(key); 53 | node.val = value; 54 | if (node.pre !== this.head) { 55 | node.pre.next = node.next; 56 | node.next.pre = node.pre; 57 | node.pre = this.head; 58 | node.next = this.head.next; 59 | node.pre.next = node; 60 | node.next.pre = node; 61 | } 62 | } 63 | }; -------------------------------------------------------------------------------- /3 手撕万物/2 JavaScript应用/02 对象的key驼峰式转下划线.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 02 对象的key驼峰式转下划线 3 | * Dasen Sun 4 | * 2022-03-13 5 | **************************************************/ 6 | 7 | 8 | // 实现函数:将对象小驼峰式的属性名转下划线 9 | // 如:userName -> user_name 10 | 11 | // 首先:遍历对象属性的方法 12 | // for-in只能遍历到自身的属性和自身继承的可枚举的属性的值和方法,不能得到symbol属性 13 | // Object.keys()该方法返回的是一个数组,它只能访问到自身的可枚举的属性值,不含symbol 14 | // Object.getOwnPropertyNames()返回一个除symbol以外的所有自身属性的数组 15 | // Object.getOwnPropertySymbols()返回一个包含了自身symbol属性的数组 16 | // Reflect.ownKeys()返回一个包含所有属性的数组,不论是不是自身的,不论是不是可枚举的,不管是不是symbol属性 17 | 18 | // 根据希望处理哪些属性类型,来选用以上各种方法 19 | 20 | 21 | // 工具函数:驼峰转下划线 22 | 23 | const humpToUnderline = function (name) { 24 | let newName = ""; 25 | for (const c of name) { 26 | if (c.charCodeAt() <= "Z".charCodeAt() && c.charCodeAt() >= "A".charCodeAt()) { 27 | newName += "_" + c.toLowerCase(); 28 | } else { 29 | newName += c; 30 | } 31 | } 32 | return newName; 33 | }; 34 | 35 | 36 | // 1 简易版本:不考虑属性配置 37 | 38 | const formatPropertyNamesA = function (obj) { 39 | const names = []; 40 | for (const key in obj) { 41 | names.push(key); 42 | } 43 | for (const key of names) { 44 | const newKey = humpToUnderline(key); 45 | obj[newKey] = obj[key]; 46 | delete obj[key]; 47 | } 48 | }; 49 | 50 | 51 | // 2 完善版本:考虑属性配置 52 | 53 | const formatPropertyNamesB = function (obj) { 54 | const names = []; 55 | const unenumerable = {}; 56 | for (const key in obj) { 57 | // 属性可配置才进行操作,不可配置的属性忽略 58 | if (Object.getOwnPropertyDescriptor(obj, key).configurable) { 59 | names.push(key); 60 | } 61 | } 62 | // 获取自身的不可枚举但可配置的属性 63 | for (const key of Object.getOwnPropertyNames(obj)) { 64 | if (names.indexOf(key) === -1 && Object.getOwnPropertyDescriptor(obj, key).configurable) { 65 | unenumerable[key] = Object.getOwnPropertyDescriptor(obj, key); 66 | } 67 | } 68 | // 处理可迭代属性 69 | for (const key of names) { 70 | const newKey = humpToUnderline(key); 71 | obj[newKey] = obj[key]; 72 | delete obj[key]; 73 | } 74 | // 处理不可迭代属性 75 | for (const key in unenumerable) { 76 | const newKey = humpToUnderline(key); 77 | Object.defineProperty(obj, newKey, unenumerable[key]); 78 | delete obj[key]; 79 | } 80 | }; 81 | 82 | 83 | // 测试1:简单的对象 84 | 85 | const obj = { 86 | userName: "Dasen", 87 | userCurrentAge: 23, 88 | }; 89 | formatPropertyNamesA(obj); 90 | console.log(obj); // {user_name: 'Dasen', user_current_age: 23} 91 | 92 | 93 | // 测试2:有原型的对象 94 | 95 | const proto = { 96 | userName: "Dasen", 97 | userCurrentAge: 23, 98 | }; 99 | 100 | function Person() { 101 | this.nickName = "Dear " + this.userName; 102 | } 103 | Person.prototype = proto; 104 | const person = new Person(); 105 | formatPropertyNamesA(person); 106 | console.log(person); // Person {nick_name: 'Dear Dasen', user_name: 'Dasen', user_current_age: 23} 107 | 108 | 109 | // 测试3:有带有属性配置的对象 110 | 111 | const person2 = { 112 | userName: "Dasen", 113 | userCurrentAge: 23, 114 | }; 115 | Object.defineProperty(person2, "nickName", { 116 | configurable: false, 117 | enumerable: true, 118 | value: "Dear Dasen", 119 | }); 120 | // nickName 属性不可配置,删除操作会静默失败,结果会多一个重复的属性 nick_name 121 | // formatPropertyNamesA(person2); // {nickName: 'Dear Dasen', user_name: 'Dasen', user_current_age: 23, nick_name: 'Dear Dasen'} 122 | formatPropertyNamesB(person2); 123 | console.log(person2); // {nickName: 'Dear Dasen', user_name: 'Dasen', user_current_age: 23} 124 | console.log(Object.getOwnPropertyDescriptor(person2, "nickName")); // {value: 'Dear Dasen', writable: false, enumerable: true, configurable: false} -------------------------------------------------------------------------------- /3 手撕万物/2 JavaScript应用/03 千分位数值分隔.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 03 千分位数值分隔 3 | * Dasen Sun 4 | * 2022-03-14 5 | **************************************************/ 6 | 7 | 8 | function formatNumber(n) { 9 | const num = Number(n); 10 | if (isNaN(num)) return NaN; 11 | const numSplit = num.toString().split("."); 12 | let integer, decimal; 13 | if (numSplit.length === 1) { 14 | integer = numSplit[0]; 15 | decimal = ""; 16 | } else { 17 | integer = numSplit[0]; 18 | decimal = numSplit[1]; 19 | } 20 | let intFormat = ""; 21 | let cnt = 0; 22 | for (let i = integer.length - 1; i >= 0; i--) { 23 | if (cnt % 3 === 0 && i !== integer.length - 1) { 24 | intFormat = "," + intFormat; 25 | cnt = 0; 26 | } 27 | intFormat = integer[i] + intFormat; 28 | cnt++; 29 | } 30 | return intFormat + (decimal && "." + decimal); 31 | } 32 | 33 | 34 | // 测试 35 | 36 | console.log(formatNumber(1234567890)); // 1,234,567,890 37 | console.log(formatNumber(1314.05)); // 1,314.05 38 | console.log(formatNumber("1234567890")); // 1,234,567,890 39 | console.log(formatNumber("1314521.1314")); // 1,314,521.1314 -------------------------------------------------------------------------------- /3 手撕万物/2 JavaScript应用/04 数据格式处理合集.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 04 数据格式处理合集 3 | * Dasen Sun 4 | * 2022-03-22 5 | **************************************************/ 6 | 7 | 8 | // 1 实现函数 fn([['a', 'b'], ['n', 'm'], ['0', '1']]) => ['an0', 'am0', 'an1', 'am1', 'bn0', 'bm0', 'bn1', 'bm0'] 9 | 10 | const fn1 = (data) => { 11 | let last = data[0]; 12 | const cnt = data.length; 13 | for (let i = 1; i < cnt; ++i) { 14 | const cur = []; 15 | for (const a of last) { 16 | for (const b of data[i]) { 17 | cur.push(a + b); 18 | } 19 | } 20 | last = cur; 21 | } 22 | return last; 23 | }; 24 | console.log(fn1([ 25 | ['a', 'b'], 26 | ['n', 'm'], 27 | ['0', '1'] 28 | ])); // ['an0', 'an1', 'am0', 'am1', 'bn0', 'bn1', 'bm0', 'bm1'] 29 | 30 | 31 | // 2 实现函数 f(['ab', 'c', 'd', 'ab', 'c']) => ['ab1', 'c1', 'd', 'ab2', 'c2'] 32 | 33 | const fn2 = (data) => { 34 | const map = new Map(); 35 | const len = data.length; 36 | for (let i = 0; i < len; ++i) { 37 | const key = data[i]; 38 | if (map.has(key)) { 39 | const obj = map.get(key); 40 | if (obj.cnt === 1) data[obj.first] += 1; 41 | ++obj.cnt; 42 | data[i] += obj.cnt; 43 | } else map.set(key, { 44 | first: i, 45 | cnt: 1 46 | }); 47 | } 48 | return data; 49 | }; 50 | console.log(fn2(['ab', 'c', 'd', 'ab', 'c'])); // ['ab1', 'c1', 'd', 'ab2', 'c2'] 51 | 52 | 53 | // 3 54 | // 输入一串字符串,根据字符串求出每个字母的数量并返回结果对象。(数字为1时可省略) 55 | // 示例一:输入:A3B2,输出:{"A": 3, "B": 2} 56 | // 示例二:输入:A(A(A2B)2)3C2,输出:{"A": 16, "B": 6, "C": 2} 57 | 58 | const fn3 = (s) => { 59 | const len = s.length; 60 | const stack = []; 61 | const code0 = "0".charCodeAt(); 62 | const code9 = "9".charCodeAt(); 63 | let cnt = ""; // 统计连续的数字 64 | // 计算倍数的函数 65 | const multiply = (m) => { 66 | const o = stack[stack.length - 1]; 67 | for (const key in o) o[key] *= m; 68 | }; 69 | // 合并连续的统计对象 70 | const add = (objs) => { 71 | const rst = {}; 72 | for (const o of objs) { 73 | for (const key in o) { 74 | if (key in rst) rst[key] += o[key]; 75 | else rst[key] = o[key]; 76 | } 77 | } 78 | return rst; 79 | }; 80 | // 遍历每个字符 81 | for (let i = 0; i < len; ++i) { 82 | const c = s[i]; 83 | const codec = c.charCodeAt(); 84 | if (c === "(") stack.push("("); // 左括号,直接入栈 85 | else if (c === ")") { 86 | // 右括号,弹出每项合并再入栈,直到遇到左括号 87 | const objs = []; 88 | while (stack.length) { 89 | const item = stack.pop(); 90 | if (item !== "(") objs.push(item) 91 | else break; 92 | } 93 | stack.push(add(objs)); 94 | } else if (codec >= code0 && codec <= code9) { 95 | // 数字,追加到cnt末尾,如果下一个字符非数字的话,将统计到的数字乘到栈顶元素 96 | cnt += c; 97 | if (i === len - 1 || s[i + 1].charCodeAt() < code0 || s[i + 1].charCodeAt() > code9) { 98 | multiply(Number(cnt)); 99 | cnt = ""; 100 | } 101 | } else { 102 | // 其他字符,作为统计对象(出现次数为1)入栈 103 | stack.push({ [c]: 1 }); 104 | } 105 | } 106 | return add(stack); 107 | }; 108 | console.log(fn3("A(A(A2B)2)3C2")); // {A: 16, B: 6, C: 2} 109 | console.log(fn3("A3B2")); // {A: 3, B: 2} 110 | console.log(fn3("A3(BBC2)12D(E(F2G5)3A3EB2)10")); // {A: 33, C: 24, B: 44, D: 1, E: 20, F: 60, G: 150} -------------------------------------------------------------------------------- /3 手撕万物/2 JavaScript应用/05 阿拉伯数字转汉字数字.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 05 阿拉伯数字转汉字数字 3 | * Dasen Sun 4 | * 2022-03-19 5 | **************************************************/ 6 | 7 | 8 | function numberToChinese(num) { 9 | if (typeof num === "number") num = num.toString(); 10 | else if (typeof num === "string") { 11 | // 检查格式 12 | for (let i = 0; i < num.length; ++i) { 13 | if (num[i].charCodeAt() > "9".charCodeAt() || num[i].charCodeAt() < "0".charCodeAt()) { 14 | if (i !== 0) throw new TypeError(); 15 | else if (num[i] !== "-") throw new TypeError(); 16 | } 17 | } 18 | } else throw new TypeError(); 19 | let sign = ""; 20 | if (num[0] === "-") { 21 | num = num.substring(1); 22 | sign = "负"; 23 | } 24 | const map = ["零", "一", "二", "三", "四", "五", "六", "七", "八", "九"]; 25 | const part4 = (n, isSuffix) => { 26 | if (n < 10) { 27 | if (isSuffix && n === 0) return ""; 28 | else return (isSuffix ? "零" : "") + map[n]; 29 | } else if (n < 100) { 30 | if (!isSuffix && Math.floor(n / 10) === 1) return "十" + (n % 10 ? map[n % 10] : ""); 31 | else return (isSuffix ? "零" : "") + map[Math.floor(n / 10)] + "十" + (n % 10 ? map[n % 10] : ""); 32 | } else { 33 | let result = ""; 34 | const k = Math.floor(n / 1000); 35 | const h = Math.floor(n % 1000 / 100); 36 | const t = Math.floor(n % 100 / 10); 37 | const g = Math.floor(n % 10); 38 | if (k) result += map[k] + "千"; 39 | else if (isSuffix) result += "零"; 40 | if (!(n % 1000)) return result; 41 | if (h) result += map[h] + "百"; 42 | else if (result.length && result[result.length - 1] !== "零") result += "零"; 43 | if (!(n % 100)) return result; 44 | if (t) result += map[t] + "十"; 45 | else if (result.length && result[result.length - 1] !== "零") result += "零"; 46 | if (!(n % 10)) return result; 47 | if (g) result += map[g]; 48 | return result; 49 | } 50 | }; 51 | const part8 = (n, isSuffix) => { 52 | n = Number(n); 53 | let result; 54 | if (n < 10000) result = part4(n); 55 | else result = part4(Math.floor(n / 10000), isSuffix) + "万" + part4(n % 10000, true) 56 | return result; 57 | }; 58 | const len = num.length; 59 | if (len <= 8) return sign + part8(num); 60 | else { 61 | const front = num.substring(0, len - Math.floor(len / 8) * 8); 62 | const back = num.substring(len - Math.floor(len / 8) * 8); 63 | let result = ""; 64 | let unit = ""; 65 | for (let i = back.length - 8, j = 0; i >= 0; i -= 8, ++j) { 66 | result = part8(back.substring(i, i + 8), front.length) + unit + result; 67 | unit += "亿"; 68 | } 69 | return sign + part8(front) + unit + result; 70 | } 71 | } 72 | 73 | 74 | // 测试 75 | 76 | console.log(numberToChinese("0")); // 零 77 | console.log(numberToChinese("100")); // 一百 78 | console.log(numberToChinese("10005")); // 一万零五 79 | console.log(numberToChinese("10500")); // 一万零五百 80 | console.log(numberToChinese("88888888")); // 八千八百八十八万八千八百八十八 81 | console.log(numberToChinese("123456789")); // 一亿二千三百四十五万六千七百八十九 82 | console.log(numberToChinese("123123456789")); // 一千二百三十一亿二千三百四十五万六千七百八十九 83 | console.log(numberToChinese("123123123123456789")); // 十二亿亿三千一百二十三万一千二百三十一亿二千三百四十五万六千七百八十九 84 | console.log(numberToChinese("-1")); // 负一 85 | console.log(numberToChinese("-10500")); // 负一万零五百 86 | console.log(numberToChinese(10500)); // 一万零五百 87 | 88 | // console.log(numberToChinese("10-00")); // TypeError 89 | // console.log(numberToChinese("*1")); // TypeError 90 | // console.log(numberToChinese({})); // TypeError -------------------------------------------------------------------------------- /3 手撕万物/2 JavaScript应用/06 各种排序.js: -------------------------------------------------------------------------------- 1 | // 1 交换排序 - 冒泡排序 2 | 3 | // 时间复杂度 : 最好 O(n) 最坏/平均 O(n²) 4 | // 空间复杂度 : O(1) 5 | // 稳定排序 6 | 7 | function bubbleSort(arr) { 8 | const len = arr.length; 9 | for (let i = 0; i < len; ++i) { 10 | for (let j = 0; j < i; ++j) { 11 | if (arr[j] > arr[j + 1]) { 12 | const t = arr[j]; 13 | arr[j] = arr[j + 1]; 14 | arr[j + 1] = t; 15 | } 16 | } 17 | } 18 | return arr; 19 | } 20 | 21 | 22 | // 2 交换排序 - 快速排序 23 | 24 | // 时间复杂度 : 最好/平均 O(nlogn) 最坏 O(n²) 25 | // 空间复杂度 : O(nlogn) 26 | // 不稳定排序 27 | 28 | function quickSort(arr, start, end) { 29 | if (start === undefined) { 30 | start = 0; 31 | end = arr.length - 1 32 | } 33 | let i = start, 34 | j = end, 35 | a = arr[i]; 36 | if (i >= j) return arr; 37 | while (i < j) { 38 | while (arr[j] >= a && i < j) --j; 39 | arr[i] = arr[j]; 40 | while (arr[i] <= a && i < j) ++i; 41 | arr[j] = arr[i]; 42 | } 43 | arr[i] = a; 44 | quickSort(arr, start, i - 1); 45 | quickSort(arr, i + 1, end); 46 | return arr; 47 | } 48 | 49 | 50 | // 3 插入排序 - 简单插入排序 51 | 52 | // 时间复杂度 : 最好 O(n) 最坏/平均 O(n²) 53 | // 空间复杂度 : O(1) 54 | // 稳定排序 55 | 56 | function insertSort(arr) { 57 | ; 58 | } 59 | 60 | 61 | // 4 插入排序 - 希尔排序 62 | 63 | // 时间复杂度 : 最好 O(n) 最坏 O(n²) 平均 O(n^1.3) 64 | // 空间复杂度 : O(1) 65 | // 不稳定排序 66 | 67 | function shellSort(arr) { 68 | ; 69 | } 70 | 71 | 72 | // 5 选择排序 - 简单选择排序 73 | 74 | // 时间复杂度 : 最好/最坏/平均 O(n²) 75 | // 空间复杂度 : O(1) 76 | // 不稳定 77 | 78 | function selectSort(arr) { 79 | ; 80 | } 81 | 82 | 83 | // 6 选择排序 - 堆排序 84 | 85 | // 时间复杂度 : 最好/最坏/平均 O(nlogn) 86 | // 空间复杂度 : O(1) 87 | // 不稳定排序 88 | 89 | function heapSort(arr) { 90 | ; 91 | } 92 | 93 | 94 | // 7 归并排序 95 | 96 | // 时间复杂度 : 最好/最坏/平均 O(nlogn) 97 | // 空间复杂度 : O(n) 98 | // 稳定排序 99 | 100 | function mergeSort(arr) { 101 | ; 102 | } 103 | 104 | 105 | // 8 基数排序 106 | 107 | // 时间复杂度 : 最好/最坏/平均 O(n*k) 108 | // 空间复杂度 : O(n+k) 109 | // 稳定排序 110 | 111 | function radixSort(arr) { 112 | ; 113 | } 114 | 115 | 116 | // 9 计数排序 117 | 118 | // 时间复杂度 : 最好/最坏/平均 O(n+k) 119 | // 空间复杂度 : O(n+k) 120 | // 稳定排序 121 | 122 | function countingSort(arr) { 123 | ; 124 | } 125 | 126 | 127 | // 10 桶排序 128 | 129 | // 时间复杂度 : 最好 O(n) 最坏 O(n²) 平均 O(n+k) 130 | // 空间复杂度 : O(n+k) 131 | // 稳定排序 132 | 133 | function bucketSort(arr) { 134 | ; 135 | } 136 | 137 | 138 | // 11 测试 139 | 140 | // 洗牌算法打乱数组 -------------------------------------------------------------------------------- /3 手撕万物/2 JavaScript应用/07 支持过期时间的LocalStorage.js: -------------------------------------------------------------------------------- 1 | /************************************************** 2 | * 07 支持过期时间的LocalStorage 3 | * Dasen Sun 4 | * 2022-04-17 5 | **************************************************/ 6 | 7 | const store = { 8 | timeSign: '|-expires-|', 9 | storage: localStorage || window.localStorage, 10 | set(key, value, time) { 11 | let _time; 12 | try { 13 | _time = time ? 14 | new Date(time).getTime() : 15 | new Date().getTime() + 1000 * 60 * 60 * 24 * 30; // 默认时间:30天 16 | } catch (e) { 17 | // 传入的 time 不合法 18 | _time = new Date().getTime() + 1000 * 60 * 60 * 24 * 30; // 默认时间:30天 19 | } 20 | this.storage.setItem(key, _time + this.timeSign + value); 21 | }, 22 | get(key) { 23 | let value = this.storage.getItem(key); 24 | if (value) { 25 | const index = value.indexOf(this.timeSign); 26 | if (index === -1) return value; // 没有找到标志,说明该项目没有设置过期时间 27 | const time = Number(value.slice(0, index)); 28 | if (time > new Date().getTime()) { 29 | // 没有过期,取出值 30 | value = value.slice(index + this.timeSign.length); 31 | } else { 32 | // 过期了,删除值 33 | this.storage.removeItem(key); 34 | value = null; 35 | } 36 | } 37 | return value; 38 | }, 39 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JavaScript代码笔记 2 | 3 | 本仓库托管了我在学习 JavaScript 的过程中写下的示例代码和练习代码,作为代码笔记以便时常温习。 4 | 5 | 本仓库中所有的代码笔记实际上都是作为我的 JavaScript 学习笔记的附录进行整理的,它们都有对应的博客文章来记录更详细的笔记内容,可访问我的博客来查看更多笔记内容,欢迎各位交流学习、批评指正。 6 | 7 | [![](https://img.shields.io/static/v1?label=%E8%AE%BF%E9%97%AE&message=%E5%A4%A7%E6%A3%AE%E7%9A%84%E5%8D%9A%E5%AE%A2%20-%20JS%E4%B8%93%E6%A0%8F&labelColor=rgb(32,110,211)&color=rgb(58,186,233)&logo=Symantec&style=flat-square)](https://sadose.github.io/categories/js/) 8 | 9 | ## JavaScript高级程序设计 10 | 11 | 此部分是我在阅读红宝书的同时对照书中的示例代码自己进行的练习,所有的代码练习都有对应的学习笔记。 12 | 13 | - 01 语言基础  14 | [![](https://img.shields.io/static/v1?label=&message=%E7%AC%94%E8%AE%B0&color=orange)](https://sadose.github.io/2021/12/04/js001/) 15 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](1%20JavaScript%E9%AB%98%E7%BA%A7%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1/01%20%E8%AF%AD%E8%A8%80%E5%9F%BA%E7%A1%80.js) 16 | - 02 值、作用域与内存  17 | [![](https://img.shields.io/static/v1?label=&message=%E7%AC%94%E8%AE%B0&color=orange)](https://sadose.github.io/2021/12/07/js002/) 18 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](1%20JavaScript%E9%AB%98%E7%BA%A7%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1/02%20%E5%80%BC%E3%80%81%E4%BD%9C%E7%94%A8%E5%9F%9F%E4%B8%8E%E5%86%85%E5%AD%98.js) 19 | - 03 基本引用类型  20 | [![](https://img.shields.io/static/v1?label=&message=%E7%AC%94%E8%AE%B0&color=orange)](https://sadose.github.io/2021/12/08/js003/) 21 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](1%20JavaScript%E9%AB%98%E7%BA%A7%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1/03%20%E5%9F%BA%E6%9C%AC%E5%BC%95%E7%94%A8%E7%B1%BB%E5%9E%8B.js) 22 | - 04 集合引用类型  23 | [![](https://img.shields.io/static/v1?label=&message=%E7%AC%94%E8%AE%B0&color=orange)](https://sadose.github.io/2021/12/10/js004/) 24 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](1%20JavaScript%E9%AB%98%E7%BA%A7%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1/04%20%E9%9B%86%E5%90%88%E5%BC%95%E7%94%A8%E7%B1%BB%E5%9E%8B.js) 25 | - 05 迭代器与生成器  26 | [![](https://img.shields.io/static/v1?label=&message=%E7%AC%94%E8%AE%B0&color=orange)](https://sadose.github.io/2021/12/13/js005/) 27 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](1%20JavaScript%E9%AB%98%E7%BA%A7%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1/05%20%E8%BF%AD%E4%BB%A3%E5%99%A8%E4%B8%8E%E7%94%9F%E6%88%90%E5%99%A8.js) 28 | - 06 面向对象  29 | [![](https://img.shields.io/static/v1?label=&message=%E7%AC%94%E8%AE%B0&color=orange)](https://sadose.github.io/2021/12/15/js006/) 30 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](1%20JavaScript%E9%AB%98%E7%BA%A7%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1/06%20%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1.js) 31 | - 07 代理与反射  32 | [![](https://img.shields.io/static/v1?label=&message=%E7%AC%94%E8%AE%B0&color=orange)](https://sadose.github.io/2021/12/17/js007/) 33 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](1%20JavaScript%E9%AB%98%E7%BA%A7%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1/07%20%E4%BB%A3%E7%90%86%E4%B8%8E%E5%8F%8D%E5%B0%84.js) 34 | - 08 函数  35 | [![](https://img.shields.io/static/v1?label=&message=%E7%AC%94%E8%AE%B0&color=orange)](https://sadose.github.io/2021/12/23/js008/) 36 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](1%20JavaScript%E9%AB%98%E7%BA%A7%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1/08%20%E5%87%BD%E6%95%B0.js) 37 | - 09 异步编程  38 | [![](https://img.shields.io/static/v1?label=&message=%E7%AC%94%E8%AE%B0&color=orange)](https://sadose.github.io/2021/12/24/js009/) 39 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](1%20JavaScript%E9%AB%98%E7%BA%A7%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1/09%20%E5%BC%82%E6%AD%A5%E7%BC%96%E7%A8%8B.js) 40 | - 10 BOM  41 | [![](https://img.shields.io/static/v1?label=&message=%E7%AC%94%E8%AE%B0&color=orange)](https://sadose.github.io/2022/01/16/js010/) 42 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](1%20JavaScript%E9%AB%98%E7%BA%A7%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1/10%20BOM.js) 43 | - 11 客户端检测  44 | [![](https://img.shields.io/static/v1?label=&message=%E7%AC%94%E8%AE%B0&color=orange)](https://sadose.github.io/2022/02/02/js011/) 45 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](1%20JavaScript%E9%AB%98%E7%BA%A7%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1/11%20%E5%AE%A2%E6%88%B7%E7%AB%AF%E6%A3%80%E6%B5%8B.js) 46 | - 12 DOM基础  47 | [![](https://img.shields.io/static/v1?label=&message=%E7%AC%94%E8%AE%B0&color=orange)](https://sadose.github.io/2022/02/13/js012/) 48 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](1%20JavaScript%E9%AB%98%E7%BA%A7%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1/12%20DOM%E5%9F%BA%E7%A1%80.js) 49 | - 13 DOM扩展  50 | [![](https://img.shields.io/static/v1?label=&message=%E7%AC%94%E8%AE%B0&color=orange)](https://sadose.github.io/2022/03/01/js013/) 51 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](1%20JavaScript%E9%AB%98%E7%BA%A7%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1/13%20DOM%E6%89%A9%E5%B1%95.js) 52 | - 14 DOM2和DOM3  53 | [![](https://img.shields.io/static/v1?label=&message=%E7%AC%94%E8%AE%B0&color=orange)](https://sadose.github.io/2022/03/07/js014/) 54 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](1%20JavaScript%E9%AB%98%E7%BA%A7%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1/14%20DOM2%E5%92%8CDOM3.js) 55 | - 15 事件  56 | [![](https://img.shields.io/static/v1?label=&message=%E7%AC%94%E8%AE%B0&color=orange)](https://sadose.github.io/2022/03/11/js015/) 57 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](1%20JavaScript%E9%AB%98%E7%BA%A7%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1/15%20%E4%BA%8B%E4%BB%B6.js) 58 | 59 | 60 | ## 手撕JavaScript 61 | 62 | 这部分内容是深入学习JavaScript的API时,手动实现简单的JavaScript API以及拓展的JavaScript特性的练习。 63 | 64 | - 01 跨浏览器事件工具  65 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](2%20%E6%89%8B%E6%92%95JavaScript/01%20%E8%B7%A8%E6%B5%8F%E8%A7%88%E5%99%A8%E4%BA%8B%E4%BB%B6%E5%B7%A5%E5%85%B7.js) 66 | - 02 尾递归(斐波那契数列)  67 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](2%20%E6%89%8B%E6%92%95JavaScript/02%20%E5%B0%BE%E9%80%92%E5%BD%92%EF%BC%88%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97%EF%BC%89.js) 68 | - 03 节流防抖  69 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](2%20%E6%89%8B%E6%92%95JavaScript/03%20%E8%8A%82%E6%B5%81%E9%98%B2%E6%8A%96.js) 70 | - 05 手写Promise方法合集  71 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](2%20%E6%89%8B%E6%92%95JavaScript/05%20%E6%89%8B%E5%86%99Promise%E6%96%B9%E6%B3%95%E5%90%88%E9%9B%86.js) 72 | - 06 Promise周边  73 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](2%20%E6%89%8B%E6%92%95JavaScript/06%20Promise%E5%91%A8%E8%BE%B9.js) 74 | - 07 手写函数方法  75 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](2%20%E6%89%8B%E6%92%95JavaScript/07%20%E6%89%8B%E5%86%99%E5%87%BD%E6%95%B0%E6%96%B9%E6%B3%95.js) 76 | - 09 实现delay函数  77 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](2%20%E6%89%8B%E6%92%95JavaScript/09%20%E5%AE%9E%E7%8E%B0delay%E5%87%BD%E6%95%B0.js) 78 | - 10 解析URL  79 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](2%20%E6%89%8B%E6%92%95JavaScript/10%20%E8%A7%A3%E6%9E%90URL.js) 80 | - 11 柯里化的add函数  81 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](2%20%E6%89%8B%E6%92%95JavaScript/11%20%E6%9F%AF%E9%87%8C%E5%8C%96%E7%9A%84add%E5%87%BD%E6%95%B0.js) 82 | - 12 调用计数器(支持重置)  83 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](2%20%E6%89%8B%E6%92%95JavaScript/12%20%E8%B0%83%E7%94%A8%E8%AE%A1%E6%95%B0%E5%99%A8%EF%BC%88%E6%94%AF%E6%8C%81%E9%87%8D%E7%BD%AE%EF%BC%89.js) 84 | - 13 手写数组方法  85 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](2%20%E6%89%8B%E6%92%95JavaScript/13%20%E6%89%8B%E5%86%99%E6%95%B0%E7%BB%84%E6%96%B9%E6%B3%95.js) 86 | - 14 实现内存函数缓存函数调用结果  87 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](2%20%E6%89%8B%E6%92%95JavaScript/14%20%E5%AE%9E%E7%8E%B0%E5%86%85%E5%AD%98%E5%87%BD%E6%95%B0%E7%BC%93%E5%AD%98%E5%87%BD%E6%95%B0%E8%B0%83%E7%94%A8%E7%BB%93%E6%9E%9C.js) 88 | - 15 手写new操作  89 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](2%20%E6%89%8B%E6%92%95JavaScript/15%20%E6%89%8B%E5%86%99new%E6%93%8D%E4%BD%9C.js) 90 | - 16 实现sleep函数  91 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](2%20%E6%89%8B%E6%92%95JavaScript/16%20%E5%AE%9E%E7%8E%B0sleep%E5%87%BD%E6%95%B0.js) 92 | - 17 手写isNaN函数  93 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](2%20%E6%89%8B%E6%92%95JavaScript/17%20%E6%89%8B%E5%86%99isNaN%E5%87%BD%E6%95%B0.js) 94 | - 19 读写object路径上的值  95 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](2%20%E6%89%8B%E6%92%95JavaScript/19%20%E8%AF%BB%E5%86%99object%E8%B7%AF%E5%BE%84%E4%B8%8A%E7%9A%84%E5%80%BC.js) 96 | - 20 手写字符串方法  97 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](2%20%E6%89%8B%E6%92%95JavaScript/20%20%E6%89%8B%E5%86%99%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%96%B9%E6%B3%95.js) 98 | - 21 数组扁平化  99 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](2%20%E6%89%8B%E6%92%95JavaScript/21%20%E6%95%B0%E7%BB%84%E6%89%81%E5%B9%B3%E5%8C%96.js) 100 | - 22 数组去重  101 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](2%20%E6%89%8B%E6%92%95JavaScript/22%20%E6%95%B0%E7%BB%84%E5%8E%BB%E9%87%8D.js) 102 | - 23 手写instanceof  103 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](2%20%E6%89%8B%E6%92%95JavaScript/23%20%E6%89%8B%E5%86%99instanceof.js) 104 | - 24 手写Object静态方法  105 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](2%20%E6%89%8B%E6%92%95JavaScript/24%20%E6%89%8B%E5%86%99Object%E9%9D%99%E6%80%81%E6%96%B9%E6%B3%95.js) 106 | - 25 对象扁平化  107 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](2%20%E6%89%8B%E6%92%95JavaScript/25%20%E5%AF%B9%E8%B1%A1%E6%89%81%E5%B9%B3%E5%8C%96.js) 108 | - 26 使用Promise封装Ajax请求  109 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](2%20%E6%89%8B%E6%92%95JavaScript/26%20%E4%BD%BF%E7%94%A8Promise%E5%B0%81%E8%A3%85Ajax%E8%AF%B7%E6%B1%82.js) 110 | - 27 事件委托  111 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](2%20%E6%89%8B%E6%92%95JavaScript/27%20%E4%BA%8B%E4%BB%B6%E5%A7%94%E6%89%98.js) 112 | - 28 移除空属性  113 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](2%20%E6%89%8B%E6%92%95JavaScript/28%20%E7%A7%BB%E9%99%A4%E7%A9%BA%E5%B1%9E%E6%80%A7.js) 114 | - 29 实现compose函数  115 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](2%20%E6%89%8B%E6%92%95JavaScript/29%20%E5%AE%9E%E7%8E%B0compose%E5%87%BD%E6%95%B0.js) 116 | - 30 遍历DOM树  117 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](2%20%E6%89%8B%E6%92%95JavaScript/30%20%E9%81%8D%E5%8E%86DOM%E6%A0%91.js) 118 | - 31 实现repeat包装函数  119 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](2%20%E6%89%8B%E6%92%95JavaScript/31%20%E5%AE%9E%E7%8E%B0repeat%E5%8C%85%E8%A3%85%E5%87%BD%E6%95%B0.js) 120 | - 32 实现每隔一定时间间隔轮询数据  121 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](2%20%E6%89%8B%E6%92%95JavaScript/32%20%E5%AE%9E%E7%8E%B0%E6%AF%8F%E9%9A%94%E4%B8%80%E5%AE%9A%E6%97%B6%E9%97%B4%E9%97%B4%E9%9A%94%E8%BD%AE%E8%AF%A2%E6%95%B0%E6%8D%AE.js) 122 | - 33 实现Jsonp跨域请求  123 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](2%20%E6%89%8B%E6%92%95JavaScript/33%20%E5%AE%9E%E7%8E%B0Jsonp%E8%B7%A8%E5%9F%9F%E8%AF%B7%E6%B1%82.js) 124 | - 34 对象展开  125 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](2%20%E6%89%8B%E6%92%95JavaScript/34%20%E5%AF%B9%E8%B1%A1%E5%B1%95%E5%BC%80.js) 126 | - 35 使用ES5语法实现const变量声明  127 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](2%20%E6%89%8B%E6%92%95JavaScript/35%20%E4%BD%BF%E7%94%A8ES5%E8%AF%AD%E6%B3%95%E5%AE%9E%E7%8E%B0const%E5%8F%98%E9%87%8F%E5%A3%B0%E6%98%8E.js) 128 | - 36 实现通用函数柯里化  129 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](2%20%E6%89%8B%E6%92%95JavaScript/36%20%E5%AE%9E%E7%8E%B0%E9%80%9A%E7%94%A8%E5%87%BD%E6%95%B0%E6%9F%AF%E9%87%8C%E5%8C%96.js) 130 | 131 | 132 | ## 手撕万物 133 | 134 | 这一部分励志把能手撕的东西尽量都手撕一遍,深入理解JavaScript及前端涉及的各方各面的内容。包括设计模式等。 135 | 136 | ### 设计模式相关 137 | 138 | - 字节青训营课程笔记:前端设计模式应用  139 | [![](https://img.shields.io/static/v1?label=&message=%E7%AC%94%E8%AE%B0&color=orange)]() 140 | - 01 单例模式实现请求缓存  141 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](3%20%E6%89%8B%E6%92%95%E4%B8%87%E7%89%A9/1%20%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%9B%B8%E5%85%B3/01%20%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F%E5%AE%9E%E7%8E%B0%E8%AF%B7%E6%B1%82%E7%BC%93%E5%AD%98.ts) 142 | - 02 发布订阅模式实现上线提醒  143 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](3%20%E6%89%8B%E6%92%95%E4%B8%87%E7%89%A9/1%20%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%9B%B8%E5%85%B3/02%20%E5%8F%91%E5%B8%83%E8%AE%A2%E9%98%85%E6%A8%A1%E5%BC%8F%E5%AE%9E%E7%8E%B0%E4%B8%8A%E7%BA%BF%E6%8F%90%E9%86%92.ts) 144 | - 03 代理模式实现用户上线订阅  145 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](3%20%E6%89%8B%E6%92%95%E4%B8%87%E7%89%A9/1%20%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%9B%B8%E5%85%B3/03%20%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F%E5%AE%9E%E7%8E%B0%E7%94%A8%E6%88%B7%E4%B8%8A%E7%BA%BF%E8%AE%A2%E9%98%85.ts) 146 | - 04 使用迭代器模式模拟DOM树  147 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](3%20%E6%89%8B%E6%92%95%E4%B8%87%E7%89%A9/1%20%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%9B%B8%E5%85%B3/04%20%E4%BD%BF%E7%94%A8%E8%BF%AD%E4%BB%A3%E5%99%A8%E6%A8%A1%E5%BC%8F%E6%A8%A1%E6%8B%9FDOM%E6%A0%91.ts) 148 | - 05 组合模式实现文件结构  149 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](3%20%E6%89%8B%E6%92%95%E4%B8%87%E7%89%A9/1%20%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%9B%B8%E5%85%B3/05%20%E7%BB%84%E5%90%88%E6%A8%A1%E5%BC%8F%E6%A8%A1%E6%8B%9F%E6%96%87%E4%BB%B6%E7%BB%93%E6%9E%84.ts) 150 | - 06 EventBus  151 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](3%20%E6%89%8B%E6%92%95%E4%B8%87%E7%89%A9/1%20%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%9B%B8%E5%85%B3/06%20EventBus.js) 152 | 153 | ### JavaScript应用 154 | 155 | - 01 LRU缓存置换算法  156 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](3%20%E6%89%8B%E6%92%95%E4%B8%87%E7%89%A9/2%20JavaScript%E5%BA%94%E7%94%A8/01%20LRU%E7%BC%93%E5%AD%98%E7%BD%AE%E6%8D%A2%E7%AE%97%E6%B3%95.js) 157 | - 02 对象的key驼峰式转下划线  158 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](3%20%E6%89%8B%E6%92%95%E4%B8%87%E7%89%A9/2%20JavaScript%E5%BA%94%E7%94%A8/02%20%E5%AF%B9%E8%B1%A1%E7%9A%84key%E9%A9%BC%E5%B3%B0%E5%BC%8F%E8%BD%AC%E4%B8%8B%E5%88%92%E7%BA%BF.js) 159 | - 03 千分位数值分隔  160 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](3%20%E6%89%8B%E6%92%95%E4%B8%87%E7%89%A9/2%20JavaScript%E5%BA%94%E7%94%A8/03%20%E5%8D%83%E5%88%86%E4%BD%8D%E6%95%B0%E5%80%BC%E5%88%86%E9%9A%94.js) 161 | - 04 数据格式处理合集  162 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](3%20%E6%89%8B%E6%92%95%E4%B8%87%E7%89%A9/2%20JavaScript%E5%BA%94%E7%94%A8/04%20%E6%95%B0%E6%8D%AE%E6%A0%BC%E5%BC%8F%E5%A4%84%E7%90%86%E5%90%88%E9%9B%86.js) 163 | - 05 阿拉伯数字转汉字数字  164 | [![](https://img.shields.io/static/v1?label=&message=%E4%BB%A3%E7%A0%81&color=blue)](3%20%E6%89%8B%E6%92%95%E4%B8%87%E7%89%A9/2%20JavaScript%E5%BA%94%E7%94%A8/05%20%E9%98%BF%E6%8B%89%E4%BC%AF%E6%95%B0%E5%AD%97%E8%BD%AC%E6%B1%89%E5%AD%97%E6%95%B0%E5%AD%97.js) 165 | --------------------------------------------------------------------------------