├── .gitignore ├── 00-ES6 ├── 01-let & const │ └── index.js ├── 02-Template_string │ └── index.js ├── 03-Destructuring │ └── index.js ├── 04-Block_scope │ └── index.js ├── 05-Function │ ├── 01-Default_Args │ │ └── index.js │ └── 02-Arrow_Function │ │ ├── index.html │ │ └── index.js ├── 06-Spread │ └── README.md └── README.md ├── 01-Algorithm ├── 01-Recursion │ ├── README.md │ └── index.js ├── 02-Factorial │ └── factorial.js ├── 03-Palindrome │ └── 01-Palindrome.js ├── 04-SubString │ └── SubString.js ├── 05-Sort │ ├── README.md │ ├── bubbleSort.js │ ├── quickSort.js │ ├── selectionSort.js │ └── shellSort.js ├── 06-Cache Algorithm │ └── FIFO.js ├── 07-Search Algorithm │ └── README.md ├── Demo │ └── 01-Longest String.js └── README.md ├── 02-Higher-order Function ├── 01-Introduction.js ├── 02-Map.js ├── 03-Reduce.js ├── 04-Filter.js ├── 05-Sort.js ├── 06-Some.js ├── 07-Every.js └── README.md ├── 03-General Knowledge ├── 01-Falsy.html ├── 02-Truthy.js ├── 03-~&~~ │ └── index.js ├── 04-Shift Operators │ └── index.js └── README.md ├── 04-Async ├── 01-Highorder.js ├── 02-Callback.js ├── 03-Generator.js ├── 04-Promise.js ├── 05-Async+Await.js ├── 06-CreatePromise.js ├── README.md ├── a.txt ├── b.txt ├── c.txt └── test.js ├── 05-Regexp ├── 01-Grammar.js └── README.md ├── 06-Date ├── 01-ParseTime.js └── README.md ├── 07-OOP ├── 01-Create Object.js ├── 02-Inheritance.js ├── 03-Class.js └── README.md ├── 08-This ├── README.md ├── apply.js ├── bind.js ├── call.js ├── index.html ├── index.js └── useStrict.js ├── 09-Skills ├── 01-shuffle │ ├── README.md │ ├── index.html │ ├── self-training.html │ └── training.js ├── 02-debounce │ ├── index.html │ └── index.js ├── 03-traffic light │ └── index.js ├── 04-throttle │ └── index.js ├── 05-AccessNestObject │ ├── README.md │ └── index.js └── README.md ├── 10-Design-patterns ├── 01-Factory │ └── index.js └── 02-Singleton │ └── index.js ├── 11-Event Loop ├── 01-stack │ ├── index.html │ └── index.js └── README.md ├── 12-MVVM └── 01-defineProperty │ ├── compile.js │ ├── index.html │ ├── mvvm.js │ ├── observer.js │ └── watcher.js ├── 13-Prototype ├── JQuery │ └── index.js ├── README.md ├── Zepto │ └── index.js └── index.html ├── 14-DataStructure └── Linked-list │ └── README.md ├── 15-TypeScript └── README.md ├── 16-Source Code ├── README.md └── splice.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # Editor directories and files 6 | .idea 7 | .vscode -------------------------------------------------------------------------------- /00-ES6/01-let & const/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2019-3-18 5 | **/ 6 | 7 | // js 8 | // var i = 10 9 | // i = 100 10 | // var j = 20 11 | // j = 200 12 | 13 | // es6 14 | let i = 10 15 | i = 100 // 正确 16 | const j = 20 17 | j = 200 // 报错 18 | -------------------------------------------------------------------------------- /00-ES6/02-Template_string/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2019-3-18 5 | **/ 6 | 7 | // 模板字符串 8 | 9 | // js 10 | 11 | // var name = 'stephent' 12 | // var age = 18 13 | // var html = '' 14 | 15 | // html = 'My name is ' + name + ',' + 'I\'m' + age + 'years old.' 16 | 17 | // ES6 18 | const name = 'stephen' 19 | const age = 18 20 | const html = `My name is ${name} , I'm ${age} years old.` 21 | 22 | console.log(html) 23 | -------------------------------------------------------------------------------- /00-ES6/03-Destructuring/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2019-3-18 5 | **/ 6 | 7 | // 变量的解构赋值 8 | 9 | // js 10 | 11 | // var obj = { a: 100, b: 200 } 12 | // var a = obj.a 13 | // var b = obj.b 14 | 15 | // var arr = ['xx', 'xxx', 'x'] 16 | // var x = arr[0] 17 | 18 | // es6 19 | 20 | const obj = { a: 10, b: 20, c: 30 } 21 | const { a, c } = obj 22 | console.log(a) 23 | console.log(c) 24 | 25 | const arr = ['xx', 'xxx', 'x'] 26 | const [x, y, z] = arr 27 | console.log(x) 28 | console.log(y) 29 | console.log(z) 30 | -------------------------------------------------------------------------------- /00-ES6/04-Block_scope/index.js: -------------------------------------------------------------------------------- 1 | // 块级作用域 2 | 3 | // js 4 | 5 | var obj = { a: 100, b: 200 } 6 | for (var i in obj) { 7 | console.log(i) 8 | } 9 | console.log(i) // b 10 | 11 | // es6 12 | 13 | const obj1 = { a: 10, b: 20 } 14 | for (let n in obj1) { 15 | console.log(n) 16 | } 17 | console.log(n) // undefined -------------------------------------------------------------------------------- /00-ES6/05-Function/01-Default_Args/index.js: -------------------------------------------------------------------------------- 1 | // 默认参数 2 | 3 | // js 4 | 5 | function A(a, b) { 6 | if (b == null) { 7 | b = 0 8 | a = 1 9 | } 10 | console.log(a) 11 | } 12 | 13 | // es6 14 | 15 | function B(a, b = 0) { 16 | a = 1 17 | console.log(a, b) 18 | } 19 | -------------------------------------------------------------------------------- /00-ES6/05-Function/02-Arrow_Function/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 箭头函数 this 9 | 10 | 11 | 12 |
13 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /00-ES6/05-Function/02-Arrow_Function/index.js: -------------------------------------------------------------------------------- 1 | // 箭头函数 2 | 3 | // js 4 | var arr = [1, 2, 3] 5 | arr.map(function (item) { 6 | return item + 1 7 | }) 8 | 9 | // es6 10 | const arr1 = [1, 2, 3, 4] 11 | arr1.map(item => item + 1) 12 | arr1.map((item, index) => { 13 | console.log(index) 14 | return item + 1 15 | }) 16 | 17 | // js 18 | 19 | function fn() { 20 | console.log('real', this) 21 | var arr2 = [1, 2, 3] 22 | // js 23 | arr2.map(function (item) { 24 | console.log('js', this) 25 | return item + 1 26 | }) 27 | 28 | // 箭头函数 29 | arr2.map(item => { 30 | console.log('es6', this) 31 | return item + 1 32 | }) 33 | } 34 | fn.call({ a: 100 }) 35 | -------------------------------------------------------------------------------- /00-ES6/06-Spread/README.md: -------------------------------------------------------------------------------- 1 | # 扩展运算符(spread) 2 | 3 | ## es5 实现扩展符 4 | 5 | ```javascript 6 | ...a 7 | 8 | = 9 | 10 | function _toConsumableArray (e) { 11 | return _arrayWithoutHoles(e) || _iterableToArray(e) || _nonIterableSpread(); 12 | } 13 | 14 | function _nonIterableSpread () { 15 | throw new TypeError("Invalid attempt to spread non-iterable instance"); 16 | } 17 | 18 | // 将 e 变换为数组 19 | function _iterableToArray (e) { 20 | if ( 21 | Symbol.iterator in Object(e) || 22 | "[object Arguments]" === Object.prototype.toString.call(e) 23 | ) 24 | return Array.from(e); 25 | } 26 | // 拷贝数组并返回 27 | function _arrayWithoutHoles (e) { 28 | if (Array.isArray(e)) { 29 | for (var t = 0, o = new Array(e.length); t < e.length; t++) o[t] = e[t]; 30 | return o; 31 | } 32 | } 33 | 34 | ``` 35 | -------------------------------------------------------------------------------- /00-ES6/README.md: -------------------------------------------------------------------------------- 1 | # ES6 常见知识 2 | 3 | ## 目录 4 | 5 | - let/const 6 | - 块级作用域(Block scope) 7 | - 字符串扩展 8 | - 模板字符串(Template string) 9 | - 解构赋值(Destructuring) 10 | - 函数扩展(Funnction) 11 | - 函数默认参数 12 | - 箭头函数 13 | - 箭头函数 14 | 15 | ### let/const 16 | 17 | #### let 18 | 19 | let 用来声明变量, 作用类似 var; 20 | 但是 let 声明的变量, 只在所写的代码块有效. 21 | 22 | 比如中函数体内使用 let 声明变量, 在函数外部使用会报错. 23 | 24 | #### const 25 | 26 | const 是用来声明一个不可变的常量 27 | 28 | ```js 29 | const PI = 3.1415; 30 | PI = 3.14; // TypeError: Assignment to constant variable 31 | ``` 32 | 33 | ### template string 34 | 35 | 模板字符串顾名思义,就是在字符串的基础上,使用变量,甚至进行简单的计算。 36 | 使用方式: 37 | 38 | ```js 39 | var n = "stephen"; 40 | console.log(`my name is ${n}`); 41 | // 等价于 42 | console.log("my name is" + n + ""); 43 | ``` 44 | 45 | ### spread operator in es5 46 | 47 | ES5 实现扩展府。先了解扩展符的功能: 48 | 49 | - 使用在函数参数, 相当于 apply 50 | - 对数组进行操作, 相当于 concat 51 | - 对对象进行浅拷贝, 相当于 Object.assign() 52 | -------------------------------------------------------------------------------- /01-Algorithm/01-Recursion/README.md: -------------------------------------------------------------------------------- 1 | # 递归 Recursion 2 | 3 | ## 何为递归 4 | 5 | 程序调用自身的编程技巧称为递归,递归做为一种算法在程序设计语言中广泛应用。 6 | 7 | **举个例子:** 8 | 比如说阶乘, 9 | ``` 10 | factorial(5) = factorial(4) * 5 11 | factorial(4) = factorial(3) *4 12 | ... 13 | ... 14 | ... 15 | factorial(0) = 1 16 | ``` 17 | 从第一个调用 factorial(5) 开始,一直递归调用 `factorial` 函数直到参数为 0。 18 | 19 | 总结来说: 20 | ``` 21 | factorial(n) = factorial(n-1) ) n 22 | factorial(0) = 1 23 | ``` 24 | 25 | 26 | ## 递归调用栈 27 | 28 | 回到上面的例子: 29 | ``` 30 | function factorail(n) { 31 | if (n === 0) return 1 32 | return n * factorial(n - 1) 33 | } 34 | ``` 35 | 36 | 如果我们传入参数 3,将会递归调用 factorial(2)、factorial(1) 和 factorial(0), 37 | 会调用 3 次。 38 | 每次函数调用都会压入调用栈,如下: 39 | ``` 40 | factorial(0) // 0的阶乘为1 41 | factorial(1) // 该调用依赖factorial(0) 42 | factorial(2) // 该调用依赖factorial(1) 43 | factorial(3) // 该掉用依赖factorial(2) 44 | ``` 45 | 46 | 这样的话,如果传入的 n 值特别大,那么这个调用栈将会非常之大。 47 | 如何解决这个问题呢?使用尾递归。 48 | 49 | ## 尾递归 50 | 51 | 尾递归是一种递归的写法,可以避免不断的将函数压栈导致堆栈溢出。 52 | 通过设置一个累加数,并且每一次都将当前的值累加上去,然后递归调用。 53 | 54 | ``` 55 | function factorial(n, total = 1) { 56 | if (n === 0) return total 57 | return factorial(n-1, n * total) 58 | } 59 | ``` 60 | -------------------------------------------------------------------------------- /01-Algorithm/01-Recursion/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2019-8-14 5 | **/ 6 | 7 | // 递归 8 | 9 | function factorail(n) { 10 | if (n === 0) return 1 11 | return n * factorial(n - 1) 12 | } 13 | -------------------------------------------------------------------------------- /01-Algorithm/02-Factorial/factorial.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2018/8/28 5 | **/ 6 | 7 | // while 8 | // 阶乘 9 | 10 | function factorialWhile(num) { 11 | let result = 1 12 | while (num) { 13 | result *= num 14 | num-- 15 | } 16 | return result 17 | } 18 | console.log('factorialWhile(3): ', factorialWhile(3)) 19 | 20 | 21 | // for 22 | function factorialFor(num) { 23 | let result = 1 24 | for (let i = 2; i <= num; i++) { 25 | result *= i; 26 | } 27 | return result 28 | } 29 | console.log('factorial(4): ', factorialFor(4)) 30 | 31 | 32 | // recursion 递归 33 | function factorialRec(num) { 34 | if (num <= 0) { 35 | return 1 36 | } else { 37 | return num * arguments.callee(num - 1) 38 | } 39 | } 40 | console.log('factorialRec(5): ', factorialRec(5)) 41 | 42 | 43 | // tail recursion 尾递归 44 | function factorialTail(num) { 45 | let total = 1 46 | if (num === 0) return total 47 | return (num * factorialTail(num - 1)) 48 | } 49 | console.log('factorialTail(6): ', factorialTail(6)) 50 | 51 | // in "use strict" 52 | // recursion 严格模式下的递归 53 | var factorialStrict = (function fun(num) { 54 | if (num <= 1) return 1 55 | return num * fun(num - 1) 56 | }) 57 | console.log('Recursion in use strict:', factorialStrict(5)) 58 | -------------------------------------------------------------------------------- /01-Algorithm/03-Palindrome/01-Palindrome.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2018-8-29 5 | **/ 6 | 7 | // Palindrome 回文:字符串中顺读和倒读都一样的词语 8 | // 增加一个处理,忽略标点符号、大小写和空格,顺读和倒读一样。 9 | 10 | function palindrome(str) { 11 | let regex = /[\W_]/g; 12 | str = str.replace(regex, '') 13 | let str1 = str.toLowerCase() 14 | let str2 = str.split('').reverse().join('').toLowerCase() 15 | return str1 === str2 16 | } -------------------------------------------------------------------------------- /01-Algorithm/04-SubString/SubString.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2018-9-9 5 | **/ 6 | 7 | // JS截取字符串可使用 substring()或者slice() 8 | 9 | // substring: substring(start,end)表示从start到end之间的字符串,包括start位置的字符但是不包括end位置的字符。 10 | 11 | // substr: substr(start,length)表示从start位置开始,截取length长度的字符串。 12 | 13 | // split: split('')使用一个指定的分隔符把一个字符串分割存储到数组 14 | 15 | // join: join('')使用一个指定的分隔符把一个字符串合并为同一个字符串 16 | 17 | // slice: slice(start,end)返回一个新的数组,包含从 start 到 end (不包括该元素)的 arrayObject 中的元素。 -------------------------------------------------------------------------------- /01-Algorithm/05-Sort/README.md: -------------------------------------------------------------------------------- 1 | # 排序 2 | 3 | ## 目录 4 | 5 | - [冒泡排序](#冒泡排序) 6 | 7 | - [快速排序](#快速排序) 8 | 9 | - [插入排序](#插入排序) 10 | 11 | - [选择排序](#选择排序) 12 | 13 | - [希尔排序](#希尔排序) 14 | 15 | - [归并排序](#归并排序) 16 | 17 | ## 冒泡排序 18 | 19 | 时间复杂度:O(n^2) 20 | 空间复杂度:O(1) 21 | 22 | 思路: 23 | 24 | 1. 比较相邻元素。如果前一个比后一个大,就交换他们。 25 | 2. 对每一对相邻元素遍历比对大小,做完之后,最后的元素会是最大的数,把它抛出。 26 | 3. 对除去最后元素的数组(因为最后是最大)重复以上步骤。 27 | 4. 比对的数组越来越小,直到没有数字比对。 28 | 29 | ```js 30 | function bubbleSort(arr) { 31 | if (arr.length <= 1) { 32 | return arr 33 | } 34 | for (let i = 0; i < arr.length; i++) { 35 | for (let j = 0; j < arr.length - 1 - i; j++) { 36 | if (arr[j] > arr[j + 1]) { 37 | let temp = arr[j + 1] 38 | arr[j + 1] = arr[j] 39 | arr[j] = temp 40 | } 41 | } 42 | } 43 | return arr 44 | } 45 | ``` 46 | 47 | ### 改进 48 | 49 | 1. 记录最后一次交换的位置 pos。因为最后一次交换位置之后项,都已经排好序了,所以下次遍历的时候 50 | 遍历到这个位置 pos 就好了,不需要遍历整个数组。 51 | 52 | ```js 53 | function bubbleSort(arr) { 54 | let i = arr.length - 1 55 | while (i > 0) { 56 | let pos = 0 57 | for (let j = 0; j < i; j++) { 58 | if (arr[j] > arr[j + 1]) { 59 | pos = j 60 | // 交换 61 | let temp = arr[j] 62 | arr[j] = arr[j + 1] 63 | arr[j + 1] = temp 64 | } 65 | } 66 | i = pos 67 | } 68 | } 69 | ``` 70 | 71 | 2. 冒泡排序每一次排序可以得到最大值,或者最小值。可以考虑利用每次排序使用正向和反向冒泡,这样一次可以得出最大值和最小值;排序次数可以少几乎一半。 72 | 73 | ```js 74 | function bubbleSort(arr) { 75 | let min = 0 76 | let max = arr.length - 1 77 | let temp, j 78 | while (min < max) { 79 | for (j = min; j < max; j++) { 80 | if (arr[j] > arr[j + 1]) { 81 | // 前比后大,交换 82 | } 83 | } 84 | max-- // 找出来最大值, 最大值位置往前移 85 | for (j = max; j > min; j--) { 86 | if (arr[j] < arr[j - 1]) { 87 | // 前比后小,交换 88 | } 89 | } 90 | min++ 91 | } 92 | return arr 93 | } 94 | ``` 95 | 96 | ## 快速排序 97 | 98 | 时间复杂度:O(n\*log n) 99 | 空间复杂度:O(log n) 100 | 101 | 思路: 102 | 103 | 1. 先从数列中取出一个数作为“基准”; 104 | 2. 将比这个“基准”小的数全放到“基准”的左边,大于或等于的数全放到“基准”的右边。 105 | 3. 再对左右区间重复第二步,直到各区间只有一个数。 106 | 107 | ```js 108 | const quickSort = function (arr) { 109 | if (arr.length <= 1) { 110 | return arr 111 | } 112 | // 基准位置(理论上可以选取任意位置) 113 | let midIndex = Math.floor(arr.length / 2) 114 | let pivot = arr.splice(midIndex, 1)[0] 115 | let left = [] 116 | let right = [] 117 | for (let i = 0; i < arr.length; i++) { 118 | if (arr[i] < pivot) { 119 | left.push(arr[i]) 120 | } else { 121 | right.push(arr[i]) 122 | } 123 | } 124 | return quickSort(left).concat([pivot], quickSort(right)) 125 | } 126 | ``` 127 | 128 | ## 插入排序 129 | 130 | 时间复杂度:O(n^2) 131 | 空间复杂度:O(1) 132 | 133 | 思路: 134 | 135 | 1. 假定第一个元素是排好序的; 136 | 2. 取下一个元素 i,取改元素的值为 key,i 左边的元素为 j = i - 1; 137 | 3. 当 i 左边的元素不是最后一个元素 (j>=0),并且 该元素的值大于 i 的值(`arr[j] > key`);说明该元素需要移到下一个位置( arr[j+1] = arr[j]); 138 | 4. 继续往左遍历( `j--` ),重复 `3` 步骤; 139 | 5. 将 key 值放到最后遍历到的位置(arr[j+1] = key)。 140 | 6. 重复 `2-5` 步骤,直到遍历完( `i++` )。 141 | 142 | ```js 143 | function insertSort(arr) { 144 | if (arr.length <= 1) { 145 | return arr 146 | } 147 | 148 | for (let i = 1; i < arr.length; i++) { 149 | let key = arr[i] 150 | let j = i - 1 151 | while (j >= 0 && arr[j] > key) { 152 | arr[j + 1] = arr[j] 153 | j-- 154 | } 155 | arr[j + 1] = key 156 | } 157 | 158 | return arr 159 | } 160 | ``` 161 | 162 | ## 选择排序 163 | 164 | 时间复杂度:O(n^2) 165 | 空间复杂度:O(1) 166 | 167 | 思路: 168 | 169 | 1. 在未排序序列中找到最小(大)元素,存放到排序序列的起始位置; 170 | 2. 从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾; 171 | 3. 重复第二步,直到所有元素均排序完毕。 172 | 173 | ```js 174 | function selection(arr) { 175 | if (arr.length <= 1) { 176 | return arr 177 | } 178 | let minIndex, temp 179 | for (let i = 0; i < arr.length - 1; i++) { 180 | minIndex = i 181 | // 再循环一遍,找最小的数 182 | for (let j = i + 1; j < arr.length; j++) { 183 | if (arr[j] < arr[minIndex]) { 184 | minIndex = j 185 | } 186 | } 187 | // 找到最小数后和 arr[i] 互换位置 188 | temp = arr[i] 189 | arr[i] = arr[minIndex] 190 | arr[minIndex] = temp 191 | } 192 | return arr 193 | } 194 | ``` 195 | 196 | ## 希尔排序 197 | 198 | > shell sort 199 | 200 | 也称递减增量排序算法 201 | 202 | 时间复杂度:O(n log n) 203 | 空间复杂度:O(1) 204 | 205 | 思路: 206 | 207 | 1. 在希尔排序中最重要的是分组,我们先找到一个间隔,每隔一定的间隔将这些数字排位一组; 208 | 2. 对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至 1 时,整个文件恰被分成一组,算法便终止。 209 | 210 | ```js 211 | function shellSort(arr) { 212 | if (arr.length <= 1) { 213 | return arr 214 | } 215 | // 算间隔 216 | let len = Math.floor(arr.length / 2) 217 | while (len > 0) { 218 | for (let i = len; i < arr.length; i++) { 219 | let temp = arr[i] 220 | for (let j = i - len; j >= 0 && temp < arr[j]; j = j - len) { 221 | arr[j + len] = arr[j] 222 | } 223 | arr[j + len] = temp 224 | } 225 | len = Math.floor(len / 2) 226 | } 227 | } 228 | ``` 229 | 230 | ## 归并排序 231 | 232 | > merge sort 233 | 234 | 归并排序采用分治法(Divide and Conquer)。将已有序的子序列合并,得到完全游戏的序列。 235 | 236 | 将两个有序表合成有序表,成为 2-路归并。 237 | 238 | 思路: 239 | 240 | 1. 将长度 n 的序列分为两个长度为 n/2 的子序列; 241 | 2. 对两个子序列才有归并排序。 242 | 243 | ```js 244 | function mergeSort(arr) { 245 | if (arr.length <= 1) { 246 | return arr 247 | } 248 | let len = Math.floor(arr.length / 2) 249 | let left = arr.slice(0, len) 250 | let right = arr.slice(len, arr.length) 251 | 252 | return merge(mergeSort(left), mergeSort(right)) 253 | } 254 | 255 | function merge(left, right) { 256 | let res = [] 257 | 258 | while (left.length && right.length) { 259 | if (left[0] < right[0]) { 260 | res.push(left.shift()) 261 | } else { 262 | res.push(right.shift()) 263 | } 264 | } 265 | 266 | while (left.length) { 267 | res.push(left.shift()) 268 | } 269 | 270 | while (right.length) { 271 | res.push(right.shift()) 272 | } 273 | 274 | return res 275 | } 276 | ``` 277 | -------------------------------------------------------------------------------- /01-Algorithm/05-Sort/bubbleSort.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2019-8-28 5 | **/ 6 | 7 | // 冒泡排序 8 | 9 | function bubbleSort(arr) { 10 | if (arr.length <= 1) { 11 | return arr 12 | } 13 | for (let i = 0; i < arr.length; i++) { 14 | for (let j = 0; j < arr.length - 1 - i; j++) { 15 | if (arr[j] > arr[j + 1]) { 16 | let temp = arr[j + 1] 17 | arr[j + 1] = arr[j] 18 | arr[j] = temp 19 | } 20 | } 21 | } 22 | return arr 23 | } 24 | -------------------------------------------------------------------------------- /01-Algorithm/05-Sort/quickSort.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2019-8-14 5 | **/ 6 | 7 | 8 | // 快速排序 9 | 10 | const quickSort = function (arr) { 11 | if (arr.length <= 1) { 12 | return arr 13 | } 14 | // 基准位置(理论上可以选取任意位置) 15 | let midIndex = Math.floor(arr.length / 2) 16 | let pivot = arr.splice(midIndex, 1)[0] 17 | let left = [] 18 | let right = [] 19 | for (let i = 0; i < arr.length; i++) { 20 | if (arr[i] < pivot) { 21 | left.push(arr[i]) 22 | } else { 23 | right.push(arr[i]) 24 | } 25 | } 26 | return quickSort((left).concat([pivot], quickSort(right))) 27 | } 28 | -------------------------------------------------------------------------------- /01-Algorithm/05-Sort/selectionSort.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2019-8-14 5 | **/ 6 | 7 | 8 | // 选择排序 9 | 10 | function selection(arr) { 11 | if (arr.length <= 1) { 12 | return arr 13 | } 14 | let minIndex, temp 15 | for (let i = 0; i < arr.length - 1; i++) { 16 | minIndex = i; 17 | // 再循环一遍,找最小的数 18 | for (let j = i + 1; j < arr.length; j++) { 19 | if (arr[j] < arr[minIndex]) { 20 | minIndex = j 21 | } 22 | } 23 | // 找到最小数后和 arr[i] 互换位置 24 | temp = arr[i] 25 | arr[i] = arr[minIndex] 26 | arr[minIndex] = temp 27 | } 28 | return arr 29 | } 30 | -------------------------------------------------------------------------------- /01-Algorithm/05-Sort/shellSort.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2019-8-14 5 | **/ 6 | 7 | 8 | // 希尔排序 9 | 10 | function shellSort(arr) { 11 | if (arr.length <= 1) { 12 | return arr 13 | } 14 | // 算间隔 15 | let len = Math.floor(arr.length / 2) 16 | while (len > 0) { 17 | for (let i = len; i < arr.length; i++) { 18 | let temp = arr[i]; 19 | for (let j = i - len; j >= 0 && temp < arr[j]; j = j - len) { 20 | arr[j + len] = arr[j] 21 | } 22 | arr[j + len] = temp 23 | } 24 | len = Math.floor(len / 2) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /01-Algorithm/06-Cache Algorithm/FIFO.js: -------------------------------------------------------------------------------- 1 | // FIFO queue 2 | 3 | // FIFO is a cache algorithm。 4 | // when the cache is full 5 | // we remove the cached item that is at the front of the FIFO queue 6 | // since it was added first and add the new item at the tail 7 | 8 | class FIFOCacke { 9 | constructor(limit) { 10 | this.limit = limit || 10 11 | this.map = {} 12 | this.keys = [] 13 | } 14 | set(key, value) { 15 | let map = this.map 16 | let keys = this.keys 17 | if (!Object.prototype.hasOwnProperty.call(map, key)) { 18 | if (keys.length === this.limit) { 19 | // remove item at the tail 20 | delete map[keys.shift()] 21 | } 22 | keys.push(key) 23 | } 24 | map[key] = value 25 | } 26 | get(key) { 27 | return this.map[key] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /01-Algorithm/07-Search Algorithm/README.md: -------------------------------------------------------------------------------- 1 | # 查找算法 2 | 3 | ## 目录 4 | 5 | ## 顺序查找 6 | 7 | 简而言之,就是从第一个元素开始,遍历全部元素,也叫暴力查找。 8 | 9 | ```js 10 | function search(arr, target) { 11 | for (let i = 0; i arr[mid]) { 46 | low = mid + 1 47 | } else { 48 | return mid 49 | } 50 | } 51 | return -1 52 | } 53 | ``` 54 | -------------------------------------------------------------------------------- /01-Algorithm/Demo/01-Longest String.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2018-9-11 5 | **/ 6 | 7 | // Find the longest word in a string 8 | 9 | 10 | // for loop 11 | 12 | function findLongestWord1(str) { 13 | let arr = str.split(" ") 14 | let long = 0 15 | for (let i = 0; i < arr.length; i++) { 16 | if (arr[i], length > long) { 17 | long = arr[i].length 18 | } 19 | } 20 | return long 21 | } 22 | 23 | 24 | // map() sort() 25 | // map 匹配数组中各个字符串的长度, 返回字符串长度 26 | // sort 由大到小排序 27 | 28 | function findLongestWord2(str) { 29 | let arr = str.split(" ") 30 | arr = arr.map(function (item) { 31 | return item.length 32 | }).sort(function (a, b) { 33 | return b - a 34 | }) 35 | return arr[0] 36 | } 37 | 38 | 39 | // map() reduce() 40 | 41 | function findLongestWord3(str) { 42 | let arr = str.split(" ") 43 | return arr.map(function (val) { 44 | return val.length 45 | }).reduce(function (p, c) { 46 | return Math.max(p, c) 47 | }) 48 | // ES6 49 | // return arr.map(val => val.length).reduce((a, b) => Math.max(a, b)) 50 | } 51 | 52 | findLongestWord3('Find the longest word in a string') 53 | 54 | 55 | // sort() 56 | 57 | function findLongestWord4(str) { 58 | let arr = str.split(" ") 59 | let str1 = arr.sort((a, b) => b.length - a.length) 60 | let lenMax = str1[0].length 61 | console.log(lenMax) 62 | return lenMax 63 | } 64 | 65 | findLongestWord4("Find the longest word in a string") 66 | 67 | // sort() pop() 68 | function findLongestWord5(str) { 69 | let arr = str.split(" ") 70 | let str1 = arr.sort((a, b) => a.length - b.length) 71 | let long = str1.pop().length 72 | return long 73 | } -------------------------------------------------------------------------------- /01-Algorithm/README.md: -------------------------------------------------------------------------------- 1 | # Algorithm 算法学习 2 | 3 | ## 目录 4 | 5 | - **[Recursion 递归](https://github.com/stephentian/daily-js/tree/master/01-Algorithm/01-Recursion)** 6 | - **[Factorial 阶乘](https://github.com/stephentian/daily-js/blob/master/01-Algorithm/02-Factorial/factorial.js)** 7 | - **[Palindrome 回文](https://github.com/stephentian/daily-js/blob/master/01-Algorithm/03-Palindrome/01-Palindrome.js)** 8 | - **[SubString 字符串切割](https://github.com/stephentian/daily-js/blob/master/01-Algorithm/04-SubString/SubString.js)** 9 | - **[Sort 排序](https://github.com/stephentian/daily-js/tree/master/01-Algorithm/05-Sort)** 10 | - **[Cache Algorithm 缓存算法](https://github.com/stephentian/daily-js/tree/master/01-Algorithm/06-Cache%20Algorithm)** 11 | - **[Search Algorithm 查找算法](./07-Search%20Algorithm)** 12 | -------------------------------------------------------------------------------- /02-Higher-order Function/01-Introduction.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2018-9-12 5 | **/ 6 | 7 | // Higher-order Function 8 | // 高阶函数 9 | // JavaScript 的函数其实都指向某个变量。既然变量可以指向函数,函数的参数就能接收变量 10 | // 一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数 11 | 12 | function add(x, y, f) { 13 | return f(x) + f(y) 14 | } 15 | 16 | add(-5, 6, Math.abs) 17 | -------------------------------------------------------------------------------- /02-Higher-order Function/02-Map.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: tianre96@gmail.com 4 | * day: 2018-9-12 5 | **/ 6 | 7 | // the function will runs on each element of the array 8 | 9 | var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9] 10 | 11 | function pow(x) { 12 | return x * x 13 | } 14 | 15 | var results = arr.map(pow) 16 | console.log(results) 17 | -------------------------------------------------------------------------------- /02-Higher-order Function/03-Reduce.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2018-9-12 5 | **/ 6 | 7 | // The reduce() method executes a reducer function (that you provide) on each member of the array resulting in a single output value 8 | // reduce() 方法对累加器和数组中的每个元素(从左到右)应用一个函数,将其减少为单个值 9 | 10 | var arr = [1, 2, 3, 4, 5] 11 | var results = arr.reduce((x, y) => x + y) 12 | console.log(results) 13 | 14 | // Transform [1, 2, 3, 4, 5] into 12345 15 | var results1 = arr.reduce((x, y) => x * 10 + y) 16 | console.log(results1) 17 | -------------------------------------------------------------------------------- /02-Higher-order Function/04-Filter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2018-9-12 5 | **/ 6 | 7 | // The filter() method creates a new array with all elements that pass the test implemented by the provided function 8 | // filtering out all value 9 | // filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素 10 | // filter() 在于实现一个“筛选”函数 11 | 12 | var arr = [1, 2, 3, 4, 5, 6] 13 | var result = arr.filter(v => v > 3) 14 | //console.log(result) 15 | 16 | var result1 = arr.filter((ele, index, self) => { 17 | // element value 18 | console.log(ele) 19 | // element index 20 | console.log(index) 21 | // arr 22 | //console.log(self) 23 | return true 24 | }) 25 | 26 | // Array remove duplicated 27 | // 数组去重 28 | 29 | var r, 30 | arr1 = [1, 2, 2, 3, 4, 4, 5, 1] 31 | r = arr1.filter((ele, index, self) => self.indexOf(ele) === index) 32 | console.log(r) -------------------------------------------------------------------------------- /02-Higher-order Function/05-Sort.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2018-9-13 5 | **/ 6 | 7 | // The sort() method sorts the elements in Place of an array and returns the array. 8 | // The default sort order is according to string Unicode code points. !important 9 | // sort() 方法用原地算法对数组的元素进行排序 10 | // 默认排序顺序是根据字符串Unicode码点 11 | 12 | // 直接在原数组上改变 13 | 14 | var arr = [10, 20, 1, 2] 15 | arr.sort((x, y) => x - y) 16 | console.log(arr) 17 | 18 | var items = [{ 19 | name: 'Edward', 20 | value: 21 21 | }, 22 | { 23 | name: 'Sharpe', 24 | value: 37 25 | }, 26 | { 27 | name: 'And', 28 | value: 45 29 | }, 30 | { 31 | name: 'the', 32 | value: -12 33 | }, 34 | { 35 | name: 'Magnetic', 36 | value: 100 37 | }, 38 | { 39 | name: 'Zeros', 40 | value: 37 41 | } 42 | ]; 43 | 44 | // sort by value 45 | var arr1 = items.sort(function (a, b) { 46 | return (a.value - b.value) 47 | }); 48 | console.log('arr1: ', arr1) 49 | 50 | // sort by name 51 | var arr2 = items.sort(function (a, b) { 52 | var nameA = a.name.toUpperCase(); // ignore upper and lowercase 53 | var nameB = b.name.toUpperCase(); // ignore upper and lowercase 54 | if (nameA < nameB) { 55 | return -1; 56 | } 57 | if (nameA > nameB) { 58 | return 1; 59 | } 60 | // names must be equal 61 | return 0; 62 | }); 63 | console.log('arr2: ', arr2) -------------------------------------------------------------------------------- /02-Higher-order Function/06-Some.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2020-04-28 5 | **/ 6 | 7 | // MDN: 8 | // The some() method tests whether at least one element in the array passes the test implemented by the provided function. 9 | // It returns a Boolean value 10 | 11 | const array = [1, 2, 3, 4] 12 | 13 | const even = e => e % 2 === 0 14 | 15 | console.log(array.some(even)) 16 | // true 17 | -------------------------------------------------------------------------------- /02-Higher-order Function/07-Every.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: tianre96@gmail.com 4 | * day: 2020-04-28 5 | **/ 6 | 7 | // The every() method tests whether all elements in the array pass the test impletmented by the provided function. 8 | // return a Boolean value. 9 | 10 | const isBelowFourty = v => v < 40 11 | const isBelowSixty = v => v < 60 12 | 13 | const array = [1, 10, 20, 30, 40, 50] 14 | 15 | console.log(array.every(isBelowFourty)) 16 | // false 17 | console.log(array.every(isBelowSixty)) 18 | // true 19 | -------------------------------------------------------------------------------- /02-Higher-order Function/README.md: -------------------------------------------------------------------------------- 1 | # 高阶函数 2 | > 可以接收函数作为参数的函数 3 | 4 | 函数是 js 的一等公民, 可以作为参数和返回值存在 5 | -------------------------------------------------------------------------------- /03-General Knowledge/01-Falsy.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Document 14 | 15 | 16 | 17 |
18 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /03-General Knowledge/02-Truthy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2018-9-17 5 | **/ 6 | 7 | // A truthy value is a value that is considered 'true' when evaluated in a Boolean context 8 | // Truthy(真值)指的是在Boolean上下文中转换后的值为真的值 9 | 10 | // true 11 | // {} 12 | // [] 13 | // numbers !== 0 (e.g. 1, 2, ...) 14 | // String (e.g. 'string') 15 | // new Date() 16 | // -42 17 | // 3.14 18 | // -3.14 19 | // Infinity 20 | // -Infinity 21 | 22 | console.log('true: ', Boolean(true)) 23 | console.log('{}: ', Boolean({})) 24 | console.log('[]: ', Boolean([])) 25 | console.log('1: ', Boolean(1)) 26 | console.log('0: ', Boolean(0)) 27 | console.log('-1: ', Boolean(-1)) 28 | console.log('string: ', Boolean('string')) 29 | console.log('new Date(): ', Boolean(new Date())) 30 | console.log('-42: ', Boolean(-42)) 31 | console.log('-41: ', Boolean(-41)) 32 | console.log('3.14: ', Boolean(3.14)) 33 | console.log('-3.14: ', Boolean(-3.14)) 34 | console.log('-3.1: ', Boolean(-3.1)) 35 | console.log('Infinity: ', Boolean(Infinity)) 36 | console.log('-Infinity: ', Boolean(-Infinity)) 37 | console.log('PI: ', Boolean(Math.PI)) 38 | // console.log(Boolean()) -------------------------------------------------------------------------------- /03-General Knowledge/03-~&~~/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2019-5-21 5 | **/ 6 | 7 | 8 | // ~ 和 ~~ 9 | 10 | // 1. 介绍 11 | 12 | // ~ 通俗的讲, 就是 x 进行按位非操作, 结果为 -(x + 1) 13 | // 对于浮点数的结果, 负的向上取整, 正的向下取整 14 | 15 | // console.log(~2.5) 16 | // -3 17 | 18 | // console.log(~3.6) 19 | // -4 20 | 21 | 22 | 23 | // 同理, ~~ 就是对 x 取 -(-(x+1) + 1) 24 | 25 | // console.log(~~2.5) 26 | // 2 27 | 28 | 29 | // 2. 延伸 30 | 31 | // 利用两个按位取反的符号,进行类型的转换,转换成数字符号 32 | 33 | // true 34 | console.log(~~false === 0) 35 | console.log(~~true === 1) 36 | console.log(~~undefined === 0) 37 | console.log(~~!undefined === 1) 38 | console.log(~~null === 0) 39 | console.log(~~!null === 1) 40 | console.log(~~'' === 0) 41 | // console.log(~~false === 0) 42 | 43 | 44 | 45 | // ~ 用法 46 | 47 | // 判断数值中是否有某元素时 48 | 49 | // 原来: if(arr.indexOf(ele) > -1) { ... } 50 | // 现在: if(~arr.indexof(ele)) { ... } 51 | 52 | 53 | // ~~ 用法 54 | 55 | // ~~value 可以代替 parseInt 56 | // 原来: parseInt(-2.99) //-2 57 | // 现在: ~~(2.99) // -2 58 | -------------------------------------------------------------------------------- /03-General Knowledge/04-Shift Operators/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2019-5-24 5 | **/ 6 | 7 | // 移位操作符 8 | 9 | 10 | 11 | // 1 12 | // << 有符号左移 13 | 14 | // 用法: 15 | 16 | // a << b 17 | // 将 a 的二进制形式向左移 b (< 32) 比特位,右边用 0 填充 18 | // 也可以认为 a*(2^b) 19 | // 10 << 1 20 | // 20 21 | console.log(10 << 2) // 40 22 | 23 | 24 | 25 | // 2 26 | // >> 有符号右移 27 | 28 | // 用法: 29 | 30 | // a >> b 31 | // 将 a 的二进制表示向右移 b (< 32) 位,丢弃被移出的位、 32 | // 也可以认为 a/(2^b) 33 | // 10 >> 1 34 | // 5 35 | // 20 >> 2 36 | // 5 37 | // -4 >> 1 38 | // -2 39 | 40 | // 小数的往小的计 41 | // 9 >> 1 42 | // 4 43 | 44 | 45 | 46 | // 3 47 | // >>> 无符号右移 48 | 49 | // 用法: 50 | 51 | // 对于非负数,有符号右移和无符号右移总是返回相同的结果 52 | // 9 >>> 2 53 | // 2 54 | // 对于负数却不尽相同 55 | // -4 >>> 1 56 | // 2147483646 57 | -------------------------------------------------------------------------------- /03-General Knowledge/README.md: -------------------------------------------------------------------------------- 1 | # JavaScript General Knowledge 2 | 3 | > JS 常识 4 | 5 | - [JavaScript General Knowledge](#javascript-general-knowledge) 6 | - [1. 真值与虚值](#1-真值与虚值) 7 | - [真值](#真值) 8 | - [虚值](#虚值) 9 | - [2. Chrome 上返回的 undefined](#2-chrome-上返回的-undefined) 10 | - [3. ~ 和 ~~](#3--和-) 11 | - [this 和 var 定义的变量](#this-和-var-定义的变量) 12 | - [在函数中](#在函数中) 13 | - [在{}对象中](#在对象中) 14 | 15 | ## 1. 真值与虚值 16 | 17 | ### 真值 18 | 19 | ### 虚值 20 | 21 | ## 2. Chrome 上返回的 undefined 22 | 23 | 函数没有返回值的情况下默认返回 undefined 24 | 25 | ## 3. ~ 和 ~~ 26 | 27 | ~ 为 按位取反 28 | 29 | ## this 和 var 定义的变量 30 | 31 | ### 在函数中 32 | 33 | ```js 34 | function Fool() { 35 | let name = "Jack" 36 | this.name = "Mick" 37 | funtion fn() { 38 | console.log("name:", name) 39 | console.log("this.name:", this.name) 40 | } 41 | fn() 42 | } 43 | 44 | Fool() 45 | // Jack 46 | // Mick 47 | ``` 48 | 49 | - 在函数中,var定义的是局部变量,而this定义的是属性。 50 | - 这两者没有覆盖。 51 | - 新创建的对象是无法获取到局部变量的。 52 | 53 | ### 在{}对象中 54 | 55 | 和 window 对象一样,{} 创建的对象不能用 new ,因为没有构造函数 56 | -------------------------------------------------------------------------------- /04-Async/01-Highorder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2019/2/26 5 | **/ 6 | 7 | // 高阶函数 8 | 9 | // 一、 10 | // 写一些判断参数类型的函数 11 | // 1. 判断一个参数是否是字符串 12 | function isString(param) { 13 | return Object.prototype.toString.call(param) === '[object String]' 14 | } 15 | // 2. 判断一个参数是否为数组 16 | function isArray(param) { 17 | return Object.prototype.toString.call(param) === '[object Array]' 18 | } 19 | // 3. isNumber ... 20 | console.log(isString({})) 21 | console.log(isArray([])) 22 | // 如果要判断其他类型的话,js 中类型很多,就要写很多函数 23 | 24 | // 思考:能不能写一个方法直接批量返回这些函数呢? 25 | 26 | 27 | // 二、 28 | // 写一个函数判断类型, 通过它批量生成函数 29 | function isType(type) { 30 | return function (param) { 31 | return Object.prototype.toString.call(param) === `[object ${type}]` 32 | } 33 | } 34 | 35 | let isString1 = isType('String') 36 | let isArray1 = isType('Array') 37 | console.log(isString1('11')) 38 | console.log(isArray1([1])) 39 | 40 | // 类似函数工厂, 生产函数 41 | // 这其实也是闭包的应用, 当函数执行时, 会产生私有作用域, 上下文执行环境, 可以一直引用里面的 type 42 | 43 | 44 | // 三、 45 | // lodash 中有个 after 方法 46 | // 作用: 指定一个函数被调用多少次才会执行 47 | function sleep() { 48 | console.log('睡着了') 49 | } 50 | 51 | // 函数 after 52 | // 接受参数 times 和 fn(函数) 53 | // 返回一个新的函数, 当新的函数执行 times 次后, 执行 fn 54 | // function after(times, fn) { 55 | // ... 56 | // } 57 | // let newSleep = after(3, sleep) 58 | // newSleep() 59 | // newSleep() 60 | // newSleep() // 执行 sleep 61 | 62 | // 思考: after 里的内容 63 | 64 | function after(times, fn) { 65 | let count = 1 66 | return function () { 67 | if (count++ == times) { 68 | fn() 69 | } 70 | } 71 | } 72 | let newSleep = after(3, sleep) 73 | newSleep() 74 | newSleep() 75 | newSleep() 76 | -------------------------------------------------------------------------------- /04-Async/02-Callback.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2019/2/28 5 | **/ 6 | 7 | // 回调函数 8 | 9 | // 写一个异步读取 txt 文件的函数, 使用回调函数接收信息 10 | // error first, 回调函数的第一个参数为错误对象 11 | 12 | let fs = require('fs') 13 | 14 | // fs.readFile('./a.txt', 'utf8', function (err, data) { 15 | // if (err) { 16 | // console.log(err) 17 | // } else { 18 | // console.log(data) 19 | // } 20 | // }) 21 | // 打印出 aaa 22 | 23 | // 用一个函数把上面的封装起来 24 | // function read(path) { 25 | // fs.readFile(path, 'utf8', function (err, data) { 26 | // // throw Error('报错了!') 27 | // if (err) { 28 | // console.log(err) 29 | // } else { 30 | // console.log(data) 31 | // } 32 | // }) 33 | // } 34 | // read('./a.txt') 35 | 36 | 37 | 38 | // 回调函数有以下问题 39 | // 1. 不能 return, 无法正确获取返回值 40 | // let returnData = read('./a.txt') 41 | // console.log('returnData: ', returnData) // undefined 42 | 43 | // 2. 不能使用 try catch 捕获错误 44 | // try { 45 | // let returnData = read('./a.txt') 46 | // } catch (e) { 47 | // console.log('err:', e) // 当 read 函数 throw Error 时, catch 没捕获到错误 48 | // } 49 | 50 | // 3. 回调地狱 51 | // 比如你要读取多个文件,并且要把文件内容拼接起来 52 | // 假设 3 个文件 53 | 54 | // fs.readFile('./a.txt', 'utf8', function (err, dataA) { 55 | // if (err) { 56 | // console.log(err) 57 | // } else { 58 | // fs.readFile('./b.txt', 'utf8', function (err, dataB) { 59 | // if (err) { 60 | // console.log(err) 61 | // } else { 62 | // fs.readFile('./c.txt', 'utf8', function (err, dataC) { 63 | // if (err) { 64 | // console.log(err) 65 | // } else { 66 | // console.log(dataA + dataB + dataC) 67 | // } 68 | // }) 69 | // } 70 | // }) 71 | // } 72 | // }) 73 | /** 74 | * 这种回调金字塔有以下问题 75 | * 1. 非常难看 76 | * 2. 难以维护 77 | * 3. 效率低, 需要一步一步等待结果 78 | **/ 79 | 80 | 81 | 82 | // 如何解决回调函数嵌套问题? 83 | 84 | // 1. 事件发布/订阅模型 85 | // EventEmitter 是 node.js 核心模块 event 的一个类 86 | // 通过它可以创建事件发射器的实例, 实例有两个主要方法: 1. on(注册监听), 2. emit(发射事件) 87 | // let EventEmitter = require('events') 88 | // let task = new EventEmitter() 89 | // let html = {} 90 | // html 来存放最终拼接数据 91 | 92 | // task.on('ready', function (key, value) { 93 | // html[key] = value; 94 | // if (Object.keys(html).length == 2) { 95 | // console.log('html:', html) 96 | // } 97 | // }) 98 | 99 | // 将读取文件函数分开写, 无需等待读取完第一个文件再读取第二个, 两个读取文件函数同步执行 100 | // fs.readFile('./a.txt', 'utf8', function (err, dataA) { 101 | // task.emit('ready', 'dataA', dataA) 102 | // }) 103 | // fs.readFile('./b.txt', 'utf8', function (err, dataB) { 104 | // task.emit('ready', 'dataB', dataB) 105 | // }) 106 | 107 | // 2. 通过哨兵函数来处理 108 | // let html1 = {} 109 | // function done(key, value) { 110 | // html1[key] = value; 111 | // if (Object.keys(html1).length == 2) { 112 | // console.log(html1) 113 | // } 114 | // } 115 | // fs.readFile('./d.txt', 'utf8', function (err, dataC) { 116 | // done('dataC', dataC) 117 | // }) 118 | // fs.readFile('./b.txt', 'utf8', function (err, dataB) { 119 | // done('dataB', dataB) 120 | // }) 121 | 122 | // 但是上面的写法不通用 123 | 124 | // 我们可以将函数内的数据抽象化, 125 | // 比如 done 里面的 2, 可能我们要读取 3 个文件, ... n 个文件, 故不能写死 126 | // 可以写个工厂函数 127 | 128 | function render(length, cb) { 129 | let html2 = {}; 130 | return function (key, value) { 131 | html2[key] = value; 132 | if (Object.keys(html2).length == length) { 133 | cb(html2) 134 | } 135 | } 136 | } 137 | let done = render(2, function (html) { 138 | console.log(html) 139 | }) 140 | fs.readFile('./c.txt', 'utf8', function (err, dataC) { 141 | done('dataC', dataC) 142 | }) 143 | fs.readFile('./b.txt', 'utf8', function (err, dataB) { 144 | done('dataB', dataB) 145 | }) 146 | -------------------------------------------------------------------------------- /04-Async/03-Generator.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2019/3/1 5 | **/ 6 | 7 | // 生成器 8 | // 生成器是一个函数, 可以用来生成迭代器 9 | // 生成器和普通函数不同, 10 | // 普通函数一旦调用必须执行完, 11 | // 生成器函数中间可以暂停, 执行一会儿停一下再执行 12 | 13 | // 生成器特点: 函数名前加 * 14 | 15 | function* go(a) { 16 | console.log(1) 17 | let b = yield a 18 | console.log(2) 19 | let c = yield b 20 | console.log(3) 21 | return c 22 | } 23 | 24 | let it = go('a') 25 | console.log(it) 26 | 27 | // 生成器函数不会立即执行, 而是返回一个迭代器 28 | // 迭代器是一个对象, 每调用一次 next 就会返回一个对象 29 | // next 第一次被调用不需要参数, 传参数也没意义 30 | // 对象有两个属性, 一个是 value(yield后面的值), 一个是 done(表示是否迭代完成) 31 | 32 | let r1 = it.next() 33 | console.log(r1) 34 | //{ value: 'a', done: false } 35 | 36 | let r2 = it.next('B') 37 | console.log(r2) 38 | //{ value: 'B', done: false } 39 | 40 | let r3 = it.next('C') 41 | console.log(r3) 42 | //{ value: 'C', done: true } 43 | -------------------------------------------------------------------------------- /04-Async/04-Promise.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2018-10-25 5 | **/ 6 | 7 | // 1. 链式调用 8 | // const promise = doSomething() 9 | // const promise2 = promise.then(successCallback, failureCallback) 10 | // 或者 11 | // const promise2 = doSomething().then(successCallback, failreCallback) 12 | 13 | // 在过去 14 | // doSomething(function(result) { 15 | // doSomethingElse(result, function(newResult) { 16 | // doThirdThing(newResult, function(finalResult) { 17 | // console.log('Got the final result: ' + finalResult); 18 | // }, failureCallback); 19 | // }, failureCallback); 20 | // }, failureCallback); 21 | 22 | // 通过新式函数, then里的参数是可选的 23 | // doSomething().then(function(result) { 24 | // return doSomethingElse(result); 25 | // }) 26 | // .then(function(newResult) { 27 | // return doThirdThing(newResult); 28 | // }) 29 | // .then(function(finalResult) { 30 | // console.log('Got the final result: ' + finalResult); 31 | // }) 32 | // .catch(failureCallback); 33 | 34 | 35 | // 2. Catch 的后续操作 36 | // new Promise((resolve, reject) => { 37 | // console.log('Initial') 38 | // resolve() 39 | // }) 40 | // .then(() => { 41 | // throw new Error('Something failed') 42 | 43 | // console.log('Do this') 44 | // }) 45 | // .catch(() => { 46 | // console.log('Do that') 47 | // }) 48 | // .then(() => { 49 | // console.log('Do this whatever happened before') 50 | // }) 51 | 52 | 53 | // 3. 创建一个 Promise 54 | // const myFirstPromise = new Promise((resolve, reject) => { 55 | // 做一些异步操作,最终会调用下面两者之一 56 | // resolve(someValue) 57 | // 或 58 | // reject("failure reason") 59 | // }) 60 | 61 | // 让某个函数有 Promise 功能,只需返回一个 Promise 即可 62 | // 比如封装一个 get 请求 63 | // function myAsyncFunc(url) { 64 | // return new Promise((resolve, reject) => { 65 | // const xhr = new XMLHttpRequest() 66 | // xhr.open("GET", url) 67 | // xhr.onload = () => resolve(xhr.responseText) 68 | // xhr.onerror = () => reject(xhr.statusText) 69 | // xhr.send() 70 | // }) 71 | // } 72 | 73 | 74 | // 4. 解决回调地狱 75 | // let fs = require('fs') 76 | // fs.readFile('./a.txt','utf8',function(err,data){ 77 | // fs.readFile(data,'utf8',function(err,data){ 78 | // fs.readFile(data,'utf8',function(err,data){ 79 | // console.log(data) 80 | // }) 81 | // }) 82 | // }) 83 | 84 | // 使用 Promise 后 85 | // let fs = require('fs') 86 | // function readFile(url) { 87 | // return new Promise((resolve, reject) => { 88 | // fs.readFile(url, 'utf-8', function(error, data) { 89 | // error && reject(error) 90 | // resolve(data) 91 | // }) 92 | // }) 93 | // } 94 | 95 | // readFile('./a.txt').then(data => { 96 | // return readFile(data) 97 | // }).then(data => { 98 | // return readFile(data) 99 | // }).then(data => { 100 | // console.log(data) 101 | // }) -------------------------------------------------------------------------------- /04-Async/05-Async+Await.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2018-10-26 5 | **/ 6 | 7 | // 1. 与其他异步操作的对比 8 | 9 | // 先定义一个 Fetch 方法 10 | // var fetch = require('node-fetch') 11 | 12 | // function fetchUser() { 13 | // return new Promise((resolve, reject) => { 14 | // fetch('https://api.github.com/users/stephentian') 15 | // .then((res) => { 16 | // resolve(res) 17 | // }, (error) => { 18 | // reject(error) 19 | // }) 20 | // }) 21 | // } 22 | 23 | // Promise 方式 24 | // function getUserByPromise() { 25 | // fetchUser() 26 | // .then((data) => { 27 | // console.log(data) 28 | // }, (error) => { 29 | // console.log(error) 30 | // }) 31 | // } 32 | // getUserByPromise() 33 | 34 | // Promise 的方式虽然解决了 callback hell 35 | // 但是这种方式充满了 Promise的 then() 方法,如果处理流程复杂的话,整段代码将充满 then 36 | 37 | // Generator 方式 38 | // function* fetchUserByGenerator() { 39 | // const user = yield fetchUser() 40 | // return user 41 | // } 42 | // const g = fetchUserByGenerator() 43 | // const result = g.next().value 44 | // result.then((v) => { 45 | // console.log(v) 46 | // }, (error) => { 47 | // console.log(error) 48 | // }) 49 | 50 | // Async 方式 51 | // async function getUserBuAsync() { 52 | // let user = await fetchUser() 53 | // return user 54 | // } 55 | // getUserBuAsync() 56 | // .then(v => console.log(v)) 57 | 58 | // 2. async 返回一个 Promise 对象 59 | async function f() { 60 | return "Hello World" 61 | } 62 | f().then(v => console.log(v)) 63 | // 如果 async 函数内部抛出异常,则会导致返回的 Promise 对象状态变为 reject 状态 64 | // 抛出的错误而会被 catch 方法回调函数接收到 65 | async function e() { 66 | throw new Error('error!') 67 | } 68 | e().then(v => console.log(v)) 69 | .catch(err => console.log(err)) -------------------------------------------------------------------------------- /04-Async/06-CreatePromise.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2019/3/1 5 | **/ 6 | 7 | // 手写一个 Promise 8 | // 首先分析 Promise 内部结构 9 | 10 | // 1. 标准 Promise 11 | 12 | // 1.1 promise 主体函数 13 | 14 | // 1.1.1 定义状态 15 | // 等待 16 | // 成功 17 | // 失败 18 | 19 | // const PENDING = 'pending' 20 | // const FULFILLED = 'fulfilled' 21 | // const REJECTED = 'rejected' 22 | 23 | // function MyPromise(executor) { 24 | // let self = this // 先缓存当前 promise 实例, 避免异步执行时, this 获取不对 25 | // self.value = null // promise 的值 26 | // self.status = PENDING // 设置初始状态 27 | // self.onResolvedCallbacks = [] // 存放成功的回调函数数组 28 | // self.onRejectedCallbacks = [] // 存放失败的回调函数数组 29 | 30 | // // 1.1.2 resolve 方法 31 | // // 只有 promise 状态为 pending, 可以转成成功状态 32 | // // 并将成功的返回值赋值给 value 33 | // // 如果已经是成功状态, 则什么都不做 34 | // function resolve(value) { 35 | // if (value != null && value.then && typeof value.then == 'function') { 36 | // return value.then(resolve, reject) 37 | // } 38 | 39 | // // 思考: 为什么要用 setTimeout 包起来? 40 | // setTimeout(function () { 41 | // if (self.status == PENDING) { 42 | // self.status = FULFILLED 43 | // self.value = value 44 | // // 调用所有成功回调函数 45 | // self.onResolvedCallbacks.forEach(cb => cb(self.value)) 46 | // } 47 | // }) 48 | // } 49 | 50 | // // 1.1.3 reject 方法 51 | // // 如果是 pending, 转成失败状态 52 | // // 并将失败原因赋值给 value 53 | // function reject(reason) { 54 | // setTimeout(function () { 55 | // if (self.status == PENDING) { 56 | // self.status = REJECTED 57 | // self.value = reason 58 | // self.onRejectedCallbacks.forEach(cb => cb(self.value)) 59 | // } 60 | // }) 61 | // } 62 | 63 | // // 1.1.4 函数可能出现异常 64 | // // 需要捕获, 如果出错了需要使用 reject 65 | // try { 66 | // executor(resolve, reject) 67 | // } catch (e) { 68 | // reject(e) 69 | // } 70 | // } 71 | 72 | // 1.2 resolvePromise 函数 73 | 74 | // resolvePromise 针对 resolve 不同值的情况进行处理 75 | 76 | // 1.2.1 函数包含参数 77 | // promise2: promise1.then 返回的新的 promise 对象 78 | // x: promise1 中 onFulfilled 的返回值 79 | // resolve: promise2 的 resolve 方法 80 | // reject: promise2 的 reject 方法 81 | 82 | function resolvePromise(promise2, x, resolve, reject) { 83 | // 如果 onFulfilled 返回的的 x 就是 promise2, 就导致循环引用出错 84 | if (promise2 === x) { 85 | return reject(new TypeError("循环使用")) 86 | } 87 | 88 | // 避免多次调用 89 | let called = false 90 | // 如果 x 是一个 promise 对象 91 | if (x instanceof MyPromise) { 92 | // 如果为等待状态需等待直至 x 被执行或拒绝, 并解析 y 值 93 | if (x.status == PENDING) { 94 | x.then( 95 | function (y) { 96 | resolvePromise(promise2, y, resolve, reject) 97 | }, 98 | reason => { 99 | reject(reason) 100 | } 101 | ) 102 | } else { 103 | // 如果 x 处于 执行状态/拒绝状态(值已经被解析为普通值), 用相同的值执行传递下去 104 | x.then(resolve, reject) 105 | } 106 | // 如果 x 为对象 或者 函数 107 | } else if (x != null && (typeof x == "objec" || typeof x == "function")) { 108 | // 是否为 theneable 对象(具有 then 方法的对象/函数) 109 | try { 110 | let then = x.then 111 | if (typeof then === "function") { 112 | then.call( 113 | (x, y) => { 114 | if (called) return 115 | called = true 116 | resolvePromise(newPromise, y, resolve, reject) 117 | }, 118 | reason => { 119 | if (called) return 120 | called = true 121 | reject(reason) 122 | } 123 | ) 124 | } else { 125 | // 否说明为普通对象/函数, 直接 resolve 126 | resolve(x) 127 | } 128 | } catch (e) { 129 | if (called) return 130 | called = true 131 | reject(e) 132 | } 133 | } else { 134 | // 如果 x 是一个普通的值, 则用 x 的值去 resolve promise2 135 | resolve(x) 136 | } 137 | } 138 | 139 | // 1.3 then 方法 140 | 141 | // onFulfilled 用来接收 promise 成功或者失败的返回值 142 | // 如果成功和失败的回调没有传, 则表示 then 没有逻辑, 会把值往后抛 143 | 144 | MyPromise.prototype.then = function (onFulfilled, onRejected) { 145 | // 判断 then 接收的参数是否为 function, 是则忽略 146 | onFulfilled = 147 | typeof onFulfilled == "function" 148 | ? onFulfilled 149 | : function (value) { 150 | return value 151 | } 152 | onRejected = 153 | typeof onRejected == "function" 154 | ? onRejected 155 | : function (reason) { 156 | throw reason 157 | } 158 | 159 | let self = this 160 | let promise2 161 | 162 | // 根据三种情况处理 163 | // if (self.status == PENDING) { 164 | // self.onResolvedCallbacks.push(onFulfilled) 165 | // } 166 | // if (self.status === FULFILLED) { 167 | // onFulfilled(self.value) 168 | // } 169 | // if (self.status === REJECTED) { 170 | // onRejected(self.value) 171 | // } 172 | 173 | // 如果当前 promise 已经是成功状态, 直接将值 传递给 onFulfilled 174 | // 因为 then 有链式调用, 所以返回一个新的 promise 对象 175 | // 状态为 FULFILLED, 即为成功状态时, 直接调用 onFulfilled 176 | // 考虑到有可能 throw, 将代码包在 try catch 里 177 | if (self.status == FULFILLED) { 178 | return (promise2 = new MyPromise(function (resolve, reject) { 179 | setTimeout(function () { 180 | try { 181 | let x = onFulfilled(self.value) 182 | // 如果获取了返回值 x, 则走解析 promise 的过程 183 | resolvePromise(promise2, x, resolve, reject) 184 | } catch (e) { 185 | // 如果执行成功的回调过程中出错了, 用错误原因把 promise2 rejecte 186 | reject(e) 187 | } 188 | }) 189 | })) 190 | } 191 | 192 | if (self.status == REJECTED) { 193 | return (promise2 = new MyPromise(function (resolve, reject) { 194 | setTimeout(function () { 195 | try { 196 | let x = onRejected(self.value) 197 | resolvePromise(promise2, x, resolve, reject) 198 | } catch (e) { 199 | reject(e) 200 | } 201 | }) 202 | })) 203 | } 204 | 205 | if (self.status == PENDING) { 206 | return (promise2 = new MyPromise(function (resolve, reject) { 207 | self.onResolvedCallbacks.push(function () { 208 | try { 209 | let x = onFulfilled(self.value) 210 | resolvePromise(promise2, x, resolve, reject) 211 | } catch (e) { 212 | reject(e) 213 | } 214 | }) 215 | self.onRejectedCallbacks.push(function () { 216 | try { 217 | let x = onRejected(self.value) 218 | resolvePromise(promise2, x, resolve, reject) 219 | } catch (e) { 220 | reject(e) 221 | } 222 | }) 223 | })) 224 | } 225 | } 226 | 227 | // 1.4 catch 228 | // catch 的原理就是只传失败的回调 229 | MyPromise.prototype.catch = function (onRejected) { 230 | this.then(null, onRejected) 231 | } 232 | 233 | MyPromise.deferred = MyPromise.defer = function () { 234 | let defer = [] 235 | defer.promise = new MyPromise(function (resolve, reject) { 236 | defer.resolve = resolve 237 | defer.reject = reject 238 | }) 239 | return defer 240 | } 241 | 242 | // 2. 实现一个同步的 Promise 243 | 244 | const PENDING = "pending" 245 | const FULFILLED = "fulfilled" 246 | const REJECTED = "rejected" 247 | function SyncPromise(executor) { 248 | let self = this 249 | self.status = PENDING 250 | self.value = undefined 251 | self.reason = undefined 252 | 253 | function resolve(value) { 254 | if (self.status === PENDING) { 255 | self.status = FULFILLED 256 | self.value = value 257 | } 258 | } 259 | 260 | function reject(reason) { 261 | if (self.status === PENDING) { 262 | self.status = REJECTED 263 | self.reason = reason 264 | } 265 | } 266 | 267 | try { 268 | executor(resolve, reject) 269 | } catch (e) { 270 | reject(e) 271 | } 272 | } 273 | 274 | SyncPromise.prototype.then = function (onFulfilled, onRejected) { 275 | let self = this 276 | if (self.status === FULFILLED) { 277 | onFulfilled(self.value) 278 | } 279 | if (self.status === REJECTED) { 280 | onRejected(self.reason) 281 | } 282 | } 283 | 284 | // 测试同步 Promise 285 | new SyncPromise((resolve, reject) => { 286 | resolve("1") 287 | }).then( 288 | data => { 289 | console.log(data) 290 | }, 291 | reason => { 292 | console.log(reason) 293 | } 294 | ) 295 | 296 | new SyncPromise((resolve, reject) => { 297 | reject("请求失败") 298 | }).then( 299 | data => { 300 | console.log(data) 301 | }, 302 | reason => { 303 | console.log(reason) 304 | } 305 | ) 306 | 307 | // resolve 和 reject 都能准确输出 308 | 309 | // 发现存在的问题 310 | 311 | // 1. 异步不支持 312 | new SyncPromise((resolve, reject) => { 313 | setTimeout(() => { 314 | resolve(2) 315 | }) 316 | }).then( 317 | data => { 318 | console.log(data) 319 | }, 320 | reason => { 321 | console.log(reason) 322 | } 323 | ) 324 | // 同步的话, 会先执行 .then() 325 | // 而 status 还是 PENDING, 无法进入 FULFILLED 326 | 327 | // 2. then 没有容错 328 | new SyncPromise((resolve, reject) => { 329 | resolve(1) 330 | }).then() 331 | 332 | // 比如没有给 then 传递参数, 程序就会报错 333 | 334 | // 3. 实现异步 Promise 335 | 336 | const APENDING = "pending" 337 | const AFULFILLED = "fulfilled" 338 | const AREJECTED = "rejected" 339 | function AsyncPromise() { 340 | let self = this 341 | self.status = APENDING 342 | self.value = undefined 343 | self.reason = undefined 344 | self.onFulfillCallbacks = [] 345 | self.onRejectedCallbacks = [] 346 | 347 | function resolve(value) { 348 | if (self.status === APENDING) { 349 | self.status = AFULFILLED 350 | self.value = value 351 | } 352 | } 353 | 354 | function reject(reason) { 355 | if (self.status === APENDING) { 356 | self.status = AREJECTED 357 | self.reason = reason 358 | } 359 | } 360 | 361 | try { 362 | fn(resolve, reject) 363 | } catch (e) { 364 | reject(e) 365 | } 366 | } 367 | 368 | function resolveAsyncPromise(newPromise, x, resolve, reject) { 369 | if (newPromise === x) { 370 | return reject(new TypeError("循环使用")) 371 | } 372 | 373 | let called = false 374 | if (x instanceof AsyncPromise) { 375 | if (x.status === APENDING) { 376 | x.then( 377 | y => { 378 | resolveAsyncPromise(newPromise, y, resolve, reject) 379 | }, 380 | reason => { 381 | reject(reason) 382 | } 383 | ) 384 | } else { 385 | x.then(resolve, reject) 386 | } 387 | } else if (x != null && (typeof x === "object" || typeof x === "function")) { 388 | try { 389 | let then = x.then 390 | if (typeof then === "function") { 391 | then.call( 392 | (x, y) => { 393 | if (called) return 394 | called = true 395 | resolveAsyncPromise(promise, y, resolve, reject) 396 | }, 397 | reason => { 398 | if (called) return 399 | called = true 400 | reject(reason) 401 | } 402 | ) 403 | } else { 404 | resolve(x) 405 | } 406 | } catch (e) { 407 | if (called) return 408 | called = true 409 | reject(e) 410 | } 411 | } else { 412 | resolve(x) 413 | } 414 | } 415 | 416 | AsyncPromise.prototype.then = function (onFulfilled, onRejected) { 417 | let self = this 418 | let newPromise 419 | onFulfilled = 420 | typeof onFulfilled === "function" 421 | ? onFulfilled 422 | : function (value) { 423 | return value 424 | } 425 | onRejected = 426 | typeof onRejected === "function" 427 | ? onRejected 428 | : function (reason) { 429 | throw reason 430 | } 431 | 432 | if (self.status === APENDING) { 433 | return (newPromise = new AsyncPromise((resolve, reject) => { 434 | self.onFulfillCallbacks.push(value => { 435 | try { 436 | let x = onFulfilled(value) 437 | resolvePromise(newPromise, x, resolve, reject) 438 | } catch (e) { 439 | reject(e) 440 | } 441 | }) 442 | 443 | self.onRejectedCallbacks.push(reason => { 444 | try { 445 | let x = onRejected(reason) 446 | resolveAsyncPromise(newPromise, x, resolve, reject) 447 | } catch (e) { 448 | reject(e) 449 | } 450 | }) 451 | })) 452 | } 453 | } 454 | -------------------------------------------------------------------------------- /04-Async/README.md: -------------------------------------------------------------------------------- 1 | # Promise 及 Async + Await 学习 2 | 3 | ## 目录 4 | 5 | - **[异步发展流程](#异步发展流程)** 6 | - **[Promise](#promise)** 7 | - **[Async + Await](#async--await)** 8 | - **[异步函数的比较](#异步函数的比较)** 9 | - **[避免太过循序](#避免太过循序)** 10 | 11 | ## 异步发展流程 12 | 13 | ### 1. 同步与异步 14 | 15 | 同步:有一个任务分成两步执行,执行完第一步再执行第二步,连续的执行就叫同步 16 | 异步:有一个任务分成两步执行,执行第一步,在等待第一步完成这段时间执行其他任务,再执行第二步,不连续的执行叫异步 17 | 18 | ### 2. 将异步转换为同步的方式(发展流程) 19 | 20 | - 回调函数实现 21 | - 事件监听 22 | - 发布订阅 23 | - Promise/A+ 和生成器函数 24 | - async/await 25 | 26 | 27 | ## Promise 28 | 29 | ### 1. 链式调用 30 | 31 | 在过去 32 | 33 | ``` 34 | doSomething(function(result) { 35 | doSomethingElse(result, function(newResult) { 36 | doThirdThing(newResult, function(finalResult) { 37 | console.log('Got the final result: ' + finalResult); 38 | }, failureCallback); 39 | }, failureCallback); 40 | }, failureCallback); 41 | ``` 42 | 43 | 通过新式函数, then 里的参数是可选的 44 | 45 | ``` 46 | doSomething().then(function(result) { 47 | return doSomethingElse(result); 48 | }) 49 | .then(function(newResult) { 50 | return doThirdThing(newResult); 51 | }) 52 | .then(function(finalResult) { 53 | console.log('Got the final result: ' + finalResult); 54 | }) 55 | .catch(failureCallback); 56 | ``` 57 | 58 | ### 2. Catch 的后续操作 59 | 60 | ``` 61 | new Promise((resolve, reject) => { 62 | console.log('Initial') 63 | resolve() 64 | }) 65 | .then(() => { 66 | throw new Error('Something failed') 67 | 68 | console.log('Do this') 69 | }) 70 | .catch(() => { 71 | console.log('Do that') 72 | }) 73 | .then(() => { 74 | console.log('Do this whatever happened before') 75 | }) 76 | ``` 77 | 78 | ### 3. 创建一个 Promise 79 | 80 | ``` 81 | const myFirstPromise = new Promise((resolve, reject) => { 82 | 做一些异步操作,最终会调用下面两者之一 83 | resolve(someValue) 84 | 或 85 | reject("failure reason") 86 | }) 87 | ``` 88 | 89 | 让某个函数有 Promise 功能,只需返回一个 Promise 即可 90 | 比如封装一个 get 请求 91 | 92 | ``` 93 | function myAsyncFunc(url) { 94 | return new Promise((resolve, reject) => { 95 | const xhr = new XMLHttpRequest() 96 | xhr.open("GET", url) 97 | xhr.onload = () => resolve(xhr.responseText) 98 | xhr.onerror = () => reject(xhr.statusText) 99 | xhr.send() 100 | }) 101 | } 102 | ``` 103 | 104 | ### 4. 解决回调地狱 105 | 106 | 使用 Promise 前 107 | 108 | ``` 109 | let fs = require('fs') 110 | fs.readFile('./a.txt','utf8',function(err,data){ 111 | fs.readFile(data,'utf8',function(err,data){ 112 | fs.readFile(data,'utf8',function(err,data){ 113 | console.log(data) 114 | }) 115 | }) 116 | }) 117 | ``` 118 | 119 | 使用 Promise 后 120 | 121 | ``` 122 | let fs = require('fs') 123 | function readFile(url) { 124 | return new Promise((resolve, reject) => { 125 | fs.readFile(url, 'utf-8', function(error, data) { 126 | error && reject(error) 127 | resolve(data) 128 | }) 129 | }) 130 | } 131 | 132 | readFile('./a.txt').then(data => { 133 | return readFile(data) 134 | }).then(data => { 135 | return readFile(data) 136 | }).then(data => { 137 | console.log(data) 138 | }) 139 | ``` 140 | 141 | ## Async + Await 142 | 143 | ### 1. 与其他异步操作的对比 144 | 145 | 先定义一个 Fetch 方法 146 | 147 | ``` 148 | var fetch = require('node-fetch') 149 | 150 | function fetchUser() { 151 | return new Promise((resolve, reject) => { 152 | fetch('https://api.github.com/users/stephentian') 153 | .then((res) => { 154 | resolve(res) 155 | }, (error) => { 156 | reject(error) 157 | }) 158 | }) 159 | } 160 | ``` 161 | 162 | (1) Promise 方式, 使用上面封装好的函数 163 | 164 | ``` 165 | function getUserByPromise() { 166 | fetchUser() 167 | .then((data) => { 168 | console.log(data) 169 | }, (error) => { 170 | console.log(error) 171 | }) 172 | } 173 | getUserByPromise() 174 | ``` 175 | 176 | Promise 的方式虽然解决了 callback hell 177 | 但是这种方式充满了 Promise 的 then() 方法,如果处理流程复杂的话,整段代码将充满 then 178 | 179 | (2) Generator 方式 180 | 181 | ``` 182 | function* fetchUserByGenerator() { 183 | const user = yield fetchUser() 184 | return user 185 | } 186 | const g = fetchUserByGenerator() 187 | const result = g.next().value 188 | result.then((v) => { 189 | console.log(v) 190 | }, (error) => { 191 | console.log(error) 192 | }) 193 | ``` 194 | 195 | (3) Async 方式 196 | 197 | ``` 198 | async function getUserBuAsync() { 199 | let user = await fetchUser() 200 | return user 201 | } 202 | getUserBuAsync() 203 | .then(v => console.log(v)) 204 | ``` 205 | 206 | ### 2. async 返回一个 Promise 对象 207 | 208 | ``` 209 | async function f() { 210 | return "Hello World" 211 | } 212 | f().then(v => console.log(v)) 213 | ``` 214 | 215 | 如果 async 函数内部抛出异常,则会导致返回的 Promise 对象状态变为 reject 状态 216 | 抛出的错误而会被 catch 方法回调函数接收到 217 | 218 | ``` 219 | async function e() { 220 | throw new Error('error!') 221 | } 222 | e().then(v => console.log(v)) 223 | .catch(err => console.log(err)) 224 | ``` 225 | 226 | ### 3. Async 其他函数的用法 227 | 228 | (1) 箭头函数 229 | 230 | ``` 231 | // map some URLs to json-promises 232 | const jsonPromises = urls.map(async url => { 233 | const response = await fetch(url); 234 | return response.json(); 235 | }); 236 | ``` 237 | 238 | > array.map(func) 不在乎我提供给它的是不是异步函数,只把它当作一个返回 Promise 的函数来看待 239 | > 它不会等到第一个函数执行完毕就会调用第二个函数 240 | 241 | (2) 对象方法 242 | 243 | ``` 244 | const storage = { 245 | async getAvatar(name) { 246 | const cache = await caches.open('avatars') 247 | return cache.match(`/avatars/${name}.jpg`) 248 | } 249 | } 250 | storage.getAvatar('abcsss').then() 251 | ``` 252 | 253 | (3) 类方法 254 | 255 | ``` 256 | class Storage { 257 | constructor() { 258 | this.cachePromise = caches.open('avatars') 259 | } 260 | 261 | async getAvatar(name) { 262 | const cache = awat this.cachePromise 263 | return cache.match(`/avatars/${name}.jpg`) 264 | } 265 | } 266 | 267 | const storage = new Storage() 268 | storage.getAvatar('aabbb').then() 269 | ``` 270 | 271 | > 类构造函数以及 getter/settings 方法不能是异步的 272 | 273 | ## 异步函数的比较 274 | 275 | 假设我们想获取某个网址并以文本形式记录响应日志 276 | 277 | **Promise 编写代码** 278 | 279 | ``` 280 | function logFetch(url) { 281 | return fetch(url) 282 | .then(response => response.text()) 283 | .then(text => { 284 | console.log(text); 285 | }).catch(err => { 286 | console.error('fetch failed', err); 287 | }); 288 | } 289 | ``` 290 | 291 | **利用异步函数 Async + Await 具有相同作用的代码** 292 | 293 | ``` 294 | async function logFetch(url) { 295 | try { 296 | const response = await fetch(url); 297 | console.log(await response.text()); 298 | } 299 | catch (err) { 300 | console.log('fetch failed', err); 301 | } 302 | } 303 | ``` 304 | 305 | > 代码行数虽然相同,但去掉了所有回调 306 | > 这可以提高代码的可读性,对不太熟悉 Promise 的人而言,帮助就更大了 307 | 308 | ## 避免太过循序 309 | 310 | 尽管编写的是看似同步的代码,也一定不要错失并行执行的机会 311 | 312 | ``` 313 | async function series() { 314 | await wait(500); 315 | await wait(500); 316 | return "done!"; 317 | } 318 | ``` 319 | 320 | 以上代码执行完毕需要 1000 毫秒 321 | 322 | ``` 323 | asyc function parallel() { 324 | const wait1 = wait(500) 325 | const wait2 = wait(500) 326 | await wait1 327 | await wait2 328 | return 'done!' 329 | } 330 | ``` 331 | 332 | 以上代码只需 500 毫秒就可执行完毕,因为两个 wait 是同时发生的 333 | 334 | **按顺序输出获取的数据** 335 | 假定我们想获取一系列网址,并尽快按正确顺序将它们记录到日志中 336 | 337 | ``` 338 | function logInOrder(urls) { 339 | // fetch all the URLs 340 | const textPromises = urls.map(url => { 341 | return fetch(url).then(response => response.text()); 342 | }); 343 | 344 | // log them in order 345 | textPromises.reduce((chain, textPromise) => { 346 | return chain.then(() => textPromise) 347 | .then(text => console.log(text)); 348 | }, Promise.resolve()); 349 | } 350 | ``` 351 | 352 | 如果使用异步函数改写以上代码,又容易让代码变得过于循序 353 | 354 | ``` 355 | async function logInOrder(urls) { 356 | for (const url of urls) { 357 | const response = await fetch(url); 358 | console.log(await response.text()); 359 | } 360 | } 361 | ``` 362 | 363 | **推荐的编码方式** - 可读性强、并行效率高 364 | 365 | ``` 366 | async function logInOrder(urls) { 367 | // fetch all the URLs in parallel 368 | const textPromises = urls.map(async url => { 369 | const response = await fetch(url); 370 | return response.text(); 371 | }); 372 | 373 | // log them in sequence 374 | for (const textPromise of textPromises) { 375 | console.log(await textPromise); 376 | } 377 | } 378 | ``` 379 | 380 | 以并行方式获取和读取网址,但将“智能”的 reduce 部分替换成标准单调乏味但可读性强的 for 循环 381 | -------------------------------------------------------------------------------- /04-Async/a.txt: -------------------------------------------------------------------------------- 1 | a 2 | -------------------------------------------------------------------------------- /04-Async/b.txt: -------------------------------------------------------------------------------- 1 | bb 2 | -------------------------------------------------------------------------------- /04-Async/c.txt: -------------------------------------------------------------------------------- 1 | ccc 2 | -------------------------------------------------------------------------------- /04-Async/test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2019/3/2 5 | **/ 6 | 7 | // MyPromise 测试用例 8 | 9 | let MyPromise = require('./06-CreatPromise') 10 | let p1 = new MyPromise(function (resolve, reject) { 11 | setTimeout(function () { 12 | let num = Math.random() 13 | if (num < .5) { 14 | resolve(num) 15 | } else { 16 | reject('失败, 小于0.5') 17 | } 18 | }) 19 | }) 20 | p1.then(function (data) { 21 | console.log(data) 22 | }, function (err) { 23 | console.log(err) 24 | }) 25 | -------------------------------------------------------------------------------- /05-Regexp/01-Grammar.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2018-11-2 5 | **/ 6 | 7 | var regex1 = /.a/; 8 | var string1 = 'ba'; 9 | var string2 = ' a'; 10 | var string3 = '@abc'; 11 | var string4 = ` 12 | abc 13 | xyz 14 | `; 15 | 16 | console.log(regex1.test(string1)); 17 | // true 18 | console.log(regex1.test(string2)); 19 | // true 20 | console.log(regex1.test(string3)); 21 | // true 22 | 23 | console.log(regex1.test(string4)); 24 | // false 无法匹配换行符 -------------------------------------------------------------------------------- /05-Regexp/README.md: -------------------------------------------------------------------------------- 1 | # Regex 正则表达式 2 | 3 | > 多看,多认,多写 4 | 5 | ## 目录 6 | 7 | - **[前提知识](#前提知识)** 8 | - **[语法 Grammar](#语法-grammar)** 9 | - **[方法 Function](#方法-function)** 10 | - **[常见用例](#常见用例)** 11 | 12 | ## 前提知识 13 | 14 | #### 1. **创建正则** 15 | - 字面量方式 16 | ``` 17 | var regex = /***/ 18 | ``` 19 | - 实例方式 20 | ``` 21 | var reg = new Regep('/***/') 22 | ``` 23 | #### 2. **贪婪模式 Greedy** 24 | 默认模式,正则表达式一般趋向于最大长度匹配(往长了走) 25 | 26 | #### 3. **非贪婪模式** 27 | 非贪婪模式, 懒惰匹配, 匹配长度更短的字符(往短了走) 28 | 29 | #### 4. **lastIndex** 30 | 整数, 下一个匹配字符位置, 从 0 开始算 31 | 32 | ## 语法 Grammar 33 | 34 | ### 基本元字符 35 | 36 | 1. `.`: 匹配任何单个字符(除了换行符) 37 | 2. `\`: 将特殊字符转为字面量 38 | 3. `|`: "或" 操作符 39 | 4. `[]`: 字符集合,匹配集合中的一个字符 40 | 5. `[^]`: 对集合里, "非" 操作符 41 | 6. `-`: 定义一个区间,比如 `[a-d]` 表示去 a-d 之间(a, b, c, d) 42 | 43 | ### 数量元字符 44 | 45 | 1. `{n}`: 匹配 n 次 46 | 2. `{m, n}`: 匹配字符至少 m 次, 至多 n 次 47 | 3. `+`: 匹配字符一次或多次 48 | 4. `*`: 匹配字符 0 次或多次 49 | 5. `?`: 匹配字符 0 次或 1 次 50 | 51 | > 在这些量词后加 `?` 会变成 **非贪婪模式** 52 | 53 | ### 位置元字符 54 | 55 | 1. `^`: 匹配表达式的开始 56 | 2. `$`: 匹配表达式的结束 57 | 3. `\b`: 匹配单词边界 58 | 4. `\B`: 匹配非单词边界 59 | 5. `(?=p)`: 匹配 p 前面的位置 60 | 6. `(?!p)`: 匹配不在 p 的位置 61 | 62 | ### 特殊元字符 63 | 64 | 1. `\d`: 数字, 同 `[0-9]` 65 | 2. `\D`: 非数字, 同 `[^0-9]` 66 | 3. `\s`: 空白符, 包括空格, 水平制表符(`\t`), 垂直制表符(`\v`), 换行符(`\n`), 回车符(`\r`), 换页符(`\f`) 67 | 4. `\S`: 非空白符 68 | 5. `\w`: 数字、字母、下划线, 同 [0-9a-zA-Z] 69 | 6. `\W`: 非数字字母字符 70 | 7. `\n`: 换行符 71 | 72 | ### 标志字符 73 | 74 | 1. `g`: global, 全局搜索 75 | 2. `i`: ignore, 不区分大小写 76 | 3. `m`: multiline, 多行搜索 77 | 78 | ## 方法 Function 79 | 80 | ### 正则对象方法 81 | 82 | #### 1. `test` 83 | 匹配返回 true, 不匹配则返回 false 84 | ``` 85 | var regex = /a/ 86 | var string = 'aaa' 87 | regex.test(string) // return true 88 | ``` 89 | 90 | #### 2. `exec` 91 | 没有匹配是返回 null, 匹配时返回 92 | ``` 93 | [0: (整个模式匹配的最长的字符串), 94 | 1: (第二长的字符串), 95 | ...(没有则为 null), 96 | index: 0, 97 | input: ''(要匹配的字符串), 98 | length: 1(前面匹配到的个数)] 99 | ``` 100 | 101 | ### string 的方法 102 | 103 | #### 1. `match` 104 | 105 | 返回值和接受参数(正则)是否包含 `g` 有关 106 | - 如果没有 `g` 标志, match 对 string 做一次匹配 107 | ``` 108 | var regex = /[a-d]/i 109 | var string = 'aBcDef' 110 | string.match(regex) 111 | // ouput 112 | [ 113 | 0: "a" 114 | groups: undefined 115 | index: 0 116 | input: "aBcDef" 117 | ] 118 | ``` 119 | - 如果有, 会返回一个数组 120 | ``` 121 | var regexg = /[a-d]/gi 122 | string.match(regexg) 123 | // output 124 | ['a', 'B', 'c', 'D'] 125 | ``` 126 | 127 | #### 2. `search` 128 | 129 | - 接受一个正则作为参数 130 | - 如果参数不为正则, 会隐式的使用 new Regexp() 将其转化为正则 131 | - 匹配到返回 0, 没匹配到返回 -1 132 | 133 | #### 3. `replace` 134 | 135 | - 接受两个参数, 第一个为被替换文本, 第二个为要替换的文本 136 | - 第一个参数可以是正则, 也可以是字符串 137 | - 第二个参数可以是字符串, 也可以是函数 138 | 139 | | 变量名 | 作用 | 140 | | ------ | ------ | 141 | | $$ | 插入一个 $ | 142 | | $& | 插入匹配的子串 | 143 | | $` | 插入当前匹配的子串左边的内容 | 144 | | $' | 插入当前匹配的子串右边的内容 | 145 | | $n | 假如第一个参数是 RegExp对象,并且 n 是个小于100的非负整数,那么插入第 n 个括号匹配的字符串 | 146 | 147 | ``` 148 | var re = /(\w+)\s(\w+)/ 149 | var str = "John Smith" 150 | var newstr = str.replace(re, "$2, $1") 151 | console.log(newstr) 152 | // Smith, John 153 | 154 | var string = '2018-11-12' 155 | var today = string.replace(/-/g, '/') 156 | console.log(today) 157 | // '2018/11/12' 158 | 159 | string.replace(/-/g, function(result, key, index, group) { 160 | // 输出 三个参数 161 | // 匹配的正则 162 | // 捕获到的字符 163 | // 正则匹配到的每段字符的第一个字符的索引 164 | // 用于匹配的字符串 165 | }) 166 | ``` 167 | 168 | #### 4. `split` 169 | 170 | - 接受两个参数, 返回一个数组 171 | - 第一个是用来分割字符串的字符或者正则 172 | - 第二个参数可选作为限制分割多少个字符,也是返回的数组的长度限制 173 | 174 | ``` 175 | var string = '123a4Bc56Def' 176 | console.log(string.split(/[a-z]+/i)) 177 | // output 178 | ["123", "4", "56", ""] 179 | 180 | console.log(string.split(/[a-z]+/i, 2)) 181 | // output 182 | ["123", "4"] 183 | ``` 184 | 185 | ## 常见用例 186 | 187 | 1. 写一个 function , 将 'get-element-by-id', 转换为驼峰命名法形式的字符串 'getElementById' 188 | ``` 189 | var f = function(str) { 190 | return str.replace(/-\w/g, function(v) { 191 | return v.slice(1).toUpperCase() 192 | }) 193 | } 194 | ``` 195 | 196 | 2. -------------------------------------------------------------------------------- /06-Date/01-ParseTime.js: -------------------------------------------------------------------------------- 1 | function parseTime(time, cFormat) { 2 | if (arguments.length === 0) { 3 | return null; 4 | } 5 | 6 | if ((time + "").length === 10) { 7 | time = +time * 1000; 8 | } 9 | 10 | const format = cFormat || "{y}-{m}-{d} {h}:{i}:{s}"; 11 | let date; 12 | if (typeof time === "object") { 13 | date = time; 14 | } else { 15 | date = new Date(parseInt(time)); 16 | } 17 | const formatObj = { 18 | y: date.getFullYear(), 19 | m: date.getMonth() + 1, 20 | d: date.getDate(), 21 | h: date.getHours(), 22 | i: date.getMinutes(), 23 | s: date.getSeconds(), 24 | a: date.getDay() 25 | }; 26 | const timeStr = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { 27 | console.log(result) 28 | console.log(key) 29 | let value = formatObj[key]; 30 | console.log('value:', value) 31 | if (key === "a") 32 | return ["周一", "周二", "周三", "周四", "周五", "周六", "周日"][value - 1]; 33 | if (result.length > 0 && value < 10) { 34 | value = "0" + value; 35 | } 36 | return value || 0; 37 | }); 38 | return timeStr; 39 | } 40 | 41 | // console.log(parseTime('2018-11-13T07:36:20.370Z', '{y}-{m}-{d} {h}:{i}')) 42 | // console.log(parseTime('1542094580370', '{y}-{m}-{d} {h}:{i}:{s}')) 43 | console.log(parseTime('1542094580370', '{y}-{m}-{d} {h}:{i}:{s} {a}')) -------------------------------------------------------------------------------- /06-Date/README.md: -------------------------------------------------------------------------------- 1 | # Date 对象 2 | -------------------------------------------------------------------------------- /07-OOP/01-Create Object.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2018/11/16 5 | **/ 6 | 7 | // 1. 创建对象 8 | var arr = [1, 2, 3, 4, 5] 9 | 10 | // 可以在 arr 里看到 __proto__ 11 | console.log(arr) 12 | 13 | var str = 'StepenTian' 14 | 15 | console.log(str.__proto__) 16 | // 虽然在 str 看不到 __proto__,但它其实有这个隐式属性,指向 String.prototype -------------------------------------------------------------------------------- /07-OOP/02-Inheritance.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2018/11/21 5 | **/ 6 | 7 | // 1. New 8 | function Animals(name) { 9 | this.name = name 10 | } 11 | var animal1 = new Animals('animal1') 12 | console.log('animal1:', animal1) 13 | 14 | // New 相当于如下 15 | var animal2 = {} 16 | animal2.__proto__ = Animals.prototype 17 | Animals.call(animal2, 'animal2') 18 | console.log('animal2:', animal2) 19 | 20 | 21 | // 2. 原型继承 22 | function Student(name) { 23 | this.name = name || 'Unnamed' 24 | } 25 | 26 | // 简单把功能及参数继承过来 27 | function PrimaryStudent(name, grade) { 28 | // 将 Student 里的变量继承过来,即把 this 作用域转移过来 29 | Student.call(this, name) 30 | this.grade = grade || 1 31 | } 32 | 33 | // 使用构造函数创建对象 34 | var student1 = new Student('aaa') 35 | var student2 = new PrimaryStudent('bbb') 36 | console.log(Student.prototype.isPrototypeOf(PrimaryStudent.prototype)) 37 | // false, 说明不是继承 38 | 39 | // 当前的原型链是: 40 | // student1 --> Student.prototype --> Object.prototype --> null 41 | // student2 --> PrimaryStudent.prototype --> Object.prototype --> null 42 | 43 | // 如果是 继承, 原型链应该为: 44 | // student2 --> PrimaryStudent.prototype --> Student.prototype --> Object.prototype --> null 45 | 46 | // 有这种方式: 47 | function Student(name) { 48 | this.name = name || 'Unnamed' 49 | } 50 | 51 | // 利用 call 把 Student 中通过 this 指定的属性和方法复制到子类的实例中 52 | function PrimaryStudent(name, grade) { 53 | Student.call(this, name) 54 | this.grade = grade || 1 55 | } 56 | PrimaryStudent.prototype = new Student() 57 | PrimaryStudent.prototype.constructor = PrimaryStudent 58 | 59 | // 验证 60 | var student3 = new PrimaryStudent('ccc', 3) 61 | console.log(student3) 62 | console.log(student3.__proto__ === PrimaryStudent.prototype) 63 | console.log(student3.__proto__.__proto__ === Student.prototype) 64 | console.log(Student.prototype.isPrototypeOf(PrimaryStudent.prototype)) 65 | -------------------------------------------------------------------------------- /07-OOP/03-Class.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2018-11-27 5 | **/ 6 | 7 | // Class 继承 8 | 9 | // 普通构造函数 10 | function Student(name) { 11 | this.name = name || 'Unnamed' 12 | } 13 | 14 | Student.prototype.sayName = function () { 15 | console.log('name: ', this.name) 16 | } 17 | 18 | // Class 来实现 19 | // 20 | class Student { 21 | constructor(name) { 22 | this.name = name 23 | } 24 | sayName() { 25 | console.log('name: ', this.name) 26 | } 27 | } 28 | 29 | // class 继承 30 | class PrimaryStudent extends Student { 31 | constructor(name, grade) { 32 | // 需要用 super 来调用父类的构造函数 33 | super(name) 34 | this.grade = grade 35 | } 36 | myGrade() { 37 | console.log('My grade is: ', this.grade) 38 | } 39 | } -------------------------------------------------------------------------------- /07-OOP/README.md: -------------------------------------------------------------------------------- 1 | # OOP 面向对象编程 2 | > JavaScript在设计之初就是一种面向对象的语言,虽然我们在日常的开发中很少能体验到这一点。 3 | 4 | ## 目录 5 | 6 | **[1. 创建对象](#1-创建对象)** 7 | * [原始对象](#原始对象) 8 | * [初级封装](#初级封装) 9 | * [构造函数](#构造函数) 10 | * [实例和实例化](#实例和实例化) 11 | * [原型 prototype](#原型-prototype) 12 | * [new](#new) 13 | 14 | **[2. 一些概念](#2-一些概念)** 15 | * [instanceof](#instanceof) 16 | * [constructor](#constructor) 17 | * [instanceof 和 constructor](#instanceof-和-constructor) 18 | * [isPrototypeOf](#isprototypeof) 19 | * [hasOwnProperty](#hasownproperty) 20 | * [prototype、\__proto__ 和 constructor-的区别](#prototype-__proto__-和-constructor-的区别) 21 | 22 | **[3. 封装]()** 23 | 24 | **[4. 继承]()** 25 | 26 | **[5. 多态]()** 27 | 28 | ## 前言 29 | 30 | JavaScript是一款基于 `原型`模式的面向对象语言。 31 | 32 | 我们可以把`原型(prototype)`理解为是一个**使用说明书**,每一个类型都会有一个对应的使用说明书,凡是使用说明上规定的成员(参数方法),都是可以使用的。(记住这个概念,后面必考) 33 | 34 | 比如电脑的说明书上规定了开机、关机等行为,配备了GTX1060显卡、I9处理器等参数,那么带有这个说明书的每一台电视机都会具备这些功能及配置。 35 | 36 | 记住面向对象拥有三大特征: 37 | 1. 封装 38 | 2. 继承 39 | 3. 多态 40 | 41 | 后面我们会讲。 42 | 43 | 44 | ## 1. 创建对象 45 | 46 | ### 原始对象 47 | 48 | 可以创建一个 Object 对象 49 | 50 | 51 | ``` 52 | var obj = { 53 | name: 'StepenTian', 54 | age: 18, 55 | getSex: function() { 56 | return 'male' 57 | } 58 | } 59 | console.log(obj.getSex()) 60 | 61 | // 返回 male 62 | ``` 63 | 64 | 我们可以为该实例手动添加任何成员,可以是字符、数字、布尔甚至于一个方法。 65 | 定义的方式,即可以用`实例名.成员名 = 内容`,也可以使用`实例名["成员名"] = 内容`。 66 | 67 | ``` 68 | var str = "phone" 69 | obj[str] = 15900000000 70 | 71 | console.log(obj['phone']) 72 | console.log(obj['name']) 73 | ``` 74 | 75 | 创建一个 Array 76 | 77 | ``` 78 | var arr = [1, 2, 3, 4, 5] 79 | console.log(arr) 80 | ``` 81 | 82 | 在 Chrome 控制台打印出 83 | 展开数组发现, 数组里怎么还有个 `__proto__:Array` ?(但其实 `arr.__proto__` 不是 Array 对象) 84 | 展开 `__proto__`, 可以发现一系列数组(`pop, push, shift, unshift`)的方法, 原来操作 `arr` 的方法是从这来的 85 | 86 | 87 | ### 初级封装 88 | 89 | 为了能够得到更好的封装效果, 我们通过一个 function 来统一地构建一个对象的实例 90 | 91 | ``` 92 | function Person(name, age) { 93 | return { 94 | name: name, 95 | age: age, 96 | getSex: function() { 97 | return 'male' 98 | } 99 | } 100 | } 101 | 102 | ``` 103 | 104 | ### 构造函数 105 | 106 | 上面的 Person 并不是真正的构造函数,JavaScript中提供了真正的构造函数,它的语法和定义一个 function 其实是一样的: 107 | 108 | ``` 109 | function Person(name, age) { 110 | this.name = name, 111 | this.age = age, 112 | this.sayName = function() { 113 | return this.name 114 | } 115 | } 116 | ``` 117 | [不懂 this,你可以先去看:this]() 118 | 119 | Person 内部通过 this 关键字给实例增加了新的属性和方法。 120 | 121 | ### 实例和实例化实例化 122 | 123 | 使用 new 来创造 Person 的一个对象,这个对象叫 ‘实例’,创造的过程,我们叫实例化 124 | 125 | 可以想象成,我们就是造物主,在创造一个一个人,这些人有名字,有年龄,会说话 126 | 我们可以创造两个例子 亚当和夏娃 127 | 128 | ``` 129 | var adam = new Person('adam', 22) // 设定一个法定结婚年龄好 130 | var eva = new Person('eva', 20) 131 | console.log(adam.sayName == eva.sayName) 132 | // false 133 | ``` 134 | 我们还可以发现 adam 和 eva 的 sayName 并不指向不一个内存, 135 | 也就意味着,当我们有很多实例的时候,内存开销会非常大。 136 | 137 | 造两个人好累啊,要教他们各种技能,生火,做饭,缝衣服...... 138 | 生小孩这种技能不好教,得想个办法,所以 139 | 为什么不给他们发个说明书呢?(还记得说明书的概念吗?) 140 | 141 | ### 原型 prototype 142 | 143 | 任何类型都会有属于自己的原型,并且原型上定义的成员,可以在每个实例中引用,并且是共用的。 144 | 145 | 可以理解为,每个人都有一本属于自己的使用手册,在人类这本使用手册上说明的属性和技能,每个人都可以有。 146 | 147 | 所以我们在 prototype 上定义方法时,发现不同实例运用的方法是相同的 148 | 149 | ``` 150 | function Person(name, age) { 151 | this.name = name, 152 | this.age = age 153 | } 154 | 155 | Person.prototype.sayName = function() { 156 | return this.name 157 | } 158 | 159 | var adam = new Person('adam', 22) 160 | var eva = new Person('eva', 20) 161 | console.log(adam.sayName == eva.sayName) 162 | // true 163 | ``` 164 | 165 | 由此在设计JavaScript面向对象类型的时候,我们一般遵循以下规则: 166 | 167 | 1. 因为实例不同而不同的内容,用this关键字声明 168 | 2. 无论实例怎样内容完全相同的成员,定义在prototype上 169 | 170 | 171 | ### new 172 | 173 | 1. 定义一个函数 Animal 174 | 175 | ``` 176 | function Animals(name) { 177 | this.name = name 178 | } 179 | ``` 180 | 181 | 2. 仔细看, 发现其实就是个首字母大写的普通函数; 可以使用 `new` 让它变成构造函数 182 | 183 | ``` 184 | var animal1 = new Animals('animal1') 185 | console.log('animal1:', animal1) 186 | ``` 187 | 188 | 3. 打印出 `animal1`, 发现 `name` 变为了 `animal1` 189 | 190 | 4. 让我们换一种方式实现, 先定义一个 `animal2` 空对象, 它是由 Animals 构造的, 所以 `__proto__` 应该为构造函数的原型 191 | 192 | ``` 193 | var animal2 = {} 194 | animal2.__proto__ = Animals.prototype 195 | Animals.call(animal2, 'animal2') 196 | 197 | console.log('animal2:', animal2) 198 | ``` 199 | 200 | 会发现,两次打印出的内容是一样的(除了 name 属性) 201 | 202 | 203 | ## 2. 一些概念 204 | 205 | ### instanceof 206 | 207 | 使用 构造函数 创建一个 新的对象, 该对象被认为是构造函数的一个 实例(即 `instance`), 208 | `instanceof`就是来判断 该对象 是否由 某个构造函数 创建 209 | 210 | ### constructor 211 | 212 | 1. 用于返回创建该对象的函数 213 | 214 | ``` 215 | var object = [] 216 | console.log(object.constructor) // Array 217 | ``` 218 | 219 | 2. 在**一个**类中只能有一个名为 “constructor” 的特殊方法, 用于创建和初始化类中创建的一个对象的一种特殊方法 220 | 221 | ``` 222 | class Polgon { 223 | constructor() { 224 | this.name = 'Polygon' 225 | } 226 | } 227 | ``` 228 | 229 | ### instanceof 和 constructor 230 | 231 | 232 | 233 | ### isPrototypeOf 234 | 235 | ### hasOwnProperty 236 | 237 | ### prototype、\__proto__ 和 constructor 的区别 238 | -------------------------------------------------------------------------------- /08-This/README.md: -------------------------------------------------------------------------------- 1 | # This 2 | 3 | ## 目录 4 | - [What is this ?](#what-is-this-) 5 | - [全局环境](#全局环境) 6 | - [函数内部](#函数内部) 7 | * [非严格模式](#非严格模式) 8 | * [严格模式](#严格模式) 9 | - [call 和 apply](#call-和-apply) 10 | - [原生 js 实现 call 和 apply](#原生-js-实现-call-和-apply) 11 | * [Call](#call) 12 | * [Apply](#apply) 13 | - [bind](#bind) 14 | - [原生 js 实现 bind](#原生-js-实现-bind) 15 | - [箭头函数中的 this](#箭头函数中的-this) 16 | - [原型链中的 this](#原型链中的-this) 17 | - [DOM 中的 this](#dom-中的-this) 18 | - [This 原理](#this-原理) 19 | 20 | 21 | 22 | ## What is this ? 23 | 24 | 在中文中,人们常常用“这个、这个”来指代一些东西,但是如果不加 “手势、眼神”等肢体语言的话,是无法理解的。 25 | 在代码中,`this` 也一样,有全局环境和函数内,在严格模式和非严格模式,都有不同的作用; 26 | 在一般情况下,**`this` 只受最靠近的执行环境影响** 27 | 28 | ## 全局环境 29 | 30 | 全局环境,即全局执行上下文 31 | 32 | ``` 33 | // 在浏览器中 34 | console.log(this === window) // true 35 | 36 | // 在 Node 中 37 | console.log(this === module.exports) // true 38 | ``` 39 | 40 | --- 41 | 42 | ## 函数内部 43 | 44 | ### 非严格模式 45 | 46 | this 默认指向全局对象 window 47 | ``` 48 | function f1() { 49 | return this 50 | } 51 | 52 | // 在浏览器中 53 | console.log(f1() === window) // true 54 | 55 | // 在 Node 中 56 | console.log(f1() === global) // true 57 | ``` 58 | 59 | ### 严格模式 60 | 61 | this 默认为 `undefined` 62 | 63 | --- 64 | 65 | ## call 和 apply 66 | 67 | 某个函数通过使用继承自 `Function.prototype` 的 `call` 或 `apply` 方法, 68 | 指定一个特定的的 `this` 和传入若干参数,然后调用 69 | 70 | 1. 将 `this` 的指从一个上下文传到另一个 71 | 72 | ``` 73 | var obj = { 74 | a: 'Custom' 75 | } 76 | 77 | var a = 'Global' 78 | 79 | function whatsThis(arg) { 80 | console.log(this.a) 81 | } 82 | 83 | whatsThis() 84 | // 'Global' 85 | 86 | whatsThis.call(obj) 87 | whatsThis.apply(obj) 88 | // 'Custom' 89 | ``` 90 | 91 | 2. 在绑定到调用的对象时,还可以传入参数(提现 `call` 和 `apply` 的区别) 92 | 93 | ``` 94 | function add(c, d) { 95 | console.log(this.a + this.b + c + d) 96 | } 97 | 98 | var o = { 99 | a: 1, 100 | b: 3 101 | } 102 | 103 | add.call(o, 5, 7) 104 | // 16 105 | 106 | add.apply(o, [0, 1]) 107 | // 5 108 | ``` 109 | 110 | --- 111 | 112 | ## 原生 js 实现 call 和 apply 113 | 114 | 由上节内容可知 `call` 和 `apply` 的作用: 115 | - 改变 `this` 指向 116 | - 传入参数(可不传) 117 | - 执行函数 118 | 119 | #### Call 120 | 121 | **1.** 想看一般怎么改变 `this` 指向: 122 | 123 | ``` 124 | var a = 'global a' 125 | var obj1 = { 126 | a: 'obj1 a' 127 | } 128 | var obj2 = { 129 | a: 'obj2 a' 130 | } 131 | 132 | function f () { 133 | console.log(this.a) 134 | } 135 | 136 | // 直接运行, 打印 'global a' 137 | f() 138 | ``` 139 | 140 | **2.** 如果 `obj1` 和 `obj2` 里有 `f` 就好了, 就可以直接调用, 就可以打印里面的 `a` 了. 141 | 可惜没有,我们自己造一个吧: 142 | 143 | ``` 144 | obj1.f = f 145 | obj1.f() 146 | // 'obj1 a' 147 | ``` 148 | 149 | **3.** 成功!但是有个缺点,obj1 里多了个 `f` 函数,改变了对象,所以我们要把它删除 150 | 151 | ``` 152 | obj1.f = f 153 | obj1.f() 154 | delete obj1.f 155 | ``` 156 | 157 | **4.** 第一步成功,我们可以实现一个 1.0 版本的 `call`, 并且用 `this` 来指代调用的函数 158 | 159 | ``` 160 | Function.prototype.myCall = function(obj) { 161 | obj.f = this 162 | obj.f() 163 | delete obj.f 164 | } 165 | ``` 166 | 167 | **5.** 传入参数, 第一个为传入对象, 我们要让 `ob.f` 执行 `obj.f(argument[1], arguments[2], ...)` 168 | 169 | ``` 170 | // 新定义一个可接受参数的函数 171 | function fn(b, c) { 172 | console.log('参数1: ', b) 173 | console.log('参数2:', c) 174 | console.log('this.a: ', this.a) 175 | } 176 | 177 | Function.prototype.myCall = function(obj) { 178 | obj.f = this 179 | var args = [] 180 | for (var i = 1; i < arguments.length; i++) { 181 | args.push('arguments[' + i + ']') 182 | } 183 | 184 | console.log(args) 185 | // [arguments[1], arguments[2]] 186 | 187 | obj.f(args) 188 | // 情形一:直接传入是数组 189 | // '参数1: ["arguments[1]", "arguments[2]"]' 190 | // '参数2: undefined' 191 | // 失败! 192 | 193 | obj.f(args.toString()) 194 | // 情形二:传入字符串 195 | // 打印出 '参数1: arguments[1],arguments[2]' 196 | // '参数2: undefined' 197 | // 失败! 198 | 199 | obj.f(arg[0], args[1]) 200 | // 情形三:传入参数两个,但是都是字符串 201 | // 参数1: arguments[1] 202 | // 参数2: arguments[2] 203 | // 失败! 204 | 205 | obj.f(arguments[1], arguments[2]) 206 | // 情形四:传参成功,但是参数不确定的话,这种方式就不能用 207 | // 参数1: 1 208 | // 参数2: abc 209 | // 成功! 210 | 211 | obj.f(eval(args[0]), eval(args[1])) 212 | // 情形五:改进 '情形三',使用 `eval` 函数,传参成功,但是参数不确定的话,这种方式也不能用 213 | // 参数1: 1 214 | // 参数2: abc 215 | // 成功! 216 | 217 | delete obj.f 218 | } 219 | 220 | fn.myCall(obj, 1, 'abc') 221 | ``` 222 | 223 | 224 | **6.** 由上面 **情形三** 可以知道, 我们已经把 传入的参数 传递给函数了,但是传递的是字符串; 225 | 我们可以结合 **情形五** 来改进 226 | 227 | - 使用 `eval` 228 | 229 | ``` 230 | Function.prototype.myCall = function(obj) { 231 | var args = [] 232 | for (var i = 1; i < arguments.length; i++) { 233 | args.push('arguments[' + i + ']') 234 | } 235 | obj.f = this 236 | eval('obj.f(' + args + ')') 237 | delete obj.f 238 | } 239 | 240 | ``` 241 | 242 | - 使用 ES6 中的解构语法 243 | 244 | ``` 245 | Function.prototype.myCall = function (obj, ...args) { 246 | obj.f = this 247 | obj.f(...args) 248 | delete obj.f 249 | } 250 | 251 | fn.myCall(obj, 1, 'abc') 252 | // 参数1: 1 253 | // 参数2: abc 254 | // 成功! 255 | ``` 256 | 257 | **7.** 根据上面的版本进行优化 258 | (1) 如果第一个参数为null(没有传参),this指向window 259 | (2) 函数调用要有返回值 260 | 261 | ``` 262 | Function.prototype.myCall1 = function(obj) { 263 | var args = [], 264 | argsLength = arguments.length, 265 | obj = obj || window, 266 | result; 267 | 268 | obj.f = this 269 | 270 | for (var i = 1; i < argsLength; i++) { 271 | args.push('arguments[' + i + ']') 272 | } 273 | result = eval('obj.f(' + args + ')') 274 | 275 | delete obj.f 276 | return result 277 | } 278 | 279 | var a = 'global a' 280 | var obj = { 281 | a: 'obj a' 282 | } 283 | 284 | function fn(b, c) { 285 | return this.a + ',' + b + ',' + c 286 | } 287 | 288 | console.log(fn.myCall1(obj, 2, 'EFG')) 289 | // 'obj a, 2, EFG' 290 | // 成功! 291 | ``` 292 | 293 | **ES 6** 294 | ``` 295 | Function.prototype.myCall2 = function (obj) { 296 | var obj = obj || window 297 | obj.f = this 298 | 299 | var args = [...arguments].sclice(1) 300 | var result = obj.f(...args) 301 | 302 | delete obj.f 303 | return result 304 | } 305 | ``` 306 | 307 | #### Apply 308 | 309 | `apply` 的实现方式和 `call` 类似,可以先思考下 `apply` 和 `call` 的区别: 310 | `call` 方法接受的是若干个参数的列表,而 `apply` 方法接受的是一个包含多个参数的数组。 311 | 所以要判断接收的参数是否为数组,还有循环数组要从 `0` 开始 312 | 313 | ``` 314 | Function.prototype.myApply = function (obj, arr) { 315 | var obj = obj || window, 316 | result, 317 | argsLength = arguments.length; 318 | obj.f = this 319 | 320 | if (!arr) { 321 | result = obj.f() 322 | } else { 323 | var args = [] 324 | for (var i = 1; i < argsLength; i++) { 325 | args.push('arguments[' + i + ']') 326 | } 327 | result = eval('obj.f(' + args + ')') 328 | } 329 | 330 | delete obj.f 331 | return result 332 | } 333 | ``` 334 | 335 | **ES 6** 336 | ``` 337 | Function.prototype.myApply2 = function (obj) { 338 | var obj = obj || window 339 | obj.f = this 340 | 341 | var reslut 342 | if (argument[1]) { 343 | result = obj.f(...argument[1]) 344 | } else { 345 | result = obj.f() 346 | } 347 | 348 | delete obj.f 349 | return result 350 | } 351 | ``` 352 | 353 | --- 354 | 355 | ## bind 356 | 357 | ES5 引入了 `Function.prototype.bind`。 358 | `f.bind(someObject)` 会创建一个与 `f`具有相同函数体和作用域的函数。 359 | 在新函数中, `this` 被 **永久** 绑定到 `bind` 的第一个参数,无论函数如何被调用。 360 | 361 | ``` 362 | function f() { 363 | return this.a 364 | } 365 | var obj1 = { 366 | a: 'obj1 的 a' 367 | } 368 | var obj2 = { 369 | a: 'obj2 的 a' 370 | } 371 | 372 | var firstBind = f.bind(obj1) 373 | console.log(firstBind) 374 | // 'obj1 的 a' 375 | // 创建了 firstBind 函数,和 f 具有相同函数体和作用域 376 | 377 | var secondBind = firstBind.bind(obj2) 378 | console.log(secondBind) 379 | // 新函数中, this 被永久绑定到 bind 的第一个参数了 380 | ``` 381 | 382 | --- 383 | 384 | ## 原生 js 实现 bind 385 | 386 | ``` 387 | Function.prototype.myBind = function() { 388 | if(typeof this !== 'function') { 389 | throw new Error('非函数不能调用 Function.prototype.bind') 390 | } else { 391 | var self = this, 392 | args = Array.prototype.slice.call(arguments, 1) 393 | F = function() {}; 394 | var fBound = function () { 395 | var bindArgs = Array.prototype.slice.call(arguments); 396 | self.apply(this instanceof self ? this : context, args.concat(bindArgs)); 397 | } 398 | F.prototype = this.prototype 399 | fBound.prototype = new F() 400 | return fBound 401 | } 402 | } 403 | ``` 404 | --- 405 | 406 | ## 箭头函数中的 this 407 | 408 | 在箭头函数中,`this` 与封闭词法上下文的 `this` 保持一致。 409 | 1. 箭头函数在全局代码中,`this` 将被设置为全局对象 410 | 411 | ``` 412 | var a = 'global' 413 | var foo = (() => this.a) 414 | console.log(foo()) 415 | // global 416 | ``` 417 | 418 | **注意** 将this传递给call、bind、或者apply,它将被忽略 419 | 420 | ``` 421 | var a = 'global a' 422 | var foo = (() => this.a) 423 | var obj = { 424 | a: 'obj a', 425 | f: foo 426 | } 427 | console.log('obj.f(): ', obj.f()) 428 | console.log('foo.call(obj): ', foo.call(obj)) 429 | console.log('foo.apply(obj): ', foo.apply(obj)) 430 | // 都为 'global a' 431 | ``` 432 | 433 | 2. 对象里的 箭头函数 this 将始终指向对象, 434 | 但是若不执行情况下赋值给另一个函数, `this` 将指向另一个函数的执行环境 435 | 436 | ``` 437 | var a = 'global a' 438 | var obj = { 439 | a: 'obj a', 440 | f: function() { 441 | var x = (() => this.a) 442 | return x 443 | } 444 | } 445 | 446 | var fn1 = obj.f() 447 | console.log('fn1: ', fn1()) 448 | // 'obj a' 449 | 450 | var fn2 = obj.f 451 | console.log('fn2: ', fn2()()) 452 | // 'global a' 453 | ``` 454 | 455 | --- 456 | 457 | ## 原型链中的 this 458 | 459 | 类似于在 原型链 中定义方法一样,`this` 指向调用该方法的对象 460 | 461 | ``` 462 | var obj = { 463 | a: 1, 464 | b: 2, 465 | f: function() { 466 | return this.a + this.b 467 | } 468 | } 469 | 470 | var objChild = Object.create(obj) 471 | objChild.a = 1 472 | objChild.b = 4 473 | 474 | console.log(obj.f()) 475 | // 3 476 | console.log(objChild.f()) 477 | // 5 478 | // objChild 中没有 `f` 属性,于是在原型链中查找,在 `obj` 中找到 `f`, 479 | // 但是 480 | // 最先查找是以 `objChild.f` 开始查找,所以 `this` 指向 `objChild` 481 | ``` 482 | 483 | --- 484 | 485 | ## DOM 中的 this 486 | 487 | 当函数被用作事件处理函数时,它的 `this` 指向触发事件的元素 488 | 489 | ``` 490 | function bluify(e) { 491 | // console.log(this === e.currentTarget) 492 | // true 493 | 494 | if (this.style.backgroundColor == 'blue') { 495 | this.style.backgroundColor = 'red' 496 | } else { 497 | this.style.backgroundColor = 'blue' 498 | } 499 | } 500 | 501 | // 获取所有 li 元素 502 | var elements = document.getElementsByTagName('li') 503 | 504 | // 当被点击时,元素变蓝色 505 | for (var i = 0; i < elements.length; i++) { 506 | elements[i].addEventListener('click', bluify, false) 507 | } 508 | ``` 509 | 510 | --- 511 | 512 | ## This 的原理 513 | 514 | 想思考个问题: 515 | 为什么 `obj.foo()` 就是在 `obj` 环境执行,而 `var foo = obj.foo`,`foo()` 就变成了全局环境执行? 516 | 517 | 这跟内存里的数据结构有关系 518 | -------------------------------------------------------------------------------- /08-This/apply.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2019-3-8 5 | **/ 6 | 7 | // apply 8 | 9 | Function.prototype.myApply = function (context) { 10 | context = context || window 11 | context.fn = this 12 | let result 13 | if (arguments[1]) { 14 | result = context.fn(...arguments[1]) 15 | } else { 16 | result = context.fn() 17 | } 18 | delete context.fn 19 | return result 20 | } 21 | 22 | // ES5 23 | Function.prototype.myApply2 = function (obj, arr) { 24 | obj = obj || window 25 | let result 26 | let argsLength = arguments.length 27 | obj.fn = this 28 | if (!arr) { 29 | result = obj.fn() 30 | } else { 31 | let args = [] 32 | for (let i = 0; i < argsLength; i++) { 33 | args.push('arr[' + i + ']') 34 | } 35 | result = eval('obj.fn(' + args + ')') 36 | } 37 | delete obj.fn 38 | return result 39 | } 40 | -------------------------------------------------------------------------------- /08-This/bind.js: -------------------------------------------------------------------------------- 1 | Function.prototype.myBind0 = function (context) { 2 | const self = this, 3 | args = [...arguments].slice(1) 4 | return function F() { 5 | if (this instanceof F) { 6 | return new self(...args, ...arguments) 7 | } 8 | return self.apply(context, args.concat(...arguments)) 9 | } 10 | } 11 | 12 | Function.prototype.myBind1 = function (obj, ...arg1) { //arg1收集剩余参数 13 | return (...arg2) => { //返回箭头函数, this绑定调用这个方法(myFind)的函数对象 14 | return this.apply(obj, arg1.concat(arg2)); // 将参数合并 15 | } 16 | } 17 | 18 | Function.prototype.myBind2 = function (obj, ...arg1) { 19 | return (...arg2) => { 20 | let args = arg1.concat(arg2); 21 | let val; 22 | obj._fn_ = this; 23 | val = obj._fn_(...args); 24 | delete obj._fn_; 25 | return val 26 | } 27 | } 28 | 29 | Function.prototype.myBind3 = function (obj) { 30 | let _this = this; 31 | let argArr = []; 32 | let arg1 = []; 33 | for (let i = 1; i < arguments.length; i++) { // 从1开始 34 | arg1.push(arguments[i]); // 这里用arg1数组收集下参数 35 | // 获取arguments是从1开始, 但arg1要从 0(i-1)开始 36 | // 若是用Array.prototype.slice.call(argument)就方便多了 37 | argArr.push('arg1[' + (i - 1) + ']'); // 如果用arguments在返回的函数里运行 会获取不到这个函数里的参数了 38 | } 39 | return function () { 40 | let val; 41 | for (let i = 0; i < arguments.length; i++) { // 从0开始 42 | argArr.push('arguments[' + i + ']'); 43 | } 44 | obj._fn_ = _this; 45 | val = eval('obj._fn_(' + argArr + ')'); 46 | delete obj._fn_; 47 | return val 48 | }; 49 | } 50 | 51 | Function.prototype.myFind4 = function (obj) { 52 | if (obj === null || obj === undefined) { 53 | obj = window; 54 | } else { 55 | obj = Object(obj); 56 | } 57 | let _this = this; 58 | let argArr = []; 59 | let arg1 = []; 60 | for (let i = 1; i < arguments.length; i++) { 61 | arg1.push(arguments[i]); 62 | argArr.push('arg1[' + (i - 1) + ']'); 63 | } 64 | return function () { 65 | let val; 66 | for (let i = 0; i < arguments.length; i++) { 67 | argArr.push('arguments[' + i + ']'); 68 | } 69 | obj._fn_ = _this; 70 | console.log(argArr); 71 | val = eval('obj._fn_(' + argArr + ')'); 72 | delete obj._fn_; 73 | return val 74 | }; 75 | } 76 | -------------------------------------------------------------------------------- /08-This/call.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2019-3-7 5 | **/ 6 | 7 | // call 8 | 9 | // ES6 10 | 11 | Function.prototype.myCall = function (context) { 12 | constext = context || window 13 | context.fn = this 14 | const args = [...arguments].slice(1) 15 | const result = context.fn(...args) 16 | delete context.fn 17 | return result 18 | } 19 | 20 | Function.prototype.myCall2 = function (obj, ...args) { 21 | obj.fn = this 22 | const result = obj.fn(...args) 23 | delete obj.fn 24 | return result 25 | } 26 | 27 | // ES5 28 | 29 | 30 | function myCall(context) { 31 | let context = context || window 32 | context.fn = this 33 | let args = [] 34 | for (let i = 1; i < arguments.length; i++) { 35 | args.push('arguments[' + i + ']') 36 | } 37 | let result = eval('context.fn(' + args + ')') 38 | delete context.fn 39 | return result 40 | } 41 | -------------------------------------------------------------------------------- /08-This/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | This 9 | 10 | 11 | 12 |
13 |

