├── .gitignore ├── Handwritten ├── babel.config.js ├── package-lock.json ├── package.json ├── src │ ├── debounce.js │ ├── deepCopy.js │ ├── instance_of.js │ ├── new.js │ ├── shallowCopy.js │ └── throtte.js └── test │ ├── debounce.html │ ├── debounce.test.js │ ├── deepCopy.test.js │ ├── instance_of.test.js │ ├── new.test.js │ ├── shallowCopy.test.js │ ├── throttle.html │ └── throttle.test.js ├── JS ├── Async │ ├── async.js │ ├── data │ │ ├── age.txt │ │ ├── base.txt │ │ ├── info.txt │ │ ├── job.txt │ │ └── name.txt │ ├── generator.js │ ├── index.js │ ├── my_async.js │ ├── package-lock.json │ ├── package.json │ └── promise.js ├── data-type.js └── for.js ├── README.md ├── Security ├── CSRF │ ├── server.js │ ├── server2.js │ └── src │ │ ├── fake.html │ │ ├── fake1.html │ │ ├── fake2.html │ │ ├── fake3.html │ │ ├── fish.html │ │ ├── fish1.html │ │ ├── fish2.html │ │ ├── fish3.html │ │ ├── index.html │ │ ├── login.html │ │ ├── safe1.html │ │ ├── safe2.html │ │ └── safe3.html ├── README.md ├── XSS │ ├── server.js │ └── src │ │ ├── after.html │ │ ├── comments.html │ │ ├── comments2.html │ │ └── login.html ├── package-lock.json ├── package.json └── yarn.lock ├── eos-cli ├── .babelrc ├── README.md ├── bin │ └── www ├── dist │ ├── config.js │ ├── index.js │ ├── init.js │ ├── install.js │ ├── main.js │ ├── utils.js │ └── utils │ │ ├── common.js │ │ ├── constants.js │ │ ├── get.js │ │ ├── rc.js │ │ └── utils.js ├── package-lock.json ├── package.json └── src │ ├── config.js │ ├── index.js │ ├── init.js │ ├── main.js │ └── utils │ ├── constants.js │ ├── get.js │ └── rc.js ├── myreact-redux ├── counter │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── manifest.json │ │ └── robots.txt │ └── src │ │ ├── components │ │ ├── Counter-no-react-redux.js │ │ ├── Counter.js │ │ └── Page.js │ │ ├── index.js │ │ ├── react-redux │ │ ├── components │ │ │ ├── Provider.js │ │ │ └── connect.js │ │ ├── index.js │ │ └── utils │ │ │ ├── shallowEqual.js │ │ │ └── storeShape.js │ │ └── store │ │ ├── action-types.js │ │ ├── actions │ │ └── counter.js │ │ ├── index.js │ │ └── reducers │ │ ├── counter.js │ │ └── index.js └── todo │ ├── package.json │ ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt │ └── src │ ├── components │ ├── App.js │ ├── Footer.js │ ├── Link.js │ ├── Todo.js │ └── TodoList.js │ ├── containers │ ├── AddTodo.js │ ├── FilterLink.js │ └── VisibleTodoList.js │ ├── index.js │ ├── react-redux │ ├── components │ │ ├── Provider.js │ │ └── connect.js │ ├── index.js │ └── utils │ │ ├── shallowEqual.js │ │ └── storeShape.js │ ├── redux │ ├── applyMiddleware.js │ ├── bindActionCreators.js │ ├── combineReducers.js │ ├── compose.js │ ├── createStore.js │ └── index.js │ └── store │ ├── action-types.js │ ├── actions │ ├── index.js │ ├── todos.js │ └── visibilityFilter.js │ ├── index.js │ └── reducers │ ├── index.js │ ├── todos.js │ └── visibilityFilter.js └── myredux ├── to-redux ├── package.json ├── public │ └── index.html ├── src │ ├── index.js │ ├── index1.js │ └── index2.js └── yarn.lock └── to-redux2 ├── README.md ├── package.json ├── public └── index.html └── src ├── components ├── Counter.js └── Pannel.js ├── index.js ├── redux ├── applyMiddleware.js ├── bindActionCreators.js ├── combineReducers.js ├── compose.js ├── createStore.js └── index.js └── store ├── action-types.js ├── actions ├── counter.js └── theme.js ├── index.js └── reducers ├── counter.js ├── index.js └── theme.js /.gitignore: -------------------------------------------------------------------------------- 1 | tempCodeRunnerFile.js 2 | node_modules 3 | package-lock.js 4 | .DS_Store -------------------------------------------------------------------------------- /Handwritten/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { 6 | targets: { 7 | node: 'current', 8 | }, 9 | }, 10 | ], 11 | ] 12 | }; -------------------------------------------------------------------------------- /Handwritten/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Handwritten", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "jest" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": {}, 13 | "dependencies": { 14 | "@babel/core": "^7.4.5", 15 | "@babel/preset-env": "^7.4.5", 16 | "babel-jest": "^24.8.0", 17 | "jest": "^24.8.0", 18 | "lodash": "^4.17.11" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Handwritten/src/debounce.js: -------------------------------------------------------------------------------- 1 | function simpleDebounce(func, wait) { 2 | let timeout = null; 3 | /* 触发时,参数传给了 debounced */ 4 | return function debounced() { 5 | let args = arguments; 6 | if (timeout) clearTimeout(timeout); 7 | timeout = setTimeout(() => { 8 | /* this绑定和参数的传递, 9 | * 注意此处使用的是箭头函数,因此不需要在外层将 this 赋值给某变量 10 | */ 11 | func.apply(this, args); 12 | }, wait); 13 | } 14 | } 15 | 16 | 17 | function debounce(func, wait, immediate = true) { 18 | let timeout, result; 19 | // 延迟执行函数 20 | const later = (context, args) => setTimeout(() => { 21 | timeout = null;// 倒计时结束 22 | if (!immediate) { 23 | //执行回调 24 | result = func.apply(context, args); 25 | context = args = null; 26 | } 27 | }, wait); 28 | let debounced = function (...params) { 29 | if (!timeout) { 30 | timeout = later(this, params); 31 | if (immediate) { 32 | //立即执行 33 | result = func.apply(this, params); 34 | } 35 | } else { 36 | clearTimeout(timeout); 37 | //函数在每个等待时延的结束被调用 38 | timeout = later(this, params); 39 | } 40 | return result; 41 | } 42 | //提供在外部清空定时器的方法 43 | debounced.cancel = function () { 44 | clearTimeout(timer); 45 | timer = null; 46 | }; 47 | return debounced; 48 | }; 49 | 50 | export { simpleDebounce, debounce } -------------------------------------------------------------------------------- /Handwritten/src/deepCopy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param {*} obj 4 | * 未考虑循环引用的问题 5 | * 6 | */ 7 | function deepCopy(obj) { //递归拷贝 8 | if (obj instanceof RegExp) return new RegExp(obj); 9 | if (obj instanceof Date) return new Date(obj); 10 | if (obj === null || typeof obj !== 'object') { 11 | //如果不是复杂数据类型,直接返回 12 | return obj; 13 | } 14 | /** 15 | * 如果obj是数组,那么 new obj.constructor() 返回的是一个数组 16 | * 如果obj是对象,那么 new obj.constructor() 返回的是一个对象 17 | */ 18 | let t = new obj.constructor(); 19 | for (let key in obj) { 20 | //递归 21 | if (obj.hasOwnProperty(key)) { 22 | if (obj.hasOwnProperty(key)) {//是否是自身的属性 23 | t[key] = deepCopy(obj[key]); 24 | } 25 | } 26 | } 27 | return t; 28 | } 29 | 30 | 31 | 32 | 33 | 34 | function deepClone(obj, hash = new WeakMap()) { //递归拷贝 35 | if (obj instanceof RegExp) return new RegExp(obj); 36 | if (obj instanceof Date) return new Date(obj); 37 | if (obj === null || typeof obj !== 'object') { 38 | //如果不是复杂数据类型,直接返回 39 | return obj; 40 | } 41 | if (hash.has(obj)) { 42 | return hash.get(obj); 43 | } 44 | /** 45 | * 如果obj是数组,那么 obj.constructor 是 [Function: Array] 46 | * 如果obj是对象,那么 obj.constructor 是 [Function: Object] 47 | */ 48 | let t = new obj.constructor(); 49 | hash.set(obj, t); 50 | for (let key in obj) { 51 | //递归 52 | if (obj.hasOwnProperty(key)) {//是否是自身的属性 53 | t[key] = deepClone(obj[key], hash); 54 | } 55 | } 56 | return t; 57 | } 58 | 59 | export { deepCopy, deepClone }; 60 | 61 | -------------------------------------------------------------------------------- /Handwritten/src/instance_of.js: -------------------------------------------------------------------------------- 1 | export default function instance_of(L, R) { 2 | let prototype = R.prototype; 3 | L = L.__proto__; 4 | while (true) { 5 | if (L === prototype) { 6 | return true; 7 | } else if (L === null) { 8 | //Object.__proto__.__proto__ === null;原型链的顶端 9 | return false 10 | } 11 | L = L.__proto__; 12 | } 13 | } -------------------------------------------------------------------------------- /Handwritten/src/new.js: -------------------------------------------------------------------------------- 1 | export default function _new() { 2 | let target = {}; 3 | //第一个参数是构造函数 4 | let [constructor, ...args] = [...arguments]; 5 | //执行[[原型]]连接;target 是 constructor 的实例 6 | target.__proto__ = constructor.prototype; 7 | //执行构造函数,将属性或方法添加到创建的空对象上 8 | let result = constructor.apply(target, args); 9 | if (result && (typeof (result) == "object" || typeof (result) == "function")) { 10 | //如果构造函数执行的结构返回的是一个对象,那么返回这个对象 11 | return result; 12 | } 13 | //如果构造函数返回的不是一个对象,返回创建的新对象 14 | return target; 15 | } 16 | 17 | 18 | -------------------------------------------------------------------------------- /Handwritten/src/shallowCopy.js: -------------------------------------------------------------------------------- 1 | export default function shallowCopy(obj) { 2 | //obj是个复杂数据类型 3 | var result = new obj.constructor(); 4 | for(var key in obj) { 5 | if(obj.hasOwnProperty(key)) { 6 | //仅拷贝对象自身的属性,不拷贝原型链上的属性 7 | result[key] = obj[key]; 8 | } 9 | } 10 | return result; 11 | } 12 | -------------------------------------------------------------------------------- /Handwritten/src/throtte.js: -------------------------------------------------------------------------------- 1 | /** 时间戳 */ 2 | function tampThrottle(func, wait) { 3 | var previous = 0; 4 | function throttled() { 5 | var context = this; 6 | var args = arguments; 7 | var now = Date.now(); 8 | if (now > previous + wait) { 9 | func.apply(context, args); 10 | previous = now; 11 | } 12 | } 13 | //防抖函数最终返回的是一个函数 14 | return throttled; 15 | } 16 | 17 | /** 定时器实现 */ 18 | function timeThrottle(func, wait) { 19 | let timeout; 20 | return function throttled() { 21 | let args = arguments; 22 | if (!timeout) { 23 | timeout = setTimeout(() => { 24 | func.apply(this, args); 25 | clearTimeout(timeout); 26 | timeout = null; 27 | }, wait); 28 | } 29 | } 30 | } 31 | 32 | function throttle(func, wait, options) { 33 | var timeout, context, args, result; 34 | var previous = 0; 35 | if (!options) options = {}; 36 | 37 | var later = function () { 38 | previous = options.leading === false ? 0 : (Date.now() || new Date().getTime()); 39 | timeout = null; 40 | result = func.apply(context, args); 41 | if (!timeout) context = args = null; 42 | }; 43 | 44 | var throttled = function () { 45 | var now = Date.now() || new Date().getTime(); 46 | if (!previous && options.leading === false) previous = now; 47 | var remaining = wait - (now - previous); 48 | context = this; 49 | args = arguments; 50 | if (remaining <= 0 || remaining > wait) { 51 | if (timeout) { 52 | clearTimeout(timeout); 53 | timeout = null; 54 | } 55 | previous = now; 56 | result = func.apply(context, args); 57 | if (!timeout) context = args = null; 58 | } else if (!timeout && options.trailing !== false) { 59 | timeout = setTimeout(later, remaining); 60 | } 61 | return result; 62 | }; 63 | 64 | throttled.cancel = function () { 65 | clearTimeout(timeout); 66 | previous = 0; 67 | timeout = context = args = null; 68 | }; 69 | 70 | return throttled; 71 | } 72 | 73 | export { tampThrottle, timeThrottle, throttle } -------------------------------------------------------------------------------- /Handwritten/test/debounce.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 26 | 27 | 28 | 29 |
30 |

点击触发事件

31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /Handwritten/test/debounce.test.js: -------------------------------------------------------------------------------- 1 | import { simpleDebounce, debounce } from '../src/debounce'; 2 | import { EventEmitter } from 'events'; 3 | 4 | /** 5 | * 每 300 ms 触发一次 down 事件,共触发 3 次 6 | * 防抖的间隔时间是 200ms,因此 frequency 被调用了 3次 7 | */ 8 | test('simpleDebounce/事件处理函数被调用3次 ', (done) => { 9 | let myEvent = new EventEmitter(); 10 | /** 高频 down 事件处理函数 */ 11 | const frequency = jest.fn(); 12 | /** 间隔时间为 200ms */ 13 | let debounceFrequency = simpleDebounce(frequency, 200); 14 | 15 | myEvent.on('down', () => { 16 | debounceFrequency('hello'); 17 | }); 18 | /** 共触发 3 次 */ 19 | let i = 0; 20 | function delay(callback, interval) { 21 | let timer = setTimeout(() => { 22 | if (i < 3) { 23 | myEvent.emit('down'); 24 | delay(callback, interval); 25 | i++; 26 | } else { 27 | clearTimeout(timer); 28 | callback(); 29 | myEvent.removeAllListeners('down'); 30 | } 31 | }, interval); 32 | } 33 | 34 | /** 35 | * 每 300 ms 触发一次 down 事件,共触发 3 次 36 | * 防抖的间隔时间是 200ms,因此 frequency 被调用了 3次 37 | */ 38 | 39 | delay(() => { 40 | expect(frequency.mock.calls.length).toBe(3); 41 | done(); 42 | }, 300); 43 | 44 | }); 45 | 46 | 47 | /** 48 | * 每 100 ms 触发一次 down 事件 49 | * 防抖的间隔时间是 200ms,因此 frequency2 被调用了 0 次 50 | */ 51 | test('simpleDebounce/ 事件处理函数被调用0次 ', (done) => { 52 | let myEvent = new EventEmitter(); 53 | /** 高频 down 事件处理函数 */ 54 | const frequency2 = jest.fn(); 55 | /** 防抖间隔时间为 200ms */ 56 | let debounceFrequency = simpleDebounce(frequency2, 200); 57 | myEvent.on('down', () => { 58 | debounceFrequency('hello'); 59 | }); 60 | /** 共触发 3 次 */ 61 | let i = 0; 62 | function delay(callback, interval) { 63 | let timer = setTimeout(() => { 64 | if (i < 3) { 65 | myEvent.emit('down'); 66 | delay(callback, interval); 67 | i++; 68 | } else { 69 | clearTimeout(timer); 70 | callback(); 71 | myEvent.removeAllListeners('down'); 72 | } 73 | }, interval); 74 | } 75 | 76 | /** 77 | * 每 100 ms 触发一次 down 事件 78 | * 防抖的间隔时间是 200ms,因此 frequency2 被调用了 0 次 79 | */ 80 | //有时,这并不是我们想要的结果,我们可能希望最后一次/第一次的事件触发能够被响应 81 | delay(() => { 82 | expect(frequency2.mock.calls.length).toBe(0); 83 | done(); 84 | }, 100); 85 | }); 86 | 87 | /** 88 | * 每 300 ms 触发一次 down 事件,共触发 3 次 89 | * 防抖的间隔时间是 200ms,因此 frequency3 被调用了 3次 90 | */ 91 | test('debounce/ 事件处理函数被调用3次 ', (done) => { 92 | let myEvent = new EventEmitter(); 93 | /** 高频 down 事件处理函数 */ 94 | const frequency3 = jest.fn(); 95 | /** 防抖间隔时间为 200ms */ 96 | let debounceFrequency = simpleDebounce(frequency3, 200); 97 | myEvent.on('down', () => { 98 | debounceFrequency('hello'); 99 | }); 100 | /** 共触发 3 次 */ 101 | let i = 0; 102 | function delay(callback, interval) { 103 | let timer = setTimeout(() => { 104 | if (i < 3) { 105 | myEvent.emit('down'); 106 | delay(callback, interval); 107 | i++; 108 | } else { 109 | clearTimeout(timer); 110 | callback(); 111 | myEvent.removeAllListeners('down'); 112 | } 113 | }, interval); 114 | } 115 | 116 | /** 117 | * 每 100 ms 触发一次 down 事件 118 | * 防抖的间隔时间是 200ms,因此 frequency3 被调用了 0 次 119 | */ 120 | delay(() => { 121 | expect(frequency3.mock.calls.length).toBe(3); 122 | done(); 123 | }, 300); 124 | }); 125 | 126 | 127 | /** 128 | * 每 300 ms 触发一次 down 事件,共触发 3 次 129 | * 防抖的间隔时间是 100ms, 第一次触发立即执行, frequency4 被调用了 1次 130 | */ 131 | 132 | test('debounce/ 事件处理函数被调用1次 ', (done) => { 133 | let myEvent = new EventEmitter(); 134 | /** 高频 down 事件处理函数 */ 135 | const frequency4 = jest.fn(); 136 | /** 防抖间隔时间为 200ms/立即执行 */ 137 | let debounceFrequency = debounce(frequency4, 200, true); 138 | myEvent.on('down', () => { 139 | debounceFrequency('Yvette'); 140 | }); 141 | /** 共触发 3 次 */ 142 | let i = 0; 143 | function delay(callback, interval) { 144 | let timer = setTimeout(() => { 145 | if (i < 3) { 146 | myEvent.emit('down'); 147 | delay(callback, interval); 148 | i++; 149 | } else { 150 | clearTimeout(timer); 151 | callback(); 152 | myEvent.removeAllListeners('down'); 153 | } 154 | }, interval); 155 | } 156 | 157 | /** 158 | * 每 100 ms 触发一次 down 事件 159 | * 防抖的间隔时间是 200ms,第一次触发立即执行,frequency4 仅被调用了 1 次 160 | */ 161 | delay(() => { 162 | expect(frequency4.mock.calls.length).toBe(1); 163 | done(); 164 | }, 100); 165 | }); -------------------------------------------------------------------------------- /Handwritten/test/deepCopy.test.js: -------------------------------------------------------------------------------- 1 | import { deepCopy, deepClone } from '../src/deepCopy'; 2 | import { cloneDeep } from '../node_modules/lodash/lodash'; 3 | 4 | test('deepCopy 普通对象', () => { 5 | let source = { 6 | name: 'Yvette', 7 | age: 20, 8 | time: new Date(), 9 | hobbies: ['reading', 'photography'], 10 | sayHi: function () { 11 | return 'Hi'; 12 | } 13 | }; 14 | let target = deepCopy(source); 15 | target.name = 'Tom'; 16 | target.hobbies.push('coding'); 17 | //深拷贝意味着 target 和 source 是完全独立的,不会相互影响 18 | expect(source.name).toBe('Yvette'); 19 | expect(source.hobbies).toEqual(['reading', 'photography']); 20 | expect(target.sayHi()).toBe('Hi'); //函数正常拷贝 21 | expect(target.time instanceof Date).toBe(true);//Date数据类型正常 22 | 23 | }); 24 | 25 | test('deepCopy 原型链上属性拷贝', () => { 26 | function SupSuper() { } 27 | SupSuper.prototype.from = 'China'; 28 | 29 | function Super() { } 30 | Super.prototype = new SupSuper(); 31 | Super.prototype.location = 'NanJing'; 32 | 33 | function Child(name, age, hobbies) { 34 | this.name = name; 35 | this.age = age; 36 | this.hobbies = hobbies; 37 | } 38 | Child.prototype = new Super(); 39 | let obj = new Child('Yvette', 18, ['reading', 'photography']); 40 | let obj2 = deepCopy(obj); 41 | expect(obj2).toEqual({ 42 | name: 'Yvette', 43 | age: 18, 44 | hobbies: ['reading', 'photography'] 45 | }); 46 | 47 | /**构造函数原型上的属性可以拷贝 */ 48 | expect(obj2.from).toBe('China'); 49 | /**Super原型上的属性获取不到,返回Undefined */ 50 | expect(obj2.location).toBeUndefined(); 51 | /**obj是能从原型上获取到location属性的 */ 52 | expect(obj.location).toBe('NanJing'); 53 | }); 54 | 55 | test('deepCopy 不支持循环引用', () => { 56 | let obj = { 57 | name: 'Yvette', 58 | age: 20 59 | } 60 | obj.info = obj; 61 | //抛出异常 62 | // expect(deepCopy(obj)).toThrow(); 63 | }); 64 | 65 | 66 | test('deepClone 普通对象', () => { 67 | let source = { 68 | name: 'Yvette', 69 | age: 20, 70 | time: new Date(), 71 | hobbies: ['reading', 'photography'], 72 | sayHi: function () { 73 | return 'Hi'; 74 | } 75 | }; 76 | let target = deepClone(source); 77 | target.name = 'Tom'; 78 | target.hobbies.push('coding'); 79 | //深拷贝意味着 target 和 source 是完全独立的,不会相互影响 80 | expect(source.name).toBe('Yvette'); 81 | expect(source.hobbies).toEqual(['reading', 'photography']); 82 | expect(target.sayHi()).toBe('Hi'); //函数正常拷贝 83 | expect(target.time instanceof Date).toBe(true);//Date数据类型正常 84 | 85 | }); 86 | 87 | test('deepClone 原型链上属性拷贝', () => { 88 | function SupSuper() { } 89 | SupSuper.prototype.from = 'China'; 90 | 91 | function Super() { } 92 | Super.prototype = new SupSuper(); 93 | Super.prototype.location = 'NanJing'; 94 | 95 | function Child(name, age, hobbies) { 96 | this.name = name; 97 | this.age = age; 98 | this.hobbies = hobbies; 99 | } 100 | Child.prototype = new Super(); 101 | let obj = new Child('Yvette', 18, ['reading', 'photography']); 102 | let obj2 = deepClone(obj); 103 | expect(obj2).toEqual({ 104 | name: 'Yvette', 105 | age: 18, 106 | hobbies: ['reading', 'photography'] 107 | }); 108 | 109 | /**构造函数原型上的属性可以拷贝 */ 110 | expect(obj2.from).toBe('China'); 111 | /**Super原型上的属性获取不到,返回Undefined */ 112 | expect(obj2.location).toBeUndefined(); 113 | /**obj是能从原型上获取到location属性的 */ 114 | expect(obj.location).toBe('NanJing'); 115 | }); 116 | 117 | test('deepClone 循环引用', () => { 118 | let obj = { 119 | name: 'Yvette', 120 | age: 20 121 | } 122 | obj.info = obj; 123 | //和 lodash 的 cloneDeep(obj) 结果一致 124 | expect(deepClone(obj)).toEqual(cloneDeep(obj)); 125 | }); -------------------------------------------------------------------------------- /Handwritten/test/instance_of.test.js: -------------------------------------------------------------------------------- 1 | import instance_of from '../src/instance_of'; 2 | 3 | test(' instance_of(L, R) ', () => { 4 | function A(){} 5 | function B(){} 6 | function C(){} 7 | A.prototype = new B(); 8 | let a = new A(); 9 | 10 | expect(instance_of({a:1}, Object)).toBe(true); 11 | expect(instance_of(A, Function)).toBe(A instanceof Function); 12 | expect(instance_of(a, C)).toBe(a instanceof C); 13 | expect(instance_of(a, A)).toBe(a instanceof A); 14 | expect(instance_of(a, B)).toBe(a instanceof B); 15 | }) 16 | -------------------------------------------------------------------------------- /Handwritten/test/new.test.js: -------------------------------------------------------------------------------- 1 | import _new from '../src/new'; 2 | 3 | test('_new 无返回值', () => { 4 | function Parent(name, age) { 5 | this.name = name; 6 | this.age = age; 7 | } 8 | expect(_new(Parent, 'Yvette', 22)).toEqual({name: 'Yvette', age: 22}); 9 | expect(_new(Parent, 'Yvette', 22)).toEqual(new Parent('Yvette', 22)); 10 | }); 11 | 12 | test('_new 返回普通值', () => { 13 | function Parent(name, age) { 14 | this.name = name; 15 | this.age = age; 16 | return 'hello'; 17 | } 18 | expect(_new(Parent, 'Yvette', 22)).toEqual({name: 'Yvette', age: 22}); 19 | expect(_new(Parent, 'Yvette', 22)).toEqual(new Parent('Yvette', 22)); 20 | }); 21 | 22 | test('_new 返回对象', () => { 23 | function Parent(name, age) { 24 | this.name = name; 25 | this.age = age; 26 | return { 27 | info: 'nothing' 28 | } 29 | } 30 | //当构造函数返回一个Object时;a = new Parent(); a 是 Parent 返回的Object 31 | expect(_new(Parent, 'Yvette', 22)).toEqual({info: 'nothing'}); 32 | expect(_new(Parent, 'Yvette', 22)).toEqual(new Parent('Yvette', 22)); 33 | }); 34 | 35 | test('_new 返回函数', () => { 36 | function Parent(name, age) { 37 | this.name = name; 38 | this.age = age; 39 | return function Hello(){ 40 | return 'hello'; 41 | } 42 | } 43 | //当构造函数返回一个函数时;a = new Parent(); a 是 Parent返回的函数 44 | expect(_new(Parent, 'Yvette', 22)()).toEqual(new Parent('Yvette', 22)()); 45 | }); 46 | -------------------------------------------------------------------------------- /Handwritten/test/shallowCopy.test.js: -------------------------------------------------------------------------------- 1 | import shallowCopy from '../src/shallowCopy'; 2 | 3 | test('shallowCopy 字面量对象', () => { 4 | let source = { 5 | name: 'Yvette', 6 | age: 20, 7 | hobbies: ['reading', 'photography'], 8 | sayHi: function() { 9 | return 'Hi'; 10 | } 11 | }; 12 | let target = shallowCopy(source); 13 | /** 14 | * 浅拷贝意味着只复制一层,那么属性值是原始数据类型的,目标对象和源对象之间互不影响 15 | * 16 | * 如果属性值是复杂数据类型,复制的是引用地址,目标对象和源对象的属性值指向的是同一块栈内存 17 | */ 18 | 19 | target.name = 'Tom'; 20 | target.hobbies.push('coding'); //源对象的hoobies属性值也发生了变化 21 | 22 | expect(source.name).toBe('Yvette'); //基本数据类型,二者不相干 23 | expect(source.hobbies).toEqual(['reading', 'photography', 'coding']);//属性值是复杂数据类型时,互相影响 24 | expect(target.sayHi()).toEqual('Hi'); 25 | }); 26 | 27 | 28 | test('shallowCopy 数组', () => { 29 | let source = ['Yvette', 'age', ['reading', 'photography']]; 30 | let target = shallowCopy(source); 31 | 32 | target[0] = 'Tom'; 33 | target[2].push('coding'); 34 | 35 | expect(source[0]).toBe('Yvette'); //基本数据类型,二者不相干 36 | expect(source[2]).toEqual(['reading', 'photography', 'coding']);//属性值是复杂数据类型时,互相影响 37 | }); -------------------------------------------------------------------------------- /Handwritten/test/throttle.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 26 | 27 | 28 | 29 |
30 |

点击触发事件

31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /Handwritten/test/throttle.test.js: -------------------------------------------------------------------------------- 1 | import { tampThrottle, timeThrottle, throttle } from '../src/throtte'; 2 | 3 | import { EventEmitter } from 'events'; 4 | 5 | /** 6 | * 每 120 ms 触发一次 down 事件,共触发 5 次 7 | * 节流的间隔时间是 100ms,因此 frequency 被调用了 5 次 8 | */ 9 | test('时间戳版 throttle/事件处理函数被调用5次 ', (done) => { 10 | let myEvent = new EventEmitter(); 11 | /** 高频 down 事件处理函数 */ 12 | const frequency = jest.fn(); 13 | /** 间隔时间为 100ms */ 14 | let debounceFrequency = tampThrottle(frequency, 100); 15 | 16 | myEvent.on('down', () => { 17 | debounceFrequency('hello'); 18 | }); 19 | /** 共触发 5 次 */ 20 | let i = 0; 21 | function delay(callback, interval) { 22 | let timer = setTimeout(() => { 23 | if (i < 5) { 24 | myEvent.emit('down'); 25 | delay(callback, interval); 26 | i++; 27 | } else { 28 | clearTimeout(timer); 29 | callback(); 30 | myEvent.removeAllListeners('down'); 31 | } 32 | }, interval); 33 | } 34 | 35 | /** 36 | * 每 120 ms 触发一次 down 事件,共触发 5 次 37 | * 节流的间隔时间是 100ms,因此 frequency 被调用了 5 次 38 | */ 39 | 40 | delay(() => { 41 | expect(frequency.mock.calls.length).toBe(5); 42 | done(); 43 | }, 120); 44 | 45 | }); 46 | 47 | 48 | /** 49 | * 每 50 ms 触发一次 down 事件,共触发5次 50 | * 节流的间隔时间是 100ms,因此 frequency2 被调用了 3 次 51 | * 我们期望的是调用3次,但是最后一次被忽略了 52 | */ 53 | test('时间戳版 throttle/ 事件处理函数被调用2次 ', (done) => { 54 | let myEvent = new EventEmitter(); 55 | /** 高频 down 事件处理函数 */ 56 | const frequency2 = jest.fn(); 57 | /** 节流间隔时间为 200ms */ 58 | let debounceFrequency = tampThrottle(frequency2, 100); 59 | myEvent.on('down', () => { 60 | debounceFrequency('hello'); 61 | }); 62 | /** 共触发 5 次 */ 63 | let i = 0; 64 | function delay(callback, interval) { 65 | let timer = setTimeout(() => { 66 | if (i < 5) { 67 | myEvent.emit('down'); 68 | delay(callback, interval); 69 | i++; 70 | } else { 71 | clearTimeout(timer); 72 | callback(); 73 | myEvent.removeAllListeners('down'); 74 | } 75 | }, interval); 76 | } 77 | 78 | /** 79 | * 每 50 ms 触发一次 down 事件,共触发 5 次 80 | * 节流的间隔时间是 100ms,因此 frequency2 调用了 3 次 81 | * 分别为 50ms 150ms 250ms 82 | */ 83 | delay(() => { 84 | expect(frequency2.mock.calls.length).toBe(3); 85 | done(); 86 | }, 50); 87 | }); 88 | 89 | /** 90 | * 每 120 ms 触发一次 down 事件,共触发 5 次 91 | * 节流的间隔时间是 100ms,因此 frequency3 被调用了 5 次 92 | */ 93 | test('定时器版 trottle/ 事件处理函数被调用3次 ', (done) => { 94 | let myEvent = new EventEmitter(); 95 | /** 高频 down 事件处理函数 */ 96 | const frequency3 = jest.fn(); 97 | /** 防抖间隔时间为 200ms */ 98 | let debounceFrequency = timeThrottle(frequency3, 100); 99 | myEvent.on('down', () => { 100 | debounceFrequency('hello'); 101 | }); 102 | /** 共触发 5 次 */ 103 | let i = 0; 104 | function delay(callback, interval) { 105 | let timer = setTimeout(() => { 106 | if (i < 5) { 107 | myEvent.emit('down'); 108 | delay(callback, interval); 109 | i++; 110 | } else { 111 | clearTimeout(timer); 112 | callback(); 113 | myEvent.removeAllListeners('down'); 114 | } 115 | }, interval); 116 | } 117 | 118 | /** 119 | * 每 120 ms 触发一次 down 事件 120 | * 防抖的间隔时间是 100ms,因此 frequency3 被调用了 5 次 121 | */ 122 | delay(() => { 123 | expect(frequency3.mock.calls.length).toBe(5); 124 | done(); 125 | }, 120); 126 | }); 127 | 128 | 129 | /** 130 | * 每 50 ms 触发一次 down 事件,共触发 5 次 131 | * 节流的间隔时间是 100ms, 第一次触发立即执行, frequency4 被调用了 2 次 132 | * 我们期望的是调用3次,但是最后一次被忽略了 133 | */ 134 | 135 | test('定时器版 trottle/ 事件处理函数被调用2次 ', (done) => { 136 | let myEvent = new EventEmitter(); 137 | /** 高频 down 事件处理函数 */ 138 | const frequency4 = jest.fn(); 139 | /** 防抖间隔时间为 200ms/立即执行 */ 140 | let debounceFrequency = timeThrottle(frequency4, 100, true); 141 | myEvent.on('down', () => { 142 | debounceFrequency('Yvette'); 143 | }); 144 | /** 共触发 5 次 */ 145 | let i = 0; 146 | function delay(callback, interval) { 147 | let timer = setTimeout(() => { 148 | if (i < 5) { 149 | myEvent.emit('down'); 150 | delay(callback, interval); 151 | i++; 152 | } else { 153 | clearTimeout(timer); 154 | callback(); 155 | myEvent.removeAllListeners('down'); 156 | } 157 | }, interval); 158 | } 159 | 160 | /** 161 | * 每 50 ms 触发一次 down 事件,共触发 5 次 162 | * 防抖的间隔时间是 100ms,第一次触发立即执行,frequency4 被调用了 2 次 163 | * 分别为 150ms 250ms(非立即执行) 164 | */ 165 | delay(() => { 166 | expect(frequency4.mock.calls.length).toBe(2); 167 | done(); 168 | }, 50); 169 | }); 170 | 171 | 172 | 173 | 174 | /** 175 | * 每 120 ms 触发一次 down 事件,共触发 5 次 176 | * 节流的间隔时间是 100ms,因此 frequency5 被调用了 5 次 177 | */ 178 | test('定时器版 trottle/ 事件处理函数被调用3次 ', (done) => { 179 | let myEvent = new EventEmitter(); 180 | /** 高频 down 事件处理函数 */ 181 | const frequency5 = jest.fn(); 182 | /** 防抖间隔时间为 200ms */ 183 | let debounceFrequency = throttle(frequency5, 100); 184 | myEvent.on('down', () => { 185 | debounceFrequency('hello'); 186 | }); 187 | /** 共触发 5 次 */ 188 | let i = 0; 189 | function delay(callback, interval) { 190 | let timer = setTimeout(() => { 191 | if (i < 5) { 192 | myEvent.emit('down'); 193 | delay(callback, interval); 194 | i++; 195 | } else { 196 | clearTimeout(timer); 197 | callback(); 198 | myEvent.removeAllListeners('down'); 199 | } 200 | }, interval); 201 | } 202 | 203 | /** 204 | * 每 120 ms 触发一次 down 事件 205 | * 防抖的间隔时间是 100ms,因此 frequency3 被调用了 5 次 206 | */ 207 | delay(() => { 208 | expect(frequency5.mock.calls.length).toBe(5); 209 | done(); 210 | }, 120); 211 | }); 212 | 213 | 214 | /** 215 | * 每 50 ms 触发一次 down 事件,共触发 5 次 216 | * 节流的间隔时间是 100ms, 第一次触发立即执行, frequency6 被调用了 3 次 217 | * 我们期望的是调用3次,但是最后一次被忽略了 218 | */ 219 | 220 | test('定时器版 trottle/ 事件处理函数被调用2次 ', (done) => { 221 | let myEvent = new EventEmitter(); 222 | /** 高频 down 事件处理函数 */ 223 | const frequency6 = jest.fn(); 224 | /** 防抖间隔时间为 200ms/立即执行 */ 225 | let debounceFrequency = throttle(frequency6, 100, true); 226 | myEvent.on('down', () => { 227 | debounceFrequency('Yvette'); 228 | }); 229 | /** 共触发 5 次 */ 230 | let i = 0; 231 | function delay(callback, interval) { 232 | let timer = setTimeout(() => { 233 | if (i < 5) { 234 | myEvent.emit('down'); 235 | delay(callback, interval); 236 | i++; 237 | } else { 238 | clearTimeout(timer); 239 | callback(); 240 | myEvent.removeAllListeners('down'); 241 | } 242 | }, interval); 243 | } 244 | 245 | /** 246 | * 每 50 ms 触发一次 down 事件,共触发 5 次 247 | * 防抖的间隔时间是 100ms,第一次触发立即执行,frequency4 被调用了 3 次 248 | */ 249 | delay(() => { 250 | expect(frequency6.mock.calls.length).toBe(3); 251 | done(); 252 | }, 50); 253 | }); -------------------------------------------------------------------------------- /JS/Async/async.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const bluebird = require('bluebird'); 3 | const readFile = bluebird.promisify(fs.readFile); 4 | 5 | 6 | async function read() { 7 | let info = await readFile('./JS/Async/data/info.txt', 'utf-8'); 8 | let base = await readFile(info, 'utf-8'); 9 | let age = await readFile(base, 'utf-8'); 10 | return age; 11 | } 12 | 13 | read().then((data) => { 14 | console.log(data); 15 | }).catch(err => { 16 | console.log(err); 17 | }); -------------------------------------------------------------------------------- /JS/Async/data/age.txt: -------------------------------------------------------------------------------- 1 | 22 -------------------------------------------------------------------------------- /JS/Async/data/base.txt: -------------------------------------------------------------------------------- 1 | ./JS/Async/data/age.txt -------------------------------------------------------------------------------- /JS/Async/data/info.txt: -------------------------------------------------------------------------------- 1 | ./JS/Async/data/base.txt -------------------------------------------------------------------------------- /JS/Async/data/job.txt: -------------------------------------------------------------------------------- 1 | engineer -------------------------------------------------------------------------------- /JS/Async/data/name.txt: -------------------------------------------------------------------------------- 1 | Yvette -------------------------------------------------------------------------------- /JS/Async/generator.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const bluebird = require('bluebird'); 3 | const readFile = bluebird.promisify(fs.readFile); 4 | 5 | /** 6 | * 读取A--->读取B--->读取C 7 | */ 8 | function* read() { 9 | let info = yield readFile('./JS/Async/data/info.txt', 'utf-8'); 10 | let base = yield readFile(info, 'utf-8'); 11 | let age = yield readFile(base, 'utf-8'); 12 | return age; 13 | } 14 | 15 | let it = read(); 16 | let { value, done } = it.next(); 17 | value.then((data) => { 18 | let { value, done } = it.next(data); //data赋值给了 info 19 | value.then((data) => { 20 | let { value, done } = it.next(data); //data赋值给了 base 21 | value.then((data) => { 22 | let { value, done } = it.next(data); //data赋值给base 23 | console.log(value); //输出22 24 | }); 25 | }); 26 | }); 27 | 28 | 29 | // 引入co 30 | 31 | const co = require('co'); 32 | co(read()).then(data => { 33 | console.log(data); //输出22 34 | }).catch(err => { 35 | console.log(err); 36 | }); 37 | 38 | 39 | /** 40 | * 自己实现一个 co 41 | * 接受一个迭代器 42 | * 43 | */ 44 | 45 | function my_co (it) { 46 | return new Promise((resolve, reject) => { 47 | function next(data) { 48 | let {value, done} = it.next(data); 49 | if(!done) { 50 | value.then(val => { 51 | next(val); 52 | }, reject); 53 | }else{ 54 | resolve(value); 55 | } 56 | 57 | } 58 | next(); 59 | }); 60 | } 61 | 62 | my_co(read()).then(data => { 63 | console.log(data); //输出22 64 | }); -------------------------------------------------------------------------------- /JS/Async/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | /** 4 | * Promise 之前 5 | * 6 | * 如果使用回调函数实现多个异步并行执行,同一时刻获取最终结果 7 | * 可以借助 发布订阅/观察者模式 8 | */ 9 | 10 | let pubsub = { 11 | arry: [], 12 | emit() { 13 | this.arry.forEach(fn => fn()); 14 | }, 15 | on(fn) { 16 | this.arry.push(fn); 17 | } 18 | } 19 | 20 | let data = []; 21 | pubsub.on(() => { 22 | if(data.length === 3) { 23 | console.log(data); //[ '22', 'Yvette', 'engineer' ];数组顺序随机 24 | } 25 | }); 26 | fs.readFile('./JS/Async/data/age.txt', 'utf-8', (err, value) => { 27 | data.push(value); 28 | pubsub.emit(); 29 | }); 30 | fs.readFile('./JS/Async/data/name.txt', 'utf-8', (err, value) => { 31 | data.push(value); 32 | pubsub.emit(); 33 | }); 34 | fs.readFile('./JS/Async/data/job.txt', 'utf-8', (err, value) => { 35 | data.push(value); 36 | pubsub.emit(); 37 | }); 38 | 39 | 40 | 41 | 42 | /** 43 | * 将 fs.readFile 包装成promise接口 44 | */ 45 | function read(url) { 46 | return new Promise((resolve, reject) => { 47 | fs.readFile(url, 'utf8', (err, data) => { 48 | if(err) reject(err); 49 | resolve(data); 50 | }); 51 | }); 52 | } 53 | 54 | /** 55 | * 使用 Promise 56 | * 57 | * 通过 Promise.all 可以实现多个异步并行执行,同一时刻获取最终结果的问题 58 | */ 59 | 60 | Promise.all([ 61 | read('./JS/Async/data/age.txt'), 62 | read('./JS/Async/data/name.txt'), 63 | read('./JS/Async/data/job.txt') 64 | ]).then(data => { 65 | console.log(data); //[ '22', 'Yvette', 'engineer' ];数组顺序随机 66 | }).catch(err => console.log(err)); 67 | 68 | /** 69 | * 使用 Async/Await 70 | */ 71 | 72 | async function readAsync() { 73 | let data = await Promise.all([ 74 | read('./JS/Async/data/age.txt'), 75 | read('./JS/Async/data/name.txt'), 76 | read('./JS/Async/data/job.txt') 77 | ]); 78 | return data; 79 | } 80 | 81 | readAsync().then(data => { 82 | console.log(data); //[ '22', 'Yvette', 'engineer' ];数组顺序随机 83 | }); -------------------------------------------------------------------------------- /JS/Async/my_async.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const bluebird = require('bluebird'); 3 | const readFile = bluebird.promisify(fs.readFile); 4 | const co = require('co'); 5 | /** 6 | * 假设要实现以下功能: 7 | * 先读取A--->再读取B--->最后读取C 8 | */ 9 | function* read() { 10 | let info = yield readFile('./JS/Async/data/info.txt', 'utf-8'); 11 | let base = yield readFile(info, 'utf-8'); 12 | let age = yield readFile(base, 'utf-8'); 13 | return age; 14 | } 15 | 16 | /** 17 | * 不考虑封装的情况,我们可以写出下面的代码 18 | */ 19 | // let it = read(); 20 | // let { value, done } = it.next(); 21 | // value.then((data) => { 22 | // let { value, done } = it.next(data); //data赋值给了 info 23 | // value.then((data) => { 24 | // let { value, done } = it.next(data); //data赋值给了 base 25 | // value.then((data) => { 26 | // let { value, done } = it.next(data); //data赋值给base 27 | // //此时done为false 28 | // return value; 29 | // }); 30 | // }); 31 | // }); 32 | 33 | /** 34 | * 可以看出上面的代码,其实是重复的,我们可以进行封装 35 | */ 36 | 37 | function my_co(it) { 38 | return new Promise((resolve, reject) => { 39 | function next(data) { 40 | try { 41 | var { value, done } = it.next(data); 42 | 43 | }catch(e){ 44 | return reject(e); 45 | } 46 | if (!done) { 47 | //done为true,表示迭代完成 48 | //value 不一定是 Promise,可能是一个普通值。使用 Promise.resolve 进行包装。 49 | Promise.resolve(value).then(val => { 50 | next(val); 51 | }, reject); 52 | } else { 53 | resolve(value); 54 | } 55 | } 56 | next(); //执行一次next 57 | }); 58 | } 59 | function* test() { 60 | yield new Promise((resolve, reject) => { 61 | setTimeout(resolve, 100); 62 | }); 63 | yield new Promise((resolve, reject) => { 64 | // throw Error(1); 65 | resolve(10) 66 | }); 67 | yield 10; 68 | return 1000; 69 | } 70 | 71 | my_co(test()).then(data => { 72 | console.log(data); //输出22 73 | }).catch((err) => { 74 | console.log('err: ', err); 75 | }); 76 | 77 | 78 | my_co(read()).then(data => { 79 | console.log(data); 80 | }).catch((err) => { 81 | console.log('err: ', err); 82 | }); 83 | 84 | 85 | /** 86 | * async 是 await 的语法糖。 87 | * 以上需要使用 async/await 实现 88 | */ 89 | 90 | async function readAsync() { 91 | let info = await readFile('./JS/Async/data/info.txt', 'utf-8'); 92 | let base = await readFile(info, 'utf-8'); 93 | let age = await readFile(base, 'utf-8'); 94 | return age; 95 | } 96 | 97 | readAsync().then((data) => { 98 | console.log('async: ' + data); 99 | }).catch(err => { 100 | console.log(err); 101 | }); -------------------------------------------------------------------------------- /JS/Async/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "async", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "bluebird": { 8 | "version": "3.5.4", 9 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.4.tgz", 10 | "integrity": "sha512-FG+nFEZChJrbQ9tIccIfZJBz3J7mLrAhxakAbnrJWn8d7aKOC+LWifa0G+p4ZqKp4y13T7juYvdhq9NzKdsrjw==" 11 | }, 12 | "co": { 13 | "version": "4.6.0", 14 | "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", 15 | "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /JS/Async/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "async", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "generator.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "bluebird": "^3.5.4", 14 | "co": "^4.6.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /JS/Async/promise.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | /** 4 | * 将 fs.readFile 包装成promise接口 5 | */ 6 | function read(url) { 7 | return new Promise((resolve, reject) => { 8 | fs.readFile(url, 'utf8', (err, data) => { 9 | if(err) reject(err); 10 | resolve(data); 11 | }); 12 | }); 13 | } 14 | 15 | /** 16 | * 17 | * 解决回调地狱 18 | * 注: code Runner 的bug导致,相对路径是从根目录开始 19 | * 20 | */ 21 | 22 | read('./JS/Async/data/info.txt').then((data) => { 23 | return read(data); 24 | }).then((data) => { 25 | return read(data); 26 | }).then((data) => { 27 | console.log(data); //22 28 | }).catch((err) => { 29 | //可以对错误进行统一处理 30 | console.log(err); 31 | }); -------------------------------------------------------------------------------- /JS/data-type.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * 如何准确判断JS数据类型? 4 | * 5 | * typeof 无法准确判断复杂数据类型 6 | * instanceof 无法正确判断基本数据类型 7 | * 8 | * 本文提供两个思路: 9 | * 1.首先通过typeof判断出是否是复杂数据类型,如果是,再使用instanceof判断 10 | * 2.定义自己的数据类型 11 | * (附测试代码) 12 | */ 13 | 14 | 15 | /** 16 | * isComplex判断是否是复杂数据类型,如果是返回true,否则返回false 17 | * @param {*} data 需要被判断类型的数据 18 | */ 19 | 20 | function isComplex(data) { 21 | if (data && (typeof data === 'object' || typeof data === 'function')) { 22 | return true; 23 | } 24 | return false; 25 | } 26 | 27 | 28 | /** 29 | * 定义自己的基本数据类型 30 | */ 31 | 32 | class PrimitiveString { 33 | static [Symbol.hasInstance](data) { 34 | return typeof data === 'string'; 35 | } 36 | } 37 | 38 | class PrimitiveNumber { 39 | static [Symbol.hasInstance](data) { 40 | return typeof data === 'number'; 41 | } 42 | } 43 | 44 | class PrimitiveUndefined { 45 | static [Symbol.hasInstance](data) { 46 | return typeof data === 'undefined'; 47 | } 48 | } 49 | 50 | class PrimitiveBool { 51 | static [Symbol.hasInstance](data) { 52 | return typeof data === 'boolean'; 53 | } 54 | } 55 | 56 | class PrimitiveNull { 57 | static [Symbol.hasInstance](data) { 58 | return data === null; 59 | } 60 | } 61 | 62 | class PrimitiveSymbol { 63 | static [Symbol.hasInstance](data) { 64 | return typeof data === 'symbol'; 65 | } 66 | } 67 | 68 | 69 | 70 | 71 | /** 72 | * 测试 73 | */ 74 | 75 | let num = 2; 76 | console.log(num instanceof PrimitiveNumber); //true 77 | console.log('isComplex: ', isComplex(num)); 78 | 79 | let str = 'Yvette'; 80 | console.log(str instanceof PrimitiveString); //true 81 | console.log('isComplex: ', isComplex(str)); 82 | 83 | let flag = false; 84 | console.log(flag instanceof PrimitiveBool); //true 85 | console.log('isComplex: ', isComplex(flag)); 86 | 87 | let und = undefined; 88 | console.log(und instanceof PrimitiveUndefined); //true 89 | console.log('isComplex: ', isComplex(und)); 90 | 91 | let nul = null; 92 | console.log(nul instanceof PrimitiveNull); //true 93 | console.log('isComplex: ', isComplex(nul)); 94 | 95 | let sym = Symbol(10); 96 | console.log(sym instanceof PrimitiveSymbol); //true 97 | console.log('isComplex: ', isComplex(sym)); 98 | 99 | console.log('isComplex: ', isComplex(isComplex)); //true -------------------------------------------------------------------------------- /JS/for.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 根据代码的运行结果更多的了解 for...of for...in 和 forEach, map 之间的区别和性能对比 3 | * 4 | * for...of: 具有 iterator 接口,就可以用 for...of 循环遍历它的成员。 5 | * 数组的遍历器接口只返回具有数字索引的属性(数组原生具备 iterator 接口, 默认部署了 Symbol.iterator 属性)。 6 | * 对于普通的对象, for...of 结构不能直接使用,会报错,必须部署了 Iterator 接口后才能使用。 7 | * 8 | * for...in: 遍历对象自身的和继承的可枚举的属性, 不能直接获取属性值 9 | * 10 | * forEach: 只能遍历数组,不能中断,返回值是undefined,不修改原数组 11 | * 12 | * map: 只能遍历数组,不能中断,返回值是修改后的数组,不修改原数组 13 | * 14 | */ 15 | 16 | 17 | /** 18 | * 19 | * 遍历数组 20 | * 21 | * 差异点: 22 | * for...of 遍历的是数组的属性值; 23 | * for...in 遍历的是属性名; 24 | * forEach 循环不能中断; 25 | * 26 | */ 27 | 28 | { 29 | // let arr = ['hello', 'world']; //为了方便大家选中一块,运行代码;在每个代码块中定义了变量 30 | { 31 | let arr = ['hello', 'world']; 32 | for (let item of arr) { 33 | console.log(item); // hello world; 输出的是属性值 34 | } 35 | arr.foo = 1000; 36 | for (let item of arr) { 37 | console.log(item); // hello world; 输出的是属性值(foo不是数字索引,不会被遍历) 38 | } 39 | } 40 | 41 | { 42 | let arr = ['hello', 'world']; 43 | for (let item in arr) { 44 | console.log(item); //0 1 ; 输出的是属性名 45 | } 46 | arr.foo = 1000; 47 | for (let item in arr) { 48 | console.log(item); // 0 1 foo; 49 | } 50 | } 51 | 52 | { 53 | let arr = ['hello', 'world']; 54 | let keys = Object.keys(arr); 55 | console.log(keys) //[ '0', '1' ]; 返回的是对象的键名组成的数组, 56 | } 57 | 58 | { 59 | let arr = ['hello', 'world']; 60 | let result = arr.forEach((item, index) => { 61 | console.log(item, index); //hello 0 world 1; 需要特别注意的是 forEach是不能中断的 62 | item += 's'; 63 | }); 64 | console.log(result, arr);//undefined [ 'hello', 'world' ]; 原数组未被改变 65 | ['hello', 'world'].forEach((item, index) => { 66 | console.log(item); //hello world; 尽管我们在 hello 的时候,return了,但是不不能中断循环 67 | if(item === "hello"){ 68 | return; 69 | } 70 | }); 71 | } 72 | 73 | { 74 | let arr = ['hello', 'world']; 75 | let result = arr.map((item, index, input) => { 76 | return item + 's'; 77 | }); 78 | console.log(result); //['hellos', 'worlds'] 返回新数组 79 | console.log(arr); //[ 'hello', 'world' ] 原数组未被修改 80 | } 81 | } 82 | 83 | /** 84 | * 85 | * 遍历普通对象 86 | * 87 | * 差异点: 88 | * for...of 抛错 89 | * for...in 遍历对象自身的和继承的可枚举的属性 90 | * 普通对象没有 forEach 和 map 方法 91 | */ 92 | 93 | { 94 | let obj = { 95 | 'name': 'Yvette', 96 | 'age': 24 97 | } 98 | 99 | { 100 | try{ 101 | for(let item of obj) { 102 | console.log(item); //抛错,因为普通对象没有 Iterator 接口 103 | } 104 | }catch(e) { 105 | console.log(e); 106 | } 107 | 108 | //给普通对象增加 Iterator 接口 109 | let arr = ['hello', 'world']; 110 | obj[Symbol.iterator] = arr[Symbol.iterator].bind(arr); 111 | for(let item of obj) { 112 | console.log(item); //hello world 113 | } 114 | } 115 | 116 | { 117 | for(let item in obj) { 118 | console.log(item); //name age ; 遍历属性名 119 | } 120 | 121 | function Parent(weight, money) { 122 | this.weight = weight; 123 | this.money = money; 124 | 125 | } 126 | function Child(name) { 127 | this.name = name; 128 | } 129 | Child.prototype = new Parent(50, 1000); 130 | let Mica = new Child('Mica'); 131 | Mica.age = 20; 132 | Mica.color = 'white'; 133 | 134 | // 遍历对象自身的和继承的可枚举的属性 135 | for(let i in Mica) { 136 | console.log(i); //name age color weight money 137 | } 138 | // 设置 money 为不可枚举属性 139 | Object.defineProperty(Mica, 'money', { 140 | enumerable: false 141 | }); 142 | for(let i in Mica) { 143 | console.log(i); //name age color weight; money被设置为了不可枚举属性 144 | } 145 | console.log(Object.keys(Mica)); //[ 'name', 'age', 'color' ]; Object.keys返回的是指定对象自身可枚举属性的字符串数组 146 | } 147 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 博客目录 2 | 3 | 4 | 打造一系列前端进阶的优质文章,博客将侧重于原生JS、 React、ReactNative和Webpack等入门与底层原理分析,只有在自己实践和深入理解后,才会输出相关博文,以确保博文内容的准确性。 5 | 6 | 微信公众号: 7 | 8 | ![](https://i.loli.net/2020/02/27/fg2FYebTAR5hjUt.jpg) 9 | 10 | 欢迎关注本人的博文: 11 | 12 | - #### [segmentFault](https://segmentfault.com/u/liuyan666/articles) 13 | - #### [掘金](https://juejin.im/user/5c6256596fb9a049bd42c770/posts) 14 | 15 | > ## 面试系列 16 | 17 | 1. [【面试篇】寒冬求职季之你必须要懂的原生JS(上)](https://github.com/YvetteLau/Blog/issues/7) 18 | 2. [【面试篇】寒冬求职季之你必须要懂的原生JS(中)](https://github.com/YvetteLau/Blog/issues/28) 19 | 3. [【面试篇】寒冬求职之你必须要懂的Web安全](https://github.com/YvetteLau/Blog/issues/29) 20 | 4. [这儿有20道大厂面试题等你查收](https://github.com/YvetteLau/Blog/issues/35) 21 | 22 | > ## Step-By-Step 23 | 24 | 1. [【Step-By-Step】高频面试题深入解析 / 周刊 01](https://github.com/YvetteLau/Blog/issues/31) 25 | 2. [【Step-By-Step】高频面试题深入解析/ 周刊 02](https://github.com/YvetteLau/Blog/issues/32) 26 | 3. [【Step-By-Step】高频面试题深入解析/ 周刊 03](https://github.com/YvetteLau/Blog/issues/33) 27 | 4. [【Step-By-Step】高频面试题深入解析/ 周刊 04](https://github.com/YvetteLau/Blog/issues/34) 28 | 5. [【Step-By-Step】高频面试题深入解析/ 周刊 05](https://github.com/YvetteLau/Blog/issues/36) 29 | 6. [【Step-By-Step】高频面试题深入解析/ 周刊 06](https://github.com/YvetteLau/Blog/issues/37) 30 | 7. [【Step-By-Step】高频面试题深入解析/ 周刊 07](https://github.com/YvetteLau/Blog/issues/38) 31 | 32 | 33 | > ## Javascript 34 | 35 | 1. [Promise的源码实现(完美符合PromiseA+规范)](https://github.com/YvetteLau/Blog/issues/2) 36 | 2. [彻底搞懂浏览器Event-loop](https://github.com/YvetteLau/Blog/issues/4) 37 | 3. [嗨,你真的懂this吗?](https://github.com/YvetteLau/Blog/issues/6) 38 | 4. [细说JS异步发展历程](https://github.com/YvetteLau/Blog/issues/30) 39 | 5. [【中高级前端必备】手摸手教你撸一个脚手架](https://github.com/YvetteLau/Blog/issues/39) 40 | 6. [Proxy及其优点](https://github.com/YvetteLau/Blog/issues/40) 41 | 7. [深入理解全能的 Reducer](https://github.com/YvetteLau/Blog/issues/41) 42 | 43 | 44 | > ## React 45 | 46 | 1. [React新旧生命周期一览](https://github.com/YvetteLau/Blog/issues/3) 47 | 2. [可靠React组件设计的7个准则之SRP](https://github.com/YvetteLau/Blog/issues/42) 48 | 3. [可靠React组件设计的7个准则之封装](https://github.com/YvetteLau/Blog/issues/43) 49 | 4. [React中组件逻辑复用的那些事儿](https://github.com/YvetteLau/Blog/issues/50) 50 | 51 | > ## Webpack 52 | 53 | 1. [从零开始配置webpack(基于babel 7版本)](https://github.com/YvetteLau/Blog/issues/1) 54 | 55 | > ## 移动web 56 | 57 | 1. [9102了,你还不会移动端真机调试?](https://github.com/YvetteLau/Blog/issues/5) 58 | -------------------------------------------------------------------------------- /Security/CSRF/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 用户登录之后,返回登录标识 cookie 3 | */ 4 | 5 | const express = require('express'); 6 | const app = express(); 7 | const path = require('path'); 8 | const bodyParser = require('body-parser'); 9 | const cookieParser = require('cookie-parser'); 10 | const svgCaptcha = require('svg-captcha'); 11 | 12 | //设置路径 13 | app.use(express.static(path.join(__dirname, 'src'))); 14 | app.use(express.static(path.join(__dirname, '../'))); 15 | //将参数转换成对象 16 | app.use(bodyParser.urlencoded({ extended: true })); 17 | //req.cookie[xxx] 获取cookie 18 | app.use(cookieParser()); 19 | 20 | //用户列表 21 | let userList = [{ username: 'yvette', password: 'yvette', account: 1000 }, { username: 'loki', password: 'loki', account: 100000 }]; 22 | 23 | let SESSION_ID = 'connect.sid'; 24 | let session = {}; 25 | //登录接口 26 | app.post('/api/login', (req, res) => { 27 | let { username, password } = req.body; 28 | let user = userList.find(item => item.username === username && item.password === password); 29 | if (user) { 30 | //用户登录后,给一个标识(cookie登录) 31 | const cardId = Math.random() + Date.now(); 32 | session[cardId] = { user }; 33 | res.cookie(SESSION_ID, cardId); 34 | res.json({ code: 0 }); 35 | } else { 36 | res.json({ code: 1, error: `${username} does not exist or password mismatch` }); 37 | } 38 | 39 | }); 40 | 41 | app.get('/api/userinfo', (req, res) => { 42 | let info = session[req.cookies[SESSION_ID]]; 43 | /**增加验证码 */ 44 | //data:svg, text:验证码文本 45 | let {data, text} = svgCaptcha.create(); 46 | if (info) { 47 | //用户已经登录 48 | let username = info.user.username; 49 | info.code = text; //下次请求时,对比验证码 50 | res.json({ code: 0, info: { username, account: info.user.account, svg: data } }); 51 | } else { 52 | res.json({ code: 1, error: 'user not logged in.' }); 53 | } 54 | }); 55 | 56 | app.post('/api/transfer', (req, res) => { 57 | let info = session[req.cookies[SESSION_ID]]; 58 | if (info) { 59 | //用户已经登录 60 | let {payee, amount} = req.body; 61 | let username = info.user.username; 62 | userList.forEach(user => { 63 | if(user.username === username) { 64 | user.account -= amount; 65 | } 66 | if(user.username === payee) { 67 | user.account += amount; 68 | } 69 | }) 70 | res.json({ code: 0 }); 71 | } else { 72 | res.json({ code: 1, error: 'user not logged in.' }); 73 | } 74 | }); 75 | 76 | //转账前,先验证 验证码 77 | app.post('/api/transfer1', (req, res) => { 78 | let info = session[req.cookies[SESSION_ID]]; 79 | if (info) { 80 | //用户已经登录 81 | let {payee, amount, code} = req.body; 82 | if(code && code.toUpperCase() === info.code.toUpperCase() && Number(amount)) { 83 | //验证码正确 84 | let username = info.user.username; 85 | userList.forEach(user => { 86 | if(user.username === username) { 87 | user.account -= amount; 88 | } 89 | if(user.username === payee) { 90 | user.account += amount; 91 | } 92 | }) 93 | res.json({ code: 0 }); 94 | }else{ 95 | res.json({ code: 1, error: 'code error.' }); 96 | } 97 | 98 | } else { 99 | res.json({ code: 1, error: 'user not logged in.' }); 100 | } 101 | }); 102 | 103 | //转账前,判断请求来源(referer) 104 | app.post('/api/transfer2', (req, res) => { 105 | let info = session[req.cookies[SESSION_ID]]; 106 | if (info) { 107 | //用户已经登录 108 | let {payee, amount} = req.body; 109 | let referer = req.headers['referer'] || ''; 110 | if(Number(amount) && referer.includes('localhost:3001')) { 111 | //referer正确 112 | let username = info.user.username; 113 | userList.forEach(user => { 114 | if(user.username === username) { 115 | user.account -= amount; 116 | } 117 | if(user.username === payee) { 118 | user.account += amount; 119 | } 120 | }) 121 | res.json({ code: 0 }); 122 | }else{ 123 | res.json({ code: 1, error: 'illegal source of request .' }); 124 | } 125 | 126 | } else { 127 | res.json({ code: 1, error: 'user not logged in.' }); 128 | } 129 | }); 130 | 131 | 132 | //转账前,先验证 token 133 | app.post('/api/transfer3', (req, res) => { 134 | let info = session[req.cookies[SESSION_ID]]; 135 | if (info) { 136 | //用户已经登录 137 | let {payee, amount, token} = req.body; 138 | console.log(token, 'mytoken_' + req.cookies[SESSION_ID]) 139 | if(token === 'mytoken_' + req.cookies[SESSION_ID] && Number(amount)) { 140 | //token 正确 141 | let username = info.user.username; 142 | userList.forEach(user => { 143 | if(user.username === username) { 144 | user.account -= amount; 145 | } 146 | if(user.username === payee) { 147 | user.account += amount; 148 | } 149 | }) 150 | res.json({ code: 0 }); 151 | }else{ 152 | res.json({ code: 1, error: 'illegal.' }); 153 | } 154 | 155 | } else { 156 | res.json({ code: 1, error: 'user not logged in.' }); 157 | } 158 | }); 159 | 160 | app.listen(3001); -------------------------------------------------------------------------------- /Security/CSRF/server2.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 用户登录之后,返回登录标识 cookie 3 | */ 4 | 5 | const express = require('express'); 6 | const app = express(); 7 | const path = require('path'); 8 | const bodyParser = require('body-parser'); 9 | const cookieParser = require('cookie-parser'); 10 | 11 | 12 | //设置路径 13 | app.use(express.static(path.join(__dirname, 'src'))); 14 | app.use(express.static(path.join(__dirname, '../'))); 15 | //将参数转换成对象 16 | app.use(bodyParser.urlencoded({ extended: true })); 17 | //req.cookie[xxx] 获取cookie 18 | app.use(cookieParser()); 19 | 20 | 21 | 22 | app.listen(3002); -------------------------------------------------------------------------------- /Security/CSRF/src/fake.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 你被骗了 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 |
18 | 19 | 20 | 21 |
22 | 23 | 24 |
25 | 26 | 35 | 36 | -------------------------------------------------------------------------------- /Security/CSRF/src/fake1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 你被骗了 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 |
18 | 19 | 24 | 25 | -------------------------------------------------------------------------------- /Security/CSRF/src/fake2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 你被骗了 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 |
18 | 19 | 24 | 25 | -------------------------------------------------------------------------------- /Security/CSRF/src/fake3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 你被骗了 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 |
18 | 19 | 24 | 25 | -------------------------------------------------------------------------------- /Security/CSRF/src/fish.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 嘿嘿嘿 9 | 10 | 11 | 12 | 14 |

你的钱被偷走了~

15 | 返回查看余额 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Security/CSRF/src/fish1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 嘿嘿嘿 9 | 10 | 11 | 12 | 14 |

你的钱还很安全~

15 | 返回查看余额 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Security/CSRF/src/fish2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 嘿嘿嘿 9 | 10 | 11 | 12 | 14 |

你的钱还很安全~

15 | 返回查看余额 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Security/CSRF/src/fish3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 嘿嘿嘿 9 | 10 | 11 | 12 | 14 |

你的钱还很安全~

15 | 返回查看余额 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Security/CSRF/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 转账 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 |
18 |
19 |

转账

20 |
21 |

用户:

22 |

余额:

23 |
24 |
25 |
26 |
27 |
28 | 29 | 30 |
31 |
32 | 33 | 34 |
35 |
36 | 37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | 46 | 47 | 84 | 85 | -------------------------------------------------------------------------------- /Security/CSRF/src/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 登录 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 |
18 |
19 |

登录

20 |
21 |
22 |
23 |
24 | 25 | 26 |
27 |
28 | 29 | 30 |
31 |
32 | 33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | 42 | 43 | 66 | 67 | -------------------------------------------------------------------------------- /Security/CSRF/src/safe1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 转账 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 |
18 |
19 |

转账

20 |
21 |

用户:

22 |

余额:

23 |
24 |
25 |
26 |
27 |
28 | 29 | 30 |
31 |
32 | 33 | 34 |
35 | 36 |
37 | 38 | 39 |
40 |
41 |
42 | 43 |
44 |
45 | 46 |
47 | 48 |
49 |
50 |
51 |
52 |
53 | 54 | 55 | 56 | 96 | 97 | -------------------------------------------------------------------------------- /Security/CSRF/src/safe2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 转账 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 |
18 |
19 |

转账

20 |
21 |

用户:

22 |

余额:

23 |
24 |
25 |
26 |
27 |
28 | 29 | 30 |
31 |
32 | 33 | 34 |
35 |
36 | 37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | 46 | 47 | 84 | 85 | -------------------------------------------------------------------------------- /Security/CSRF/src/safe3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 转账 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 |
18 |
19 |

转账

20 |
21 |

用户:

22 |

余额:

23 |
24 |
25 |
26 |
27 |
28 | 29 | 30 |
31 |
32 | 33 | 34 |
35 |
36 | 37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | 46 | 47 | 88 | 89 | -------------------------------------------------------------------------------- /Security/README.md: -------------------------------------------------------------------------------- 1 | > ### 环境 2 | 3 | 1. npm install 4 | 2. 本人使用的编辑器是 VSCode(自行安装 Code Runner 插件,用于运行 server.js) 5 | 6 | > ## XSS 攻击 7 | 8 | > ### 反射型XSS攻击 9 | 10 | 1. 进入 XSS 目录,运行 server.js (启动本地服务器) 11 | 2. 在浏览器中访问 `localhost:3000/login.html` 12 | 3. 使用错误的用户名/密码进行登录(例如: 123 / 123),会跳向:`http://localhost:3000/error?type=` 13 | 4. 使用正确的用户名: yvette / yvette 登录,会跳向: `http://localhost:3000/welcome?type=` ;虽然url仍然包含恶意脚本,但是我们已经进行了转义,不会再被攻击 14 | 15 | 16 | > ### DOM 型XSS攻击 17 | 18 | 1. 浏览器中访问 `localhost:3000/after.html` 19 | 2. 输入评论内容: `2222` 20 | 21 | 当然啦,如果是登录状态,还可以拿cookie等信息~ 22 | 还可以悄悄引入其它的js文件过来,可怕~ 23 | 24 | 3. 我们可以对输入的内容进行转义,这样就不会被攻击啦~ 25 | 26 | 27 | > ### 存储型 XSS 攻击 28 | 29 | 1. 浏览器中访问 `localhost:3000/comments.html` 30 | 2. 评论需要先登录,如未登录会自动跳去登录页 31 | 3. 输入评论内容: `2222` 32 | 4. 恶意脚本未经转换,存储到了后台。任何用户访问此页面,都会执行恶意脚本。 33 | 5. 防范存储型XSS攻击,需要我们增加字符串的过滤:前端输入时过滤/服务端增加过滤/前端输出时过滤——一句话:谁都别信! 34 | 6. 浏览器中访问 `localhost:3000/comments2.html`,输入评论: `2222`,不会有弹框,因为做了过滤。 35 | 36 | > ## CSRF 攻击 37 | 38 | 偷走你的钱: 39 | 40 | 1. 进入 CSRF 目录,运行 server.js,端口号是3001 (runcode就行) 41 | 2. 在控制台: node server2.js,端口号3002 42 | 3. 浏览器中访问 `http://localhost:3001/`,没有登录的情况下自动跳转登录页 43 | 4. 使用 loki/loki 登录,可以看到 loki 的账号有 10W 的余额 44 | 5. loki 已经登录了,cookie已经有了,这个时候,有人给你发了个钓鱼网站的链接: `http://localhost:3002/fish.html`,你点过去了,你的钱就被偷偷偷走了~~~ 45 | 6. loki 的钱在不知不觉中就被转到了 yvette 的账户 46 | 7. 可怕不~ 47 | 8. 不过银行网站的安全都是做的很好的,别慌~ 48 | 49 | > ### 防御 50 | 51 | 说明:safe1.html,safe2.html,safe3.html;fish1.html/fish2.html/fish3.html 的区别仅在于请求接口不用。 52 | 53 | 54 | 1. 使用验证码【用户体验不佳】 55 | 56 | 利用svg-captcha(已安装依赖) 57 | 58 | 接口: `api/transfer1` 59 | 60 | - 浏览器访问 `http://localhost:3001/safe1.html`,登录之后发现转账需要验证码了~ 61 | - 现在登录之后,再诱惑你点钓鱼网站 `http://localhost:3002/fish1.html`,你的钱不能被转走,因为服务端需要验证你的验证码,发现验证码错误,不会转账。 62 | 63 | 2. 判断来源(referer) 【referer并不安全,应该referer是可以被修改的】 64 | 65 | 接口: `api/transfer2` 66 | 67 | - 浏览器访问 `http://localhost:3001/safe2.html`,登录(loki/loki)~ 68 | - 现在登录之后,再诱惑你点钓鱼网站 `http://localhost:3002/fish2.html`,你的钱不能被转走,因为服务端会判断请求来源,发现请求来源是 `localhost:3002`,不会转账。 69 | 70 | 3. Token【用户无感知】 71 | 72 | 接口: `api/transfer3` 73 | 74 | - 浏览器访问 `http://localhost:3001/safe3.html`,登录(loki/loki)~ 75 | - 现在登录之后,再诱惑你点钓鱼网站 `http://localhost:3002/fish3.html`,你的钱不能被转走,因为服务端会判断请求来源,发现请求来源是 `localhost:3002`,不会转账。 76 | -------------------------------------------------------------------------------- /Security/XSS/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 用户登录之后,返回登录标识 cookie 3 | */ 4 | 5 | const express = require('express'); 6 | const app = express(); 7 | const path = require('path'); 8 | const bodyParser = require('body-parser'); 9 | const cookieParser = require('cookie-parser'); 10 | 11 | 12 | //设置路径 13 | app.use(express.static(path.join(__dirname, 'src'))); 14 | app.use(express.static(path.join(__dirname, '../'))); 15 | //将参数转换成对象 16 | app.use(bodyParser.urlencoded({ extended: true })); 17 | //req.cookie[xxx] 获取cookie 18 | app.use(cookieParser()); 19 | 20 | //用户列表 21 | let userList = [{ username: 'yvette', password: 'yvette' }, { username: 'star', password: 'star' }]; 22 | 23 | let SESSION_ID = 'connect.sid'; 24 | let session = {}; 25 | //登录接口 26 | app.post('/api/login', (req, res) => { 27 | let { username, password } = req.body; 28 | let user = userList.find(item => item.username === username && item.password === password); 29 | if (user) { 30 | //用户登录后,给一个标识(cookie登录) 31 | const cardId = Math.random() + Date.now(); 32 | session[cardId] = { user }; 33 | res.cookie(SESSION_ID, cardId); 34 | res.json({ code: 0 }); 35 | } else { 36 | res.json({ code: 1, error: `${username} does not exist or password mismatch` }); 37 | } 38 | 39 | }); 40 | 41 | //1.反射型XSS攻击: http://localhost:3000/error?type= 42 | //chrome能够检测到Url上的XSS攻击(可在firefox或者是其它浏览器测试) 43 | app.get('/error', function (req, res) { 44 | res.send(`${req.query.type}`); //拿到 url 上的 type 参数,并返回给前端 45 | }); 46 | 47 | app.get('/welcome', function (req, res) { 48 | //对查询参数进行编码,避免XSS攻击 49 | res.send(`${encodeURIComponent(req.query.type)}`); 50 | //对type查询参数进行编码,即可解决当前的XSS攻击(可重启服务查看) 51 | // res.send(`${encodeURIComponent(req.query.type)}`); 52 | }); 53 | 54 | //评论列表 55 | let comments = [ 56 | { username: 'yvette', content: '大家好' }, 57 | { username: 'yvette', content: '我是刘小夕' }, 58 | { username: 'star', content: '大家好,我是Star' }, 59 | ] 60 | app.get('/getComments', function (req, res) { 61 | res.json({ code: 0, comments }); 62 | }); 63 | 64 | app.post('/addComment', function (req, res) { 65 | //cardId (req.cookies[SESSION_ID])要派上用场啦~ 66 | let info = session[req.cookies[SESSION_ID]]; 67 | if (info) { 68 | //用户已经登录 69 | let username = info.user.username; 70 | comments.push({ username, content: req.body.comment }); 71 | res.json({ code: 0, comments }); 72 | } else { 73 | res.json({ code: 1, error: 'user not logged in.' }); 74 | } 75 | }); 76 | 77 | 78 | //安全的评论列表 79 | let comments2 = [ 80 | { username: 'yvette', content: '大家好' }, 81 | { username: 'yvette', content: '我是刘小夕' }, 82 | { username: 'star', content: '大家好,我是Star' }, 83 | ] 84 | app.get('/getComments2', function (req, res) { 85 | res.json({ code: 0, comments: comments2 }); 86 | }); 87 | function encodeHtml(str) { 88 | return str.replace(/"/g, '"') 89 | .replace(/'/g, ''') 90 | .replace(//g, '>'); 92 | } 93 | app.post('/addComment2', function (req, res) { 94 | //cardId (req.cookies[SESSION_ID])要派上用场啦~ 95 | let info = session[req.cookies[SESSION_ID]]; 96 | if (info) { 97 | //用户已经登录 98 | let username = info.user.username; 99 | comments2.push({ username, content: encodeHtml(req.body.comment) }); 100 | res.json({ code: 0, comments: comments2 }); 101 | } else { 102 | res.json({ code: 1, error: 'user not logged in.' }); 103 | } 104 | }); 105 | 106 | app.listen(3000); -------------------------------------------------------------------------------- /Security/XSS/src/after.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Document 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 |
18 |
19 |

售后评价

20 |
21 |
22 |
23 |
24 | 25 | 26 |
27 |
28 | 29 | 30 |
31 |
32 |
    33 | 34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | 42 | 43 | 73 | -------------------------------------------------------------------------------- /Security/XSS/src/comments.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Document 10 | 18 | 19 | 20 | 21 |
22 |
23 |
24 |
25 |
26 |
27 |

论坛

28 |
29 |
30 |
    31 | 32 |
33 |
34 |
35 | 36 | 37 |
38 |
39 | 40 |
41 |
42 | 43 |
44 |
45 |
46 |
47 |
48 |
49 | 50 | 51 | 87 | 88 | -------------------------------------------------------------------------------- /Security/XSS/src/comments2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Document 10 | 18 | 19 | 20 | 21 |
22 |
23 |
24 |
25 |
26 |
27 |

论坛

28 |
29 |
30 |
    31 | 32 |
33 |
34 |
35 | 36 | 37 |
38 |
39 | 40 |
41 |
42 | 43 |
44 |
45 |
46 |
47 |
48 |
49 | 50 | 51 | 107 | 108 | -------------------------------------------------------------------------------- /Security/XSS/src/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 登录页 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 |
18 |
19 |

登录

20 |
21 |
22 |
23 |
24 | 25 | 26 |
27 |
28 | 29 | 30 |
31 |
32 | 33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | 42 | 43 | 67 | 68 | -------------------------------------------------------------------------------- /Security/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "security", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "accepts": { 8 | "version": "1.3.7", 9 | "resolved": "https://registry.npm.taobao.org/accepts/download/accepts-1.3.7.tgz", 10 | "integrity": "sha1-UxvHJlF6OytB+FACHGzBXqq1B80=", 11 | "requires": { 12 | "mime-types": "2.1.24", 13 | "negotiator": "0.6.2" 14 | } 15 | }, 16 | "array-flatten": { 17 | "version": "1.1.1", 18 | "resolved": "https://registry.npm.taobao.org/array-flatten/download/array-flatten-1.1.1.tgz", 19 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 20 | }, 21 | "body-parser": { 22 | "version": "1.19.0", 23 | "resolved": "https://registry.npm.taobao.org/body-parser/download/body-parser-1.19.0.tgz", 24 | "integrity": "sha1-lrJwnlfJxOCab9Zqj9l5hE9p8Io=", 25 | "requires": { 26 | "bytes": "3.1.0", 27 | "content-type": "1.0.4", 28 | "debug": "2.6.9", 29 | "depd": "1.1.2", 30 | "http-errors": "1.7.2", 31 | "iconv-lite": "0.4.24", 32 | "on-finished": "2.3.0", 33 | "qs": "6.7.0", 34 | "raw-body": "2.4.0", 35 | "type-is": "1.6.18" 36 | } 37 | }, 38 | "bootstrap": { 39 | "version": "3.4.1", 40 | "resolved": "https://registry.npm.taobao.org/bootstrap/download/bootstrap-3.4.1.tgz", 41 | "integrity": "sha1-w6NH1Bniia0R9AM+PEEyuHwIHXI=" 42 | }, 43 | "bytes": { 44 | "version": "3.1.0", 45 | "resolved": "https://registry.npm.taobao.org/bytes/download/bytes-3.1.0.tgz", 46 | "integrity": "sha1-9s95M6Ng4FiPqf3oVlHNx/gF0fY=" 47 | }, 48 | "content-disposition": { 49 | "version": "0.5.2", 50 | "resolved": "https://registry.npm.taobao.org/content-disposition/download/content-disposition-0.5.2.tgz", 51 | "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" 52 | }, 53 | "content-type": { 54 | "version": "1.0.4", 55 | "resolved": "https://registry.npm.taobao.org/content-type/download/content-type-1.0.4.tgz", 56 | "integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=" 57 | }, 58 | "cookie": { 59 | "version": "0.3.1", 60 | "resolved": "https://registry.npm.taobao.org/cookie/download/cookie-0.3.1.tgz", 61 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" 62 | }, 63 | "cookie-parser": { 64 | "version": "1.4.4", 65 | "resolved": "https://registry.npm.taobao.org/cookie-parser/download/cookie-parser-1.4.4.tgz", 66 | "integrity": "sha1-5jY95OqYw975aXuTQhwJ8wz10Yg=", 67 | "requires": { 68 | "cookie": "0.3.1", 69 | "cookie-signature": "1.0.6" 70 | } 71 | }, 72 | "cookie-signature": { 73 | "version": "1.0.6", 74 | "resolved": "https://registry.npm.taobao.org/cookie-signature/download/cookie-signature-1.0.6.tgz", 75 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 76 | }, 77 | "debug": { 78 | "version": "2.6.9", 79 | "resolved": "http://registry.npm.taobao.org/debug/download/debug-2.6.9.tgz", 80 | "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", 81 | "requires": { 82 | "ms": "2.0.0" 83 | } 84 | }, 85 | "depd": { 86 | "version": "1.1.2", 87 | "resolved": "https://registry.npm.taobao.org/depd/download/depd-1.1.2.tgz", 88 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 89 | }, 90 | "destroy": { 91 | "version": "1.0.4", 92 | "resolved": "https://registry.npm.taobao.org/destroy/download/destroy-1.0.4.tgz", 93 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 94 | }, 95 | "ee-first": { 96 | "version": "1.1.1", 97 | "resolved": "https://registry.npm.taobao.org/ee-first/download/ee-first-1.1.1.tgz", 98 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 99 | }, 100 | "encodeurl": { 101 | "version": "1.0.2", 102 | "resolved": "https://registry.npm.taobao.org/encodeurl/download/encodeurl-1.0.2.tgz", 103 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 104 | }, 105 | "escape-html": { 106 | "version": "1.0.3", 107 | "resolved": "https://registry.npm.taobao.org/escape-html/download/escape-html-1.0.3.tgz", 108 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 109 | }, 110 | "etag": { 111 | "version": "1.8.1", 112 | "resolved": "https://registry.npm.taobao.org/etag/download/etag-1.8.1.tgz", 113 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 114 | }, 115 | "express": { 116 | "version": "4.16.4", 117 | "resolved": "https://registry.npm.taobao.org/express/download/express-4.16.4.tgz", 118 | "integrity": "sha1-/d72GSYQniTFFeqX/S8b2/Yt8S4=", 119 | "requires": { 120 | "accepts": "1.3.7", 121 | "array-flatten": "1.1.1", 122 | "body-parser": "1.18.3", 123 | "content-disposition": "0.5.2", 124 | "content-type": "1.0.4", 125 | "cookie": "0.3.1", 126 | "cookie-signature": "1.0.6", 127 | "debug": "2.6.9", 128 | "depd": "1.1.2", 129 | "encodeurl": "1.0.2", 130 | "escape-html": "1.0.3", 131 | "etag": "1.8.1", 132 | "finalhandler": "1.1.1", 133 | "fresh": "0.5.2", 134 | "merge-descriptors": "1.0.1", 135 | "methods": "1.1.2", 136 | "on-finished": "2.3.0", 137 | "parseurl": "1.3.3", 138 | "path-to-regexp": "0.1.7", 139 | "proxy-addr": "2.0.5", 140 | "qs": "6.5.2", 141 | "range-parser": "1.2.1", 142 | "safe-buffer": "5.1.2", 143 | "send": "0.16.2", 144 | "serve-static": "1.13.2", 145 | "setprototypeof": "1.1.0", 146 | "statuses": "1.4.0", 147 | "type-is": "1.6.18", 148 | "utils-merge": "1.0.1", 149 | "vary": "1.1.2" 150 | }, 151 | "dependencies": { 152 | "body-parser": { 153 | "version": "1.18.3", 154 | "resolved": "https://registry.npm.taobao.org/body-parser/download/body-parser-1.18.3.tgz", 155 | "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", 156 | "requires": { 157 | "bytes": "3.0.0", 158 | "content-type": "1.0.4", 159 | "debug": "2.6.9", 160 | "depd": "1.1.2", 161 | "http-errors": "1.6.3", 162 | "iconv-lite": "0.4.23", 163 | "on-finished": "2.3.0", 164 | "qs": "6.5.2", 165 | "raw-body": "2.3.3", 166 | "type-is": "1.6.18" 167 | } 168 | }, 169 | "bytes": { 170 | "version": "3.0.0", 171 | "resolved": "https://registry.npm.taobao.org/bytes/download/bytes-3.0.0.tgz", 172 | "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" 173 | }, 174 | "http-errors": { 175 | "version": "1.6.3", 176 | "resolved": "https://registry.npm.taobao.org/http-errors/download/http-errors-1.6.3.tgz", 177 | "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", 178 | "requires": { 179 | "depd": "1.1.2", 180 | "inherits": "2.0.3", 181 | "setprototypeof": "1.1.0", 182 | "statuses": "1.4.0" 183 | } 184 | }, 185 | "iconv-lite": { 186 | "version": "0.4.23", 187 | "resolved": "https://registry.npm.taobao.org/iconv-lite/download/iconv-lite-0.4.23.tgz", 188 | "integrity": "sha1-KXhx9jvlB63Pv8pxXQzQ7thOmmM=", 189 | "requires": { 190 | "safer-buffer": "2.1.2" 191 | } 192 | }, 193 | "qs": { 194 | "version": "6.5.2", 195 | "resolved": "https://registry.npm.taobao.org/qs/download/qs-6.5.2.tgz", 196 | "integrity": "sha1-yzroBuh0BERYTvFUzo7pjUA/PjY=" 197 | }, 198 | "raw-body": { 199 | "version": "2.3.3", 200 | "resolved": "https://registry.npm.taobao.org/raw-body/download/raw-body-2.3.3.tgz", 201 | "integrity": "sha1-GzJOzmtXBuFThVvBFIxlu39uoMM=", 202 | "requires": { 203 | "bytes": "3.0.0", 204 | "http-errors": "1.6.3", 205 | "iconv-lite": "0.4.23", 206 | "unpipe": "1.0.0" 207 | } 208 | }, 209 | "setprototypeof": { 210 | "version": "1.1.0", 211 | "resolved": "https://registry.npm.taobao.org/setprototypeof/download/setprototypeof-1.1.0.tgz", 212 | "integrity": "sha1-0L2FU2iHtv58DYGMuWLZ2RxU5lY=" 213 | }, 214 | "statuses": { 215 | "version": "1.4.0", 216 | "resolved": "https://registry.npm.taobao.org/statuses/download/statuses-1.4.0.tgz", 217 | "integrity": "sha1-u3PURtonlhBu/MG2AaJT1sRr0Ic=" 218 | } 219 | } 220 | }, 221 | "finalhandler": { 222 | "version": "1.1.1", 223 | "resolved": "https://registry.npm.taobao.org/finalhandler/download/finalhandler-1.1.1.tgz", 224 | "integrity": "sha1-7r9O2EAHnIP0JJA4ydcDAIMBsQU=", 225 | "requires": { 226 | "debug": "2.6.9", 227 | "encodeurl": "1.0.2", 228 | "escape-html": "1.0.3", 229 | "on-finished": "2.3.0", 230 | "parseurl": "1.3.3", 231 | "statuses": "1.4.0", 232 | "unpipe": "1.0.0" 233 | }, 234 | "dependencies": { 235 | "statuses": { 236 | "version": "1.4.0", 237 | "resolved": "https://registry.npm.taobao.org/statuses/download/statuses-1.4.0.tgz", 238 | "integrity": "sha1-u3PURtonlhBu/MG2AaJT1sRr0Ic=" 239 | } 240 | } 241 | }, 242 | "forwarded": { 243 | "version": "0.1.2", 244 | "resolved": "https://registry.npm.taobao.org/forwarded/download/forwarded-0.1.2.tgz", 245 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 246 | }, 247 | "fresh": { 248 | "version": "0.5.2", 249 | "resolved": "https://registry.npm.taobao.org/fresh/download/fresh-0.5.2.tgz", 250 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 251 | }, 252 | "http-errors": { 253 | "version": "1.7.2", 254 | "resolved": "https://registry.npm.taobao.org/http-errors/download/http-errors-1.7.2.tgz", 255 | "integrity": "sha1-T1ApzxMjnzEDblsuVSkrz7zIXI8=", 256 | "requires": { 257 | "depd": "1.1.2", 258 | "inherits": "2.0.3", 259 | "setprototypeof": "1.1.1", 260 | "statuses": "1.5.0", 261 | "toidentifier": "1.0.0" 262 | } 263 | }, 264 | "iconv-lite": { 265 | "version": "0.4.24", 266 | "resolved": "https://registry.npm.taobao.org/iconv-lite/download/iconv-lite-0.4.24.tgz", 267 | "integrity": "sha1-ICK0sl+93CHS9SSXSkdKr+czkIs=", 268 | "requires": { 269 | "safer-buffer": "2.1.2" 270 | } 271 | }, 272 | "inherits": { 273 | "version": "2.0.3", 274 | "resolved": "https://registry.npm.taobao.org/inherits/download/inherits-2.0.3.tgz", 275 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 276 | }, 277 | "ipaddr.js": { 278 | "version": "1.9.0", 279 | "resolved": "https://registry.npm.taobao.org/ipaddr.js/download/ipaddr.js-1.9.0.tgz", 280 | "integrity": "sha1-N9905DCg5HVQ/lSi3v4w2KzZX2U=" 281 | }, 282 | "jquery": { 283 | "version": "3.5.0", 284 | "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.0.tgz", 285 | "integrity": "sha512-Xb7SVYMvygPxbFMpTFQiHh1J7HClEaThguL15N/Gg37Lri/qKyhRGZYzHRyLH8Stq3Aow0LsHO2O2ci86fCrNQ==" 286 | }, 287 | "media-typer": { 288 | "version": "0.3.0", 289 | "resolved": "https://registry.npm.taobao.org/media-typer/download/media-typer-0.3.0.tgz", 290 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 291 | }, 292 | "merge-descriptors": { 293 | "version": "1.0.1", 294 | "resolved": "https://registry.npm.taobao.org/merge-descriptors/download/merge-descriptors-1.0.1.tgz", 295 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 296 | }, 297 | "methods": { 298 | "version": "1.1.2", 299 | "resolved": "https://registry.npm.taobao.org/methods/download/methods-1.1.2.tgz", 300 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 301 | }, 302 | "mime": { 303 | "version": "1.4.1", 304 | "resolved": "https://registry.npm.taobao.org/mime/download/mime-1.4.1.tgz", 305 | "integrity": "sha1-Eh+evEnjdm8xGnbh+hyAA8SwOqY=" 306 | }, 307 | "mime-db": { 308 | "version": "1.40.0", 309 | "resolved": "https://registry.npm.taobao.org/mime-db/download/mime-db-1.40.0.tgz", 310 | "integrity": "sha1-plBX6ZjbCQ9zKmj2wnbTh9QSbDI=" 311 | }, 312 | "mime-types": { 313 | "version": "2.1.24", 314 | "resolved": "https://registry.npm.taobao.org/mime-types/download/mime-types-2.1.24.tgz", 315 | "integrity": "sha1-tvjQs+lR77d97eyhlM/20W9nb4E=", 316 | "requires": { 317 | "mime-db": "1.40.0" 318 | } 319 | }, 320 | "ms": { 321 | "version": "2.0.0", 322 | "resolved": "http://registry.npm.taobao.org/ms/download/ms-2.0.0.tgz", 323 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 324 | }, 325 | "negotiator": { 326 | "version": "0.6.2", 327 | "resolved": "https://registry.npm.taobao.org/negotiator/download/negotiator-0.6.2.tgz", 328 | "integrity": "sha1-/qz3zPUlp3rpY0Q2pkiD/+yjRvs=" 329 | }, 330 | "on-finished": { 331 | "version": "2.3.0", 332 | "resolved": "https://registry.npm.taobao.org/on-finished/download/on-finished-2.3.0.tgz", 333 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 334 | "requires": { 335 | "ee-first": "1.1.1" 336 | } 337 | }, 338 | "opentype.js": { 339 | "version": "0.7.3", 340 | "resolved": "https://registry.npm.taobao.org/opentype.js/download/opentype.js-0.7.3.tgz", 341 | "integrity": "sha1-QPuM4Yv9YOdESO/f5EKDQJg5eqs=", 342 | "requires": { 343 | "tiny-inflate": "1.0.2" 344 | } 345 | }, 346 | "parseurl": { 347 | "version": "1.3.3", 348 | "resolved": "https://registry.npm.taobao.org/parseurl/download/parseurl-1.3.3.tgz", 349 | "integrity": "sha1-naGee+6NEt/wUT7Vt2lXeTvC6NQ=" 350 | }, 351 | "path-to-regexp": { 352 | "version": "0.1.7", 353 | "resolved": "https://registry.npm.taobao.org/path-to-regexp/download/path-to-regexp-0.1.7.tgz", 354 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 355 | }, 356 | "proxy-addr": { 357 | "version": "2.0.5", 358 | "resolved": "https://registry.npm.taobao.org/proxy-addr/download/proxy-addr-2.0.5.tgz", 359 | "integrity": "sha1-NMvWSi2B9LH9IedvnwbIpFKZ7jQ=", 360 | "requires": { 361 | "forwarded": "0.1.2", 362 | "ipaddr.js": "1.9.0" 363 | } 364 | }, 365 | "qs": { 366 | "version": "6.7.0", 367 | "resolved": "https://registry.npm.taobao.org/qs/download/qs-6.7.0.tgz", 368 | "integrity": "sha1-QdwaAV49WB8WIXdr4xr7KHapsbw=" 369 | }, 370 | "range-parser": { 371 | "version": "1.2.1", 372 | "resolved": "https://registry.npm.taobao.org/range-parser/download/range-parser-1.2.1.tgz", 373 | "integrity": "sha1-PPNwI9GZ4cJNGlW4SADC8+ZGgDE=" 374 | }, 375 | "raw-body": { 376 | "version": "2.4.0", 377 | "resolved": "https://registry.npm.taobao.org/raw-body/download/raw-body-2.4.0.tgz", 378 | "integrity": "sha1-oc5vucm8NWylLoklarWQWeE9AzI=", 379 | "requires": { 380 | "bytes": "3.1.0", 381 | "http-errors": "1.7.2", 382 | "iconv-lite": "0.4.24", 383 | "unpipe": "1.0.0" 384 | } 385 | }, 386 | "safe-buffer": { 387 | "version": "5.1.2", 388 | "resolved": "http://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.1.2.tgz", 389 | "integrity": "sha1-mR7GnSluAxN0fVm9/St0XDX4go0=" 390 | }, 391 | "safer-buffer": { 392 | "version": "2.1.2", 393 | "resolved": "https://registry.npm.taobao.org/safer-buffer/download/safer-buffer-2.1.2.tgz", 394 | "integrity": "sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo=" 395 | }, 396 | "send": { 397 | "version": "0.16.2", 398 | "resolved": "https://registry.npm.taobao.org/send/download/send-0.16.2.tgz", 399 | "integrity": "sha1-bsyh4PjBVtFBWXVZhI32RzCmu8E=", 400 | "requires": { 401 | "debug": "2.6.9", 402 | "depd": "1.1.2", 403 | "destroy": "1.0.4", 404 | "encodeurl": "1.0.2", 405 | "escape-html": "1.0.3", 406 | "etag": "1.8.1", 407 | "fresh": "0.5.2", 408 | "http-errors": "1.6.3", 409 | "mime": "1.4.1", 410 | "ms": "2.0.0", 411 | "on-finished": "2.3.0", 412 | "range-parser": "1.2.1", 413 | "statuses": "1.4.0" 414 | }, 415 | "dependencies": { 416 | "http-errors": { 417 | "version": "1.6.3", 418 | "resolved": "https://registry.npm.taobao.org/http-errors/download/http-errors-1.6.3.tgz", 419 | "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", 420 | "requires": { 421 | "depd": "1.1.2", 422 | "inherits": "2.0.3", 423 | "setprototypeof": "1.1.0", 424 | "statuses": "1.4.0" 425 | } 426 | }, 427 | "setprototypeof": { 428 | "version": "1.1.0", 429 | "resolved": "https://registry.npm.taobao.org/setprototypeof/download/setprototypeof-1.1.0.tgz", 430 | "integrity": "sha1-0L2FU2iHtv58DYGMuWLZ2RxU5lY=" 431 | }, 432 | "statuses": { 433 | "version": "1.4.0", 434 | "resolved": "https://registry.npm.taobao.org/statuses/download/statuses-1.4.0.tgz", 435 | "integrity": "sha1-u3PURtonlhBu/MG2AaJT1sRr0Ic=" 436 | } 437 | } 438 | }, 439 | "serve-static": { 440 | "version": "1.13.2", 441 | "resolved": "https://registry.npm.taobao.org/serve-static/download/serve-static-1.13.2.tgz", 442 | "integrity": "sha1-CV6Ecv1bRiN9tQzkhqQ/S4bGzsE=", 443 | "requires": { 444 | "encodeurl": "1.0.2", 445 | "escape-html": "1.0.3", 446 | "parseurl": "1.3.3", 447 | "send": "0.16.2" 448 | } 449 | }, 450 | "setprototypeof": { 451 | "version": "1.1.1", 452 | "resolved": "https://registry.npm.taobao.org/setprototypeof/download/setprototypeof-1.1.1.tgz", 453 | "integrity": "sha1-fpWsskqpL1iF4KvvW6ExMw1K5oM=" 454 | }, 455 | "statuses": { 456 | "version": "1.5.0", 457 | "resolved": "https://registry.npm.taobao.org/statuses/download/statuses-1.5.0.tgz", 458 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 459 | }, 460 | "svg-captcha": { 461 | "version": "1.4.0", 462 | "resolved": "https://registry.npm.taobao.org/svg-captcha/download/svg-captcha-1.4.0.tgz", 463 | "integrity": "sha1-MurTxkY5NsIYuzvJ7QT+pO7/5JI=", 464 | "requires": { 465 | "opentype.js": "0.7.3" 466 | } 467 | }, 468 | "tiny-inflate": { 469 | "version": "1.0.2", 470 | "resolved": "https://registry.npm.taobao.org/tiny-inflate/download/tiny-inflate-1.0.2.tgz", 471 | "integrity": "sha1-k9nez/yIBb1X6uQxDwt0Xptvs6c=" 472 | }, 473 | "toidentifier": { 474 | "version": "1.0.0", 475 | "resolved": "https://registry.npm.taobao.org/toidentifier/download/toidentifier-1.0.0.tgz", 476 | "integrity": "sha1-fhvjRw8ed5SLxD2Uo8j013UrpVM=" 477 | }, 478 | "type-is": { 479 | "version": "1.6.18", 480 | "resolved": "https://registry.npm.taobao.org/type-is/download/type-is-1.6.18.tgz", 481 | "integrity": "sha1-TlUs0F3wlGfcvE73Od6J8s83wTE=", 482 | "requires": { 483 | "media-typer": "0.3.0", 484 | "mime-types": "2.1.24" 485 | } 486 | }, 487 | "unpipe": { 488 | "version": "1.0.0", 489 | "resolved": "https://registry.npm.taobao.org/unpipe/download/unpipe-1.0.0.tgz", 490 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 491 | }, 492 | "utils-merge": { 493 | "version": "1.0.1", 494 | "resolved": "https://registry.npm.taobao.org/utils-merge/download/utils-merge-1.0.1.tgz", 495 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 496 | }, 497 | "vary": { 498 | "version": "1.1.2", 499 | "resolved": "https://registry.npm.taobao.org/vary/download/vary-1.1.2.tgz", 500 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 501 | } 502 | } 503 | } 504 | -------------------------------------------------------------------------------- /Security/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "security", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "dependencies": { 7 | "body-parser": "^1.19.0", 8 | "bootstrap": "3", 9 | "cookie-parser": "^1.4.4", 10 | "express": "^4.16.4", 11 | "jquery": "^3.5.0", 12 | "svg-captcha": "^1.4.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Security/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | accepts@~1.3.5: 6 | version "1.3.7" 7 | resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" 8 | dependencies: 9 | mime-types "~2.1.24" 10 | negotiator "0.6.2" 11 | 12 | array-flatten@1.1.1: 13 | version "1.1.1" 14 | resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" 15 | 16 | body-parser@1.18.3: 17 | version "1.18.3" 18 | resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.3.tgz#5b292198ffdd553b3a0f20ded0592b956955c8b4" 19 | dependencies: 20 | bytes "3.0.0" 21 | content-type "~1.0.4" 22 | debug "2.6.9" 23 | depd "~1.1.2" 24 | http-errors "~1.6.3" 25 | iconv-lite "0.4.23" 26 | on-finished "~2.3.0" 27 | qs "6.5.2" 28 | raw-body "2.3.3" 29 | type-is "~1.6.16" 30 | 31 | body-parser@^1.19.0: 32 | version "1.19.0" 33 | resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" 34 | dependencies: 35 | bytes "3.1.0" 36 | content-type "~1.0.4" 37 | debug "2.6.9" 38 | depd "~1.1.2" 39 | http-errors "1.7.2" 40 | iconv-lite "0.4.24" 41 | on-finished "~2.3.0" 42 | qs "6.7.0" 43 | raw-body "2.4.0" 44 | type-is "~1.6.17" 45 | 46 | bootstrap@3: 47 | version "3.4.1" 48 | resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-3.4.1.tgz#c3a347d419e289ad11f4033e3c4132b87c081d72" 49 | 50 | bytes@3.0.0: 51 | version "3.0.0" 52 | resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" 53 | 54 | bytes@3.1.0: 55 | version "3.1.0" 56 | resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" 57 | 58 | content-disposition@0.5.2: 59 | version "0.5.2" 60 | resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" 61 | 62 | content-type@~1.0.4: 63 | version "1.0.4" 64 | resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" 65 | 66 | cookie-parser@^1.4.4: 67 | version "1.4.4" 68 | resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.4.4.tgz#e6363de4ea98c3def9697b93421c09f30cf5d188" 69 | dependencies: 70 | cookie "0.3.1" 71 | cookie-signature "1.0.6" 72 | 73 | cookie-signature@1.0.6: 74 | version "1.0.6" 75 | resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" 76 | 77 | cookie@0.3.1: 78 | version "0.3.1" 79 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" 80 | 81 | debug@2.6.9: 82 | version "2.6.9" 83 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 84 | dependencies: 85 | ms "2.0.0" 86 | 87 | depd@~1.1.2: 88 | version "1.1.2" 89 | resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" 90 | 91 | destroy@~1.0.4: 92 | version "1.0.4" 93 | resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" 94 | 95 | ee-first@1.1.1: 96 | version "1.1.1" 97 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" 98 | 99 | encodeurl@~1.0.2: 100 | version "1.0.2" 101 | resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" 102 | 103 | escape-html@~1.0.3: 104 | version "1.0.3" 105 | resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" 106 | 107 | etag@~1.8.1: 108 | version "1.8.1" 109 | resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" 110 | 111 | express@^4.16.4: 112 | version "4.16.4" 113 | resolved "https://registry.yarnpkg.com/express/-/express-4.16.4.tgz#fddef61926109e24c515ea97fd2f1bdbf62df12e" 114 | dependencies: 115 | accepts "~1.3.5" 116 | array-flatten "1.1.1" 117 | body-parser "1.18.3" 118 | content-disposition "0.5.2" 119 | content-type "~1.0.4" 120 | cookie "0.3.1" 121 | cookie-signature "1.0.6" 122 | debug "2.6.9" 123 | depd "~1.1.2" 124 | encodeurl "~1.0.2" 125 | escape-html "~1.0.3" 126 | etag "~1.8.1" 127 | finalhandler "1.1.1" 128 | fresh "0.5.2" 129 | merge-descriptors "1.0.1" 130 | methods "~1.1.2" 131 | on-finished "~2.3.0" 132 | parseurl "~1.3.2" 133 | path-to-regexp "0.1.7" 134 | proxy-addr "~2.0.4" 135 | qs "6.5.2" 136 | range-parser "~1.2.0" 137 | safe-buffer "5.1.2" 138 | send "0.16.2" 139 | serve-static "1.13.2" 140 | setprototypeof "1.1.0" 141 | statuses "~1.4.0" 142 | type-is "~1.6.16" 143 | utils-merge "1.0.1" 144 | vary "~1.1.2" 145 | 146 | finalhandler@1.1.1: 147 | version "1.1.1" 148 | resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105" 149 | dependencies: 150 | debug "2.6.9" 151 | encodeurl "~1.0.2" 152 | escape-html "~1.0.3" 153 | on-finished "~2.3.0" 154 | parseurl "~1.3.2" 155 | statuses "~1.4.0" 156 | unpipe "~1.0.0" 157 | 158 | forwarded@~0.1.2: 159 | version "0.1.2" 160 | resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" 161 | 162 | fresh@0.5.2: 163 | version "0.5.2" 164 | resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" 165 | 166 | http-errors@1.6.3, http-errors@~1.6.2, http-errors@~1.6.3: 167 | version "1.6.3" 168 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" 169 | dependencies: 170 | depd "~1.1.2" 171 | inherits "2.0.3" 172 | setprototypeof "1.1.0" 173 | statuses ">= 1.4.0 < 2" 174 | 175 | http-errors@1.7.2: 176 | version "1.7.2" 177 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" 178 | dependencies: 179 | depd "~1.1.2" 180 | inherits "2.0.3" 181 | setprototypeof "1.1.1" 182 | statuses ">= 1.5.0 < 2" 183 | toidentifier "1.0.0" 184 | 185 | iconv-lite@0.4.23: 186 | version "0.4.23" 187 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" 188 | dependencies: 189 | safer-buffer ">= 2.1.2 < 3" 190 | 191 | iconv-lite@0.4.24: 192 | version "0.4.24" 193 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" 194 | dependencies: 195 | safer-buffer ">= 2.1.2 < 3" 196 | 197 | inherits@2.0.3: 198 | version "2.0.3" 199 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 200 | 201 | ipaddr.js@1.9.0: 202 | version "1.9.0" 203 | resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65" 204 | 205 | jquery@^3.5.0: 206 | version "3.5.0" 207 | resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.5.0.tgz#9980b97d9e4194611c36530e7dc46a58d7340fc9" 208 | 209 | media-typer@0.3.0: 210 | version "0.3.0" 211 | resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" 212 | 213 | merge-descriptors@1.0.1: 214 | version "1.0.1" 215 | resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" 216 | 217 | methods@~1.1.2: 218 | version "1.1.2" 219 | resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" 220 | 221 | mime-db@1.40.0: 222 | version "1.40.0" 223 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32" 224 | 225 | mime-types@~2.1.24: 226 | version "2.1.24" 227 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81" 228 | dependencies: 229 | mime-db "1.40.0" 230 | 231 | mime@1.4.1: 232 | version "1.4.1" 233 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" 234 | 235 | ms@2.0.0: 236 | version "2.0.0" 237 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 238 | 239 | negotiator@0.6.2: 240 | version "0.6.2" 241 | resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" 242 | 243 | on-finished@~2.3.0: 244 | version "2.3.0" 245 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" 246 | dependencies: 247 | ee-first "1.1.1" 248 | 249 | opentype.js@^0.7.3: 250 | version "0.7.3" 251 | resolved "https://registry.yarnpkg.com/opentype.js/-/opentype.js-0.7.3.tgz#40fb8ce18bfd60e74448efdfe442834098397aab" 252 | dependencies: 253 | tiny-inflate "^1.0.2" 254 | 255 | parseurl@~1.3.2: 256 | version "1.3.3" 257 | resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" 258 | 259 | path-to-regexp@0.1.7: 260 | version "0.1.7" 261 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" 262 | 263 | proxy-addr@~2.0.4: 264 | version "2.0.5" 265 | resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.5.tgz#34cbd64a2d81f4b1fd21e76f9f06c8a45299ee34" 266 | dependencies: 267 | forwarded "~0.1.2" 268 | ipaddr.js "1.9.0" 269 | 270 | qs@6.5.2: 271 | version "6.5.2" 272 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" 273 | 274 | qs@6.7.0: 275 | version "6.7.0" 276 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" 277 | 278 | range-parser@~1.2.0: 279 | version "1.2.0" 280 | resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" 281 | 282 | raw-body@2.3.3: 283 | version "2.3.3" 284 | resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.3.tgz#1b324ece6b5706e153855bc1148c65bb7f6ea0c3" 285 | dependencies: 286 | bytes "3.0.0" 287 | http-errors "1.6.3" 288 | iconv-lite "0.4.23" 289 | unpipe "1.0.0" 290 | 291 | raw-body@2.4.0: 292 | version "2.4.0" 293 | resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" 294 | dependencies: 295 | bytes "3.1.0" 296 | http-errors "1.7.2" 297 | iconv-lite "0.4.24" 298 | unpipe "1.0.0" 299 | 300 | safe-buffer@5.1.2: 301 | version "5.1.2" 302 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 303 | 304 | "safer-buffer@>= 2.1.2 < 3": 305 | version "2.1.2" 306 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 307 | 308 | send@0.16.2: 309 | version "0.16.2" 310 | resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1" 311 | dependencies: 312 | debug "2.6.9" 313 | depd "~1.1.2" 314 | destroy "~1.0.4" 315 | encodeurl "~1.0.2" 316 | escape-html "~1.0.3" 317 | etag "~1.8.1" 318 | fresh "0.5.2" 319 | http-errors "~1.6.2" 320 | mime "1.4.1" 321 | ms "2.0.0" 322 | on-finished "~2.3.0" 323 | range-parser "~1.2.0" 324 | statuses "~1.4.0" 325 | 326 | serve-static@1.13.2: 327 | version "1.13.2" 328 | resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.2.tgz#095e8472fd5b46237db50ce486a43f4b86c6cec1" 329 | dependencies: 330 | encodeurl "~1.0.2" 331 | escape-html "~1.0.3" 332 | parseurl "~1.3.2" 333 | send "0.16.2" 334 | 335 | setprototypeof@1.1.0: 336 | version "1.1.0" 337 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" 338 | 339 | setprototypeof@1.1.1: 340 | version "1.1.1" 341 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" 342 | 343 | "statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2": 344 | version "1.5.0" 345 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" 346 | 347 | statuses@~1.4.0: 348 | version "1.4.0" 349 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" 350 | 351 | svg-captcha@^1.4.0: 352 | version "1.4.0" 353 | resolved "https://registry.yarnpkg.com/svg-captcha/-/svg-captcha-1.4.0.tgz#32ead3c6463936c218bb3bc9ed04fea4eeffe492" 354 | dependencies: 355 | opentype.js "^0.7.3" 356 | 357 | tiny-inflate@^1.0.2: 358 | version "1.0.3" 359 | resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz#122715494913a1805166aaf7c93467933eea26c4" 360 | 361 | toidentifier@1.0.0: 362 | version "1.0.0" 363 | resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" 364 | 365 | type-is@~1.6.16, type-is@~1.6.17: 366 | version "1.6.18" 367 | resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" 368 | dependencies: 369 | media-typer "0.3.0" 370 | mime-types "~2.1.24" 371 | 372 | unpipe@1.0.0, unpipe@~1.0.0: 373 | version "1.0.0" 374 | resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" 375 | 376 | utils-merge@1.0.1: 377 | version "1.0.1" 378 | resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" 379 | 380 | vary@~1.1.2: 381 | version "1.1.2" 382 | resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" 383 | -------------------------------------------------------------------------------- /eos-cli/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "targets": { 7 | "node": "current" 8 | } 9 | } 10 | ] 11 | ] 12 | } -------------------------------------------------------------------------------- /eos-cli/README.md: -------------------------------------------------------------------------------- 1 | ### 安装依赖 2 | 3 | `npm install` 4 | 5 | ### 启动 6 | 7 | `npm run watch` 8 | 9 | ### 执行 `npm link` 10 | 11 | 此时就可以使用 `eos` 命令了。 12 | 13 | - `eos init vue-template myVue` 14 | - `eos config get` 15 | - `eos config set type orgs` 16 | - `eos config set registry vuejs-templates` 17 | 18 | - `eos config set type users` 19 | - `eos config set registry YvetteLau` 20 | 21 | ### 发布 22 | 23 | 开发完成后,即可发布至 npm. 24 | -------------------------------------------------------------------------------- /eos-cli/bin/www: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | require('../dist/main.js'); -------------------------------------------------------------------------------- /eos-cli/dist/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _rc = require('./utils/rc'); 4 | 5 | let config = async (action, key, value) => { 6 | switch (action) { 7 | case 'get': 8 | if (key) { 9 | let result = await (0, _rc.get)(key); 10 | console.log(result); 11 | } else { 12 | let obj = await (0, _rc.getAll)(); 13 | Object.keys(obj).forEach(key => { 14 | console.log(`${key}=${obj[key]}`); 15 | }); 16 | } 17 | break; 18 | case 'set': 19 | (0, _rc.set)(key, value); 20 | break; 21 | case 'remove': 22 | (0, _rc.remove)(key); 23 | break; 24 | default: 25 | break; 26 | } 27 | }; // 管理 .eosrc 文件 (当前用户目录下) 28 | 29 | module.exports = config; -------------------------------------------------------------------------------- /eos-cli/dist/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | // 主的流程控制 7 | let apply = (action, ...args) => { 8 | //babel-env 9 | require(`./${action}`)(...args); 10 | }; 11 | 12 | exports.default = apply; -------------------------------------------------------------------------------- /eos-cli/dist/init.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _get = require('./utils/get'); 4 | 5 | var _ora = require('ora'); 6 | 7 | var _ora2 = _interopRequireDefault(_ora); 8 | 9 | var _inquirer = require('inquirer'); 10 | 11 | var _inquirer2 = _interopRequireDefault(_inquirer); 12 | 13 | var _fs = require('fs'); 14 | 15 | var _fs2 = _interopRequireDefault(_fs); 16 | 17 | var _chalk = require('chalk'); 18 | 19 | var _chalk2 = _interopRequireDefault(_chalk); 20 | 21 | var _logSymbols = require('log-symbols'); 22 | 23 | var _logSymbols2 = _interopRequireDefault(_logSymbols); 24 | 25 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 26 | 27 | let init = async (templateName, projectName) => { 28 | //项目不存在 29 | if (!_fs2.default.existsSync(projectName)) { 30 | //命令行交互 31 | _inquirer2.default.prompt([{ 32 | name: 'description', 33 | message: 'Please enter the project description: ' 34 | }, { 35 | name: 'author', 36 | message: 'Please enter the author name: ' 37 | }]).then(async answer => { 38 | //下载模板 选择模板 39 | //通过配置文件,获取模板信息 40 | let loading = (0, _ora2.default)('downloading template ...'); 41 | loading.start(); 42 | (0, _get.downloadLocal)(templateName, projectName).then(() => { 43 | loading.succeed(); 44 | const fileName = `${projectName}/package.json`; 45 | if (_fs2.default.existsSync(fileName)) { 46 | const data = _fs2.default.readFileSync(fileName).toString(); 47 | let json = JSON.parse(data); 48 | json.name = projectName; 49 | json.author = answer.author; 50 | json.description = answer.description; 51 | //修改项目文件夹中 package.json 文件 52 | _fs2.default.writeFileSync(fileName, JSON.stringify(json, null, '\t'), 'utf-8'); 53 | console.log(_logSymbols2.default.success, _chalk2.default.green('Project initialization finished!')); 54 | } 55 | }, () => { 56 | loading.fail(); 57 | }); 58 | }); 59 | } else { 60 | //项目已经存在 61 | console.log(_logSymbols2.default.error, _chalk2.default.red('The project already exists')); 62 | } 63 | }; 64 | 65 | module.exports = init; -------------------------------------------------------------------------------- /eos-cli/dist/install.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _get = require('./utils/get'); 8 | 9 | var _ora = require('ora'); 10 | 11 | var _ora2 = _interopRequireDefault(_ora); 12 | 13 | var _inquirer = require('inquirer'); 14 | 15 | var _inquirer2 = _interopRequireDefault(_inquirer); 16 | 17 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 18 | 19 | let install = async () => { 20 | //下载模板 选择模板 21 | //通过配置文件,获取模板信息 22 | let loading = (0, _ora2.default)('fetching template ...'); 23 | loading.start(); 24 | let list = await (0, _get.repoList)(); 25 | loading.succeed(); 26 | list = list.map(({ name }) => name); 27 | 28 | let result = await _inquirer2.default.prompt([{ 29 | type: 'list', 30 | name: 'project', 31 | choices: list, 32 | questions: 'choice your template' 33 | }]); 34 | //项目名字 35 | let project = result.project; 36 | //获取当前项目的版本号 37 | loading = (0, _ora2.default)('fetching tag ...'); 38 | loading.start(); 39 | list = await (0, _get.tagList)(project); 40 | loading.succeed(); 41 | list = list.map(({ name }) => name); 42 | 43 | let answer = await _inquirer2.default.prompt([{ 44 | type: 'list', 45 | name: 'tag', 46 | choices: list, 47 | questions: 'choice tag' 48 | }]); 49 | let tag = answer.tag; 50 | console.log(project, tag); 51 | 52 | //下载文件(先下载到缓存文件) 53 | //ly-cli init 54 | loading = (0, _ora2.default)('download project...'); 55 | loading.start(); 56 | await (0, _get.downloadLocal)(project, tag); 57 | loading.succeed(); 58 | }; 59 | 60 | exports.default = install; -------------------------------------------------------------------------------- /eos-cli/dist/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _commander = require('commander'); 4 | 5 | var _commander2 = _interopRequireDefault(_commander); 6 | 7 | var _constants = require('./utils/constants'); 8 | 9 | var _index = require('./index'); 10 | 11 | var _index2 = _interopRequireDefault(_index); 12 | 13 | var _chalk = require('chalk'); 14 | 15 | var _chalk2 = _interopRequireDefault(_chalk); 16 | 17 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 18 | 19 | /** 20 | * eos commands 21 | * - config 22 | * - init 23 | */ 24 | 25 | let actionMap = { 26 | init: { 27 | description: 'generate a new project from a template', 28 | usages: ['eos init templateName projectName'] 29 | }, 30 | config: { 31 | alias: 'cfg', 32 | description: 'config .eosrc', 33 | usages: ['eos config set ', 'eos config get ', 'eos config remove '] 34 | 35 | } 36 | //other commands 37 | }; 38 | 39 | Object.keys(actionMap).forEach(action => { 40 | _commander2.default.command(action).description(actionMap[action].description).alias(actionMap[action].alias) //别名 41 | .action(() => { 42 | switch (action) { 43 | case 'config': 44 | //配置 45 | (0, _index2.default)(action, ...process.argv.slice(3)); 46 | break; 47 | case 'init': 48 | (0, _index2.default)(action, ...process.argv.slice(3)); 49 | break; 50 | default: 51 | break; 52 | } 53 | }); 54 | }); 55 | 56 | function help() { 57 | console.log('\r\nUsage:'); 58 | Object.keys(actionMap).forEach(action => { 59 | actionMap[action].usages.forEach(usage => { 60 | console.log(' - ' + usage); 61 | }); 62 | }); 63 | console.log('\r'); 64 | } 65 | 66 | _commander2.default.usage(' [options]'); 67 | _commander2.default.on('-h', help); 68 | _commander2.default.on('--help', help); 69 | _commander2.default.version(_constants.VERSION, '-V --version').parse(process.argv); 70 | 71 | // eos 不带参数时 72 | if (!process.argv.slice(2).length) { 73 | _commander2.default.outputHelp(make_green); 74 | } 75 | function make_green(txt) { 76 | return _chalk2.default.green(txt); 77 | } -------------------------------------------------------------------------------- /eos-cli/dist/utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; -------------------------------------------------------------------------------- /eos-cli/dist/utils/common.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | let betterRequire = exports.betterRequire = absPath => { 7 | let module = require(absPath); 8 | if (module.default) { 9 | return module.default; 10 | } 11 | return module; 12 | }; -------------------------------------------------------------------------------- /eos-cli/dist/utils/constants.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.DEFAULTS = exports.RC = exports.VERSION = undefined; 7 | 8 | var _package = require('../../package.json'); 9 | 10 | //当前 package.json 的版本号 11 | const VERSION = exports.VERSION = _package.version; 12 | 13 | // 用户的根目录 14 | const HOME = process.env[process.platform === 'win32' ? 'USERPROFILE' : 'HOME']; 15 | 16 | // 配置文件目录 17 | const RC = exports.RC = `${HOME}/.eosrc`; 18 | 19 | // RC 配置下载模板的地方,给 github 的 api 使用 20 | // https://api.github.com/users/YvetteLau/repos 21 | // https://api.github.com/${type}/${registry}/repos 22 | // 模板下载地址可配置 23 | const DEFAULTS = exports.DEFAULTS = { 24 | registry: 'YvetteLau', 25 | type: 'users' 26 | }; -------------------------------------------------------------------------------- /eos-cli/dist/utils/get.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.downloadLocal = undefined; 7 | 8 | var _rc = require('./rc'); 9 | 10 | var _downloadGitRepo = require('download-git-repo'); 11 | 12 | var _downloadGitRepo2 = _interopRequireDefault(_downloadGitRepo); 13 | 14 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 15 | 16 | const downloadLocal = exports.downloadLocal = async (templateName, projectName) => { 17 | let config = await (0, _rc.getAll)(); 18 | let api = `${config.registry}/${templateName}`; 19 | return new Promise((resolve, reject) => { 20 | (0, _downloadGitRepo2.default)(api, projectName, err => { 21 | if (err) { 22 | reject(err); 23 | } 24 | resolve(); 25 | }); 26 | }); 27 | }; -------------------------------------------------------------------------------- /eos-cli/dist/utils/rc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.remove = exports.set = exports.getAll = exports.get = undefined; 7 | 8 | var _constants = require('./constants'); 9 | 10 | var _ini = require('ini'); 11 | 12 | var _util = require('util'); 13 | 14 | var _chalk = require('chalk'); 15 | 16 | var _chalk2 = _interopRequireDefault(_chalk); 17 | 18 | var _fs = require('fs'); 19 | 20 | var _fs2 = _interopRequireDefault(_fs); 21 | 22 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 23 | 24 | const exits = (0, _util.promisify)(_fs2.default.exists); 25 | const readFile = (0, _util.promisify)(_fs2.default.readFile); 26 | const writeFile = (0, _util.promisify)(_fs2.default.writeFile); 27 | 28 | //RC 是配置文件 29 | //DEFAULTS 是默认的配置 30 | const get = exports.get = async key => { 31 | const exit = await exits(_constants.RC); 32 | let opts; 33 | if (exit) { 34 | opts = await readFile(_constants.RC, 'utf8'); 35 | opts = (0, _ini.decode)(opts); 36 | return opts[key]; 37 | } 38 | return ''; 39 | }; 40 | 41 | const getAll = exports.getAll = async () => { 42 | const exit = await exits(_constants.RC); 43 | let opts; 44 | if (exit) { 45 | opts = await readFile(_constants.RC, 'utf8'); 46 | opts = (0, _ini.decode)(opts); 47 | return opts; 48 | } 49 | return {}; 50 | }; 51 | 52 | const set = exports.set = async (key, value) => { 53 | const exit = await exits(_constants.RC); 54 | let opts; 55 | if (exit) { 56 | opts = await readFile(_constants.RC, 'utf8'); 57 | opts = (0, _ini.decode)(opts); 58 | if (!key) { 59 | console.log(_chalk2.default.red(_chalk2.default.bold('Error:')), _chalk2.default.red('key is required')); 60 | return; 61 | } 62 | if (!value) { 63 | console.log(_chalk2.default.red(_chalk2.default.bold('Error:')), _chalk2.default.red('value is required')); 64 | return; 65 | } 66 | Object.assign(opts, { [key]: value }); 67 | } else { 68 | opts = Object.assign(_constants.DEFAULTS, { [key]: value }); 69 | } 70 | await writeFile(_constants.RC, (0, _ini.encode)(opts), 'utf8'); 71 | }; 72 | 73 | const remove = exports.remove = async key => { 74 | const exit = await exits(_constants.RC); 75 | let opts; 76 | if (exit) { 77 | opts = await readFile(_constants.RC, 'utf8'); 78 | opts = (0, _ini.decode)(opts); 79 | delete opts[key]; 80 | await writeFile(_constants.RC, (0, _ini.encode)(opts), 'utf8'); 81 | } 82 | }; -------------------------------------------------------------------------------- /eos-cli/dist/utils/utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; -------------------------------------------------------------------------------- /eos-cli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eos-cli", 3 | "version": "1.0.2", 4 | "description": "脚手架", 5 | "main": "index.js", 6 | "bin": { 7 | "eos": "./bin/www" 8 | }, 9 | "scripts": { 10 | "compile": "babel src -d dist", 11 | "watch": "npm run compile -- --watch", 12 | "postinstall": "eos config set" 13 | }, 14 | "keywords": [], 15 | "author": "", 16 | "license": "ISC", 17 | "dependencies": { 18 | "babel-cli": "^6.26.0", 19 | "babel-env": "^2.4.1", 20 | "chalk": "^2.4.2", 21 | "commander": "^2.20.0", 22 | "download-git-repo": "^2.0.0", 23 | "ini": "^1.3.5", 24 | "inquirer": "^6.5.0", 25 | "log-symbols": "^3.0.0", 26 | "ora": "^3.4.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /eos-cli/src/config.js: -------------------------------------------------------------------------------- 1 | // 管理 .eosrc 文件 (当前用户目录下) 2 | 3 | import { get, set, getAll, remove } from './utils/rc'; 4 | 5 | let config = async (action, key, value) => { 6 | switch (action) { 7 | case 'get': 8 | if (key) { 9 | let result = await get(key); 10 | console.log(result); 11 | } else { 12 | let obj = await getAll(); 13 | Object.keys(obj).forEach(key => { 14 | console.log(`${key}=${obj[key]}`); 15 | }) 16 | } 17 | break; 18 | case 'set': 19 | set(key, value); 20 | break; 21 | case 'remove': 22 | remove(key); 23 | break; 24 | default: 25 | break; 26 | } 27 | } 28 | 29 | module.exports = config; -------------------------------------------------------------------------------- /eos-cli/src/index.js: -------------------------------------------------------------------------------- 1 | // 主的流程控制 2 | let apply = (action, ...args) => { 3 | //babel-env 4 | require(`./${action}`)(...args); 5 | }; 6 | 7 | export default apply; -------------------------------------------------------------------------------- /eos-cli/src/init.js: -------------------------------------------------------------------------------- 1 | import { downloadLocal } from './utils/get'; 2 | import ora from 'ora'; 3 | import inquirer from 'inquirer'; 4 | import fs from 'fs'; 5 | import chalk from 'chalk'; 6 | import symbol from 'log-symbols'; 7 | 8 | let init = async (templateName, projectName) => { 9 | //项目不存在 10 | if (!fs.existsSync(projectName)) { 11 | //命令行交互 12 | inquirer.prompt([ 13 | { 14 | name: 'description', 15 | message: 'Please enter the project description: ' 16 | }, 17 | { 18 | name: 'author', 19 | message: 'Please enter the author name: ' 20 | } 21 | ]).then(async (answer) => { 22 | //下载模板 选择模板 23 | //通过配置文件,获取模板信息 24 | let loading = ora('downloading template ...'); 25 | loading.start(); 26 | downloadLocal(templateName, projectName).then(() => { 27 | loading.succeed(); 28 | const fileName = `${projectName}/package.json`; 29 | if(fs.existsSync(fileName)){ 30 | const data = fs.readFileSync(fileName).toString(); 31 | let json = JSON.parse(data); 32 | json.name = projectName; 33 | json.author = answer.author; 34 | json.description = answer.description; 35 | //修改项目文件夹中 package.json 文件 36 | fs.writeFileSync(fileName, JSON.stringify(json, null, '\t'), 'utf-8'); 37 | console.log(symbol.success, chalk.green('Project initialization finished!')); 38 | } 39 | }, () => { 40 | loading.fail(); 41 | }); 42 | }); 43 | }else { 44 | //项目已经存在 45 | console.log(symbol.error, chalk.red('The project already exists')); 46 | } 47 | } 48 | 49 | module.exports = init; -------------------------------------------------------------------------------- /eos-cli/src/main.js: -------------------------------------------------------------------------------- 1 | import program from 'commander'; 2 | import { VERSION } from './utils/constants'; 3 | import apply from './index'; 4 | import chalk from 'chalk'; 5 | 6 | /** 7 | * eos commands 8 | * - config 9 | * - init 10 | */ 11 | 12 | let actionMap = { 13 | init: { 14 | description: 'generate a new project from a template', 15 | usages: [ 16 | 'eos init templateName projectName' 17 | ] 18 | }, 19 | config: { 20 | alias: 'cfg', 21 | description: 'config .eosrc', 22 | usages: [ 23 | 'eos config set ', 24 | 'eos config get ', 25 | 'eos config remove ' 26 | ] 27 | 28 | }, 29 | //other commands 30 | } 31 | 32 | Object.keys(actionMap).forEach((action) => { 33 | program.command(action) 34 | .description(actionMap[action].description) 35 | .alias(actionMap[action].alias) //别名 36 | .action(() => { 37 | switch (action) { 38 | case 'config': 39 | //配置 40 | apply(action, ...process.argv.slice(3)); 41 | break; 42 | case 'init': 43 | apply(action, ...process.argv.slice(3)); 44 | break; 45 | default: 46 | break; 47 | } 48 | }); 49 | }); 50 | 51 | function help() { 52 | console.log('\r\nUsage:'); 53 | Object.keys(actionMap).forEach((action) => { 54 | actionMap[action].usages.forEach(usage => { 55 | console.log(' - ' + usage); 56 | }); 57 | }); 58 | console.log('\r'); 59 | } 60 | 61 | 62 | program.usage(' [options]'); 63 | program.on('-h', help); 64 | program.on('--help', help); 65 | program.version(VERSION, '-V --version').parse(process.argv); 66 | 67 | // eos 不带参数时 68 | if (!process.argv.slice(2).length) { 69 | program.outputHelp(make_green); 70 | } 71 | function make_green(txt) { 72 | return chalk.green(txt); 73 | } -------------------------------------------------------------------------------- /eos-cli/src/utils/constants.js: -------------------------------------------------------------------------------- 1 | import {version} from '../../package.json'; 2 | 3 | //当前 package.json 的版本号 4 | export const VERSION = version; 5 | 6 | // 用户的根目录 7 | const HOME = process.env[process.platform === 'win32' ? 'USERPROFILE' : 'HOME']; 8 | 9 | // 配置文件目录 10 | export const RC = `${HOME}/.eosrc`; 11 | 12 | // RC 配置下载模板的地方,给 github 的 api 使用 13 | // https://api.github.com/users/YvetteLau/repos 14 | // https://api.github.com/${type}/${registry}/repos 15 | // 模板下载地址可配置 16 | export const DEFAULTS = { 17 | registry: 'YvetteLau', 18 | type: 'users' 19 | } 20 | -------------------------------------------------------------------------------- /eos-cli/src/utils/get.js: -------------------------------------------------------------------------------- 1 | import { getAll } from './rc'; 2 | import downloadGit from 'download-git-repo'; 3 | 4 | export const downloadLocal = async (templateName, projectName) => { 5 | let config = await getAll(); 6 | let api = `${config.registry}/${templateName}`; 7 | return new Promise((resolve, reject) => { 8 | downloadGit(api, projectName, (err) => { 9 | if (err) { 10 | reject(err); 11 | } 12 | resolve(); 13 | }); 14 | }); 15 | } -------------------------------------------------------------------------------- /eos-cli/src/utils/rc.js: -------------------------------------------------------------------------------- 1 | import { RC, DEFAULTS } from './constants'; 2 | import { decode, encode } from 'ini'; 3 | import { promisify } from 'util'; 4 | import chalk from 'chalk'; 5 | import fs from 'fs'; 6 | 7 | const exits = promisify(fs.exists); 8 | const readFile = promisify(fs.readFile); 9 | const writeFile = promisify(fs.writeFile); 10 | 11 | //RC 是配置文件 12 | //DEFAULTS 是默认的配置 13 | export const get = async (key) => { 14 | const exit = await exits(RC); 15 | let opts; 16 | if (exit) { 17 | opts = await readFile(RC, 'utf8'); 18 | opts = decode(opts); 19 | return opts[key]; 20 | } 21 | return ''; 22 | } 23 | 24 | export const getAll = async () => { 25 | const exit = await exits(RC); 26 | let opts; 27 | if (exit) { 28 | opts = await readFile(RC, 'utf8'); 29 | opts = decode(opts); 30 | return opts; 31 | } 32 | return {}; 33 | } 34 | 35 | export const set = async (key, value) => { 36 | const exit = await exits(RC); 37 | let opts; 38 | if (exit) { 39 | opts = await readFile(RC, 'utf8'); 40 | opts = decode(opts); 41 | if(!key) { 42 | console.log(chalk.red(chalk.bold('Error:')), chalk.red('key is required')); 43 | return; 44 | } 45 | if(!value) { 46 | console.log(chalk.red(chalk.bold('Error:')), chalk.red('value is required')); 47 | return; 48 | } 49 | Object.assign(opts, { [key]: value }); 50 | } else { 51 | opts = Object.assign(DEFAULTS, { [key]: value }); 52 | } 53 | await writeFile(RC, encode(opts), 'utf8'); 54 | } 55 | 56 | export const remove = async (key) => { 57 | const exit = await exits(RC); 58 | let opts; 59 | if (exit) { 60 | opts = await readFile(RC, 'utf8'); 61 | opts = decode(opts); 62 | delete opts[key]; 63 | await writeFile(RC, encode(opts), 'utf8'); 64 | } 65 | } 66 | 67 | -------------------------------------------------------------------------------- /myreact-redux/counter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "counter", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "react": "^16.10.1", 7 | "react-dom": "^16.10.1", 8 | "react-redux": "^7.1.1", 9 | "react-scripts": "3.1.2", 10 | "redux": "^4.0.4", 11 | "redux-logger": "^3.0.6", 12 | "redux-persist": "^6.0.0", 13 | "redux-promise": "^0.6.0", 14 | "redux-thunk": "^2.3.0" 15 | }, 16 | "scripts": { 17 | "start": "react-scripts start", 18 | "build": "react-scripts build", 19 | "test": "react-scripts test", 20 | "eject": "react-scripts eject" 21 | }, 22 | "eslintConfig": { 23 | "extends": "react-app" 24 | }, 25 | "browserslist": { 26 | "production": [ 27 | ">0.2%", 28 | "not dead", 29 | "not op_mini all" 30 | ], 31 | "development": [ 32 | "last 1 chrome version", 33 | "last 1 firefox version", 34 | "last 1 safari version" 35 | ] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /myreact-redux/counter/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YvetteLau/Blog/67719f4c082d7dee3e1f99895e2e4c1c77e35fbe/myreact-redux/counter/public/favicon.ico -------------------------------------------------------------------------------- /myreact-redux/counter/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 29 | React App 30 | 31 | 32 | 33 |
34 | 35 | 36 | -------------------------------------------------------------------------------- /myreact-redux/counter/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YvetteLau/Blog/67719f4c082d7dee3e1f99895e2e4c1c77e35fbe/myreact-redux/counter/public/logo192.png -------------------------------------------------------------------------------- /myreact-redux/counter/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YvetteLau/Blog/67719f4c082d7dee3e1f99895e2e4c1c77e35fbe/myreact-redux/counter/public/logo512.png -------------------------------------------------------------------------------- /myreact-redux/counter/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /myreact-redux/counter/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /myreact-redux/counter/src/components/Counter-no-react-redux.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import store from '../store'; 3 | import actions from '../store/actions/counter'; 4 | /** 5 | * reducer 是 combineReducer({counter, ...}) 6 | * state 的结构为 7 | * { 8 | * counter: {number: 0}, 9 | * .... 10 | * } 11 | */ 12 | class Counter extends React.Component { 13 | constructor(props) { 14 | super(props); 15 | this.state = { 16 | number: store.getState().counter.number 17 | } 18 | } 19 | componentDidMount() { 20 | this.unsub = store.subscribe(() => { 21 | if(this.state.number === store.getState().counter.number) { 22 | return; 23 | } 24 | this.setState({ 25 | number: store.getState().counter.number 26 | }); 27 | }); 28 | } 29 | render() { 30 | return ( 31 |
32 |

{`number: ${this.state.number}`}

33 | 34 | 35 |
36 | ) 37 | } 38 | componentWillUnmount() { 39 | this.unsub(); 40 | } 41 | } -------------------------------------------------------------------------------- /myreact-redux/counter/src/components/Counter.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from '../react-redux'; 3 | import actions from '../store/actions/counter'; 4 | 5 | class Counter extends Component { 6 | render() { 7 | return ( 8 |
9 |

{`number: ${this.props.number}`}

10 | 11 | 12 |
13 | ) 14 | } 15 | } 16 | 17 | const mapStateToProps = state => ({ 18 | number: state.counter.number 19 | }); 20 | 21 | export default connect(mapStateToProps, actions)(Counter); 22 | -------------------------------------------------------------------------------- /myreact-redux/counter/src/components/Page.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from '../react-redux'; 3 | 4 | class Page extends Component { 5 | render() { 6 | console.log(this.props.store); 7 | return ( 8 |
Page
9 | ) 10 | } 11 | } 12 | 13 | export default connect(()=>({}), ()=>({}))(Page); -------------------------------------------------------------------------------- /myreact-redux/counter/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import {Provider} from './react-redux'; 4 | import Counter from './components/Counter'; 5 | import store from './store'; 6 | 7 | 8 | 9 | ReactDOM.render(, document.getElementById('root')); 10 | 11 | -------------------------------------------------------------------------------- /myreact-redux/counter/src/react-redux/components/Provider.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import storeShape from '../utils/storeShape'; 3 | 4 | export default class Provider extends Component { 5 | static childContextTypes = { 6 | store: storeShape.isRequired 7 | } 8 | 9 | constructor(props) { 10 | super(props); 11 | this.store = props.store; 12 | } 13 | 14 | getChildContext() { 15 | return { 16 | store: this.store 17 | } 18 | } 19 | 20 | render() { 21 | /** 22 | * 早前返回的是 return Children.only(this.props.children) 23 | * 导致Provider只能包裹一个子组件,后来取消了此限制 24 | * 因此此处,我们直接返回 this.props.children 25 | */ 26 | return this.props.children 27 | } 28 | } -------------------------------------------------------------------------------- /myreact-redux/counter/src/react-redux/components/connect.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { bindActionCreators } from 'redux'; 3 | import storeShape from '../utils/storeShape'; 4 | import shallowEqual from '../utils/shallowEqual'; 5 | /** 6 | * mapStateToProps 默认不关联state 7 | * mapDispatchToProps 默认值为 dispatch => ({dispatch}),将 `store.dispatch` 方法作为属性传递给组件 8 | */ 9 | const defaultMapStateToProps = state => ({}); 10 | const defaultMapDispatchToProps = dispatch => ({ dispatch }); 11 | function getDisplayName(WrappedComponent) { 12 | return WrappedComponent.displayName || WrappedComponent.name || 'Component'; 13 | } 14 | 15 | export default function connect(mapStateToProps, mapDispatchToProps) { 16 | if(!mapStateToProps) { 17 | mapStateToProps = defaultMapStateToProps; 18 | } 19 | if (!mapDispatchToProps) { 20 | //当 mapDispatchToProps 为 null/undefined/false...时,使用默认值 21 | mapDispatchToProps = defaultMapDispatchToProps; 22 | } 23 | return function wrapWithConnect(WrappedComponent) { 24 | return class Connect extends Component { 25 | static contextTypes = { 26 | store: storeShape 27 | }; 28 | static displayName = `Connect(${getDisplayName(WrappedComponent)})`; 29 | 30 | constructor(props, context) { 31 | super(props, context); 32 | this.store = context.store; 33 | //源码中是将 store.getState() 给了 this.state 34 | this.state = mapStateToProps(this.store.getState(), this.props); 35 | if (typeof mapDispatchToProps === 'function') { 36 | this.mappedDispatch = mapDispatchToProps(this.store.dispatch, this.props); 37 | } else { 38 | //传递了一个 actionCreator 对象过来 39 | this.mappedDispatch = bindActionCreators(mapDispatchToProps, this.store.dispatch); 40 | } 41 | } 42 | componentDidMount() { 43 | this.unsub = this.store.subscribe(() => { 44 | const mappedState = mapStateToProps(this.store.getState()); 45 | if (shallowEqual(this.state, mappedState)) { 46 | return; 47 | } 48 | this.setState(mappedState); 49 | }); 50 | } 51 | componentWillUnmount() { 52 | this.unsub(); 53 | } 54 | render() { 55 | return ( 56 | 57 | ) 58 | } 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /myreact-redux/counter/src/react-redux/index.js: -------------------------------------------------------------------------------- 1 | // Provider 通过context提供store 2 | // connect 连接仓库和组件,仓库的取值和订阅逻辑都放在在里面 3 | import connect from './components/connect'; 4 | import Provider from './components/Provider'; 5 | 6 | export { 7 | connect, 8 | Provider 9 | } -------------------------------------------------------------------------------- /myreact-redux/counter/src/react-redux/utils/shallowEqual.js: -------------------------------------------------------------------------------- 1 | export default function shallowEqual(objA, objB) { 2 | if (objA === objB) { 3 | return true 4 | } 5 | 6 | const keysA = Object.keys(objA) 7 | const keysB = Object.keys(objB) 8 | 9 | if (keysA.length !== keysB.length) { 10 | return false 11 | } 12 | 13 | const hasOwn = Object.prototype.hasOwnProperty 14 | for (let i = 0; i < keysA.length; i++) { 15 | if (!hasOwn.call(objB, keysA[i]) || 16 | objA[keysA[i]] !== objB[keysA[i]]) { 17 | return false 18 | } 19 | } 20 | 21 | return true 22 | } 23 | -------------------------------------------------------------------------------- /myreact-redux/counter/src/react-redux/utils/storeShape.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | 3 | export default PropTypes.shape({ 4 | subscribe: PropTypes.func.isRequired, 5 | dispatch: PropTypes.func.isRequired, 6 | getState: PropTypes.func.isRequired 7 | }); 8 | -------------------------------------------------------------------------------- /myreact-redux/counter/src/store/action-types.js: -------------------------------------------------------------------------------- 1 | export const INCREMENT = 'INCREMENT'; 2 | export const DECREMENT = 'DECREMENT'; -------------------------------------------------------------------------------- /myreact-redux/counter/src/store/actions/counter.js: -------------------------------------------------------------------------------- 1 | import { INCREMENT, DECREMENT } from '../action-types'; 2 | 3 | const counter = { 4 | add(number) { 5 | return { 6 | type: INCREMENT, 7 | number 8 | } 9 | }, 10 | minus(number) { 11 | return { 12 | type: DECREMENT, 13 | number 14 | } 15 | } 16 | } 17 | 18 | export default counter; -------------------------------------------------------------------------------- /myreact-redux/counter/src/store/index.js: -------------------------------------------------------------------------------- 1 | import reducer from './reducers'; 2 | import reduxLogger from 'redux-logger'; 3 | import { createStore, applyMiddleware } from '../redux'; 4 | /** 5 | * state = { 6 | * counter: { 7 | * number: XXX 8 | * } 9 | * } 10 | */ 11 | 12 | let store = createStore(reducer); 13 | 14 | export default applyMiddleware(reduxLogger)(createStore)(reducer); -------------------------------------------------------------------------------- /myreact-redux/counter/src/store/reducers/counter.js: -------------------------------------------------------------------------------- 1 | import { INCREMENT, DECREMENT } from '../action-types'; 2 | 3 | function counter(state = { number: 0 }, action) { 4 | switch (action.type) { 5 | case INCREMENT: 6 | return { 7 | ...state, 8 | number: state.number + action.number 9 | } 10 | case DECREMENT: 11 | return { 12 | ...state, 13 | number: state.number - action.number 14 | } 15 | default: 16 | return state; 17 | } 18 | } 19 | 20 | export default counter; -------------------------------------------------------------------------------- /myreact-redux/counter/src/store/reducers/index.js: -------------------------------------------------------------------------------- 1 | import counter from './counter'; 2 | import { combineReducers } from '../../redux'; 3 | 4 | export default combineReducers({ 5 | counter 6 | }); -------------------------------------------------------------------------------- /myreact-redux/todo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "counter", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "react": "^16.3.1", 7 | "react-dom": "^16.3.1", 8 | "react-scripts": "3.1.2" 9 | }, 10 | "scripts": { 11 | "start": "react-scripts start", 12 | "build": "react-scripts build", 13 | "test": "react-scripts test", 14 | "eject": "react-scripts eject" 15 | }, 16 | "eslintConfig": { 17 | "extends": "react-app" 18 | }, 19 | "browserslist": { 20 | "production": [ 21 | ">0.2%", 22 | "not dead", 23 | "not op_mini all" 24 | ], 25 | "development": [ 26 | "last 1 chrome version", 27 | "last 1 firefox version", 28 | "last 1 safari version" 29 | ] 30 | } 31 | } -------------------------------------------------------------------------------- /myreact-redux/todo/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YvetteLau/Blog/67719f4c082d7dee3e1f99895e2e4c1c77e35fbe/myreact-redux/todo/public/favicon.ico -------------------------------------------------------------------------------- /myreact-redux/todo/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 29 | React App 30 | 31 | 32 | 33 |
34 | 35 | 36 | -------------------------------------------------------------------------------- /myreact-redux/todo/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YvetteLau/Blog/67719f4c082d7dee3e1f99895e2e4c1c77e35fbe/myreact-redux/todo/public/logo192.png -------------------------------------------------------------------------------- /myreact-redux/todo/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YvetteLau/Blog/67719f4c082d7dee3e1f99895e2e4c1c77e35fbe/myreact-redux/todo/public/logo512.png -------------------------------------------------------------------------------- /myreact-redux/todo/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /myreact-redux/todo/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /myreact-redux/todo/src/components/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Footer from './Footer'; 3 | import AddTodo from '../containers/AddTodo'; 4 | import VisibleTodoList from '../containers/VisibleTodoList'; 5 | 6 | const App = () => ( 7 |
8 | 9 | 10 |
11 |
12 | ) 13 | 14 | export default App; -------------------------------------------------------------------------------- /myreact-redux/todo/src/components/Footer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import FilterLink from '../containers/FilterLink'; 3 | 4 | const Footer = () => ( 5 |

6 | Show: 7 | {" "} 8 | All 9 | {', '} 10 | Active 11 | {', '} 12 | Completed 13 |

14 | ) 15 | 16 | 17 | export default Footer; -------------------------------------------------------------------------------- /myreact-redux/todo/src/components/Link.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const Link = ({ active, children, onClick }) => { 5 | if (active) { 6 | return {children} 7 | } 8 | return ( 9 | { 12 | e.preventDefault(); 13 | onClick(); 14 | }} 15 | > 16 | {children} 17 | 18 | 19 | ) 20 | } 21 | 22 | Link.propTypes = { 23 | active: PropTypes.bool.isRequired, 24 | children: PropTypes.node.isRequired, 25 | onClick: PropTypes.func.isRequired 26 | } 27 | 28 | export default Link -------------------------------------------------------------------------------- /myreact-redux/todo/src/components/Todo.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const Todo = ({ onClick, completed, text }) => { 5 | return ( 6 |
  • 12 | {text} 13 |
  • 14 | ) 15 | } 16 | 17 | Todo.propTypes = { 18 | onClick: PropTypes.func.isRequired, 19 | completed: PropTypes.bool.isRequired, 20 | text: PropTypes.string.isRequired 21 | } 22 | 23 | export default Todo; -------------------------------------------------------------------------------- /myreact-redux/todo/src/components/TodoList.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Todo from './Todo'; 4 | 5 | const TodoList = ({todos, onTodoClick}) => { 6 | return ( 7 |
      8 | {todos.map((todo, index) => ( 9 | onTodoClick(index)} /> 10 | ))} 11 |
    12 | ) 13 | } 14 | 15 | TodoList.propTypes = { 16 | todos: PropTypes.arrayOf( 17 | PropTypes.shape({ 18 | completed: PropTypes.bool.isRequired, 19 | text: PropTypes.string.isRequired 20 | }).isRequired 21 | ).isRequired, 22 | onTodoClick: PropTypes.func.isRequired 23 | } 24 | 25 | export default TodoList; -------------------------------------------------------------------------------- /myreact-redux/todo/src/containers/AddTodo.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from '../react-redux'; 3 | import actions from '../store/actions'; 4 | 5 | let AddTodo = ({ dispatch }) => { 6 | let input; 7 | return ( 8 |
    9 |
    { 11 | e.preventDefault(); 12 | if (!input.value.trim()) { 13 | return; 14 | } 15 | dispatch(actions.addTodo(input.value)); 16 | input.value = ''; 17 | }} 18 | > 19 | { input = node }} 21 | /> 22 | 25 |
    26 |
    27 | ) 28 | } 29 | 30 | AddTodo = connect()(AddTodo); 31 | 32 | export default AddTodo; -------------------------------------------------------------------------------- /myreact-redux/todo/src/containers/FilterLink.js: -------------------------------------------------------------------------------- 1 | import { connect } from '../react-redux'; 2 | import actions from '../store/actions'; 3 | import Link from '../components/Link'; 4 | 5 | const mapStateToProps = (state, ownProps) => { 6 | return { 7 | active: ownProps.filter === state.visibilityFilter 8 | } 9 | } 10 | 11 | const mapDispatchToProps = (dispatch, ownProps) => { 12 | return { 13 | onClick: () => { 14 | dispatch(actions.setVisibilityFilter(ownProps.filter)); 15 | } 16 | } 17 | } 18 | 19 | const FilterLink = connect( 20 | mapStateToProps, 21 | mapDispatchToProps 22 | )(Link); 23 | 24 | export default FilterLink; -------------------------------------------------------------------------------- /myreact-redux/todo/src/containers/VisibleTodoList.js: -------------------------------------------------------------------------------- 1 | import { connect } from '../react-redux'; 2 | import TodoList from '../components/TodoList'; 3 | import actions from '../store/actions'; 4 | 5 | const getVisibleTodos = (todos, filter) => { 6 | switch (filter) { 7 | case 'SHOW_COMPLETED': 8 | return todos.filter(t => t.completed); 9 | case 'SHOW_ACTIVE': 10 | return todos.filter(t => !t.completed); 11 | case 'SHOW_ALL': 12 | default: 13 | return todos; 14 | } 15 | } 16 | 17 | const mapStateToProps = state => { 18 | return { 19 | todos: getVisibleTodos(state.todos, state.visibilityFilter) 20 | } 21 | } 22 | 23 | const mapDispatchToProps = dispatch => { 24 | return { 25 | onTodoClick: id => { 26 | dispatch(actions.toggleTodo(id)) 27 | } 28 | } 29 | } 30 | 31 | const VisibleTodoList = connect( 32 | mapStateToProps, 33 | mapDispatchToProps 34 | )(TodoList); 35 | 36 | export default VisibleTodoList; -------------------------------------------------------------------------------- /myreact-redux/todo/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import {Provider} from './react-redux'; 4 | import store from './store' 5 | import App from './components/App'; 6 | 7 | 8 | 9 | ReactDOM.render(, document.getElementById('root')); 10 | -------------------------------------------------------------------------------- /myreact-redux/todo/src/react-redux/components/Provider.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import storeShape from '../utils/storeShape'; 3 | 4 | export default class Provider extends Component { 5 | static childContextTypes = { 6 | store: storeShape.isRequired 7 | } 8 | 9 | constructor(props) { 10 | super(props); 11 | this.store = props.store; 12 | } 13 | 14 | getChildContext() { 15 | return { 16 | store: this.store 17 | } 18 | } 19 | 20 | render() { 21 | /** 22 | * 早前返回的是 return Children.only(this.props.children) 23 | * 导致Provider只能包裹一个子组件,后来取消了此限制 24 | * 因此此处,我们直接返回 this.props.children 25 | */ 26 | return this.props.children 27 | } 28 | } -------------------------------------------------------------------------------- /myreact-redux/todo/src/react-redux/components/connect.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { bindActionCreators } from '../../redux'; 3 | import storeShape from '../utils/storeShape'; 4 | import shallowEqual from '../utils/shallowEqual'; 5 | /** 6 | * mapStateToProps 默认不关联state 7 | * mapDispatchToProps 默认值为 dispatch => ({dispatch}),将 `store.dispatch` 方法作为属性传递给组件 8 | */ 9 | const defaultMapStateToProps = state => ({}); 10 | const defaultMapDispatchToProps = dispatch => ({ dispatch }); 11 | function getDisplayName(WrappedComponent) { 12 | return WrappedComponent.displayName || WrappedComponent.name || 'Component'; 13 | } 14 | 15 | export default function connect(mapStateToProps, mapDispatchToProps) { 16 | if(!mapStateToProps) { 17 | mapStateToProps = defaultMapStateToProps; 18 | } 19 | if (!mapDispatchToProps) { 20 | //当 mapDispatchToProps 为 null/undefined/false...时,使用默认值 21 | mapDispatchToProps = defaultMapDispatchToProps; 22 | } 23 | return function wrapWithConnect(WrappedComponent) { 24 | return class Connect extends Component { 25 | static contextTypes = { 26 | store: storeShape 27 | }; 28 | static displayName = `Connect(${getDisplayName(WrappedComponent)})`; 29 | 30 | constructor(props, context) { 31 | super(props, context); 32 | this.store = context.store; 33 | //源码中是将 store.getState() 给了 this.state 34 | this.state = mapStateToProps(this.store.getState(), this.props); 35 | if (typeof mapDispatchToProps === 'function') { 36 | this.mappedDispatch = mapDispatchToProps(this.store.dispatch, this.props); 37 | } else { 38 | //传递了一个 actionCreator 对象过来 39 | this.mappedDispatch = bindActionCreators(mapDispatchToProps, this.store.dispatch); 40 | } 41 | } 42 | componentDidMount() { 43 | this.unsub = this.store.subscribe(() => { 44 | const mappedState = mapStateToProps(this.store.getState(), this.props); 45 | if (shallowEqual(this.state, mappedState)) { 46 | return; 47 | } 48 | this.setState(mappedState); 49 | }); 50 | } 51 | componentWillUnmount() { 52 | this.unsub(); 53 | } 54 | render() { 55 | return ( 56 | 57 | ) 58 | } 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /myreact-redux/todo/src/react-redux/index.js: -------------------------------------------------------------------------------- 1 | // Provider 通过context提供store 2 | // connect 连接仓库和组件,仓库的取值和订阅逻辑都放在在里面 3 | import connect from './components/connect'; 4 | import Provider from './components/Provider'; 5 | 6 | export { 7 | connect, 8 | Provider 9 | } -------------------------------------------------------------------------------- /myreact-redux/todo/src/react-redux/utils/shallowEqual.js: -------------------------------------------------------------------------------- 1 | export default function shallowEqual(objA, objB) { 2 | if (objA === objB) { 3 | return true 4 | } 5 | 6 | const keysA = Object.keys(objA) 7 | const keysB = Object.keys(objB) 8 | 9 | if (keysA.length !== keysB.length) { 10 | return false 11 | } 12 | 13 | const hasOwn = Object.prototype.hasOwnProperty 14 | for (let i = 0; i < keysA.length; i++) { 15 | if (!hasOwn.call(objB, keysA[i]) || 16 | objA[keysA[i]] !== objB[keysA[i]]) { 17 | return false 18 | } 19 | } 20 | 21 | return true 22 | } 23 | -------------------------------------------------------------------------------- /myreact-redux/todo/src/react-redux/utils/storeShape.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | 3 | export default PropTypes.shape({ 4 | subscribe: PropTypes.func.isRequired, 5 | dispatch: PropTypes.func.isRequired, 6 | getState: PropTypes.func.isRequired 7 | }); 8 | -------------------------------------------------------------------------------- /myreact-redux/todo/src/redux/applyMiddleware.js: -------------------------------------------------------------------------------- 1 | import compose from './compose'; 2 | 3 | const applyMiddleware = (...middlewares) => createStore => (...args) => { 4 | let store = createStore(...args); 5 | let dispatch; 6 | const middlewareAPI = { 7 | getState: store.getstate, 8 | dispatch: (...args) => dispatch(...args) 9 | } 10 | let middles = middlewares.map(middleware => middleware(middlewareAPI)); 11 | dispatch = compose(...middles)(store.dispatch); 12 | return { 13 | ...store, 14 | dispatch 15 | } 16 | } 17 | 18 | export default applyMiddleware; -------------------------------------------------------------------------------- /myreact-redux/todo/src/redux/bindActionCreators.js: -------------------------------------------------------------------------------- 1 | function bindActionCreator(actionCreator, dispatch) { 2 | return (...args) => dispatch(actionCreator(...args)); 3 | } 4 | function bindActionCreators(actionCreator, dispatch) { 5 | //actionCreators 可以是一个普通函数或者是一个对象 6 | if (typeof actionCreator === 'function') { 7 | //如果是函数,返回一个函数,调用时,dispatch 这个函数的返回值 8 | bindActionCreator(actionCreator, dispatch); 9 | } else if (typeof actionCreator === 'object') { 10 | //如果是一个对象,那么对象的每一项都要都要返回 bindActionCreator 11 | const boundActionCreators = {} 12 | for (let key in actionCreator) { 13 | boundActionCreators[key] = bindActionCreator(actionCreator[key], dispatch); 14 | } 15 | return boundActionCreators; 16 | } 17 | } 18 | 19 | export default bindActionCreators; -------------------------------------------------------------------------------- /myreact-redux/todo/src/redux/combineReducers.js: -------------------------------------------------------------------------------- 1 | export default function combineReducers(reducers) { 2 | return function combination(state={}, action) { 3 | let nextState = {}; 4 | let hasChanged = false; //状态是否改变 5 | for(let key in reducers) { 6 | const previousStateForKey = state[key]; 7 | const nextStateForKey = reducers[key](previousStateForKey, action); 8 | nextState[key] = nextStateForKey; 9 | //只有所有的 nextStateForKey 均与 previousStateForKey 相等时,hasChanged 的值 false 10 | hasChanged = hasChanged || nextStateForKey !== previousStateForKey; 11 | } 12 | //state 没有改变时,返回原对象 13 | return hasChanged ? nextState : state; 14 | } 15 | } 16 | 17 | -------------------------------------------------------------------------------- /myreact-redux/todo/src/redux/compose.js: -------------------------------------------------------------------------------- 1 | export default function compose(...funcs) { 2 | //如果没有中间件 3 | if (funcs.length === 0) { 4 | return arg => arg 5 | } 6 | //中间件长度为1 7 | if (funcs.length === 1) { 8 | return funcs[0] 9 | } 10 | 11 | return funcs.reduce((prev, current) => (...args) => prev(current(...args))); 12 | } -------------------------------------------------------------------------------- /myreact-redux/todo/src/redux/createStore.js: -------------------------------------------------------------------------------- 1 | export default function createStore(reducer) { 2 | let state; 3 | let listeners = []; 4 | const getState = () => state; 5 | const subscribe = (ln) => { 6 | listeners.push(ln); 7 | //订阅之后,也要允许取消订阅。不能只准订,不准退~ 8 | const unsubscribe = () => { 9 | listeners = listeners.filter(listener => ln !== listener); 10 | } 11 | return unsubscribe; 12 | }; 13 | const dispatch = (action) => { 14 | //reducer(state, action) 返回一个新状态 15 | state = reducer(state, action); 16 | listeners.forEach(ln => ln()); 17 | } 18 | //你要是有个 action 的 type 的值正好和 `@@redux/__INIT__${Math.random()}` 相等,我敬你是个狠人 19 | dispatch({ type: `@@redux/__INIT__${Math.random()}` }); 20 | 21 | return { 22 | getState, 23 | dispatch, 24 | subscribe 25 | } 26 | } -------------------------------------------------------------------------------- /myreact-redux/todo/src/redux/index.js: -------------------------------------------------------------------------------- 1 | import combineReducers from './combineReducers'; 2 | import createStore from './createStore'; 3 | import applyMiddleware from './applyMiddleware'; 4 | import compose from './compose'; 5 | import bindActionCreators from './bindActionCreators'; 6 | 7 | export { 8 | combineReducers, 9 | createStore, 10 | applyMiddleware, 11 | bindActionCreators, 12 | compose 13 | } 14 | -------------------------------------------------------------------------------- /myreact-redux/todo/src/store/action-types.js: -------------------------------------------------------------------------------- 1 | export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER'; 2 | 3 | export const ADD_TODO = 'ADD_TODO'; 4 | 5 | export const TOGGLE_TODO = 'TOGGLE_TODO'; -------------------------------------------------------------------------------- /myreact-redux/todo/src/store/actions/index.js: -------------------------------------------------------------------------------- 1 | import todos from './todos'; 2 | import visibilityFilter from './visibilityFilter'; 3 | 4 | export default { 5 | ...todos, 6 | ...visibilityFilter 7 | } -------------------------------------------------------------------------------- /myreact-redux/todo/src/store/actions/todos.js: -------------------------------------------------------------------------------- 1 | import * as types from '../action-types'; 2 | 3 | const todos = { 4 | addTodo(text) { 5 | return { 6 | type: types.ADD_TODO, 7 | text 8 | } 9 | }, 10 | toggleTodo(index) { 11 | return { 12 | type: types.TOGGLE_TODO, 13 | index 14 | } 15 | } 16 | } 17 | 18 | export default todos; -------------------------------------------------------------------------------- /myreact-redux/todo/src/store/actions/visibilityFilter.js: -------------------------------------------------------------------------------- 1 | import * as types from '../action-types'; 2 | 3 | const setVisibilityFilter = { 4 | setVisibilityFilter(filter) { 5 | return { 6 | type: types.SET_VISIBILITY_FILTER, 7 | filter 8 | } 9 | } 10 | } 11 | 12 | export default setVisibilityFilter; -------------------------------------------------------------------------------- /myreact-redux/todo/src/store/index.js: -------------------------------------------------------------------------------- 1 | import {createStore} from '../redux'; 2 | import todoApp from './reducers'; 3 | 4 | const store = createStore(todoApp); 5 | 6 | export default store; 7 | -------------------------------------------------------------------------------- /myreact-redux/todo/src/store/reducers/index.js: -------------------------------------------------------------------------------- 1 | import {combineReducers} from '../../redux'; 2 | import todos from './todos'; 3 | import visibilityFilter from './visibilityFilter' 4 | 5 | const todoApp = combineReducers({ 6 | todos, 7 | visibilityFilter 8 | }); 9 | export default todoApp; -------------------------------------------------------------------------------- /myreact-redux/todo/src/store/reducers/todos.js: -------------------------------------------------------------------------------- 1 | import * as types from '../action-types'; 2 | 3 | export default function todos(state = [], action) { 4 | switch (action.type) { 5 | case types.ADD_TODO: 6 | return [ 7 | ...state, 8 | { 9 | text: action.text, 10 | completed: false 11 | } 12 | ] 13 | case types.TOGGLE_TODO: 14 | return state.map((todo, index) => { 15 | return action.index === index ? 16 | { 17 | ...todo, 18 | completed: !todo.completed 19 | } : 20 | todo 21 | }) 22 | default: 23 | return state; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /myreact-redux/todo/src/store/reducers/visibilityFilter.js: -------------------------------------------------------------------------------- 1 | import * as types from '../action-types'; 2 | 3 | const VisibilityFilters = { 4 | SHOW_ALL: 'SHOW_ALL' 5 | } 6 | 7 | export default function visibilityFilter(state = VisibilityFilters.SHOW_ALL, action) { 8 | switch (action.type) { 9 | case types.SET_VISIBILITY_FILTER: 10 | return action.filter; 11 | default: 12 | return state; 13 | } 14 | } -------------------------------------------------------------------------------- /myredux/to-redux/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux-1", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "react": "^16.9.0", 7 | "react-dom": "^16.9.0", 8 | "react-redux": "^7.1.0", 9 | "react-scripts": "3.1.1" 10 | }, 11 | "scripts": { 12 | "start": "react-scripts start", 13 | "build": "react-scripts build", 14 | "test": "react-scripts test", 15 | "eject": "react-scripts eject" 16 | }, 17 | "eslintConfig": { 18 | "extends": "react-app" 19 | }, 20 | "browserslist": { 21 | "production": [ 22 | ">0.2%", 23 | "not dead", 24 | "not op_mini all" 25 | ], 26 | "development": [ 27 | "last 1 chrome version", 28 | "last 1 firefox version", 29 | "last 1 safari version" 30 | ] 31 | }, 32 | "devDependencies": { 33 | "redux": "^4.0.4" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /myredux/to-redux/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | React App 11 | 54 | 55 | 56 | 57 | 58 |
    59 | 62 |
    63 |
    大家好,我是前端宇宙作者刘小夕
    64 | 65 | 66 |
    67 |
    68 | 69 | 70 | -------------------------------------------------------------------------------- /myredux/to-redux/src/index.js: -------------------------------------------------------------------------------- 1 | function createStore(reducer) { 2 | let state; 3 | let listeners = []; 4 | const getState = () => state; 5 | const subscribe = (ln) => { 6 | listeners.push(ln); 7 | //订阅之后,也要允许取消订阅。不能只准订,不准退~ 8 | const unsubscribe = () => { 9 | listeners = listeners.filter(listener => ln !== listener); 10 | } 11 | return unsubscribe; 12 | }; 13 | const dispatch = (action) => { 14 | //reducer(state, action) 返回一个新状态 15 | state = reducer(state, action); 16 | listeners.forEach(ln => ln()); 17 | } 18 | //你要是有个 action 的 type 的值正好和 `@@redux/__INIT__${Math.random()}` 相等,我敬你是个狠人 19 | dispatch({ type: `@@redux/__INIT__${Math.random()}` }); 20 | 21 | return { 22 | getState, 23 | dispatch, 24 | subscribe 25 | } 26 | } 27 | 28 | const initialState = { 29 | color: 'blue' 30 | } 31 | 32 | function reducer(state = initialState, action) { 33 | switch (action.type) { 34 | case 'CHANGE_COLOR': 35 | return { 36 | ...state, 37 | color: action.color 38 | } 39 | default: 40 | return state; 41 | } 42 | } 43 | const store = createStore(reducer); 44 | 45 | function renderApp(state) { 46 | console.log(state) 47 | renderHeader(state); 48 | renderContent(state); 49 | } 50 | function renderHeader(state) { 51 | const header = document.getElementById('header'); 52 | header.style.color = state.color; 53 | } 54 | function renderContent(state) { 55 | const content = document.getElementById('content'); 56 | content.style.color = state.color; 57 | } 58 | 59 | document.getElementById('to-blue').onclick = function () { 60 | store.dispatch({ 61 | type: 'CHANGE_COLOR', 62 | color: 'rgb(0, 51, 254)' 63 | }); 64 | // unsub(); 65 | } 66 | document.getElementById('to-pink').onclick = function () { 67 | store.dispatch({ 68 | type: 'CHANGE_COLOR', 69 | color: 'rgb(247, 109, 132)' 70 | }); 71 | } 72 | 73 | renderApp(store.getState()); 74 | var unsub = store.subscribe(() => renderApp(store.getState())); -------------------------------------------------------------------------------- /myredux/to-redux/src/index1.js: -------------------------------------------------------------------------------- 1 | let state = { 2 | color: 'blue' 3 | } 4 | //渲染应用 5 | function renderApp() { 6 | renderHeader(); 7 | renderContent(); 8 | } 9 | //渲染 title 部分 10 | function renderHeader() { 11 | const header = document.getElementById('header'); 12 | header.style.color = state.color; 13 | } 14 | //渲染内容 15 | function renderContent() { 16 | const content = document.getElementById('content'); 17 | content.style.color = state.color; 18 | } 19 | 20 | renderApp(); 21 | 22 | document.getElementById('to-blue').onclick = function () { 23 | state.color = 'rgb(0, 51, 254)'; 24 | renderApp(); 25 | } 26 | document.getElementById('to-pink').onclick = function () { 27 | state.color = 'rgb(247, 109, 132)'; 28 | renderApp(); 29 | } -------------------------------------------------------------------------------- /myredux/to-redux/src/index2.js: -------------------------------------------------------------------------------- 1 | function createStore() { 2 | let state = { 3 | color: 'blue' 4 | } 5 | const getState = () => state; 6 | function changeState(action) { 7 | switch (action.type) { 8 | case 'CHANGE_COLOR': 9 | state = { 10 | ...state, 11 | color: action.color 12 | } 13 | return state; 14 | default: 15 | return state; 16 | } 17 | } 18 | return { 19 | getState, 20 | changeState 21 | } 22 | } 23 | 24 | function renderApp(state) { 25 | renderHeader(state); 26 | renderContent(state); 27 | } 28 | function renderHeader(state) { 29 | const header = document.getElementById('header'); 30 | header.style.color = state.color; 31 | } 32 | function renderContent(state) { 33 | const content = document.getElementById('content'); 34 | content.style.color = state.color; 35 | } 36 | 37 | document.getElementById('to-blue').onclick = function () { 38 | store.changeState({ 39 | type: 'CHANGE_COLOR', 40 | color: 'rgb(0, 51, 254)' 41 | }); 42 | renderApp(store.getState()); 43 | } 44 | document.getElementById('to-pink').onclick = function () { 45 | store.changeState({ 46 | type: 'CHANGE_COLOR', 47 | color: 'rgb(247, 109, 132)' 48 | }); 49 | renderApp(store.getState()); 50 | } 51 | const store = createStore(); 52 | renderApp(store.getState()); -------------------------------------------------------------------------------- /myredux/to-redux2/README.md: -------------------------------------------------------------------------------- 1 | > state 的结构 2 | 3 | ```javascript 4 | let state = { 5 | theme: { 6 | color: 'blue' 7 | }, 8 | counter: { 9 | number: 0 10 | } 11 | } 12 | ``` 13 | -------------------------------------------------------------------------------- /myredux/to-redux2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux-1", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "react": "^16.9.0", 7 | "react-dom": "^16.9.0", 8 | "react-redux": "^7.1.0", 9 | "react-scripts": "3.1.1" 10 | }, 11 | "scripts": { 12 | "start": "react-scripts start", 13 | "build": "react-scripts build", 14 | "test": "react-scripts test", 15 | "eject": "react-scripts eject" 16 | }, 17 | "eslintConfig": { 18 | "extends": "react-app" 19 | }, 20 | "browserslist": { 21 | "production": [ 22 | ">0.2%", 23 | "not dead", 24 | "not op_mini all" 25 | ], 26 | "development": [ 27 | "last 1 chrome version", 28 | "last 1 firefox version", 29 | "last 1 safari version" 30 | ] 31 | }, 32 | "devDependencies": { 33 | "redux": "^4.0.4", 34 | "redux-logger": "^3.0.6" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /myredux/to-redux2/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | React App 13 | 68 | 69 | 70 | 71 |
    72 | 73 |
    74 | 75 | 76 | -------------------------------------------------------------------------------- /myredux/to-redux2/src/components/Counter.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import store from '../store'; 3 | import actions from '../store/actions/counter'; 4 | 5 | export default class Counter extends Component { 6 | constructor() { 7 | super(); 8 | this.state = { 9 | number: store.getState().counter.number 10 | } 11 | } 12 | componentDidMount() { 13 | this.unsub = store.subscribe(()=>{ 14 | this.setState({ 15 | number: store.getState().counter.number 16 | }) 17 | }) 18 | } 19 | render() { 20 | return ( 21 |
    22 | 30 | {this.state.number} 31 | 39 |
    40 | ) 41 | } 42 | componentWillUnmount() { 43 | this.unsub(); 44 | } 45 | } -------------------------------------------------------------------------------- /myredux/to-redux2/src/components/Pannel.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import store from '../store'; 3 | import actions from '../store/actions/theme'; 4 | window.store = store; 5 | 6 | export default class Pannel extends Component { 7 | constructor() { 8 | super(); 9 | this.state = { 10 | color: store.getState().theme 11 | } 12 | } 13 | componentDidMount() { 14 | this.unsub = store.subscribe(() => { 15 | this.setState({ 16 | color: store.getState().theme 17 | }); 18 | }); 19 | } 20 | render() { 21 | return ( 22 | <> 23 | 26 |
    27 |
    大家好,我是前端宇宙作者刘小夕
    28 | 37 | 46 |
    47 | 48 | ) 49 | } 50 | componentWillUnmount() { 51 | this.unsub(); 52 | } 53 | } -------------------------------------------------------------------------------- /myredux/to-redux2/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Counter from './components/Counter'; 4 | import Pannel from './components/Pannel'; 5 | 6 | 7 | ReactDOM.render(<>, document.getElementById('root')); -------------------------------------------------------------------------------- /myredux/to-redux2/src/redux/applyMiddleware.js: -------------------------------------------------------------------------------- 1 | import compose from './compose'; 2 | 3 | const applyMiddleware = (...middlewares) => createStore => (...args) => { 4 | let store = createStore(...args); 5 | let dispatch; 6 | const middlewareAPI = { 7 | getState: store.getstate, 8 | dispatch: (...args) => dispatch(...args) 9 | } 10 | let middles = middlewares.map(middleware => middleware(middlewareAPI)); 11 | dispatch = compose(...middles)(store.dispatch); 12 | return { 13 | ...store, 14 | dispatch 15 | } 16 | } 17 | 18 | export default applyMiddleware; -------------------------------------------------------------------------------- /myredux/to-redux2/src/redux/bindActionCreators.js: -------------------------------------------------------------------------------- 1 | function bindActionCreator(actionCreator, dispatch) { 2 | return (...args) => dispatch(actionCreator(...args)); 3 | } 4 | function bindActionCreators(actionCreator, dispatch) { 5 | //actionCreators 可以是一个普通函数或者是一个对象 6 | if (typeof actionCreator === 'function') { 7 | //如果是函数,返回一个函数,调用时,dispatch 这个函数的返回值 8 | bindActionCreator(actionCreator, dispatch); 9 | } else if (typeof actionCreator === 'object') { 10 | //如果是一个对象,那么对象的每一项都要都要返回 bindActionCreator 11 | const boundActionCreators = {} 12 | for (let key in actionCreator) { 13 | boundActionCreators[key] = bindActionCreator(actionCreator[key], dispatch); 14 | } 15 | return boundActionCreators; 16 | } 17 | } 18 | 19 | export default bindActionCreators; -------------------------------------------------------------------------------- /myredux/to-redux2/src/redux/combineReducers.js: -------------------------------------------------------------------------------- 1 | export default function combineReducers(reducers) { 2 | return function combination(state={}, action) { 3 | let nextState = {}; 4 | let hasChanged = false; //状态是否改变 5 | for(let key in reducers) { 6 | const previousStateForKey = state[key]; 7 | const nextStateForKey = reducers[key](previousStateForKey, action); 8 | nextState[key] = nextStateForKey; 9 | //只有所有的 nextStateForKey 均与 previousStateForKey 相等时,hasChanged 的值 false 10 | hasChanged = hasChanged || nextStateForKey !== previousStateForKey; 11 | } 12 | //state 没有改变时,返回原对象 13 | return hasChanged ? nextState : state; 14 | } 15 | } 16 | 17 | -------------------------------------------------------------------------------- /myredux/to-redux2/src/redux/compose.js: -------------------------------------------------------------------------------- 1 | export default function compose(...funcs) { 2 | //如果没有中间件 3 | if (funcs.length === 0) { 4 | return arg => arg 5 | } 6 | //中间件长度为1 7 | if (funcs.length === 1) { 8 | return funcs[0] 9 | } 10 | 11 | return funcs.reduce((prev, current) => (...args) => prev(current(...args))); 12 | } -------------------------------------------------------------------------------- /myredux/to-redux2/src/redux/createStore.js: -------------------------------------------------------------------------------- 1 | export default function createStore(reducer) { 2 | let state; 3 | let listeners = []; 4 | const getState = () => state; 5 | const subscribe = (ln) => { 6 | listeners.push(ln); 7 | //订阅之后,也要允许取消订阅。不能只准订,不准退~ 8 | const unsubscribe = () => { 9 | listeners = listeners.filter(listener => ln !== listener); 10 | } 11 | return unsubscribe; 12 | }; 13 | const dispatch = (action) => { 14 | //reducer(state, action) 返回一个新状态 15 | state = reducer(state, action); 16 | listeners.forEach(ln => ln()); 17 | } 18 | //你要是有个 action 的 type 的值正好和 `@@redux/__INIT__${Math.random()}` 相等,我敬你是个狠人 19 | dispatch({ type: `@@redux/__INIT__${Math.random()}` }); 20 | 21 | return { 22 | getState, 23 | dispatch, 24 | subscribe 25 | } 26 | } -------------------------------------------------------------------------------- /myredux/to-redux2/src/redux/index.js: -------------------------------------------------------------------------------- 1 | import combineReducers from './combineReducers'; 2 | import createStore from './createStore'; 3 | import applyMiddleware from './applyMiddleware'; 4 | import compose from './compose'; 5 | import bindActionCreators from './bindActionCreators'; 6 | 7 | export { 8 | combineReducers, 9 | createStore, 10 | applyMiddleware, 11 | bindActionCreators, 12 | compose 13 | } 14 | -------------------------------------------------------------------------------- /myredux/to-redux2/src/store/action-types.js: -------------------------------------------------------------------------------- 1 | export const INCRENENT = 'INCRENENT'; 2 | export const DECREMENT = 'DECREMENT'; 3 | 4 | export const CHANGE_COLOR = 'CHANGE_COLOR'; -------------------------------------------------------------------------------- /myredux/to-redux2/src/store/actions/counter.js: -------------------------------------------------------------------------------- 1 | import { INCRENENT, DECREMENT } from '../action-types'; 2 | 3 | const counter = { 4 | add(number) { 5 | return { 6 | type: INCRENENT, 7 | number 8 | } 9 | }, 10 | minus(number) { 11 | return { 12 | type: DECREMENT, 13 | number 14 | } 15 | } 16 | } 17 | 18 | export default counter; -------------------------------------------------------------------------------- /myredux/to-redux2/src/store/actions/theme.js: -------------------------------------------------------------------------------- 1 | import { CHANGE_COLOR } from '../action-types'; 2 | 3 | let theme = { 4 | changeColor(color) { 5 | return { 6 | type: CHANGE_COLOR, 7 | color 8 | } 9 | } 10 | } 11 | 12 | export default theme; -------------------------------------------------------------------------------- /myredux/to-redux2/src/store/index.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from '../redux'; 2 | import reduxLogger from 'redux-logger'; 3 | import reducer from './reducers'; 4 | 5 | export default applyMiddleware(reduxLogger)(createStore)(reducer); -------------------------------------------------------------------------------- /myredux/to-redux2/src/store/reducers/counter.js: -------------------------------------------------------------------------------- 1 | import { INCRENENT, DECREMENT } from '../action-types'; 2 | 3 | export default function counter(state={number: 0}, action) { 4 | switch (action.type) { 5 | case INCRENENT: 6 | return { 7 | ...state, 8 | number: state.number + action.number 9 | } 10 | case DECREMENT: 11 | return { 12 | ...state, 13 | number: state.number - action.number 14 | } 15 | default: 16 | return state; 17 | } 18 | } -------------------------------------------------------------------------------- /myredux/to-redux2/src/store/reducers/index.js: -------------------------------------------------------------------------------- 1 | import counter from './counter'; 2 | import theme from './theme'; 3 | import {combineReducers} from '../../redux'; 4 | 5 | export default combineReducers({ 6 | counter, 7 | theme 8 | });; -------------------------------------------------------------------------------- /myredux/to-redux2/src/store/reducers/theme.js: -------------------------------------------------------------------------------- 1 | import { CHANGE_COLOR } from '../action-types'; 2 | 3 | export default function theme(state = {color: 'blue'}, action) { 4 | switch (action.type) { 5 | case CHANGE_COLOR: 6 | return { 7 | ...state, 8 | color: action.color 9 | } 10 | default: 11 | return state; 12 | } 13 | } --------------------------------------------------------------------------------