This --->

14 | 15 |
  • 16 |
  • 17 |
  • 18 |
  • 19 |
  • 20 |
    21 | 22 | 23 |
    24 | 208 | 209 | 210 | -------------------------------------------------------------------------------- /08-This/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2018-11-28 5 | **/ 6 | 7 | 8 | // 1. 函数内部 9 | 10 | // function f1() { 11 | // return this 12 | // } 13 | 14 | // // 浏览器环境 15 | // true 16 | // console.log('this === window:', this === window) 17 | 18 | // // node 环境 19 | // // true 20 | // console.log('f1() === global:', f1() === global) 21 | // // true 22 | // console.log(this === module.exports) 23 | 24 | 25 | 26 | // 2. 简单回调 27 | 28 | // 情形一 29 | var a = 'Window环境' 30 | 31 | function foo() { 32 | console.log(this.a) 33 | } 34 | 35 | var obj = { 36 | a: 'Obj环境', 37 | foo: foo 38 | } 39 | 40 | function doFoo() { 41 | obj.foo() 42 | } 43 | 44 | function doFoo2(fn) { 45 | fn() 46 | } 47 | 48 | doFoo() 49 | // obj 环境 50 | doFoo2(obj.foo) 51 | // win 环境 52 | 53 | // 情形二 54 | var a = 'Window环境' 55 | 56 | var obj = { 57 | a: 'Obj环境', 58 | foo: function () { 59 | console.log(this.a) 60 | } 61 | } 62 | 63 | function doFoo() { 64 | obj.foo() 65 | } 66 | 67 | function doFoo2(fn) { 68 | fn() 69 | } 70 | 71 | doFoo() 72 | doFoo2(obj.foo) 73 | 74 | // 两次的结果是一样的 75 | // 由此可以看出 this 指向函数 执行时的当前对象,而非声明环境 76 | // 这个方法还学到了: 如何让 函数里的 this, 指向全局 77 | 78 | 79 | // 3. call 和 apply 80 | // 在浏览器中 81 | 82 | var obj = { 83 | a: 'Custom' 84 | } 85 | 86 | var a = 'Global' 87 | 88 | function whatsThis(arg) { 89 | console.log(this.a) 90 | } 91 | 92 | whatsThis() 93 | // 'Global' 94 | 95 | whatsThis.call(obj) 96 | whatsThis.apply(obj) 97 | // 'Custom' 98 | 99 | // 3. node 环境中的 this 100 | 101 | // node 环境中把 var 去掉,将变量挂载到全局变量中 -------------------------------------------------------------------------------- /08-This/useStrict.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // console.log(window) 4 | 5 | console.log(this === exports) 6 | // true 7 | console.log(this === module.exports) 8 | // true 9 | 10 | function f1() { 11 | return this 12 | } 13 | 14 | console.log(f1() === global) 15 | // false 16 | 17 | // 严格模式下 18 | console.log(f1() === undefined) 19 | // true -------------------------------------------------------------------------------- /09-Skills/01-shuffle/README.md: -------------------------------------------------------------------------------- 1 | # shuffle 洗牌 2 | > 洗牌算法,将数组随机排序 3 | 4 | *待更新动画演示* 5 | 6 | ### 关于 sort 7 | 8 | JavaScript 开发中有时会遇到要将一个数组随机排序(shuffle)的需求,一般采用array.sort()方法,传入一个比较函数 9 | 10 | ``` 11 | var arr = [1, 2, 3, 4, 5] 12 | arr.sort(function() { 13 | return .5 - Math.random() 14 | }) 15 | 16 | // ES6 17 | arr.sort(() => .5 - Math.random()) 18 | 19 | ``` 20 | 21 | 这种写法看似都是随机,但其实是有问题的,**它不是真正的随机** 22 | 23 | ### 验证 sort 随机性 24 | 25 | 使用 表格及动画 26 | 27 | 见 self-training.html 28 | 29 | ### 解决方案 30 | 31 | #### 使用 Fisher–Yates shuffle 32 | 33 | Lodash 库中的 shuffle 算法, 使用的实际是 Fisher–Yates 洗牌算法 34 | 35 | ``` 36 | var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] 37 | 38 | function shuffle(arr) { 39 | var i = arr.length, 40 | t, 41 | j; 42 | while(i) { 43 | // 在剩下的元素随机选择一位 44 | j = Math.floor(Math.random() * i--); 45 | t = arr[i]; 46 | arr[i] = arr[j]; 47 | arr[j] = t; 48 | } 49 | return arr 50 | } 51 | 52 | // ES6 53 | function shuffle(arr) { 54 | let i = arr.length 55 | while(i) { 56 | let j = Math.floor(Math.random() * i--); 57 | [arr[j], arr[i]] = [arr[i], arr[j]] 58 | } 59 | return arr 60 | } 61 | 62 | shuffle(arr) 63 | ``` 64 | 65 | ### 洗牌算法思路 66 | 67 | 取自:[刘哇勇进阶方案](http://www.cnblogs.com/Wayou/p/fisher_yates_shuffle.html) 68 | 69 | #### 1. 随机取数 70 | 71 | 如果要给每张洗牌, 最随机的方法是,在牌组里随机抽一张,放进另一个牌组,直到所有牌被抽出; 72 | 在代码里,在数组里随机抽取一个数(数组下标),放入一个空数组中,直到原数组所有元素都取掉。 73 | 74 | ``` 75 | function shuffle(arr) { 76 | var copy = [], 77 | n = arr.length, 78 | i; 79 | while(n) { 80 | // 在数组里随机选取数组下标 81 | i = Math.floor(Math.random() * arr.length) 82 | if (i in arr) { 83 | copy.push(arr[i]) 84 | delete arr[i] 85 | n-- 86 | } 87 | } 88 | return copy 89 | } 90 | ``` 91 | 92 | **注意:** 93 | 1. Math.random()产生[0,1)的小数 94 | 2. delete 操作只将数组元素的值删除,但不影响数组长度,删除后原来位置的值变为undefined 95 | 96 | 即使一个序号上的元素已经被处理过了,由于随机函数产生的数是随机的,所有这个被处理过的元素序号可能在之后的循环中不断出现, 97 | 一是效率问题,另一个就是逻辑问题了,存在一种可能是永远运行不完! 98 | 99 | #### 2. 改进方案 100 | -------------------------------------------------------------------------------- /09-Skills/01-shuffle/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | js 数组随机排序 9 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 |
    结果位置 0位置 1位置 2位置 3位置 4位置 5位置 6位置 7位置 8位置 9
    A出现次数0000000000
    B出现次数0000000000
    C出现次数0000000000
    D出现次数0000000000
    E出现次数0000000000
    F出现次数0000000000
    G出现次数0000000000
    H出现次数0000000000
    I出现次数0000000000
    J出现次数0000000000
    192 |
    193 | 194 |
    195 | 196 | 336 | 337 | -------------------------------------------------------------------------------- /09-Skills/01-shuffle/self-training.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 36 | 37 | 38 | 39 |
    40 |
    41 |
    42 |
    43 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /09-Skills/01-shuffle/training.js: -------------------------------------------------------------------------------- 1 | class TopAnimate { 2 | constructor(config) { 3 | this.container = document.getElementById(config.el); // 组件容器 4 | this.topNum = config.options.topNum || 8; // 展示多少行数据 5 | this.labelWidth = config.options.labelWidth || 120; // 文字标签宽度 6 | this.spacing = config.options.spacing || 30; // 每行间距 7 | this.maxWidth = config.options.maxWidth || 500; // 最大宽度 8 | this.barHeight = config.options.barHeight || 30; // 数值条高度 9 | this.colors = config.options.colors || []; // 数值条颜色数组 10 | this.title = config.options.title; // 组件标题 11 | this.data; // 组件数据 12 | this.domArr = []; // 组件dom数组 13 | 14 | } 15 | _initData() { 16 | 17 | // 如果color颜色数组长度小于数据长度,则需要填补 18 | if (this.colors.length < this.data.length) { 19 | for (let i = this.colors.length; i < this.data.length; i++) { 20 | this.colors.push('#0057ff') 21 | } 22 | } 23 | 24 | const sort = this.data 25 | const maxValue = sort[0].value; 26 | 27 | // 生成DOM结构 28 | const wrap = this.container; 29 | wrap.style.position = 'relative'; 30 | wrap.style.width = '100%'; 31 | wrap.style.height = '100%'; 32 | 33 | // 生成title 34 | const title = document.createElement('h3'); 35 | title.innerText = this.title; 36 | title.style.position = 'absolute'; 37 | title.style.top = '0'; 38 | title.style.margin = '0'; 39 | wrap.appendChild(title); 40 | // const baseLine = document.createElement('div'); 41 | // baseLine.className = 'base-line'; 42 | // baseLine.style.position = 'absolute'; 43 | // baseLine.style.width = '4px'; 44 | // baseLine.style.height = '100%'; 45 | // baseLine.style.backgroundColor = '#333'; 46 | // baseLine.style.left = this.labelWidth + 'px'; 47 | // baseLine.style.top = '0'; 48 | // wrap.appendChild(baseLine); 49 | 50 | // 循环生成DOM 51 | const flag = document.createDocumentFragment(); 52 | sort.slice(0, this.topNum).forEach((item, index) => { 53 | // 每行容器 54 | const rowEl = document.createElement('div'); 55 | rowEl.className = 'row'; 56 | rowEl.style.position = 'absolute'; 57 | rowEl.style.top = `${index * (this.barHeight + this.spacing) + 40 }px`; 58 | rowEl.style.transition = 'top 1s'; 59 | 60 | // 标签DOM 61 | const label = document.createElement('span'); 62 | label.className = 'item-name'; 63 | label.innerText = item.name; 64 | label.style.width = this.labelWidth + 4 + 'px'; 65 | label.style.flexGrow = 0; 66 | rowEl.appendChild(label); 67 | 68 | // 数值DOM 69 | const valueEl = document.createElement('span'); 70 | valueEl.className = "item-val"; 71 | valueEl.innerText = item.value; 72 | valueEl.style.position = 'absolute'; 73 | valueEl.style.display = 'inline'; 74 | valueEl.style.padding = '0 10px'; 75 | 76 | // 数值条DOM 77 | const bar = document.createElement('div'); 78 | bar.className = 'item-bar'; 79 | bar.style.height = this.barHeight + 'px'; 80 | bar.style.lineHeight = this.barHeight + 'px'; 81 | bar.style.width = `${item.value / maxValue * this.maxWidth}px`; 82 | bar.style.position = 'relative'; 83 | bar.style.transition = 'width 1s'; 84 | bar.appendChild(valueEl); 85 | 86 | rowEl.appendChild(bar); 87 | flag.appendChild(rowEl); 88 | // 用于更新数据时dom操作 89 | this.domArr.push({ 90 | data: item, 91 | row: rowEl, 92 | label: label, 93 | value: valueEl, 94 | bar: bar 95 | }); 96 | }); 97 | wrap.appendChild(flag) 98 | 99 | // 判断最小值的文字宽度与背景色div宽度 100 | Array.prototype.slice.call(document.getElementsByClassName('item-val')).forEach((item, index) => { 101 | if (item.clientWidth > item.parentNode.clientWidth) { 102 | item.parentNode.style.backgroundColor = 'unset'; 103 | item.style.left = '0'; 104 | item.style.right = 'unset'; 105 | } else { 106 | item.parentNode.style.backgroundColor = this.colors[index]; 107 | item.style.right = '0'; 108 | item.style.left = 'unset'; 109 | } 110 | }) 111 | } 112 | 113 | // 更新数据 114 | update(data) { 115 | // 判断是否是第一次更新数据 116 | 117 | if (!data instanceof Array) { 118 | console.error('options.data is not a Array!'); 119 | return false 120 | } 121 | const sort = data.slice(0, this.topNum).sort((a, b) => b.value - a.value); 122 | 123 | if (!this.data) { 124 | this.data = sort; 125 | this._initData(); 126 | return false 127 | } 128 | const maxValue = sort[0].value; 129 | 130 | // 对比dom更新值 131 | sort.forEach((newItem, index) => { 132 | this.domArr.some((item, domIndex) => { 133 | if (newItem.name === item.data.name) { 134 | item.bar.style.width = `${newItem.value / maxValue * this.maxWidth}px`; 135 | item.value.innerText = newItem.value; 136 | item.row.style.top = `${index * 40 + 50}px`; 137 | if (item.value.clientWidth > newItem.value / maxValue * this.maxWidth) { 138 | console.log(item.value.clientWidth, newItem.value / maxValue * this.maxWidth) 139 | item.bar.style.backgroundColor = 'unset'; 140 | item.value.style.left = '0'; 141 | item.value.style.right = 'unset'; 142 | } else { 143 | item.bar.style.backgroundColor = this.colors[domIndex]; 144 | item.value.style.right = '0'; 145 | item.value.style.left = 'unset'; 146 | } 147 | return true 148 | } 149 | }) 150 | }) 151 | } 152 | } -------------------------------------------------------------------------------- /09-Skills/02-debounce/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 函数去抖(防抖) 9 | 10 | 11 | 12 |
    13 | 14 | 15 | 16 |
    17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /09-Skills/02-debounce/index.js: -------------------------------------------------------------------------------- 1 | window.onload = function () { 2 | // 简单版本 3 | // fn 为需要防抖的函数, wait 为等待时间 4 | const debounce = (fn, wait = 50) => { 5 | // 创建一个定时器 6 | let timer = 0 7 | return function (...args) { 8 | // 如果 timer 没到时间,用户再点击就不断清除定时器 9 | // 然后 生成一个新定时器,并不会执行函数 10 | if (timer) clearTimeout(timer) 11 | timer = setTimeout(() => { 12 | fn.apply(this, args) 13 | }, wait) 14 | } 15 | } 16 | // 缺点是,第一次调用,也要等待。 17 | 18 | // 带有立即执行选项版 19 | // immediate 是否立即调用函数 20 | function decounce2(fn, wait = 50, immediate = true) { 21 | let timer, context, args 22 | 23 | // 延迟执行函数 24 | const later = () => setTimeout(() => { 25 | timer = null 26 | 27 | // 延迟执行的情况下,函数会在延迟函数中执行 28 | // 使用到之前缓存的参数和上下文 29 | if (!immediate) { 30 | fn.apply(context, args) 31 | context = args = null 32 | } 33 | }, wait) 34 | 35 | return function (...args) { 36 | if (!timer) { 37 | 38 | // 如果没有延迟时间定时器,就创建一个 39 | timer = later() 40 | if (immediate) { 41 | fn.apply(this, params) 42 | } else { 43 | context = this 44 | args = params 45 | } 46 | } else { 47 | // 如果已有延迟执行函数(later),调用的时候清除原来的并重新设定一个 48 | // 这样做延迟函数会重新计时 49 | clearTimeout(timer) 50 | timer = later() 51 | } 52 | } 53 | } 54 | // 获取当前时间戳 55 | function now() { 56 | // '+'将 new Date() 由 'object' 转换为 'number' 57 | return +new Date() 58 | } 59 | } -------------------------------------------------------------------------------- /09-Skills/03-traffic light/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2019-4-24 5 | **/ 6 | 7 | // 红路灯问题 8 | 9 | // Details 10 | 11 | // You're writing code to control your town's traffic lights. You need a function to handle each change from green, to yellow, to red, and then to green again. 12 | // Complete the function that takes a string as an argument representing the current state of the light and returns a string representing the state the light should change to. 13 | // For example, update_light('green') should return 'yellow'. 14 | 15 | // 解决方法 16 | 17 | // 普通 18 | 19 | // funciton updateLight(current) { 20 | // switch(current) { 21 | // case 'green': 22 | // return 'yellow'; 23 | // case 'yellow': 24 | // return 'red'; 25 | // case 'red': 26 | // return 'green'; 27 | // default: 28 | // throw 'Error: wrong input'; 29 | // break; 30 | // } 31 | // } 32 | 33 | 34 | // 很聪明的方式 35 | 36 | // const updataLight = current => ({ 37 | // green: 'yellow', 38 | // yellow: 'red', 39 | // red: 'yellow' 40 | // })[current] 41 | 42 | // 最优解 43 | 44 | // function updateLight(current) { 45 | // return current === 'yellow' ? 'red' : current === 'green'?'yellow':'green'; 46 | // } 47 | -------------------------------------------------------------------------------- /09-Skills/04-throttle/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2019-8-13 5 | **/ 6 | 7 | // 节流 8 | 9 | function throttle(fn, time) { 10 | let timer = null 11 | return function name() { 12 | let context = this, 13 | args = arguments 14 | if (!timer) { 15 | timer = setTimeout(() => { 16 | fn.apply(context, args) 17 | timer = null 18 | }, time); 19 | } 20 | } 21 | } 22 | 23 | function throttle1(fn, delay) { 24 | let canUse = true 25 | return function () { 26 | if (canUse) { 27 | fn.apply(this, arguments) 28 | canUse = false 29 | setTimeout(() => { 30 | canUse = true 31 | }, delay); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /09-Skills/05-AccessNestObject/README.md: -------------------------------------------------------------------------------- 1 | # Access Nested Object 2 | 3 | > 访问嵌套对象 4 | 5 | - [Get Nested Object 获取嵌套对象](#get-nested-object) 6 | - [更新嵌套对象](#update-nested-object) 7 | 8 | ## Get Nested Object 9 | 10 | javascript is wired, one of those things is the error when I try to access a nested object: 11 | 12 | `cannot read property 'xxx' of undefined` 13 | 14 | ```js 15 | const nestObject = { 16 | id: "1", 17 | name: "nestObject", 18 | options: [ 19 | { 20 | item1: "item1", 21 | childItem: { 22 | name: "childItem", 23 | }, 24 | }, 25 | { 26 | item2: "item2", 27 | }, 28 | ], 29 | child1: { 30 | name: "child1", 31 | child2: { 32 | name: "child2", 33 | // child3: { 34 | // name: "child3", 35 | // }, 36 | }, 37 | }, 38 | } 39 | ``` 40 | 41 | to access the name of child1, the most usual way 42 | 43 | ```js 44 | let child1Name = nestObject.child1.name 45 | 46 | child1Name = nestObject && nestObject.child1 && nestObject.child1.name 47 | ``` 48 | 49 | and if you want access the name of child2 50 | 51 | ```js 52 | let child2Name 53 | 54 | child2Name = 55 | nestObject && nestObject.child1 && nestObject.child1.child2 56 | ? nestObject.child1.child2.name 57 | : "" 58 | 59 | // or you can pick this trick 60 | 61 | child2Name = (((nestObject || {}).child1 || {}).child2 || {}).name 62 | 63 | // you cannot access nested array with this trick 64 | ``` 65 | 66 | the code look really messy, and if the data nested 5 or more levels deep, the code will more worse. 67 | 68 | ### Reduce 69 | 70 | Reduce is a very powerful method and it can be used to access nested objects 71 | 72 | pass the Access path as Array 73 | 74 | ```js 75 | const getNestObject = (nestObject, pathArr) => { 76 | return pathArr.reduce((obj, key) => { 77 | return obj && obj[key] !== undefined ? obj[key] : undefined 78 | }, nestObject) 79 | } 80 | ``` 81 | 82 | or pass the Access path as String 83 | 84 | ```js 85 | const getNestObject2 = (nestObject, pathString) => { 86 | let pathArr = Array.isArray(pathString) ? pathString : pathString.split(".") 87 | return pathArr.reduce((obj, key) => { 88 | return obj && obj[key] !== undefined ? obj[key] : undefined 89 | }, nestObject) 90 | } 91 | 92 | child2Name = getNestObject(nestObject, ["child1", "child2", "name"]) 93 | 94 | console.log(child2Name) 95 | ``` 96 | 97 | ## Update Nested Object 98 | 99 | Set the `nestOject.chlid1.child2.child3` property 100 | 101 | ```js 102 | const nestObject = { 103 | id: "1", 104 | name: "nestObject", 105 | options: [ 106 | { 107 | item1: "item1", 108 | }, 109 | { 110 | item2: "item2", 111 | }, 112 | ], 113 | child1: { 114 | name: "child1", 115 | child2: { 116 | name: "child2", 117 | // child3: { 118 | // name: "child3", 119 | // }, 120 | }, 121 | }, 122 | } 123 | ``` 124 | 125 | ```js 126 | const setNestObject = (nestObject, pathArr, val) => { 127 | if (!pathArr || pathArr.length === 0) { 128 | return 129 | } 130 | 131 | if (pathArr.length === 1) { 132 | nestObject[pathArr[0]] = val 133 | return true 134 | } 135 | 136 | if ( 137 | !nestObject.hasOwnProperty(pathArr[0]) || 138 | typeof nestObject[pathArr[0]] !== "object" 139 | ) { 140 | nestObject[pathArr[0]] = typeof pathArr[1] === "number" ? [] : {} 141 | } 142 | return setNestObject(nestObject[pathArr[0]], pathArr.slice(1), val) 143 | } 144 | ``` 145 | -------------------------------------------------------------------------------- /09-Skills/05-AccessNestObject/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: tianre96@gmail.com 4 | * day: 2020-04-29 5 | **/ 6 | 7 | const nestObject = { 8 | id: "1", 9 | name: "nestObject", 10 | options: [ 11 | { 12 | item1: "item1", 13 | }, 14 | { 15 | item2: "item2", 16 | }, 17 | ], 18 | child1: { 19 | name: "child1", 20 | child2: { 21 | name: "child2", 22 | // child3: { 23 | // name: "child3", 24 | // }, 25 | }, 26 | }, 27 | } 28 | 29 | let child1Name = nestObject.child1.name 30 | 31 | child1Name = nestObject && nestObject.child1 && nestObject.child1.name 32 | 33 | let child2Name 34 | 35 | child2Name = 36 | nestObject && nestObject.child1 && nestObject.child1.child2 37 | ? nestObject.child1.child2.name 38 | : "" 39 | 40 | child2Name = (((nestObject || {}).child1 || {}).child2 || {}).name 41 | 42 | // pass the Access path as array 43 | const getNestObject = (nestObject, pathArr) => { 44 | return pathArr.reduce((obj, key) => { 45 | return obj && obj[key] !== undefined ? obj[key] : undefined 46 | }, nestObject) 47 | } 48 | 49 | child2Name = getNestObject(nestObject, ["child1", "child2", "name"]) 50 | 51 | console.log(child2Name) 52 | 53 | // ---- update nest object 54 | 55 | /** 56 | * @param path: array 57 | **/ 58 | 59 | const setNestObject = (nestObject, pathArr, val) => { 60 | if (!pathArr || pathArr.length === 0) { 61 | return 62 | } 63 | 64 | // the end 65 | if (pathArr.length === 1) { 66 | nestObject[pathArr[0]] = val 67 | return true 68 | } 69 | 70 | // if the property is a Object 71 | // the property doesn't exists or not a Object 72 | if ( 73 | !nestObject.hasOwnProperty(pathArr[0]) || 74 | typeof nestObject[pathArr[0]] !== "object" 75 | ) { 76 | nestObject[pathArr[0]] = typeof pathArr[1] === "number" ? [] : {} 77 | } 78 | return setNestObject(nestObject[pathArr[0]], pathArr.slice(1), val) 79 | } 80 | -------------------------------------------------------------------------------- /09-Skills/README.md: -------------------------------------------------------------------------------- 1 | # Skills 一些技巧 2 | 3 | > 关于 javascript 日常运用中需要注意的方面及使用技巧 4 | 5 | ## 目录 6 | 7 | 1. [数组随机排序](https://github.com/stephentian/daily-js/tree/master/09-Skills/01-shuffle) 8 | 2. [debounce 防抖]() 9 | 3. [红绿灯问题]() 10 | 4. [throttle 截流]() 11 | 5. [accessing nest object 访问嵌套对象]() 12 | -------------------------------------------------------------------------------- /10-Design-patterns/01-Factory/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2019-3-5 5 | **/ 6 | 7 | // 工厂模式 8 | 9 | // 一个简单的例子 10 | 11 | class Man { 12 | constructor (name) { 13 | this.name = name 14 | } 15 | alertName() { 16 | console.log(this.name) 17 | } 18 | } 19 | 20 | class Factory { 21 | static create(name) { 22 | return new Man(name) 23 | } 24 | } 25 | 26 | Factory.create('stephen').alertName() 27 | 28 | // 工厂模式不仅仅是用了 new 实例 29 | // 假设有一份很复杂的代码需要用户调用, 用户不关心代码, 只需要一个接口去调用, 传递需要的参数即可 30 | // 这个构建过程就是工厂 31 | -------------------------------------------------------------------------------- /10-Design-patterns/02-Singleton/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2019-3-5 5 | **/ 6 | 7 | // 单例模式 8 | 9 | class Singleton { 10 | constructor () { } 11 | } 12 | 13 | Singleton.getInstance = (function () { 14 | let instance 15 | return function () { 16 | if (!instance) { 17 | instance = new Singleton() 18 | } 19 | return instance 20 | } 21 | })() 22 | 23 | let s1 = Singleton.getInstance() 24 | 25 | let s2 = Singleton.getInstance() 26 | 27 | console.log(s1 === s2) 28 | -------------------------------------------------------------------------------- /11-Event Loop/01-stack/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | stack 9 | 10 | 11 | 12 |
    13 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /11-Event Loop/01-stack/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2019-3-6 5 | **/ 6 | 7 | // function foo() { 8 | // console.log('1') 9 | // } 10 | // function bar() { 11 | // console.log('2'); 12 | // foo() 13 | // } 14 | // bar() 15 | 16 | 17 | // setImmediate(() => { 18 | // console.log('1') 19 | // }) 20 | 21 | // process.nextTick(() => { 22 | // console.log('2') 23 | // }) 24 | 25 | // setImmediate(() => { 26 | // console.log('3') 27 | // }) 28 | 29 | // new Promise((resolve, reject) => { 30 | // resolve(true) 31 | // }).then(() => { 32 | // console.log('4') 33 | // }) 34 | 35 | // setTimeout(() => { 36 | // console.log('5') 37 | // }, 0) 38 | 39 | // setTimeout(() => { 40 | // console.log('6') 41 | // }, 0) 42 | 43 | // process.nextTick(() => { 44 | // console.log('7') 45 | // }) 46 | 47 | 48 | 49 | console.log('1'); 50 | 51 | setTimeout(function () { 52 | console.log('2'); 53 | // process.nextTick(function () { 54 | // console.log('3'); 55 | // }) 56 | new Promise(function (resolve) { 57 | console.log('4'); 58 | resolve(); 59 | }).then(function () { 60 | console.log('5') 61 | }) 62 | }) 63 | 64 | new Promise(function (resolve) { 65 | console.log('7'); 66 | resolve(); 67 | }).then(function () { 68 | console.log('8') 69 | }) 70 | 71 | process.nextTick(function () { 72 | console.log('6'); 73 | }) 74 | 75 | 76 | setTimeout(function () { 77 | console.log('9'); 78 | // process.nextTick(function () { 79 | // console.log('10'); 80 | // }) 81 | new Promise(function (resolve) { 82 | console.log('11'); 83 | resolve(); 84 | }).then(function () { 85 | console.log('12') 86 | }) 87 | }) 88 | 89 | // 1, 7, 6, 8 90 | // 2, 4, 9, 11, 3, 10, 5, 12 91 | -------------------------------------------------------------------------------- /11-Event Loop/README.md: -------------------------------------------------------------------------------- 1 | ## Event Loop 2 | > 事件循环 3 | 4 | **** 5 | 6 | ### 进程和线程 7 | > process && thread 8 | 9 | 官方一点的说法:进程是 CPU 资源分配的最小单位, 线程是 CPU 调度的最小单位 10 | 通俗一点:进程是部门领导, 管着众多线程员工. 11 | 12 | ### 单线程好处 13 | 14 | 1. 避免依赖没有加载完成, 15 | 如果工作线是: 产品经理 -> UI -> 前端 + 后端 -> 测试 -> 产品经理, 16 | 测试工程师测试产品的时候, 不知道前后端还没完成或者完善好, 可能就给产品经理报了一堆的 bug, 那产品经理对你的印象就不好了. 17 | 18 | 2. 节约了上下文切换时间 19 | 20 | 3. 没有锁的问题 21 | 22 | 23 | ### 执行栈 24 | 25 | 执行栈, 在其他编程语言里也被叫做**调用栈**, 是一种存储函数调用环境的栈结构, 后进先出(LIFO) 26 | 27 | 当开始执行 js 代码时, 首先会执行一个包含全局环境的 main 函数, 然后执行我们的代码. 28 | 29 | **爆栈**: 使用递归时, 栈可存放的函数是有限制的, 一旦存放过多没有释放, 就会出现爆栈问题. 30 | 31 | #### Event Loop 32 | 33 | 1. js 自上而下解析, 将其中的同步任务按照执行顺序排列到执行栈中 34 | 2. 程序调用外部 api , 如 ajax, setTimeout 等, 会将此类异步任务排列到 Event Table 中, 继续执行栈中的任务 35 | 3. 当异步函数指定事件完成后, 将回调函数放到 事件队列(Event Queue) 中 36 | 4. 主线程将执行栈中的同步任务执行完, 检查 事件队列 中是否有任务, 37 | - 如果有, 将第一个事件对应的回调函数推送到执行栈中执行, 如果执行过程中遇到异步任务, 则继续将这个异步任务排列到事件队列中. 38 | - 如果没有, 执行栈清空, 执行结束. 39 | 5. 主线程每次执行栈清空, 就去事件队列检查是否有任务, 如果有, 就取出一个推送到执行栈中执行, 这个过程一直循环重复, 故被称为 Event Loop 40 | 41 | 42 | ### 宏任务与微任务 43 | 44 | 在上面第二步中, 异步任务有分成 宏任务 和 微任务(区别是执行顺序不同) 45 | 46 | 宏任务: script 全部代码, setTimeout, setInterval, serImmediate, I/O(磁盘读写或网络通信), UI 渲染 47 | 微任务: process.nextTick, Promise.then(Promise 得到回调后的事件), Object.observe(废弃), MutationObserver 48 | 49 | 在 ES6 中, microtask 成为 jobs, 50 | 51 | 在一次事件循环中, 首先 JS 会执行一个宏任务(全部 JS 代码), 执行完成后判断微任务队列中是否有微任务, 52 | - 如果有, 将它们依次全部执行完, 再开始下一个宏任务; 53 | - 如果没有, 跳到执行下一个宏任务 54 | 55 | **优先级** 56 | 57 | process.nextTick 高于 Promise 58 | setTimeout 高于 setImmediate 59 | -------------------------------------------------------------------------------- /12-MVVM/01-defineProperty/compile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2019-3-14 5 | **/ 6 | 7 | // Compile 8 | 9 | // Compile 主要做解析模板指令, 将模板的变量替换成数据, 然后初始化渲染页面视图 10 | // 将每个指令对应的节点绑定更新函数, 添加监听数据的订阅者, 一旦数据变动, 收到通知, 更新视图 11 | 12 | // 因为遍历解析过程中有多次操作 DOM 节点, 为提高性能和效率, 会先将跟节点 el 转换成文档碎片 fragment 进行解析编译操作 13 | // 解析完成, 再将 fragment 添加回原来的真实的 DOM 节点中 14 | 15 | function Compile(el) { 16 | this.$el = this.isElementNode(el) ? el : document.querySelector(el) 17 | if (this.$el) { 18 | this.$fragment = this.node2Fragment(this.$el) 19 | this.init() 20 | this.$el.appendChild(this.$fragment) 21 | } 22 | } 23 | 24 | Compile.prototype = { 25 | init: function () { 26 | this.compileElement(this.$fragment) 27 | }, 28 | node2Fragment: function (el) { 29 | var fragment = document.createDocumentFragment(), 30 | child; 31 | // 将原生节点拷贝到 fragment 32 | while (child = el.firstChild) { 33 | fragment.appendChild(child) 34 | } 35 | return fragment 36 | }, 37 | compileElement: function (el) { 38 | var childNodes = el.childNodes, 39 | me = this; 40 | [].slice.call(childNodes).forEach(function (node) { 41 | const text = node.textContent 42 | const reg = /\{\{.*)\}\}/ 43 | // 按元素节点方式编译 44 | if (me.isElementNode(node)) { 45 | me.compileElement(node) 46 | } else if (me.isTextNode(node) && reg.test(text)) { 47 | me.compileText(node, RegExp.$1) 48 | } 49 | // 遍历编译子节点 50 | if (node.childNodes && node.childNodes.length) { 51 | me.compileElement(node) 52 | } 53 | }) 54 | }, 55 | compile: function (node) { 56 | 57 | }, 58 | 59 | } 60 | 61 | // 指令处理集合 62 | const compileUtil = { 63 | text: function (node, vm, exp) { 64 | this.bind(node, vm, exp, 'text') 65 | }, 66 | bind: function (node, vm, exp, dir) { 67 | const updaterFn = updater[dir + 'Updater'] 68 | // 第一次初始化视图 69 | updaterFn && updaterFn(node, vm[exp]) 70 | // 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /12-MVVM/01-defineProperty/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Vue 方式的 MVVM 9 | 10 | 11 | 12 |
    13 | 14 |

    {{ word }}

    15 | 16 |
    17 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /12-MVVM/01-defineProperty/mvvm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2019-03-16 5 | **/ 6 | 7 | function MVVM(options) { 8 | this.$options = options || {} 9 | const data = this._data = this.$options.data 10 | const me = this 11 | 12 | // 数据代理 13 | // 实现 vm.xxx ---> vm._data.xxx 14 | Object.keys(data).forEach(function (key) { 15 | me._proxyData(key) 16 | }) 17 | 18 | this._initComputed() 19 | observe(data, this) 20 | this.$compile = new Compile(options.el || document.body, this) 21 | } 22 | 23 | MVVM.prototype = { 24 | $watch: function (key, cb, options) { 25 | new Watcher(this, key, cb) 26 | }, 27 | _proxyData: function (key, setter, getter) { 28 | const me = this 29 | setter = setter || Object.defineProperty(me, key, { 30 | configurable: false, 31 | enumerable: true, 32 | get: function proxyGetter() { 33 | return me._data[key] 34 | }, 35 | set: function proxySetter(newVal) { 36 | me._data[key] = newVal 37 | } 38 | }) 39 | }, 40 | _initComputed: function () { 41 | const me = this 42 | const computed = this.$options.computed 43 | if (typeof computed === 'object') { 44 | Object.defineProperty(me, key, { 45 | get: typeof computed[key] === 'function' ? computed[key] : computed[key].get, 46 | set: function () { } 47 | }) 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /12-MVVM/01-defineProperty/observer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2019-3-12 5 | **/ 6 | 7 | // Observer 观察员 8 | 9 | 10 | // 利用 Object.defineProperty 来监听属性变动 11 | // 将需要 observe 的数据进行递归遍历, 包括子属性对象的属性, 都加上 setter 和 getter 12 | // 这样的话, 给对象的某个值赋值就会触发 setter, 达到监听数据变化的目的 13 | 14 | function observe(obj) { 15 | if (!obj || typeof obj !== 'object') { 16 | return 17 | } 18 | Object.keys(obj).forEach((key) => { 19 | defineReactive(obj, key, obj[key]) 20 | }) 21 | } 22 | 23 | // function defineReactive(obj, key, val) { 24 | // observe(val) 25 | // Object.defineProperty(obj, key, { 26 | // enumerable: true, 27 | // configurable: false, // 不能再 define, 再使用 defineProperty 会报错 28 | // get: function () { 29 | // return val 30 | // }, 31 | // set: function (newVal) { 32 | // console.log('value changed', val, '--->', newVal) 33 | // val = newVal 34 | // } 35 | // }) 36 | // } 37 | 38 | // 测试 39 | 40 | // let data = { name: 'stephentian' } 41 | // observe(data) 42 | // data.name = 'tian' 43 | 44 | 45 | // 可以监听到每个数据变化了, 接下来是通知订阅者, 实现一个消息订阅器 46 | // 很简单, 维护一个数组, 用来收集订阅者, 数据变动触发 notify, 在调用订阅者的 update 方法 47 | 48 | function Dep() { 49 | this.subs = [] 50 | } 51 | 52 | Dep.prototype = { 53 | addSub: function (sub) { 54 | this.subs.push(sub) 55 | }, 56 | depend: function () { 57 | Dep.target.addDep(this) 58 | }, 59 | removeSub: function (sub) { 60 | let index = this.subs.indexOf(sub) 61 | if (index != -1) { 62 | this.subs.splice(index, 1) 63 | } 64 | }, 65 | notify: function () { 66 | this.subs.forEach(sub => { 67 | // 调用订阅者 update 方法 68 | sub.update() 69 | }) 70 | } 71 | } 72 | 73 | // 代码改善后: 74 | 75 | // function defineReactive(data, key, val) { 76 | // let dep = new Dep() 77 | // observe(val) 78 | // Object.defineProperty(data, key, { 79 | // enumerable: true, 80 | // configurable: false, 81 | // get: function () { 82 | // return val 83 | // }, 84 | // set: function (newVal) { 85 | // if (val === newVal) return 86 | // console.log('value changed', val, '--->', newVal) 87 | // val = newVal 88 | // dep.notify() // 通知所有订阅者 89 | // } 90 | // }) 91 | // } 92 | 93 | 94 | // 如何分辨谁是 订阅者, 怎么往订阅器添加订阅者呢? 95 | // 整理上面的思路, 可以明确订阅者应该是 Watcher, 96 | // 而且 let dep = new Dep() 97 | // 是在 defineReactive 方法内部定义的, 所以想通过 dep 添加订阅者, 就必须在闭包内操作 98 | // 所以我们在 getter 里操作 99 | 100 | function defineReactive(data, key, val) { 101 | let dep = new Dep() 102 | observe(val) 103 | Object.defineProperty(data, key, { 104 | enumerable: true, 105 | configurable: false, 106 | get: function () { 107 | // 由于需要在闭包内添加 watcher, 所以通过 Dep 定义一个全局 target 属性, 暂存 watcher, 添加完移除 108 | Dep.target && dep.addDep(Dep.target) 109 | return val 110 | }, 111 | set: function (newVal) { 112 | if (val === newVal) return 113 | console.log('value changed', val, '--->', newVal) 114 | val = newVal 115 | dep.notify() // 通知所有订阅者 116 | } 117 | }) 118 | } 119 | 120 | Dep.target = null 121 | -------------------------------------------------------------------------------- /12-MVVM/01-defineProperty/watcher.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2019-3-13 5 | **/ 6 | 7 | // Watcher 8 | 9 | // Watcher 订阅者作为 Observer 和 Compile 之间的通信桥梁 10 | // 主要作用: 11 | // 1. 在自身实例化时往属性订阅器(dep) 里添加自己 12 | // 2. 自身有个 update() 方法 13 | // 3. 待属性变动 dep.notice() 通知时, 能调用自身的 update() 方法, 并触发 Compile 中绑定的回调 14 | 15 | function Watcher(vm, exp, cb) { 16 | this.cb = cb 17 | this.vm = vm 18 | this.exp = exp 19 | this.depIds = {} 20 | // 此处为了触发属性的 getter, 从而在 dep 添加自己, 结合 Observer 更易理解 21 | this.value = this.get() 22 | } 23 | 24 | Watcher.prototype = { 25 | update: function () { 26 | this.run 27 | }, 28 | run: function () { 29 | let value = this.value 30 | const oldVal = this.value 31 | if (value !== oldVal) { 32 | this.value = value 33 | this.cb.call(this.vm, value, oldVal) // 执行 Compile 中绑定的回调, 更新视图 34 | } 35 | }, 36 | addDep: function (dep) { 37 | // 1. 每次调用 run() 的时候会触发相应的属性 getter 38 | // getter 里面会触发 dep.depend(), 继而触发 addDep 39 | // 2. 假如相应的属性的 dep.id 已经在当前 watcher 的 depIds 里, 说明不是一个新属性, 仅仅是改变了其值而已 40 | // 则不需要将当前 watcher 添加到该属性的 dep 里 41 | // 3. 假如相应的属性是新属性, 则将当前 watcher 添加到新属性的 dep 里 42 | // 如通过 vm.child = { name: 'a' } 改变了 child.name 的值, child.name 就是一个新属性 43 | // 则需要将当前 watcher(child.name) 加入到新的 child.name 的 dep 里 44 | // 因为此时 child.name 是一个新值, 之前到 setter, dep 都已经失效 45 | // 如果不把 watcher 加入到新的 child.name 的 dep 中 46 | // 通过 child.name = xxx 赋值的时候, 对应的 watcher 就收不到通知, 等于失效了 47 | // 4. 每个子属性的 watcher 在添加子属性的 dep 的同时, 也会添加到父属性的 dep 48 | // 监听子属性的同时监听父属性的变更, 这样, 父属性改变是, 子属性的 watcher 也能收到通知, 进行 update() 49 | // 这一步是在 this.get() --> this.getVmVal() 里完成的, forEach 是会从父级开始取值, 间接调用它的 getter 50 | // 触发了 addDep(), 在整个 forEach 中, 当前 watcher 都会加入到每个父级过程属性的 dep 51 | // 例如: 52 | // 当前 watcher 的是 'child.chile.name', 那么 child, child.child, child.child.name 三个属性的 dep 都会加入当前 watcher 53 | if (!this.depIds.hasOwnProperty(dep.id)) { 54 | dep.addDep(this) 55 | this.depIds[dep.id] = dep 56 | } 57 | }, 58 | get: function () { 59 | // 将当前的订阅者指向自己 60 | Dep.target = this 61 | // 触发 getter, 添加自己到属性订阅器 62 | let value = this.vm[exp] 63 | // 添加完毕, 重置 64 | Dep.target = null 65 | return value 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /13-Prototype/JQuery/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2019-3-19 5 | **/ 6 | 7 | // jQuery 8 | 9 | var JQuery = function (selector) { 10 | return new JQuery.fn.init(selector) 11 | } 12 | 13 | // 定义构造函数 14 | 15 | var init = JQuery.fn.init = function (selector) { 16 | var slice = Array.prototype.slice 17 | var dom = slice.call(document.querySelectorAll(selector)) 18 | 19 | var i, len = dom ? dom.length : 0 20 | for (i = 0; i < len; i++) { 21 | this[i] = dom[i] 22 | } 23 | this.length = len 24 | this.selector = selector || '' 25 | } 26 | 27 | // 初始化 JQuery.fn 28 | JQuery.fn = JQuery.prototype = { 29 | constructor: JQuery, 30 | // 其他函数 31 | css: function (key, value) { }, 32 | html: function (value) { } 33 | } 34 | 35 | // 定义原型 36 | init.prototype = JQuery.fn 37 | -------------------------------------------------------------------------------- /13-Prototype/README.md: -------------------------------------------------------------------------------- 1 | # 原型的使用 2 | > prototype 3 | 4 | ### 目录 5 | 6 | - jquery 7 | - zepto 8 | 9 | 10 | ### jQuery 11 | 12 | jQuery 为入口函数, 接收一个 selector; 返回一个 jQuery.fn.init(selector) 构造函数. 13 | 14 | 定义 init 为 jQuery.fn.init, 并给 jQuery.fn.init 为一个接收 selector 为参数的函数. 15 | 16 | 函数中根据 selector 获取 dom, 并遍历 dom 属性, 赋值为函数的属性. 17 | 18 | jQuery.fn 赋值给 jQuery.prototype, jQuery.prototype 定义为一个对象, 19 | 20 | 构造器为 jQuery, 还有 jQuery 的相关功能方法. 21 | 22 | 最后定义原型, init.prototype = jQuery.fn 23 | 24 | **jQuery.protorype 为什么要赋值给 jQuery.fn 和 构造函数的 init 的原型呢?** 25 | 26 | 1. 存在即合理 27 | 28 | 2. 为了扩展插件 29 | 30 | 一个简单的插件例子 31 | 32 | ``` 33 | $.fn.getNodeName = function () { 34 | return this[0].nodeName 35 | } 36 | ``` 37 | 38 | 不通过直接 jQuery.prototype 暴露出来, 虽然最终的目的是放入原型里. 39 | 40 | **好处:** 41 | 42 | - 只用 $ 会暴露在 window 全局变量 43 | - 将插件扩展统一到 $.fn.xxx 这一个接口中, 方便使用(这符合软件设计模式) 44 | - 设计库, 只暴露一个变量就好了. 45 | 46 | 47 | ### zepto 48 | 49 | $ 为入口函数, 接收 selector 参数, 返回一个 zepto.init(selector) 函数. 50 | 51 | zepto.init 通过 selector 获取到 dom; 52 | 53 | 将 dom 和 selector 传给 zepto.Z; 54 | 55 | zepto.Z 里初始化构造函数, new Z(dom, selector) 56 | 57 | Z 为构造函数, 将 dom 的属性映射给自身. 58 | 59 | Z.prototype 被赋值给了 $.fn 60 | 61 | $.fn 为对象, 构造器为 zepto.Z, 里面存有 zepto 的相关方法. 62 | 63 | 最后定义原型, zepto.Z.prototype = Z.prototype = $.fn 64 | 65 | **为什么要把 zepto 原型方法赋值给 $.fn, 而不直接给 Z.prototype 赋值?** 66 | 67 | 68 | ### 插件机制 69 | 70 | -------------------------------------------------------------------------------- /13-Prototype/Zepto/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | * email: stephentian@foxmail.com 4 | * day: 2019-3-19 5 | **/ 6 | 7 | // zepto 8 | 9 | var zepto = {} 10 | 11 | zepto.init = function (selector) { 12 | // 源码比较复杂, 处理情况很多, 这里简化举例 13 | // 比如要解析命令等 14 | var dom = document.querySelectorAll(selector) 15 | return zepto.Z(dom, selector) 16 | } 17 | 18 | // 使用 zepto 时的 $ 19 | // 先定义 $ 20 | 21 | var $ = function (selector) { 22 | return zepto.init(selector) 23 | } 24 | 25 | // 构造函数 26 | 27 | function Z(dom, selector) { 28 | var i, len = dom ? dom.length : 0 29 | // 把 DOM 元素的属性赋值给自己 30 | for (i = 0; i < len; i++) { 31 | this[i] = dom[i] 32 | } 33 | this.length = len 34 | this.selector = selector || '' 35 | } 36 | 37 | zepto.Z = function (dom, selector) { 38 | return new Z(dom, selector) 39 | } 40 | 41 | $.fn = { 42 | constructor: zepto.Z, 43 | css: function (key, value) { }, 44 | html: function (value) { 45 | return '这是一个模拟的html方法' 46 | } 47 | } 48 | 49 | zepto.Z.prototype = Z.prototype = $.fn 50 | -------------------------------------------------------------------------------- /13-Prototype/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 测试 jQuery 和 zepto 8 | 9 | 10 | 11 |
    12 |

    aaa

    13 |

    bbb

    14 |
    15 | 20 | 21 | -------------------------------------------------------------------------------- /14-DataStructure/Linked-list/README.md: -------------------------------------------------------------------------------- 1 | # 链表(linked-list) 2 | 3 | ## 介绍 4 | 5 | 链表是一组节点组成的集合,每个节点都使用一个对象的引用来指向它的后一个节点。指向另一节点的引用叫做链。 6 | 7 | ``` 8 | |header|next| ---> |data1|next| ---> |data2|next| ---> |data3|next| ---> null 9 | ``` 10 | 11 | ## 设计链表 12 | 13 | 链表包含两个类, 一个是 Node 类, 来表示节点的数据; 一个是 LinkedList 类, 提供插入节点、删除节点等操作. 14 | 15 | ### Node 类 16 | 17 | ``` 18 | function Node(ele) { 19 | this.ele = ele 20 | this.next = null 21 | } 22 | ``` 23 | 24 | ### LinkedList 类 25 | 26 | ``` 27 | function LList () { 28 | this.head = new Node('head') // 头节点 29 | this.find = find // 查找节点 30 | this.insert = insert // 插入节点 31 | this.remove = remove // 删除节点 32 | this.findPrev = findPrev // 查找前一个节点 33 | this.display = display // 显示节点 34 | } 35 | 36 | ``` 37 | 38 | #### 查找节点 39 | 40 | ``` 41 | function find (item) { 42 | var currentNode = this.head 43 | while(currentNode.value != item) { 44 | currentNode = currentNode.next 45 | } 46 | return currentNode 47 | } 48 | ``` 49 | 50 | #### 插入节点 51 | 52 | ``` 53 | function insert(newEle, item) { 54 | var newNode = new Node(newEle) 55 | var currentNode = this.find(item) 56 | newNode.next = currentNode.next 57 | currentNode.next = newNode 58 | } 59 | ``` 60 | 61 | ## 链表的一些操作 62 | 63 | #### 打印链表 64 | 65 | ``` 66 | function showList (list) { 67 | let node = list.head 68 | while(node) { 69 | console.log(node.value) 70 | node = node.next 71 | } 72 | } 73 | ``` 74 | 75 | #### 逆序打印链表 76 | 77 | ``` 78 | function showReverseList(list) { 79 | let node = list.head, 80 | stack = [] 81 | while(node) { 82 | stack.push(node.value) 83 | node = node.next 84 | } 85 | for(let len = stack.length - 1; len >=0; len --) { 86 | console.log(stack[len]) 87 | } 88 | } 89 | ``` 90 | 91 | #### 单向链表逆序 92 | 93 | ``` 94 | function reverseList(list) { 95 | let reList = list 96 | let a = list.head 97 | b = null 98 | if (a === null) return 99 | 100 | while(a.next != null) { 101 | b = a.next 102 | a.next = b.next 103 | b.next = reList 104 | reList = b 105 | } 106 | return reList 107 | } 108 | ``` 109 | 110 | #### 链表转化成数组 111 | 112 | ``` 113 | function list2Array(list) { 114 | if(!list) { 115 | return [] 116 | } 117 | var result = [] 118 | var p = list.head 119 | while(p) { 120 | result.push(p.value) 121 | p = p.next 122 | } 123 | return result 124 | } 125 | 126 | // 递归 127 | function list2Array(list) { 128 | if (!list) return [] 129 | 130 | var result = [list.value] 131 | var resultValues = list2Array(list.next) 132 | return result.concat(resultValues) 133 | } 134 | ``` 135 | 136 | #### 数组转链表 137 | 138 | ``` 139 | function array2List(arr) { 140 | if(!arr || arr.length === 0) { 141 | return null 142 | } 143 | var nodes = [] 144 | for(var i = 0; i < arr.length; i ++) { 145 | var node = {} 146 | node.value = arr[i] 147 | node.next = null 148 | nodes.push(node) 149 | } 150 | 151 | for(var i = 0; i < nodes.length; i++) { 152 | nodes[i].next = nodes[i+1] 153 | } 154 | return nodes[0] 155 | } 156 | 157 | // 不占额外空间 158 | function array2List(arr) { 159 | if(!arr.length) return null 160 | var node, 161 | head = {value: arr[0], next: null} 162 | var pnode = head // pnode 用来保存前一个节点 163 | 164 | for(var i = 1; i < arr.length; i++) { 165 | node = {value: arr[i], next: null} 166 | pnode.next = node 167 | pnode = node 168 | } 169 | 170 | return head 171 | } 172 | ``` 173 | -------------------------------------------------------------------------------- /15-TypeScript/README.md: -------------------------------------------------------------------------------- 1 | # TypeScript 2 | 3 | - [TypeScript](#typescript) 4 | - [介绍](#介绍) 5 | - [引导](#引导) 6 | - [声明类型](#声明类型) 7 | - [基础类型](#基础类型) 8 | - [接口](#接口) 9 | - [类](#类) 10 | - [函数](#函数) 11 | - [函数类型](#函数类型) 12 | - [参数](#参数) 13 | - [`this`](#this) 14 | - [泛型](#泛型) 15 | - [装饰器](#装饰器) 16 | - [属性装饰器](#属性装饰器) 17 | - [类型声明](#类型声明) 18 | 19 | ## 介绍 20 | 21 | 官方:TypeScript 是 JavaScript 类型的超集,它可以编译成纯 JavaScript。 22 | 23 | 可以把 TypeScript 看作是 JavaScript + 一些扩展包。 24 | 25 | ## 引导 26 | 27 | 参考官方文档: [TypeScript 文档简介](https://www.tslang.cn/docs/home.html) 28 | 29 | - 声明类型 30 | - 基础类型 31 | - 接口 32 | - 类 33 | - 函数 34 | - 泛型 35 | - 装饰器 36 | - 类型声明 37 | 38 | ## 声明类型 39 | 40 | - 类型别名( `type sn = number | string;` ) 41 | - 接口声明( `interface I { x: number[]; }` ) 42 | - 类声明( `class C { }` ) 43 | - 枚举声明( `enum E { A, B, C }` ) 44 | - 指向某个类型的 `import` 声明 45 | 46 | ## 基础类型 47 | 48 | - number 49 | - string 50 | - boolean 51 | - `[]` / Array 数组 52 | - Tuple 元组 53 | - enum 枚举 54 | - any 未指定类型 55 | - 无类型 Void 56 | - 用于函数,表示没有返回值 57 | - 用于变量,表示它只能为 `undefined` 和 `null` 58 | - null 59 | - undefined 60 | - never 永不存在的值的类型 61 | - 用于函数,表示存在无法到达的终点 62 | - object 63 | 64 | 类型断言 65 | 66 | 好比其他语言的类型转换,告诉编译器,这是什么类型。 67 | 68 | 两种形式,第一种 “尖括号” 语法: 69 | 70 | ```ts 71 | let str:any = "this is a string" 72 | let strLength:number = (str).length 73 | ``` 74 | 75 | 第二种,`as` 语法: 76 | 77 | ```ts 78 | let str:any = "this is a string" 79 | let strLength:number = (str as string).length 80 | ``` 81 | 82 | 注意:如果在 TypeScript 使用 JSX 时,只有 `as` 语法断言时生效的。 83 | 84 | ## 接口 85 | 86 | 约束对象接口,不需要全部实现,只会检查必需属性。 87 | 88 | ```ts 89 | interface LabelValue { 90 | label: string 91 | } 92 | function printLabel(obj: LabelValue) { 93 | console.log(obj.label) 94 | } 95 | ``` 96 | 97 | 可选属性(`?:`) 98 | 99 | ```ts 100 | interface LabelValue { 101 | name?: string 102 | label: string 103 | } 104 | ``` 105 | 106 | 可读属性(`readonly`) 107 | 108 | ```ts 109 | interface Point { 110 | readonly x: number; 111 | readonly y: number; 112 | } 113 | ``` 114 | 115 | ## 类 116 | 117 | `class` 和 es6 里的功能相同,不过 `ts` 里创建类还会创建一个类的实例类型。 118 | 119 | `extends` 继承 120 | 121 | 修饰符: 122 | 123 | - public 124 | - private 125 | - protected 126 | - readonly 127 | 128 | 存取器:`get`,`set` 129 | 130 | 静态属性:`static` 131 | 132 | 抽象类:`abstract` 133 | 134 | 因为创建类会生成一个类的实例类型,类可以用作 接口。 135 | 136 | ## 函数 137 | 138 | 主要定义 行为 的地方。 139 | 140 | ### 函数类型 141 | 142 | ```ts 143 | let myAdd: (x: number, y: number) => number = 144 | function(x: number, y: number): number { return x + y } 145 | ``` 146 | 147 | 只要参数类型是匹配的,即有效,不需要确认参数名是否正确 148 | 149 | 推断类型(赋值语句的一边指定类型另一边没有类型的话,TypeScript 编译器会自动识别出类型) 150 | 151 | ```ts 152 | let myAdd = function(x: number, y: number): number { return x + y; } 153 | // 或者 154 | let myAdd: (baseValue: number, increment: number) => number = 155 | function(x, y) { return x + y; } 156 | ``` 157 | 158 | ### 参数 159 | 160 | 可选参数和默认参数 161 | 162 | !可选参数必须跟在必须参数后面 163 | 164 | ```ts 165 | function(x: number, y?: number) {} 166 | function(x: number, y = 1) {} 167 | ``` 168 | 169 | 剩余参数 170 | 171 | 剩余参数会被当做个数不限的可选参数。可以一个都没有,同样也可以有任意个。 172 | 173 | ```ts 174 | function(x: number, ...restOfNumber: number[]) {} 175 | ``` 176 | 177 | ### `this` 178 | 179 | 箭头函数能保存函数创建时的 this值,而不是调用时的值。 180 | 181 | ```ts 182 | let deck = { 183 | createCardPicker: function() { 184 | () => { 185 | return { suits: this.suits } 186 | } 187 | } 188 | } 189 | ``` 190 | 191 | 但是,TypeScript 会警告你犯了一个错误:`this.suits` 为 `any` 。 192 | 193 | 修改的方法是,提供一个显式的 this参数。 194 | 195 | ```ts 196 | interface Card { 197 | suit: string; 198 | } 199 | interface Deck { 200 | suits: string[]; 201 | createCardPicker(this: Deck): () => Card; 202 | } 203 | let deck: Deck = { 204 | suits: ["a", "b", "c", "d"], 205 | createCardPicker: function(this: Deck) { 206 | () => { 207 | return { suits: this.suits } 208 | } 209 | } 210 | } 211 | ``` 212 | 213 | ## 泛型 214 | 215 | 泛型 Generics。像 C# 和 Java 这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 216 | 217 | 在定义函数、接口或者类的时候,不需要先指定具体的类型,增加复用性。 218 | 219 | 泛型变量 220 | 221 | ```ts 222 | function identity(arg: T): T { 223 | return arg; 224 | } 225 | ``` 226 | 227 | 泛型接口 228 | 229 | ```ts 230 | interface GenericIdentityFn { 231 | (arg: T): T; 232 | } 233 | 234 | let myIdentity: GenericIdentityFn = function identity(arg: T): T { 235 | return arg; 236 | } 237 | ``` 238 | 239 | 泛型方法 240 | 241 | ```ts 242 | function identity(arg: T): T { 243 | return arg; 244 | } 245 | 246 | let myIdentity: (arg: T) => T = identity; 247 | ``` 248 | 249 | 泛型类 250 | 251 | 泛型类型放在类后面。 252 | 253 | ```ts 254 | class GenericsClass { 255 | value: T, 256 | add: (x: T, y: T) => T 257 | } 258 | ``` 259 | 260 | 泛型约束 261 | 262 | 对比操作 `any` 类型,想要限制函数去处理 `.length` 属性的类型。 定义一个接口来描述约束条件。使用这个接口和 `extends` 关键字来实现约束 263 | 264 | ```ts 265 | interface Lengthwise { 266 | length: number; 267 | } 268 | 269 | function loggingIdentity(arg: T): T {} 270 | ``` 271 | 272 | ! 传入符合约束类型的值,必须包含必须的属性 273 | 274 | ## 装饰器 275 | 276 | 装饰器 Decorators,为我们在类的声明及成员上通过元编程语法添加标注提供了一种方式。 277 | 278 | 它能够被附加到类声明,方法, 访问符,属性或参数上。 279 | 280 | 简而言之:用于扩展类、类的属性或者方法。 281 | 282 | ```ts 283 | // vue @Props 284 | export default class App extends Vue { 285 | @Props({ type: Boolean, required: true }) 286 | msg!: Boolean 287 | } 288 | // 事件处理:@Emit 289 | // 变更监测:@Watch 290 | ``` 291 | 292 | 类装饰器 293 | 294 | 类装饰器应用于类构造函数,可以用来监视,修改或替换类定义。 295 | 296 | !类装饰器不能用在声明文件中( .d.ts),也不能用在任何外部上下文中(比如declare的类)。 297 | 298 | ```ts 299 | // 当 @sealed 被执行的时候,它将密封此类的构造函数和原型。 300 | function sealed(constructor: Function) { 301 | Object.seal(constructor); 302 | Object.seal(constructor.prototype); 303 | } 304 | 305 | @sealed 306 | class Greeter { 307 | greeting: string; 308 | constructor(message: string) { 309 | this.greeting = message; 310 | } 311 | greet() { 312 | return "Hello, " + this.greeting; 313 | } 314 | } 315 | ``` 316 | 317 | 方法装饰器 318 | 319 | 被应用到方法的 属性描述符上,可以用来监视,修改或者替换方法定义。 320 | 321 | 3 个参数: 322 | 323 | 1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。 324 | 2. 成员名字。 325 | 3. 成员的属性描述符。 326 | 327 | ### 属性装饰器 328 | 329 | 属性装饰器声明在一个属性声明之前(紧靠着属性声明)。 330 | 331 | 2个参数: 332 | 333 | 1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。 334 | 2. 成员的名字。 335 | 336 | ## 类型声明 337 | 338 | 模块补充 339 | 340 | ```ts 341 | declare module "vue/types/vue" { 342 | interface Vue { 343 | $axios: AxiosInstance 344 | } 345 | } 346 | ``` 347 | -------------------------------------------------------------------------------- /16-Source Code/README.md: -------------------------------------------------------------------------------- 1 | # Source Code 2 | 3 | > 源码分析 4 | 5 | ## 相关地址 6 | 7 | `v8` 源码地址: 8 | 9 | `v8 js` 部分: 10 | 11 | 1. 12 | 2. 13 | 14 | ## 目录 15 | 16 | 1. splice 17 | -------------------------------------------------------------------------------- /16-Source Code/splice.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author: stephentian 3 | */ 4 | 5 | // 实现 Array.prototype.splice 6 | 7 | // ES5 规范 8 | // https://262.ecma-international.org/5.1/#sec-15.4.4.12 9 | // ES6 规范 10 | // https://262.ecma-international.org/6.0/#sec-array.prototype.splice 11 | 12 | // splice 参数: 13 | // 1. start 14 | // 起始位 15 | // 2. delete_count 16 | // 可选,移除数组元素个数 17 | // 3. item1, item2, ... 18 | // 可选,添加数组元素 19 | 20 | // 返回值: 21 | // 被删元素组成的数组。 22 | 23 | 24 | // start 25 | // 1. 超出数组长度 26 | // 是:从末尾添加元素 27 | // 否:从 start 位置开始操作 28 | // 2. 小于 0 29 | // -n 表示 倒数第 n 个元素。等价于 arr.length - n 30 | // n 大于 arr.length,则表示开始位置为第 0 位。 31 | 32 | function ComputeSpliceStartIndex(start_i, len) { 33 | if (start_i < 0) { 34 | start_i += len 35 | return start_i < 0 ? 0 : start_i 36 | } 37 | 38 | return start_i > len ? len : start_i 39 | } 40 | 41 | // delete_count 42 | // 1. 被省略,或者 它大于 arr.length - start 43 | // start 和 start 后面的所以元素都被删除 44 | // 2. 小于 0 45 | // 不移除元素 46 | 47 | function ComputeSpliceDeleteCount(delete_count, num_arguments, len, start_i) { 48 | if (num_arguments == 1) { 49 | return len - start_i 50 | } 51 | 52 | let del_count = delete_count 53 | if (del_count < 0) { 54 | return 0 55 | } 56 | 57 | if (del_count > len - start_i) { 58 | return len - start_i 59 | } 60 | return del_count 61 | } 62 | 63 | // 拷贝需要删除的元素 64 | // SparseSlice 65 | 66 | function SparseSlice(array, start_i, del_count, deleted_elements) { 67 | for (let i = 0; i < del_count; i++) { 68 | const element = array[start_i + i]; 69 | deleted_elements[i] = element 70 | } 71 | } 72 | 73 | // 移动元素 74 | // SparseMove 75 | function SparseMove(array, start_i, del_count, num_elements_to_add) { 76 | if (num_elements_to_add === del_count) return 77 | 78 | let new_array = new Array(array.length - del_count + num_elements_to_add) 79 | 80 | for (let i = 0; i < new_array.length; i++) { 81 | if (i < start_i) { 82 | new_array[i] = array[i] 83 | } else if (num_elements_to_add < del_count && i >= start_i + (del_count - num_elements_to_add)) { 84 | // 如果 添加数目 < 删除数目,i 填充位为 start_i + (del_count - num_elements_to_add) 85 | new_array[i] = array[i+(del_count - num_elements_to_add)] 86 | } else if (num_elements_to_add > del_count && i >= start_i + num_elements_to_add) { 87 | new_array[i] = array[i-(num_elements_to_add - del_count)] 88 | } else { 89 | continue 90 | } 91 | } 92 | console.log('new_array:', new_array) 93 | 94 | // array = new_array 95 | return new_array 96 | } 97 | 98 | function ArraySplice(start, delete_count, ...item) { 99 | let num_arguments = arguments.length 100 | let array = Object(this) 101 | let len = array.length; 102 | 103 | let start_i = ComputeSpliceStartIndex(start, len) 104 | let del_count = ComputeSpliceDeleteCount(delete_count, num_arguments, len, start_i) 105 | 106 | let deleted_elements = new Array(del_count) 107 | let num_elements_to_add = item ? item.length : 0 108 | 109 | console.log('array: ', array) 110 | console.log('len: ', len) 111 | console.log('start_i: ', start_i) 112 | console.log('del_count: ', del_count) 113 | console.log('num_elements_to_add: ', num_elements_to_add) 114 | 115 | SparseSlice(array, start_i, del_count, deleted_elements) 116 | array = SparseMove(array, start_i, del_count, num_elements_to_add) 117 | 118 | // 添加/替换元素 119 | let i = start_i 120 | let j = 0 121 | while (j < item.length) { 122 | array[i++] = item[j++] 123 | } 124 | 125 | console.log('array: ', array) 126 | console.log('deleted_elements: ', deleted_elements) 127 | 128 | return deleted_elements 129 | } 130 | 131 | const months = ['Jan', 'March', 'April', 'June'] 132 | 133 | var splice1 = ArraySplice.bind(months) 134 | 135 | splice1(1, 0, 'Feb') 136 | splice1(4, 1, 'May') -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # daily-js 2 | 3 | > JavaScript Book 4 | 5 | ## 目录 6 | 7 | **[前言](#前言)** 8 | 9 | 0. **[ES6](https://github.com/stephentian/daily-js/tree/master/00-ES6)** 10 | 1. **[算法 Algorithm](https://github.com/stephentian/daily-js/tree/master/01-Algorithm)** 11 | 2. **[高阶函数 Higher-order Function](https://github.com/stephentian/daily-js/tree/master/02-Higher-order%20Function)** 12 | 3. **[JS 常识 General Knowledge](https://github.com/stephentian/daily-js/tree/master/03-General%20Knowledge)** 13 | 4. **[异步 Async](https://github.com/stephentian/daily-js/tree/master/04-Async)** 14 | 5. **[正则表达式 Regexp](https://github.com/stephentian/daily-js/tree/master/05-Regexp)** 15 | 6. **[Date](https://github.com/stephentian/daily-js/tree/master/06-Date)** 16 | 7. **[面向对象编程 OOP](https://github.com/stephentian/daily-js/tree/master/07-OOP)** 17 | 8. **[this](https://github.com/stephentian/daily-js/tree/master/08-This)** 18 | 9. **[Skills 一些技巧](https://github.com/stephentian/daily-js/tree/master/09-Skills)** 19 | 10. **[设计模式 Design patterns](https://github.com/stephentian/daily-js/tree/master/10-Design-patterns)** 20 | 11. **[事件循环 Event Loop](https://github.com/stephentian/daily-js/tree/master/11-Event%20Loop)** 21 | 12. **[MVVM](https://github.com/stephentian/daily-js/tree/master/12-MVVM)** 22 | 13. **[原型 Prototype](https://github.com/stephentian/daily-js/tree/master/13-Prototype)** 23 | 14. **[数据结构](https://github.com/stephentian/daily-js/tree/master/14-DataStructure)** 24 | 15. **[TypeScript](https://github.com/stephentian/daily-js/tree/master/15-TypeScript)** 25 | 16. **[Source Code](https://github.com/stephentian/daily-js/tree/master/16-Source%20Code)** 26 | 27 | ## 前言 28 | 29 | ### 简介 30 | 31 | JavaScript 和 C++、Java、C# 等语言一样,是一种语言。他和其它所有语言一样,拥有基本类型(字符,数字,布尔)与三大语句(顺序、条件、循环),基于JavaScript我们可以做很多事情,最重要的是 JavaScript 不仅仅运行于浏览器,还可以直接运行于操作系统(有兴趣的同学可以查看有关“windows脚本宿主”的信息) 32 | 33 | 我们平时所使用到的修改DOM的功能,仅仅是浏览器开发商提供给开发者所使用的. 那么只要浏览器开发商乐意,他们还可以提供更多的功能给我们使用。我们也可以理解为,我们写的JavaScript脚本是在浏览器厂商允许的范围内,对浏览器进行二次开发 34 | 35 | 若浏览器允许,JavaScript 还可以调用运行机器上的 ActiveX 组件(比如使用 Scripting.FileSystemObject 对客户端电脑上的文件进行各种操作). 遗憾的是,由于加载 ActiveX 组件可以变向的调用控件台等存在风险的行为,因此大部分浏览器已经禁止了这项操作 36 | 37 | ### 解释型语言和编译型语言 38 | 39 | JavaScript是一种解释型语言,解释型语言不需要编译也能立刻执行代码,非常适合用于变动性大的场景,比如浏览器,我们可以在任意一个时间将head中添加一个新的js引用,由于是解释型语言,浏览器能够立刻解释这段脚本并执行 40 | 41 | **解释型语言:** 42 | 程序不需要编译,程序在运行时才翻译成机器语言,每执 行一次都要翻译一次。因此效率比较低。比如 Basic 语言,专门有一个解释器能够直接执行 Basic 程序,每个语句都是执行的时候才翻译 43 | 44 | 解释型语言有 Python/JavaScript / Perl /Shell 等 45 | 46 | **编译型语言:** 47 | 程序在执行之前需要一个专门的编译过程,把程序编译成 为机器语言的文件,运行时不需要重新翻译,直接使用编译的结果就行了。程序执行效率高,依赖编译器,跨平台性差些。 48 | 49 | 编译型语言有 C、C++、Delphi 等 50 | 51 | **区别:** 52 | 53 | 解释型语言,执行速度慢、效率低;依靠解释器、跨平台性好 54 | 编译型语言,执行速度快、效率高;依靠编译器、跨平台性差些 55 | 56 | ### 兼容性问题 57 | 58 | JavaScript 语言就好比是我们说的“四川话”、“北京话”、“湖南话”等语言中的语法。只要按照这个语法的规则来,就是正确的语言,也就是说是正确的代码 59 | 60 | 电脑其实是一个笨家伙,他虽然看的懂语法,但他看不懂名词。如果规定电脑是 computer,那么他只知道 compouter 是电脑,PC 就不认识了。这就是兼容性 61 | 62 | 随着技术的发展,大家也正在不停地出台各种规范,希望能有一天所有浏览器都能识别完全一样的JavaScript代码 63 | 64 | **浏览器厂商比较常见的差异有:** 65 | 66 | 1. 创建XmlHttpRequest的方式不同 67 | 2. 创建Xml解析器的方式不同 68 | 3. innerText和innerHtml可能存在的差异 69 | 4. 是否提供Json序列化功能等 70 | --------------------------------------------------------------------------------