├── CSS ├── BFC.md ├── 三列布局.md ├── 两列布局.md ├── 水平垂直居中.md └── 盒模型.md ├── JAVASCRIPT ├── 0.1+0.2===0.3返回false.md ├── EventLoop.md ├── call、apply、bind.md ├── defer与async的区别.md ├── js中的数据类型.md ├── new操作符.md ├── this指向.md ├── var、let、const的区别.md ├── 事件、事件委托.md ├── 内存泄露、垃圾回收机制.md ├── 函数柯里化.md ├── 原型与原型链.md ├── 手写EventEmitter.md ├── 手写ajax.md ├── 手写instanceof.md ├── 手写promise.md ├── 手写promiseApi.md ├── 执行上下文和执行栈.md ├── 数组去重扁平.md ├── 普通函数与箭头函数的区别.md ├── 浅拷贝深拷贝.md ├── 继承.md ├── 闭包.md └── 防抖节流.md ├── OTHER ├── module.png ├── 前端安全.md ├── 性能优化.md ├── 模块化.md ├── 浏览器兼容.md └── 跨域.md ├── README.md ├── blog └── iconfont-auto-import.md ├── 剑指offer ├── 01 二维数组中查找.js ├── 02 替换空格.js └── README.md ├── 操作系统 ├── 1.png ├── 处理死锁的方法.md ├── 死锁的产生.md └── 进程与线程的区别.md ├── 数据结构与算法 ├── 二分查找.md ├── 冒泡排序.md ├── 归并排序.md ├── 快速排序.md ├── 插入排序.md ├── 数组与链表区别和优缺点.md ├── 计数排序.md ├── 选择排序.md └── 随机算法.md ├── 网络协议 ├── GEI和POST有什么区别.md ├── HTTP与HTTPS的区别及实现方式.md ├── HTTP版本.md ├── HTTP状态码.md ├── HTTP缓存.md ├── OSI模型TCPIP模型.md ├── TCP三次握手和四次挥手机制以及原因.md ├── TCP和UDP有什么区别.md ├── TCP如何保证可靠性.md ├── TCP拥塞控制.md ├── TCP流量控制.md └── 流量控制和拥塞控制的区别.md ├── 设计模式 ├── 单例模式.md └── 策略模式.md └── 读书笔记:HTTP图解 ├── 01 了解web及网络基础.md └── img ├── 1.3.3.png ├── 1.4.1.png ├── 1.4.2.png ├── 1.5.png └── 1.6.png /CSS/BFC.md: -------------------------------------------------------------------------------- 1 | # BFC 2 | 3 | 1. 什么是BFC? 4 | - BFC(Block Formatting Context) "块级格式化上下文" 5 | - BFC是一个独立渲染区域,它丝毫不会影响到外部元素 6 | 2. 如何触发BFC? 7 | - float的值不为none 8 | - overflow的值不为visible 9 | - display的值为table-cell、table-caption和inline-block还有inlie-flex之一 10 | - position的值不为static或者relative中的任何一个 11 | 3. BFC的应用? 12 | - 两列布局:固定块float,自适应块 13 | - 解决方法:给另一个非浮动元素生成BFC(overflow:hidden;) 14 | - 解决块级元素垂直方向的边距重叠问题 15 | - 给其中一个元素外包裹一层容器,并触发该容器的BFC(overflow:hidden;) 16 | - 清除浮动 17 | - 当子元素浮动,元素脱标,不能撑开父元素,给父元素添加BFC(overflow:hidden;) -------------------------------------------------------------------------------- /CSS/三列布局.md: -------------------------------------------------------------------------------- 1 | # 三列布局 2 | 1. 圣杯布局 3 | ```html 4 | 35 | 36 |
37 |
#main
38 |
#left
39 |
#right
40 |
41 | 42 | ``` 43 | 44 | 2. 双飞翼布局 45 | 46 | ```html 47 | 74 | 75 |
76 |
77 |
#main
78 |
79 |
#left
80 |
#right
81 |
82 | 83 | ``` -------------------------------------------------------------------------------- /CSS/两列布局.md: -------------------------------------------------------------------------------- 1 | # 两列布局(左固定,右自适应) 2 | 3 | ### 利用 BFC 特性,触发 .right 生成 BFC,不影响其他元素。 4 | 5 | ```html 6 | 19 | 20 |
left
21 |
right
22 | ``` -------------------------------------------------------------------------------- /CSS/水平垂直居中.md: -------------------------------------------------------------------------------- 1 | ```html 2 | 水平垂直居中 3 | 1 单行文字水平垂直居中 4 | 11 | 12 | 13 |
单行文字水平垂直居中
14 | 15 | 2 未知宽高元素水平垂直居中 16 | 30 | 31 | 32 |
未知宽高水平垂直居中
33 | 34 | 3 margin: auto实现绝对定位元素的居中 35 | 54 | 55 | 56 |
57 | 58 | 4 弹性布局 59 | 60 | 74 | 75 | 76 |
77 |

flex弹性布局

78 |

flex弹性布局

79 |
80 | 81 | 5 grid 82 | 83 | 94 | 95 | 96 |
97 |
grid实现水平垂直居中
98 |
99 | 100 | ``` -------------------------------------------------------------------------------- /CSS/盒模型.md: -------------------------------------------------------------------------------- 1 | # 盒模型 2 | 1. box-sizing有两个属性,一个是content-box(标准模型), 一个是border-box (怪异模型); 3 | 2. content-box - 默认浏览器盒模型, 会把border和padding计算入宽高 4 | 3. border-box - 怪异模型,不会把border和padding计算入宽高 5 | 4. content-box 是 W3C组织 提出、border-box 是 IE 提出 -------------------------------------------------------------------------------- /JAVASCRIPT/0.1+0.2===0.3返回false.md: -------------------------------------------------------------------------------- 1 | 0.1和0.2在转换成二进制后会无限循环,由于标准位数的限制后面多余的位数会被截掉,此时就已经出现了精度的损失,相加后因浮点数小数位的限制而截断的二进制数字在转换为十进制就会变成0.30000000000000004。 -------------------------------------------------------------------------------- /JAVASCRIPT/EventLoop.md: -------------------------------------------------------------------------------- 1 | # 事件循环 2 | 3 | - **浏览器中的事件循环(Event Loop)** 4 | - 事件循环是js的运行机制,涉及这个过程的有**任务队列**和主线程中的**调用栈**。 5 | - 调用栈 6 | 7 | - 调用栈(call-stack)采用的是后进先出的规则,所有的任务都会被放到调用栈等待主线程执行。 8 | - 任务队列 9 | 10 | - 任务队列(Task Queue),即队列,是一种先进先出的数据结构,主线程从"任务队列"中读取事件,这个过程是循环不断的。 11 | - 同步任务和异步任务 12 | 13 | - JavaScript 单线程任务被分为**同步任务**和**异步任务**,同步任务会在调用栈中按照顺序等待主线程依次执行,异步任务会在异步任务有了结果后,将注册的回调函数放入任务队列中等待主线程空闲时候(调用栈被清空),被读取到栈内等待主线程的执行。 14 | - 在`JavaScript`中,异步任务被分为两种,一种宏任务(`MacroTask`),一种叫微任务(`MicroTask`);微任务的优先级要比宏任务的优先级高,也就是说微任务要先执行,然后在执行宏任务。 15 | - 宏任务(Macro Task) 16 | - script标签中包含整体的代码块、setTimeout、setInterval、setImmediate 17 | - 微任务(Micro Task) 18 | - Promise、Mutation Observer(监听DOM变化的事件) 19 | - 事件循环的执行过程 20 | - 执行栈在执行完**同步任务**后,会查看执行栈是否为空,如果执行栈为空,就会去检查**微任务队列**是否为空,如果不为空,就将微任务队列中的所有微任务一次性执行;如果为空,就执行宏任务; 21 | - 每次当**宏任务**执行完毕后,检查**微任务队列**是否为空,如果不为空的话,会按照先进先出的规则全部执行完微任务,再进入下轮循环,执行宏任务,直到微任务队列和宏任务队列都为空 22 | 23 | - **node中的Event Loop(未)** 24 | 25 | - 文献 26 | 27 | - [一次弄懂Event Loop(彻底解决此类面试问题)](https://juejin.cn/post/6844903764202094606?utm_source=gold_browser_extension%3Futm_source%3Dgold_browser_extension) 28 | - [JavaScript 运行机制详解:再谈Event Loop](http://www.ruanyifeng.com/blog/2014/10/event-loop.html) 29 | 30 | **做题集** 31 | 32 | - [Eventloop不可怕,可怕的是遇上Promise](https://juejin.cn/post/6844903808200343559) 33 | 34 | - [你真的懂promise吗?promise then执行先后顺序,高手解答一下。附上题目](https://segmentfault.com/q/1010000018689196?_ea=19219106) 35 | 36 | - [面试题之Event Loop终极篇](https://segmentfault.com/a/1190000019494012) 37 | 38 | - [【建议星星】要就来45道Promise面试题一次爽到底(1.1w字用心整理)](https://juejin.cn/post/6844904077537574919) 39 | 40 | - [面试官眼中的Promise](https://juejin.cn/post/6844903748628660232) 41 | -------------------------------------------------------------------------------- /JAVASCRIPT/call、apply、bind.md: -------------------------------------------------------------------------------- 1 | # call、apply、bind 2 | 3 | > call、apply、bind 这三个方法的作用是改变this指向。 4 | 5 | ## 三者有什么区别? 6 | - 通过下面这个案例得出,相同之处都是改变this指向,不同之处,call apply 的差异是接收传给调用函数的参数形式不同, call apply 和 bind 的差异是call apply 直接调用,而 bind 会返回一个新函数。 7 | ```js 8 | var obj = { 9 | name: 'obj' 10 | } 11 | function test (b, c) { 12 | this.name = 'test'; 13 | console.log(this.name, b, c); 14 | } 15 | // 分别用 call、apply、bind 将 obj 对象绑定 test 方法的 this 上 16 | test.call(obj, 'b', 'c'); // obj b c 17 | test.apply(obj, ['b', 'c']); // obj b c 18 | var func = test.bind(obj, 'b'); 19 | func('c'); // obj b c 20 | ``` 21 | 22 | ## 实现 call 23 | ```js 24 | // call 25 | Function.prototype.call2 = function (context) { 26 | context = context || window; 27 | context.fn = this; 28 | 29 | var args = []; 30 | for (var i = 1; i < arguments.length; i++) { 31 | args.push('arguments[' + i + ']'); 32 | } 33 | 34 | // 数组和字符串拼接,会将数组转化成字符串(数组调用toString方法),数组成员以逗号隔开 35 | var result = eval('context.fn(' + args + ')'); 36 | 37 | delete context.fn; 38 | return result; 39 | } 40 | ``` 41 | ## 实现 apply 42 | ```js 43 | // apply 44 | Function.prototype.apply2 = function (context, arr) { 45 | context = context || window; 46 | context.fn = this; 47 | 48 | var result; 49 | if (!arr) { 50 | result = context.fn(); 51 | } else { 52 | var args = []; 53 | for (var i = 0; i < arr.length; i++) { 54 | args.push('arr[' + i + ']'); 55 | } 56 | result = eval('context.fn(' + args + ')'); 57 | } 58 | 59 | delete context.fn; 60 | return result; 61 | } 62 | ``` 63 | ## 实现 bind 64 | ```js 65 | // bind(ES5) 66 | Function.prototype.bind2 = function (context) { 67 | var self = this; 68 | var args = Array.prototype.slice.call(arguments, 1); 69 | var fBound = function () { 70 | var bindArgs = Array.prototype.slice.call(arguments); 71 | return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs)); 72 | } 73 | // 防止 绑定函数的 prototype 和调用 bind 返回的函数的 prototype 相关联 74 | // 所以可以通过空函数来进行中转 75 | var fNOP = function () {}; 76 | fNOP.prototype = this.prototype; 77 | fBound.prototype = new fNOP(); 78 | return fBound; 79 | } 80 | 81 | // test 82 | var foo = { value : 1}; 83 | 84 | var bar = function (name, age) { 85 | console.log(this.value); 86 | console.log('name:', name); 87 | console.log('age:', age); 88 | } 89 | 90 | var zoo = bar.bind(foo, 'koo'); 91 | 92 | var a = new zoo(18); 93 | 94 | console.log('a.constructor:', a.constructor); 95 | console.log('a:', a) 96 | zoo.prototype.value = 1; 97 | console.log('bar.prototype.value:', bar.prototype.value) 98 | ``` 99 | -------------------------------------------------------------------------------- /JAVASCRIPT/defer与async的区别.md: -------------------------------------------------------------------------------- 1 | - `defer`要等到整个页面在内存中正常渲染结束(DOM 结构完全生成,以及其他脚本执行完成),才会执行; 2 | - `async`一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。 3 | - 简单来讲,`defer`是“渲染完再执行”,`async`是“下载完就执行”。 4 | - 另外,如果有多个`defer`脚本,会按照它们在页面出现的顺序加载,而多个`async`脚本是不能保证加载顺序的(因为是异步的)。 -------------------------------------------------------------------------------- /JAVASCRIPT/js中的数据类型.md: -------------------------------------------------------------------------------- 1 | # JavaScript中的数据类型 2 | 3 | **基本类型**:Number、String、Boolean、Undefined、Null、Symbol(ES6)、BigInt(ES10) 4 | 5 | **引用类型**:Object 6 | 7 | - 什么是Symbol? 8 | - ES6 引入了一种新的原始数据类型`Symbol`,表示独一无二的值。因为ES5 的对象属性名都是字符串,这容易造成属性名的冲突。引入 Symbol 是为了保证每个属性的名字都是独一无二的,从根本上防止属性名的冲突。 9 | - 什么是BigInt? 10 | - BigInt是一种新的数据类型,用于当整数值大于Number数据类型支持的范围时。这种数据类型允许我们安全地对`大整数`执行算术操作,表示高分辨率的时间戳,使用大整数id,等等,而不需要使用库。 11 | 12 | **基本类型和引用类型有什么区别?** 13 | 14 | - 存储方式 15 | - 基本类型的值存储在栈中、引用类型的值存储在堆中,引用地址存储在栈中; 16 | - 是否支持添加属性和方法 17 | - 引用类型支持添加属性和方法,基本类型不支持; 18 | - 赋值操作 19 | - 从一个变量向另外一个变量复制基本类型和引用类型,情况是不同的,基本类型复制的是值的副本,而引用类型复制的引用地址。 20 | 21 | **数据类型检测** 22 | 23 | - typeof运算符 24 | 25 | - 在检测 null 时,返回"object"; 26 | - JS 存在的一个悠久 Bug。在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象然而 null 表示为全零,所以将 typeof null 错误的判断为 object 27 | - 检测引用类型,返回"object",不能准确检测具体是哪种引用类型。 28 | 29 | - instanceof运算符 30 | 31 | - instanceof的原理 32 | 33 | - instanceof是基于原型链查找,判断前面的对象的隐式原型是否是后面的对象,不是的话继续在原型链上找。 34 | 35 | - 实现一个 instanceof 36 | 37 | ```javascript 38 | function instance_of(L, R) {//L 表示左表达式,R 表示右表达式 39 | var O = R.prototype; 40 | L = L.__proto__; 41 | while (true) { 42 | if (L === null) 43 | return false; 44 | if (O === L) // 这里重点:当 O 严格等于 L 时,返回 true 45 | return true; 46 | L = L.__proto__; 47 | } 48 | } 49 | ``` -------------------------------------------------------------------------------- /JAVASCRIPT/new操作符.md: -------------------------------------------------------------------------------- 1 | - **字面量、new创建的对象和Object.create(null)创建出来的对象有什么区别?** 2 | 3 | - 字面量和new创建出来的对象会继承Object的方法和属性,他们的隐式原型会指向Object的显式原型,而 Object.create(null) 创建出来的对象原型为null,作为原型链的顶端,自然也没有继承Object的方法和属性 4 | 5 | - **new实例化对象发生哪些过程** 6 | 7 | ```js 8 | var cat =new Animal("cat"); 9 | // JS引擎执行这句代码时,在内部做了很多工作,用伪代码模拟其内部流程如下: 10 | new Animal('cat') = { 11 | var obj = {}; 12 | obj.__proto__ = Animal.prototype; 13 | var result = Animal.call(obj,"cat"); 14 | return typeof result ==='object' ? result : obj; 15 | } 16 | ``` 17 | 18 | - 第一步:创建一个空对象 obj; 19 | - 第二步:把 obj 的__proto__ 指向构造函数 Animal 的原型对象 prototype;(此时便建立了 obj 对象的原型链:**obj->Animal.prototype->Object.prototype->null** ) 20 | - 第三步: 改变构造函数 this 的指向到新建的对象;(在 obj 对象的执行环境调用 Animal 函数并传递参数 “ cat ” 。 相当于 var result=obj.Animal("cat");) 21 | - 第四步:考察第 3 步的返回值,如果无返回值 或者 返回一个非对象值,则将 obj 作为新对象返回; 22 | 否则将result 作为新对象返回。 23 | 24 | - **模拟new操作** 25 | 26 | ```js 27 | function objectFactory() { 28 | // 用new Object() 的方式新建了一个对象 obj 29 | var obj = new Object(); 30 | // 取出第一个参数,就是我们要传入的构造函数。此外因为 shift 会修改原数组,所以 arguments 会被去除第一个参数 31 | var Constructor = [].shift.call(arguments); 32 | // 将 obj 的原型指向构造函数,这样 obj 就可以访问到构造函数原型中的属性 33 | obj.__proto__ = Constructor.prototype; 34 | // 使用 apply,改变构造函数 this 的指向到新建的对象,这样 obj 就可以访问到构造函数中的属性 35 | var ret = Constructor.apply(obj, arguments); 36 | // 将 obj 的原型指向构造函数,这样 obj 就可以访问到构造函数原型中的属性 37 | return typeof ret === 'object' ? ret || obj : obj; 38 | } 39 | 40 | 代码疑点解释: 41 | 42 | var Constructor = [].shift.call(arguments); 43 | 解释:arguments是类数组,无法使用数组的方法,通过 call 使得 arguments 使用 shift 数组方法; 44 | 45 | return typeof ret === 'object' ? ret || obj : obj;中的 “ret || obj” 46 | 解释:如果构造函数 Constructor 返回 null, typeof 会将 null 检测为 'object',所以再加个与判断, 防止将 ret 为 null 时被返回。 47 | 48 | ``` -------------------------------------------------------------------------------- /JAVASCRIPT/this指向.md: -------------------------------------------------------------------------------- 1 | - 如果要判断一个运行中函数的 this 绑定,就需要找到这个函数的直接调用位置。找到之后就可以顺序应用下面这四条规则来判断 this 的绑定对象。 2 | - **四个规则进行排序:new绑定>显式绑定>隐式绑定>默认绑定** 3 | 4 | 1. 函数是否在 new 中调用(new 绑定)?如果是的话 this 绑定的是新创建的对象。var bar = new foo() 5 | 2. 函数是否通过 call、apply(显式绑定)或者硬绑定调用?如果是的话,this 绑定的是指定的对象。 var bar = foo.call(obj2) 6 | 3. 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this 绑定的是那个上下文对象。var demo2= obj.foo() 7 | 4. 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到 undefined,否则绑定到全局对象。 demo(); 8 | - **怎么改变 this 指向** 9 | - 在函数内部使用 _this = this 10 | - 使用 ES6 的箭头函数 11 | - 使用 call、apply、bind 12 | - new 实例化一个对象 13 | - 文献 14 | - [this、call、apply、bind](https://juejin.cn/post/6844903496253177863) 15 | - https://zhuanlan.zhihu.com/p/28536635 -------------------------------------------------------------------------------- /JAVASCRIPT/var、let、const的区别.md: -------------------------------------------------------------------------------- 1 | 1. var声明变量存在提升(提升当前作用域最顶端),let和const是不存在变量提升的情况 2 | 2. var没有块级作用域,let和const存在块级作用域 3 | 3. var允许重复声明,let和const在同一作用域不允许重复声明 4 | 4. var和let声明变量可以修改,const是常量不能改变 -------------------------------------------------------------------------------- /JAVASCRIPT/事件、事件委托.md: -------------------------------------------------------------------------------- 1 | 2 | - 事件绑定与解绑 3 | 4 | ```js 5 | // DOM2事件 6 | 事件绑定:el.addEventListener('事件行为', function(){}, true/false); 7 | 事件解绑:el.removeEventListener('事件行为', function(){}, true/false); 8 | // true/false 可以省略,默认是flase 9 | // false - 表示在冒泡阶段执行此方法 10 | // true - 表示在捕获阶段执行此方法 11 | 12 | // DOM0事件 13 | 事件绑定:el.onXXX = function(){}; 14 | 事件解绑:el.onXXX = null; 15 | 16 | // IE(6-8版本不兼容addEventListener) 17 | 事件绑定:el.attachEvent('on事件行为', function(){}); 18 | 事件解绑:el.detachEvent('on事件行为', function(){}); 19 | 20 | // ps:解绑事件,需要知道具体的元素、事件、方法、阶段;所以在做事件绑定的时候,记得绑定实名函数;如果不考虑解绑函数,可以使用匿名函数。 21 | 22 | // 兼容IE和非IE浏览器事件绑定的代码: 23 | function on(element, type, callback) { 24 | if (element.addEventListener) { // 支持使用 addEventListener() 25 | // 判断 type 是否以 "on" 开头 26 | if (type.slice(0,2) === "on") // 以 "on" 开头,不需要,则去掉 27 | type = type.slice(2); 28 | element.addEventListener(type, callback); 29 | } else { // 不支持使用 addEventListener() 30 | // 判断 type 是否以 "on" 开头 31 | if (type.slice(0, 2) !== "on") // 没有以 "on" 开头,需要,则加上 32 | type = "on" + type; 33 | element.attachEvent(type, callback); 34 | } 35 | } 36 | ``` 37 | 38 | 39 | 40 | - 冒泡传播原理 41 | 42 | - 捕获阶段 43 | - 从最外层向里层事件源依次进行查找 44 | - 目的是为冒泡阶段实现计算好传播的层级路径 45 | - 目标阶段 46 | - 当前元素的相关事件行为触发 47 | - 冒泡传播 48 | - 触发当前元素的某一个事件行为,不仅它的这个行为被触发了,而且它所有的祖先元素(一直到window)相关的事件行为都会被依次触发(从内到位的顺序) 49 | 50 | - **事件代理** 51 | 52 | - 事件代理( Event Delegation)又称为事件委托,是 JavaScript中常用的绑定事件的方式。顾名思义,“事件代理”就是把原本需要绑定到子元素的事件委托给父元素,让父元素承担事件监听的工作。**事件代理的原理是DOM元素的事件冒泡**。使用事件代理的好处有很多,如**减少事件数量,预测未来元素,避免内存外泄等,有利于提高性能**。 53 | - 文献 54 | - [JavaScript事件代理(事件委托)](https://blog.csdn.net/qq_38128179/article/details/86293394) 55 | - [js事件面试题大全](https://www.jianshu.com/p/5c5ef7122b00) 56 | 57 | - 取消浏览器默认行为 58 | 59 | - event.preventDefault(); 60 | - event.returnValue = false;(低版本的浏览器中可以用) 61 | 62 | - 阻止冒泡传播 63 | 64 | - event.stopPropagation(); 65 | - addEventLinstener()方法的第三个函数参数,设置为true; 66 | - event.cancelBubble = true;(低版本的浏览器中可以用) 67 | -------------------------------------------------------------------------------- /JAVASCRIPT/内存泄露、垃圾回收机制.md: -------------------------------------------------------------------------------- 1 | - **为什么会导致内存泄漏?** 2 | - 内存泄漏指我们无法在通过js访问某个对象,而垃圾回收机制却认为该对象还在被引用,因此垃圾回收 3 | 机制不会释放该对象,导致该块内存永远无法释放,积少成多,系统会越来越卡以至于崩溃 4 | 5 | - **垃圾回收机制都有哪些策略?** 6 | 7 | - 标记清除法 8 | - 基本原理:垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记(当然,可以使用任何标记方式)。然后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后,垃圾收集器完成内存清除工作,销毁那些带标记的值并回收它们所占用的内存空间。 9 | 10 | - 引用计数法 11 | - 基本原理:在每个对象中保存该对象的引用计数,当引用发生增减时对计数进行更新。引用计数的增减,一般发生在变量赋值、对象内容更新、函数结束(局部变量不再被引用)等时间点。当一个对象的引用计数变为 0 时,则说明它将来不会再被引用,因此可以释放相应的内存空间。 12 | 13 | **引用计数最大的缺点,就是无法释放循环引用的对象。** 14 | -------------------------------------------------------------------------------- /JAVASCRIPT/函数柯里化.md: -------------------------------------------------------------------------------- 1 | 2 | - 什么是柯里化? 3 | - 维基百科中对柯里化(Currying)的定义为:在数学和计算机科学中,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。 4 | 5 | > 通俗易懂的解释:用闭包把参数保存起来,当参数的数量足够执行函数了,就开始执行函数 6 | 7 | - 实现柯里化 8 | 9 | ```js 10 | // 方法一:利用闭包、递归和toString实现,可以传不固定参数个数 11 | function add () { 12 | var args = [...arguments]; 13 | 14 | var inner = function () { 15 | args.push(...arguments); 16 | return inner; 17 | } 18 | 19 | // 返回一个函数体,实际上函数调用了toString() 20 | // 利用一特性,重写toString方法 21 | inner.toString = function () { 22 | return args.reduce((pre, cur) => pre + cur, 0); 23 | } 24 | 25 | return inner; 26 | } 27 | console.log(add(1)(2)) 28 | console.log(add(1)(2)(3)); 29 | console.log(add(1, 2)(3)); 30 | 31 | // 写法二: 利用闭包、递归,需要传入函数,参数个数已确定 32 | function curry(fn, ...args) { 33 | if (args.length >= fn.length) return fn(...args); 34 | 35 | return (...args2) => curry(fn, ...args, ...args2); 36 | } 37 | const fun = (a, b, c) => a + b + c; 38 | const curryFun = curry(fun); 39 | console.log(curryFun(1)(2)(3)); // 6 40 | console.log(curryFun(1, 2)(3)); // 6 41 | // 不会接收多余的参数 42 | console.log(curryFun(1, 2, 3, 8)); // 6 43 | ``` 44 | 45 | - 函数柯里化的应用 46 | 47 | - 减少重复传递不变的部分参数 48 | 49 | ```js 50 | function simpleUrl (protocal, domain, path) { 51 | return `${protocal}://${domain}:${path}`; 52 | } 53 | 54 | const urlCurry = curry(simpleUrl, 'https'); 55 | const baidu = urlCurry('www.baidu.com', 'home'); 56 | const tianmao = urlCurry('www.tianmao.com', 'about'); 57 | console.log('baidu:', baidu); 58 | console.log('tianmao:', tianmao); 59 | 60 | ``` 61 | 62 | 63 | 64 | - 柯里化的callback传递给map,filter等函数 65 | 66 | ```js 67 | const data1 = [ 68 | {name: '123', age: 12}, 69 | {name: '12s', age: 12}, 70 | {name: '12s3', age: 12}, 71 | {name: '1a3', age: 12}, 72 | ] 73 | const data2 = [ 74 | {kk: '12s3', age: 12}, 75 | {kk: '12g2s', age: 12}, 76 | {kk: '1f2s3', age: 12}, 77 | {kk: '1a3', age: 12}, 78 | ] 79 | 80 | const curry = (prop) => (element) => element[prop]; 81 | const nameCurry = curry('name'); 82 | const kkCurry = curry('kk'); 83 | console.log(data1.map(nameCurry)); 84 | console.log(data2.map(kkCurry)); 85 | ``` 86 | -------------------------------------------------------------------------------- /JAVASCRIPT/原型与原型链.md: -------------------------------------------------------------------------------- 1 | - 原型:原型分为显式原型和隐式原型,每个对象都包含一个隐式原型,它指向构造函数的显式原型; 2 | - 原型链:原型链就是js的一个继承机制,就是对象有显示原型和隐式原型,对象的隐式原型指向其构造函数的显示原型,其构造函数的显示原型上也有隐式原型,指向其父级的显示原型,然后一级一级形成一条链,直到Object(所有对象都继承于Object),而Object的隐式原型指向null到这里原型链就结束了。 3 | - 所有实例的proto都指向构造函数的prototype 4 | - 所有的prototype都是对象,自然它的proto指向的是Object()的prototype 5 | - 所有的构造函数的隐式原型指向的都是Function()的显式原型 6 | - Object的隐式原型式null -------------------------------------------------------------------------------- /JAVASCRIPT/手写EventEmitter.md: -------------------------------------------------------------------------------- 1 | ```js 2 | class EventEmitter { 3 | constructor() { 4 | this.cache = {} 5 | this.disposable = new Set() 6 | } 7 | on(name, fn) { 8 | this.cache[name] = fn 9 | } 10 | once(name, fn) { 11 | this.cache[name] = fn 12 | this.disposable.add(name) 13 | } 14 | emit(name, ...args) { 15 | if (this.cache[name]) { 16 | this.cache[name](...args) 17 | } 18 | if (this.disposable.has(name)) { 19 | this.disposable.delete(name) 20 | this.off(name) 21 | } 22 | } 23 | off(name) { 24 | delete this.cache[name] 25 | } 26 | } 27 | 28 | const eventHandle = new EventEmitter() 29 | 30 | eventHandle.on('fn', function (a, b) { 31 | console.log(a, b) 32 | }) 33 | eventHandle.once('fn2', function (a, b) { 34 | console.log(a, b) 35 | }) 36 | 37 | eventHandle.emit('fn', 1, 2) 38 | eventHandle.off('fn') 39 | eventHandle.emit('fn', 1, 2) 40 | eventHandle.emit('fn2', 2, 3) 41 | eventHandle.emit('fn2', 2, 3) 42 | 43 | ``` -------------------------------------------------------------------------------- /JAVASCRIPT/手写ajax.md: -------------------------------------------------------------------------------- 1 | 2 | - ES5版 3 | 4 | ```js 5 | function ajax (config) { 6 | var xhr = new XMLHttpRequest(); 7 | xhr.open('get', config.url, true); 8 | 9 | xhr.onload = function () { 10 | if (xhr.readyState !== 4 && xhr.status !== 200) return; 11 | config.success(xhr.responseText); 12 | } 13 | 14 | xhr.send(null); 15 | } 16 | 17 | ajax({ 18 | url: 'https://api.github.com/users/chenjiezi', 19 | success: function (res) { 20 | console.log('res:', res); 21 | } 22 | }) 23 | ``` 24 | 25 | - ES6版 26 | 27 | ```js 28 | function ajax (url) { 29 | return new Promise((resolve, reject) => { 30 | var xhr = new XMLHttpRequest(); 31 | xhr.open('get', url, true); 32 | 33 | xhr.onload = function () { 34 | if (xhr.readyState !== 4 && xhr.status !== 200) return; 35 | resolve(xhr.responseText); 36 | } 37 | 38 | xhr.onerror = function (error) { 39 | reject(error); 40 | } 41 | 42 | xhr.send(null); 43 | }) 44 | } 45 | 46 | ajax('https://api.github.com/users/chenjiezi') 47 | .then((result) => { 48 | console.log('result:', result); 49 | }) 50 | .catch((error) => { 51 | console.log('error:', error); 52 | }) 53 | ``` 54 | -------------------------------------------------------------------------------- /JAVASCRIPT/手写instanceof.md: -------------------------------------------------------------------------------- 1 | - instanceof运算符 2 | 3 | - instanceof的原理 4 | 5 | - instanceof是基于原型链查找,判断前面的对象的隐式原型是否是后面的对象,不是的话继续在原型链上找。 6 | 7 | - 实现一个 instanceof 8 | 9 | ```javascript 10 | function instance_of(L, R) {//L 表示左表达式,R 表示右表达式 11 | var O = R.prototype; 12 | L = L.__proto__; 13 | while (true) { 14 | if (L === null) 15 | return false; 16 | if (O === L) // 这里重点:当 O 严格等于 L 时,返回 true 17 | return true; 18 | L = L.__proto__; 19 | } 20 | } 21 | ``` -------------------------------------------------------------------------------- /JAVASCRIPT/手写promise.md: -------------------------------------------------------------------------------- 1 | ```js 2 | // func(this.resolve.bind(this), this.reject.bind(this)) 和 this.reject(e) 3 | // this.reject.bind(this) 是因为 resolve 和 reject 是在外部调用的,this指向会改变,所以在 constuctor 里就要绑定this 4 | // this.reject(e) 是在 constructor 内部执行的,所以不变 5 | class MyPromise { 6 | static PENDING = 'pending' 7 | static FULFILLED = 'fulfilled' 8 | static REJECTED = 'rejected' 9 | constructor(func) { 10 | this.promiseState = MyPromise.PENDING 11 | this.promiseResult = null 12 | this.onFulfilledCallbacks = [] 13 | this.onRejectedCallbacks = [] 14 | try { 15 | func(this.resolve.bind(this), this.reject.bind(this)) 16 | } catch (error) { 17 | this.reject(error) 18 | } 19 | } 20 | resolve(result) { 21 | if (this.promiseState === MyPromise.PENDING) { 22 | this.promiseState = MyPromise.FULFILLED 23 | this.promiseResult = result 24 | this.onFulfilledCallbacks.forEach(callback => { 25 | callback(result) 26 | }) 27 | } 28 | } 29 | reject(reason) { 30 | if (this.promiseState === MyPromise.PENDING) { 31 | this.promiseState = MyPromise.REJECTED 32 | this.promiseResult = reason 33 | this.onRejectedCallbacks.forEach(callback => { 34 | callback(reason) 35 | }) 36 | } 37 | } 38 | then(onFulfilled, onRejected) { 39 | let promise2 = new MyPromise((resolve, reject) => { 40 | if (this.promiseState === MyPromise.FULFILLED) { 41 | setTimeout(() => { 42 | try { 43 | if (typeof onFulfilled === 'function') { 44 | const x = onFulfilled(this.promiseResult) 45 | resolvePromise(promise2, x, resolve, reject) 46 | } else { 47 | resolve(this.promiseResult) 48 | } 49 | } catch (e) { 50 | reject(e) 51 | } 52 | }) 53 | } else if (this.promiseState === MyPromise.REJECTED) { 54 | setTimeout(() => { 55 | try { 56 | if (typeof onRejected === 'function') { 57 | const x = onRejected(this.promiseResult) 58 | resolvePromise(promise2, x, resolve, reject) 59 | } else { 60 | reject(this.promiseResult) 61 | } 62 | } catch (e) { 63 | reject(e) 64 | } 65 | }) 66 | } else if (this.promiseState === MyPromise.PENDING) { 67 | this.onFulfilledCallbacks.push(() => { 68 | setTimeout(() => { 69 | try { 70 | if (typeof onFulfilled === 'function') { 71 | const x = onFulfilled(this.promiseResult) 72 | resolvePromise(promise2, x, resolve, reject) 73 | } else { 74 | resolve(this.promiseResult) 75 | } 76 | } catch (e) { 77 | reject(e) 78 | } 79 | }) 80 | }) 81 | this.onRejectedCallbacks.push(() => { 82 | setTimeout(() => { 83 | try { 84 | if (typeof onRejected === 'function') { 85 | const x = onRejected(this.promiseResult) 86 | resolvePromise(promise2, x, resolve, reject) 87 | } else { 88 | reject(this.promiseResult) 89 | } 90 | } catch (e) { 91 | reject(e) 92 | } 93 | }) 94 | }) 95 | } 96 | }) 97 | 98 | return promise2 99 | } 100 | } 101 | 102 | function resolvePromise(promise2, x, resolve, reject) { 103 | if (promise2 === x) { 104 | throw new TypeError('循环引用') 105 | } 106 | if (x instanceof MyPromise) { 107 | x.then(y => { 108 | resolvePromise(promise2, y, resolve, reject) 109 | }, reject) 110 | } else if (x !== null && (typeof x === 'object' || typeof x === 'function')) { 111 | try { 112 | var then = x.then 113 | } catch (e) { 114 | reject(e) 115 | } 116 | if (typeof then === 'function') { 117 | let called = false 118 | try { 119 | then.call( 120 | x, 121 | y => { 122 | if (called) return 123 | called = true 124 | resolvePromise(promise2, y, resolve, reject) 125 | }, 126 | r => { 127 | if (called) return 128 | called = true 129 | reject(r) 130 | }) 131 | } catch (e) { 132 | if (called) return 133 | called = true 134 | reject(e) 135 | } 136 | } else { 137 | resolve(x) 138 | } 139 | } else { 140 | resolve(x) 141 | } 142 | } 143 | 144 | MyPromise.deferred = function () { 145 | const result = {} 146 | result.promise = new MyPromise((resolve, reject) => { 147 | result.resolve = resolve 148 | result.reject = reject 149 | }) 150 | return result 151 | } 152 | 153 | module.exports = MyPromise 154 | ``` -------------------------------------------------------------------------------- /JAVASCRIPT/手写promiseApi.md: -------------------------------------------------------------------------------- 1 | ```js 2 | class MyPromise { 3 | constructor (func) {} 4 | resolve (result) {} 5 | reject (reason) {} 6 | then (onFulfulled, onRejected) {} 7 | static resolve (value) { 8 | if (value instanceof MyPromise) { 9 | return value 10 | } else if (value instanceof Object && 'then' in value) { 11 | return new MyPromise((resolve, reject) => { 12 | value.then(resolve, reject) 13 | }) 14 | } 15 | return new MyPromise((resolve, reject) => { 16 | resolve(value) 17 | }) 18 | 19 | } 20 | static reject (reason) { 21 | return new MyPromise((resolve, reject) => { 22 | reject(reason) 23 | }) 24 | } 25 | catch (onRejected) { 26 | return this.then(undefined, onRejected) 27 | } 28 | finally (callback) { 29 | return this.then(callback, callback) 30 | } 31 | static all (promises) { 32 | return new MyPromise((resolve, reject) => { 33 | 34 | if (Array.isArray(promises)) { 35 | const result = [] 36 | let count = 0 37 | if (promises.length === 0) { 38 | return resolve(promises) 39 | } 40 | promises.forEach((item, index) => { 41 | if (item instanceof MyPromise) { 42 | MyPromise.resolve(item).then( 43 | value => { 44 | count++ 45 | result[index] = value 46 | count === promises.length && resolve(result) 47 | }, reason => { 48 | reject(reason) 49 | }) 50 | } else { 51 | count++ 52 | result[index] = item 53 | count === promises.length && resolve(result) 54 | } 55 | }) 56 | 57 | } else { 58 | return reject(new TypeError('Argument is not iterable')) 59 | } 60 | 61 | }) 62 | } 63 | static allSettled (promises) { 64 | return new MyPromise((resolve, reject) => { 65 | const result = [] 66 | let count = 0 67 | if (Array.isArray(promises)) { 68 | if (promises.length === 0) return resolve(promises) 69 | promises.forEach((item, index) => { 70 | MyPromise.reolve(item).then( 71 | value => { 72 | count++ 73 | result[index] = { 74 | status: 'fulfilled', 75 | value: value 76 | } 77 | count === promises.length && resolve(result) 78 | }, 79 | reason => { 80 | count++ 81 | result[index] = { 82 | status: 'rejected', 83 | reason: reason 84 | } 85 | count === promises.length && resolve(result) 86 | } 87 | ) 88 | }) 89 | } else { 90 | return reject(new TypeError('Argument is no iterable')) 91 | } 92 | }) 93 | } 94 | static any (promises) { 95 | return new MyPromise((resolve, reject) => { 96 | const errors = [] 97 | let count = 0 98 | if (Array.isArray(promises)) { 99 | if (promises.length === 0) return new AggregateError('All promises were rejected') 100 | promises.forEach((item, index) => { 101 | MyPromises.resolve(item).then( 102 | value => { 103 | resolve(value) 104 | }, 105 | reason => { 106 | count++ 107 | errors.push(reason) 108 | count === promises.length && reject(new AggregateError(errors)) 109 | } 110 | ) 111 | }) 112 | } else { 113 | return reject(new TypeError('Argument is no iterable')) 114 | } 115 | }) 116 | } 117 | static race (promises) { 118 | return new MyPromise((resolve, reject) => { 119 | if (Array.isArray(promises)) { 120 | if (promises.length > 0) { 121 | promises.forEach(item => { 122 | MyPromises.resolve(item).then(resolve, reject) 123 | }) 124 | } 125 | } else { 126 | return reject(new TypeError('Argument is no iterable')) 127 | } 128 | }) 129 | } 130 | } 131 | ``` -------------------------------------------------------------------------------- /JAVASCRIPT/执行上下文和执行栈.md: -------------------------------------------------------------------------------- 1 | # 执行上下文和执行栈 2 | 3 | ## 作用域和作用域链 4 | - 规定变量和函数的可使用范围称作作用域 5 | - 每个函数都有一个作用域链,查找变量或者函数时,需要从局部作用域到全局作用域依次查找,这些作用域的集合称作作用域链。 6 | 7 | ## 执行上下文和执行栈 8 | - 执行上下文分为: 9 | 10 | - 全局执行上下文 11 | 12 | - 创建一个全局的window对象,并规定this指向window,执行js的时候就压入栈底,关闭浏览 13 | 14 | 器的时候才弹出 15 | 16 | - 函数执行上下文 17 | 18 | - 每次函数调用时,都会新创建一个函数执行上下文 19 | - 执行上下文分为创建阶段和执行阶段 20 | - 创建阶段:函数环境会创建变量对象:arguments对象(并赋值)、函数声明(并赋值)、变量声明(不赋值),函数表达式声明(不赋值);会确定this指向;会确定作用域 21 | - 执行阶段:变量赋值、函数表达式赋值,使变量对象变成活跃对象 22 | 23 | - eval执行上下文 24 | 25 | - 执行栈: 26 | 27 | - 首先栈特点:先进后出 28 | - 当进入一个执行环境,就会创建出它的执行上下文,然后进行压栈,当程序执行完成时,它的执行 29 | 上下文就会被销毁,进行弹栈。 30 | - 栈底永远是全局环境的执行上下文,栈顶永远是正在执行函数的执行上下文 31 | - 只有浏览器关闭的时候全局执行上下文才会弹出 32 | -------------------------------------------------------------------------------- /JAVASCRIPT/数组去重扁平.md: -------------------------------------------------------------------------------- 1 | 2 | - 数组去重 3 | 4 | ```js 5 | // 方法一: 通过 es6 的变量解构和Set 6 | var arr = [1,1,2,2,3,4]; 7 | var arr2 = [...new Set(arr)]; 8 | 9 | // 方法二: 通过 es6 的 Array.from 和 Set 10 | var arr = [1,1,2,2,3,4]; 11 | var arr2 = Array.from(new Set(arr)); 12 | 13 | // 方法三: forEach方法实现 14 | function dedupe(arr) { 15 | var rets = []; 16 | arr && arr.forEach(function(item){ 17 | if (!rets.includes(item)){ 18 | rets.push(item); 19 | } 20 | }); 21 | return rets; 22 | } 23 | ``` 24 | 25 | - 数组扁平 26 | 27 | ```js 28 | // var arr = [1, 2, 3, [4, 3, [2, 7], 2], 5, [5, 9, 10], 7]; 29 | // 去扁平化后 30 | // arr = [1, 2, 3, 4, 3, 2, 7, 2, 5, 5, 9, 10, 7]; 31 | 32 | // 第一种方法:如果子元素还是数组,则递归调用该方法 33 | function flatten(arr) { 34 | var rets = []; 35 | arr && arr.forEach(function(item) { 36 | if (Array.isArray(item)) { 37 | rets = rets.concat(flatten(item)); 38 | } else { 39 | rets.push(item); 40 | } 41 | }); 42 | return rets; 43 | } 44 | 45 | // 第二种方法:使用reduce简化代码 46 | function flatten(arr) { 47 | return arr.reduce(function(pre, item){ 48 | return pre.concat(Array.isArray(item) ? flatten(item) : item); 49 | }, []) 50 | } 51 | 52 | // 如果数组元素都为数字,则可以使用toString方法 53 | function flatten(arr) { 54 | var newArr = arr.toString().split(','); 55 | return newArr.map(function(item){ 56 | return +item; // 将字符串转为数字 57 | }); 58 | } 59 | ``` 60 | -------------------------------------------------------------------------------- /JAVASCRIPT/普通函数与箭头函数的区别.md: -------------------------------------------------------------------------------- 1 | - 箭头函数是匿名函数,不能作为构造函数,不能使用new。 2 | - 箭头函数不绑定arguments(可以通过rest获取函数参数) 3 | - this的作用域不同,箭头函数不绑定this,会捕获其所在的上下文的this值,作为自己的this值。 4 | - 箭头函数没有原型属性 -------------------------------------------------------------------------------- /JAVASCRIPT/浅拷贝深拷贝.md: -------------------------------------------------------------------------------- 1 | 2 | - 浅拷贝 3 | 4 | ```js 5 | // Object.assign() 6 | let obj = {a:1}; 7 | let obj1 = Object.assign({}, obj); 8 | obj1.a = 3; 9 | console.log(obj.a); // 1 10 | 11 | // ES6解构赋值 12 | let obj1 = {...obj}; 13 | let arr1 = [...arr]; 14 | 15 | // Array.slice() 16 | let arr1 = arr.slice(); 17 | 18 | // Array.concat() 19 | let arr = [1,2,3]; 20 | let arr1 = [].concat(arr); 21 | arr[0] = 4; 22 | console.log(arr1[0]); // 1 23 | 24 | ``` 25 | 26 | - 深拷贝 27 | 28 | ```js 29 | // 利用json实现深拷贝 30 | // 缺点1:会忽略 undefined 31 | // 缺点2:不能序列化函数 32 | // 缺点3:无法拷贝 Symbol 33 | function cloneDeep (obj) { 34 | return JSON.parse(JSON.stringify(obj)); 35 | } 36 | 37 | // 递归法深拷贝 38 | function cloneDeep1 (obj) { 39 | if (typeof obj !== 'object') return obj; 40 | let newObj = obj instanceof Array ? [] : {}; 41 | 42 | for (let key in obj) { 43 | newObj[key] = typeof obj[key] === 'object' ? cloneDeep1(obj[key]) : obj[key]; 44 | } 45 | 46 | return newObj; 47 | } 48 | 49 | 50 | ``` -------------------------------------------------------------------------------- /JAVASCRIPT/继承.md: -------------------------------------------------------------------------------- 1 | 2 | - **原型链继承** 3 | 4 | - 原理 5 | - 将父类的实例作为子类的原型对象 6 | - 特点 7 | - 实例是子类的实例,也是父类的实例; 8 | - 父类在原型对象上新增方法/属性,实例都可以访问到; 9 | - 缺点 10 | - 原型对象的所有属性被所有实例共享,会导致数据被篡改; 11 | - 无法实现继承多个; 12 | - 创建子类实例时,无法向父类构造函数传参。 13 | 14 | ```js 15 | function Parent () { 16 | this.name = 'parent'; 17 | this.play = [1, 2, 3]; 18 | } 19 | Parent.prototype.sayName = function () { 20 | console.log('my name is ' + this.name); 21 | } 22 | function Child () { 23 | this.type = 'child'; 24 | } 25 | Child.prototype = new Parent(); 26 | 27 | var c1 = new Child(); 28 | var c2 = new Child(); 29 | c1.play.push(4); 30 | console.log(c1.play, c2.play); // [1, 2, 3, 4] [1, 2, 3, 4] 31 | console.log(c1 instanceof Child); // true 32 | console.log(c1 instanceof Parent); // true 33 | ``` 34 | 35 | - **构造函数继承(借助call)** 36 | 37 | - 原理 38 | - 使用父类的构造函数增强子类实例,等于是复制父类的实例属性给子类(没用到原型) 39 | - 特点 40 | - 创建子类实例时,可以向父类传递参数 41 | - 可以实现多继承(call多个父类对象) 42 | - 缺点 43 | - 实例并不是父类的实例,只是子类的实例 44 | - 只能继承父类的实例属性和方法,不能继承原型属性/方法 45 | - 方法都在构造函数(Parent1)中定义,无法实现函数复用 46 | 47 | ```js 48 | function Parent1(){ 49 | this.name1 = 'parent1'; 50 | } 51 | function Parent2(){ 52 | this.name2 = 'parent2'; 53 | } 54 | function Child(){ 55 | Parent1.call(this); 56 | Parent2.call(this); 57 | this.type = 'child' 58 | } 59 | 60 | var child = new Child(); 61 | console.log(child instanceof Child); // true 62 | console.log(child instanceof Parent1); // false 63 | console.log(child instanceof Parent2); // false 64 | 65 | ``` 66 | 67 | - **组合继承(原型链+call)** 68 | 69 | - 原理 70 | 71 | - 高程3的解释:使用原型链实现对原型上的属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。 72 | - 网上文章的解释:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用 73 | 74 | - 特点 75 | 76 | - 弥补了方式2(构造继承)的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法 77 | - 既是子类的实例,也是父类的实例 78 | - 不存在引用属性共享问题 79 | - 函数可复用 80 | - 可传参 81 | 82 | - 缺点 83 | 84 | - 调用了俩次构造函数,生成了俩份实例(子类实例将子类原型上的那份屏蔽了) 85 | 86 | ```js 87 | function Parent () { 88 | this.name = 'parent'; 89 | } 90 | Parent.prototype.sayName = function () { 91 | console.log(this.name); 92 | } 93 | function Child(name) { 94 | Parent.call(this); 95 | this.name = name || 'Tom'; 96 | } 97 | Child.prototype = new Parent(); 98 | Child.prototype.constructor = Child; 99 | 100 | var child = new Child(); 101 | console.log(child.name); // Tom 102 | child.sayName(); // Tom 103 | console.log(child instanceof Parent); // true 104 | console.log(child instanceof Child); // true 105 | 106 | ``` 107 | 108 | - **寄生组合继承** 109 | 110 | - 原理 111 | 112 | - 高程3的解释:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。 113 | - 网上文章的解释:通过寄生方式,砍掉父类的实例属性,这样,在调用俩次父类的构造的时候,就不会初始化俩次实例方法/属性,避免了组合继承的缺点。 114 | 115 | - 特点 116 | 117 | - 基本上是完美的 118 | 119 | - 缺点 120 | 121 | - 实现起来较为复杂 122 | 123 | ```js 124 | function Parent5 () { 125 | this.name = 'parent5'; 126 | this.play = [1, 2, 3]; 127 | } 128 | function Child5() { 129 | Parent5.call(this); 130 | this.type = 'child5'; 131 | } 132 | Child5.prototype = Object.create(Parent5.prototype); 133 | Child5.prototype.constructor = Child5; 134 | console.log(new Child5); 135 | ``` 136 | 137 | - **ES6的 extend** 138 | 139 | - 原理 140 | 141 | - ES6的Class写法跟传统的面向对象编程(例如:java)一模一样,但只是基于原型实现的语法糖; 142 | - extend继承是用寄生组合继承的方式实现的 143 | 144 | ```js 145 | // 子类只要继承父类,可以不写 constructor ,一旦写了,则在 constructor 中的 第一句话 146 | // 必须是 super 。 147 | class Son extends Father { // Son.prototype.__proto__ = Father.prototype 148 | constructor(y) { 149 | super(200) // super(200) => Father.call(this,200) 150 | this.y = y 151 | } 152 | } 153 | ``` 154 | 155 | 156 | 157 | - 文献 158 | 159 | - [JS实现继承的几种方法总结](https://blog.csdn.net/weixin_43606158/article/details/91489176) 160 | - [JavaScript常见的六种继承方式](https://segmentfault.com/a/1190000016708006#item-7) 161 | - [(建议收藏)原生JS灵魂之问, 请问你能接得住几个?(上)](https://juejin.cn/post/6844903974378668039#heading-31) 162 | - [JavaScript常用八种继承方案](https://juejin.cn/post/6844903696111763470) 163 | 164 | -------------------------------------------------------------------------------- /JAVASCRIPT/闭包.md: -------------------------------------------------------------------------------- 1 | # 闭包 2 | - 什么是闭包 3 | 4 | - 闭包就是能够读取其他函数内部变量的函数 5 | 6 | - 闭包如何产生 7 | - 函数做为参数传递 8 | - 函数做为返回值返回 9 | 10 | - 作用: 11 | 12 | - 闭包可以读取函数内部的变量,使变量不会被垃圾回收机制回收 ; 13 | - 避免命名冲突; 14 | 15 | - 缺点 16 | 17 | - 会出现内存泄漏的问题 18 | 19 | - 应用: 20 | 21 | 设计模式中的单例模式 22 | 23 | for循环中的保留i的操作 24 | 25 | 防抖和节流 26 | 27 | 函数柯里化 -------------------------------------------------------------------------------- /JAVASCRIPT/防抖节流.md: -------------------------------------------------------------------------------- 1 | 2 | **防抖** 3 | 4 | - n秒后执行函数,如果n秒内重复触发,则重新计时。 5 | - 应用场景 6 | - 登录、发短信等按钮避免用户点击太快,以致于发送了多次请求,需要防抖 7 | - 调整浏览器窗口大小时,resize 次数过于频繁,造成计算过多,此时需要一次到位,就用到了防抖 8 | - 文本编辑器实时保存,当无任何更改操作n秒后进行保存 9 | 10 | ```js 11 | // 防抖函数-简易版 12 | function debounce (fn, delay) { 13 | let timeout; 14 | 15 | return function () { 16 | if (timeout) clearTimeout(timeout); 17 | timeout = setTimeout(function () { 18 | fn(); 19 | }, delay); 20 | } 21 | } 22 | // 防抖函数 23 | function debounce (fn, delay) { 24 | let timeout; 25 | 26 | return function () { 27 | if (timeout) clearTimeout(timeout); 28 | timeout = setTimeout(() => { 29 | fn.apply(null, arguments); 30 | }, delay); 31 | } 32 | } 33 | // 立即执行防抖函数 34 | function debounce (fn, delay) { 35 | let timeout; 36 | 37 | return function () { 38 | if (timeout) clearTimeout(timeout); 39 | let callNow = !timeout; 40 | timeout = setTimeout(() => { 41 | timeout = null; 42 | }, delay); 43 | if (callNow) fn.apply(null, arguments); 44 | } 45 | } 46 | // 立即执行防抖函数+普通防抖 47 | function debounce (fn, delay, immediate) { 48 | let timeout; 49 | 50 | return function () { 51 | if (timeout) clearTimeout(timeout); 52 | 53 | if (immediate) { 54 | let callNow = !timeout; 55 | timeout = setTimeout(() => { 56 | timeout = null; 57 | }, delay); 58 | if (callNow) fn.apply(null, arguments); 59 | } else { 60 | timeout = setTimeout(() => { 61 | fn.apply(null, arguments); 62 | }, delay); 63 | } 64 | 65 | } 66 | } 67 | ``` 68 | 69 | **节流** 70 | 71 | - n秒内只运行一次,若在n秒内重复触发,只有一次生效。 72 | - 应用场景: 73 | - `scroll` 事件,每隔一秒计算一次位置信息等 74 | - 浏览器播放事件,每个一秒计算一次进度信息等 75 | - input 框实时搜索并发送请求展示下拉列表,每隔一秒发送一次请求 (也可做防抖) 76 | 77 | ```js 78 | // 节流 --- 定时器版本 - 简易版 79 | function throttle (fn, wait) { 80 | let timer; 81 | 82 | return function () { 83 | if (!timer) { 84 | timer = setTimeout(function () { 85 | timer = null; 86 | fn(); 87 | }, wait) 88 | } 89 | } 90 | } 91 | // 节流 --- 定时器版本 92 | function throttle (fn, wait) { 93 | let timer; 94 | 95 | return function () { 96 | if (!timer) { 97 | timer = setTimeout(() => { 98 | timer = null; 99 | fn.apply(null, arguments); 100 | }, wait) 101 | } 102 | } 103 | } 104 | // 节流 --- 时间戳版本 (当前时间 - 上一次执行的时间 > 间隔时间) 105 | function throttle2 (fn, wait) { 106 | let previous = 0; 107 | 108 | return function () { 109 | let now = new Date(); 110 | if (now - previous > wait) { 111 | fn.apply(null, arguments); 112 | previous = now; 113 | } 114 | } 115 | } 116 | ``` -------------------------------------------------------------------------------- /OTHER/module.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenjz08/FE-SelfCheckList/e8fb6f83951f19e077b2a2a264b63bad21b5b447/OTHER/module.png -------------------------------------------------------------------------------- /OTHER/前端安全.md: -------------------------------------------------------------------------------- 1 | # 前端安全 2 | 3 | ## XSS 跨站脚本攻击 4 | 5 | - 如何攻击? 6 | - XSS 的原理是恶意攻击者往 Web 页面里插入恶意可执行网页脚本代码,当用户浏览该页之时,嵌入其中 Web 里面的脚本代码会被执行,从而可以达到攻击者盗取用户信息或其他侵犯用户安全隐私的目的。 7 | - 攻击方式: 8 | - 反射型 9 | - 存储型 10 | - XSS危害? 11 | - 利用虚假输入表单骗取用户个人信息。 12 | - 利用脚本窃取用户的Cookie值,被害者在不知情的情况下,帮助攻击者发送恶意请求。 13 | - 显示伪造的文章或图片。 14 | - 如何防御? 15 | - 转义字符 16 | - 转义输入输出的内容,对于引号、尖括号、斜杠进行转义 17 | - cookie中设置了HttpOnly属性,那么通过js脚本将无法读取到cookie信息,这样能有效的防止XSS攻击,窃取cookie内容 18 | 19 | ## CSRF 跨站请求伪造 20 | 21 | - 如何攻击? 22 | - 跨站请求伪造,冒充用户发起请求(在用户不知情的情况下), 完成一些违背用户意愿的事情(如修改用户信息,删初评论等)。 23 | - CSRF危害 24 | - 利用已通过认证的用户权限更新设定信息等; 25 | - 利用已通过认证的用户权限购买商品; 26 | - 利用已通过的用户权限在留言板上发表言论。 27 | - 如何防御? 28 | - 请求时附带验证信息,比如验证码或者 Token 29 | - 不让第三方网站访问到用户 Cookie 30 | - 可以对 Cookie 设置 SameSite 属性。该属性表示 Cookie 不随着跨域请求发送,可以很大程度减少 CSRF 的攻击,但是该属性目前并不是所有浏览器都兼容。 31 | - 请求来源限制,此种方法成本最低,但是并不能保证 100% 有效,因为服务器并不是什么时候都能取到 Referer,而且低版本的浏览器存在伪造 Referer 的风险。 32 | - 使用token的原理: 33 | - 第一步:后端随机产生一个 token,把这个token 保存到 session 状态中;同时后端把这个token 交给前端页面; 34 | - 第二步:前端页面提交请求时,把 token 加入到请求数据或者头信息中,一起传给后端; 35 | - 后端验证前端传来的 token 与 session 是否一致,一致则合法,否则是非法请求。 36 | 37 | ## 点击劫持 38 | 39 | - 如何攻击? 40 | - 点击劫持是一种视觉欺骗的攻击手段。攻击者将需要攻击的网站通过 iframe 嵌套的方式嵌入自己的网页中,并将 iframe 设置为透明,在页面中透出一个按钮诱导用户点击。 41 | - 如何防御? 42 | - X-FRAME-OPTIONS 43 | - `X-FRAME-OPTIONS` 是一个 HTTP 响应头,在现代浏览器有一个很好的支持。这个 HTTP 响应头 就是为了防御用 iframe 嵌套的点击劫持攻击。 44 | - 该响应头有三个值可选,分别是 45 | - DENY,表示页面不允许通过 iframe 的方式展示 46 | - SAMEORIGIN,表示页面可以在相同域名下通过 iframe 的方式展示 47 | - ALLOW-FROM,表示页面可以在指定来源的 iframe 中展示 48 | - JavaScript 防御 49 | - 对于某些远古浏览器来说,并不能支持上面的这种方式,那我们只有通过 JS 的方式来防御点击劫持了。 50 | ```html 51 | 52 | 57 | 58 | 59 | 67 | 68 | ``` 69 | - 以上代码的作用就是当通过 iframe 的方式加载页面时,攻击者的网页直接不显示所有内容了。 70 | -------------------------------------------------------------------------------- /OTHER/性能优化.md: -------------------------------------------------------------------------------- 1 | # 性能优化 2 | ## 一、资源压缩与合并 3 | ## 二、非核心代码延迟加载 4 | ## 三、利用浏览器缓存 5 | ## 四、使用CDN 6 | ## 五、DNS解析 7 | ## 详细文献:[页面性能优化办法有哪些?](https://github.com/ljianshu/Blog/issues/9) -------------------------------------------------------------------------------- /OTHER/模块化.md: -------------------------------------------------------------------------------- 1 | # 模块化 2 | 3 | 模块化的开发方式可以提高代码复用率,方便进行代码的管理。通常一个文件就是一个模块,有自己的作用域,只向外暴露特定的变量和函数。目前流行的js模块化规范有CommonJS、AMD、CMD以及ES6的模块系统。 4 | 5 | --- 6 | 7 | ## CommonJs 8 | - 用`module.exports`定义当前模块对外输出的接口 9 | - 用`require()`加载模块 10 | 11 | ```js 12 | // 定义模块math.js 13 | var basicNum = 0; 14 | function add(a, b) { 15 | return a + b; 16 | } 17 | module.exports = { //在这里写上需要向外暴露的函数、变量 18 | add: add, 19 | basicNum: basicNum 20 | } 21 | 22 | // 引用自定义的模块时,参数包含路径,可省略.js 23 | var math = require('./math'); 24 | math.add(2, 5); 25 | 26 | // 引用核心模块时,不需要带路径 27 | var http = require('http'); 28 | http.createService(...).listen(3000); 29 | ``` 30 | 31 | - commonJS用同步的方式加载模块。在服务端,模块文件都存在本地磁盘,读取非常快,所以这样做不会有问题。但是在浏览器端,限于网络原因,更合理的方案是使用异步加载。 32 | 33 | ## AMD 34 | - 实现 AMD 规范的第三方库有:`require.js`、`curl.js` 35 | - 用`require.config()`指定引用路径等 36 | - 用`define()`定义模块 37 | - 用`require()`加载模块 38 | 39 | ```js 40 | /** 网页中引入require.js及main.js **/ 41 | 42 | 43 | /** main.js 入口文件/主模块 **/ 44 | // 首先用config()指定各模块路径和引用名 45 | require.config({ 46 | baseUrl: "js/lib", 47 | paths: { 48 | "jquery": "jquery.min", //实际路径为js/lib/jquery.min.js 49 | "underscore": "underscore.min", 50 | } 51 | }); 52 | // 执行基本操作 53 | require(["jquery","underscore"],function($,_){ 54 | // some code here 55 | }); 56 | 57 | ``` 58 | 59 | ## CMD 60 | - 实现 CMD 规范的第三方库有:`sea.js` 61 | - CMD 是另一种js模块化方案,它与 AMD 很类似,不同点在于: **AMD 推崇依赖前置、提前执行,CMD 推崇依赖就近、延迟执行。** 此规范其实是在 sea.js 推广过程中产生的。 62 | 63 | ```js 64 | /** AMD写法 **/ 65 | define(["a", "b", "c", "d", "e", "f"], function(a, b, c, d, e, f) { 66 | // 等于在最前面声明并初始化了要用到的所有模块 67 | a.doSomething(); 68 | if (false) { 69 | // 即便没用到某个模块 b,但 b 还是提前执行了 70 | b.doSomething() 71 | } 72 | }); 73 | 74 | /** CMD写法 **/ 75 | define(function(require, exports, module) { 76 | var a = require('./a'); //在需要时申明 77 | a.doSomething(); 78 | if (false) { 79 | var b = require('./b'); 80 | b.doSomething(); 81 | } 82 | }); 83 | 84 | /** sea.js **/ 85 | // 定义模块 math.js 86 | define(function(require, exports, module) { 87 | var $ = require('jquery.js'); 88 | var add = function(a,b){ 89 | return a+b; 90 | } 91 | exports.add = add; 92 | }); 93 | // 加载模块 94 | seajs.use(['math.js'], function(math){ 95 | var sum = math.add(1+2); 96 | }); 97 | ``` 98 | 99 | ## ES6 Module 100 | - ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,旨在成为浏览器和服务器通用的模块解决方案。其模块功能主要由两个命令构成:`export`和`import`。`export`命令用于规定模块的对外接口,`import`命令用于输入其他模块提供的功能。 101 | 102 | ```js 103 | /** 定义模块 math.js **/ 104 | var basicNum = 0; 105 | var add = function (a, b) { 106 | return a + b; 107 | }; 108 | export { basicNum, add }; 109 | 110 | /** 引用模块 **/ 111 | import { basicNum, add } from './math'; 112 | function test(ele) { 113 | ele.textContent = add(99 + basicNum); 114 | } 115 | ``` 116 | 117 | - 如上例所示,使用import命令的时候,用户需要知道所要加载的变量名或函数名。其实ES6还提供了export default命令,为模块指定默认输出,对应的import语句不需要使用大括号。这也更趋近于ADM的引用写法。 118 | 119 | ```js 120 | /** export default **/ 121 | //定义输出 122 | export default { basicNum, add }; 123 | //引入 124 | import math from './math'; 125 | function test(ele) { 126 | ele.textContent = math.add(99 + math.basicNum); 127 | } 128 | ``` 129 | 130 | - ES6的模块不是对象,import命令会被 JavaScript 引擎静态分析,在编译时就引入模块代码,而不是在代码运行时加载,所以无法实现条件加载。也正因为这个,使得静态分析成为可能。 131 | 132 | ## ES6 模块与 CommonJS 模块的差异 133 | 134 | 1. CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。 135 | 136 | - CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。 137 | 138 | - ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的import有点像 Unix 系统的“符号连接”,原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。 139 | 140 | 2. CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。 141 | 142 | - 运行时加载: CommonJS 模块就是对象;即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,这种加载称为“运行时加载”。 143 | 144 | - 编译时加载: ES6 模块不是对象,而是通过 export 命令显式指定输出的代码,import时采用静态命令的形式。即在import时可以指定加载某个输出值,而不是加载整个模块,这种加载称为“编译时加载”。 145 | 146 | CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。 147 | 148 | - 文献: 149 | - [前端模块化:CommonJS,AMD,CMD,ES6](https://juejin.cn/post/6844903576309858318) 150 | - [前端模块化详解(完整版)](https://juejin.cn/post/6844903744518389768) 151 | 152 | 153 | -------------------------------------------------------------------------------- /OTHER/浏览器兼容.md: -------------------------------------------------------------------------------- 1 | # 浏览器兼容 2 | 3 | ## js 4 | 5 | 1. 对不兼容的js语法做向后兼容 6 | - 比如:IE(6-8版本不兼容addEventListener) 7 | - 通过判断是否有 `element.addEventListener`,如果不存在就使用 `element.attachEvent` 8 | 9 | ## css 10 | 11 | 1. 统一各个浏览器的样式风格 12 | - 通配符选择器 `* {margin:0;padding:0;}` 13 | - 第三方样式库 `normalize.css` 14 | 15 | ## 移动端 16 | 17 | 1. ios系统中js的new Date的格式问题 18 | - 前段时间做项目时,发现在ios系统下,js中去new Date的时,传入参数带有"-",如:2016-01-01,则会返回NaN。 19 | 最后在网上查了查资料,发现不支持这用日期格式,把"-"改为"/"则能够返回正常的值。 20 | 具体原因还不太清楚,个人推测可能是因为ios的 Safari 浏览器不支持Date的带"-"格式的写法。 -------------------------------------------------------------------------------- /OTHER/跨域.md: -------------------------------------------------------------------------------- 1 | # 怎么才会出现跨域问题? 2 | - 不符合`同源策略`的情况,会出现跨域问题 3 | - 所谓“同源”指的是“三个相同”。 4 | - 协议相同 5 | - 域名相同 6 | - 端口相同 7 | ```js 8 | - 举例来说,http://www.example.com/dir/page.html这个网址, 9 | 协议是http://,域名是www.example.com,端口是80(默认端口可以省略),它的同源情况如下。 10 | - http://www.example.com/dir2/other.html:同源 11 | - http://example.com/dir/other.html:不同源(域名不同) 12 | - http://v2.www.example.com/dir/other.html:不同源(域名不同) 13 | - http://www.example.com:81/dir/other.html:不同源(端口不同) 14 | - https://www.example.com/dir/page.html:不同源(协议不同) 15 | ``` 16 | # 解决跨域的方案 17 | 18 | 1. **webpack的proxy** [ˈprɒksi] 19 | 20 | 2. **CORS** 21 | 22 | - 通过自定义请求头来让服务器和浏览器进行通信 23 | - 有简单请求和非简单请求 24 | - 满足以下两个条件,就是简单请求;不满足一个或者都不满足,则是非简单请求 25 | - 请求方法是以下三种方法之一: 26 | - HEAD 27 | - GET 28 | - POST 29 | - HTTP的头信息不超过以下几种字段: 30 | - Accept 31 | - Accept-Language 32 | - Content-Language 33 | - Last-Event-ID 34 | - Conent-Type:只限于三个值application/x-www-form-urlencoded`、`multipart/form-data`、`text/plain 35 | - 简单请求,浏览器自动添加一个Origin字段 36 | - 同时后端需要设置的响应头 37 | - Access-Control-Allow-Origin (必选) 38 | - Access-Control-Expose-Headers 39 | - XMLHttpRequest 只能拿到六个字段,要想拿到其他的需要在这里指定 40 | - Access-Control-Allow-Credentials:是否可传cookie 41 | - 要是想传cookie,前端需要设置xhr.withCredentials = true,后端设置Access-Control-Allow-Credentials:true 42 | - 非简单请求:浏览器判断是否为简单请求,如果是非简单请求,则浏览器会发送一个 OPTIONS 方法的请求进行预检 43 | - 预检请求头包含的字段: 44 | - Origin 45 | - Access-Control-Request-Method 46 | - Access-Control-Request-Header 47 | - 浏览器检查了以上三个字段之后,确认允许就可以做出回应了 48 | - 通过预检后,浏览器接下来的每次请求就类似于简单请求了 49 | - 文献:[跨域资源共享 CORS 详解](http://www.ruanyifeng.com/blog/2016/04/cors.html) 50 | 51 | 3. **jsonp** 52 | 53 | - jsonp是一种跨域通信的手段,它的原理其实很简单: 54 | 55 | - 利用script标签的src属性来实现跨域; 56 | - 通过将前端的方法作为参数传递到服务器端,然后由服务器端注入参数之后再返回,实现服务端向客户端通信; 57 | - 由于使用script标签的src属性,因为只支持get方法。 58 | 59 | - jsonp简单实现 60 | 61 | - 前端代码 62 | 63 | ```js 64 | function jsonp (req) { 65 | var script = document.creatElement('script'); 66 | var url = req.url + '?callback=' + req.callback.name; 67 | script.src = url; 68 | document.getElementByTagName('head')[0].appendChild(script); 69 | } 70 | function hello (res) { 71 | console.log('hello ' + res.data); 72 | } 73 | jsonp({ 74 | url: '', 75 | callback: hello 76 | }) 77 | ``` 78 | 79 | - 服务端代码 80 | 81 | ```js 82 | var http = require('http'); 83 | var urllib = require('url'); 84 | 85 | var port = 8080; 86 | var data = {'data':'world'}; 87 | 88 | http.createServer(function(req,res){ 89 | var params = urllib.parse(req.url,true); 90 | if(params.query.callback){ 91 | console.log(params.query.callback); 92 | //jsonp 93 | var str = params.query.callback + '(' + JSON.stringify(data) + ')'; 94 | res.end(str); 95 | } else { 96 | res.end(); 97 | } 98 | 99 | }).listen(port,function(){ 100 | console.log('jsonp server is on'); 101 | }); 102 | ``` 103 | 104 | - 以上的实现简单,所以存在一些不足的地方 105 | - 我们传递的回调必须是一个全局方法,应当尽量减少全局的方法; 106 | - 需要加入一些参数校验,确保接口可以正常执行。 107 | 108 | ​ 109 | 110 | - 文献 111 | - [jsonp的原理与实现](https://segmentfault.com/a/1190000007665361) 112 | - [什么是跨域?跨域解决方法](https://blog.csdn.net/qq_38128179/article/details/84956552) - (未) 113 | 114 | 4. **nginx反向代理** 115 | 116 | - niginx模拟一个虚拟服务器,因为服务器与服务器之间是不存在跨域的 117 | - 发送数据时,客户端-》nginx-》服务端 118 | - 返回数据时,服务端-》niginx-》客户端 119 | 120 | 5. **修改浏览器的安全设置(不推荐)** 121 | 122 | - **【有安全隐患】**Chrome: 属性->目标(路径后面加上这一串) --disable-web-security --user-data-dir=D:\MyChromeDevUserData(--user-data-dir指定一个文件夹) 123 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Blog 2 | 3 | - 2022-12-07 [基于verdaccio+docker搭建npm私有仓库](https://github.com/chenjiezi/FE-SelfCheckList/issues/2) 4 | 5 | # 前端自检清单 6 | 7 | 8 | ### JAVASCRIPT 9 | - [js中的数据类型](./JAVASCRIPT/js中的数据类型.md) 10 | - [执行上下文和执行栈](./JAVASCRIPT/执行上下文和执行栈.md) 11 | - [闭包](./JAVASCRIPT/闭包.md) 12 | - [new 操作符](./JAVASCRIPT/new操作符.md) 13 | - [this 指向](./JAVASCRIPT/this指向.md) 14 | - [原型与原型链](./JAVASCRIPT/原型与原型链.md) 15 | - [继承](./JAVASCRIPT/继承.md) 16 | - [内存泄露、垃圾回收机制](./JAVASCRIPT/内存泄露、垃圾回收机制.md) 17 | - [浏览器的事件循环](./JAVASCRIPT/EventLoop.md) 18 | - [node的事件循环](#node的事件循环) 19 | - [事件、事件委托](./JAVASCRIPT/事件、事件委托.md) 20 | - [普通函数与箭头函数的区别](./JAVASCRIPT/普通函数与箭头函数的区别.md) 21 | - [var、let 和 const 的区别](./JAVASCRIPT/var、let、const的区别.md) 22 | - [0.1 + 0.2 === 0.3 返回 false](./JAVASCRIPT/0.1+0.2===0.3返回false.md) 23 | - [defer 与 async 的区别](./JAVASCRIPT/defer与async的区别.md) 24 | - 其他 25 | - [跨域](./OTHER/跨域.md) 26 | - [模块化](./OTHER/模块化.md) 27 | - [性能优化](./OTHER/性能优化.md) 28 | - [前端安全](./OTHER/前端安全.md) 29 | - [浏览器兼容](./OTHER/浏览器兼容.md) 30 | - 手撕代码 31 | - [数组去重、扁平](./JAVASCRIPT/数组去重扁平.md) 32 | - [手写 instanceOf](./JAVASCRIPT/手写instanceof.md) 33 | - [手写 call、apply、bind](./JAVASCRIPT/call、apply、bind.md) 34 | - [防抖、节流](./JAVASCRIPT/防抖节流.md) 35 | - [浅拷贝、深拷贝](./JAVASCRIPT/浅拷贝深拷贝.md) 36 | - [函数柯里化](./JAVASCRIPT/函数柯里化.md) 37 | - [手写 ajax](./JAVASCRIPT/手写ajax.md) 38 | - [手写 Promise/A+](./JAVASCRIPT/手写promise.md) 39 | - [手写 EventEmitter](./JAVASCRIPT/手写EventEmitter.md) 40 | - [手写 Promise Api](./JAVASCRIPT/手写promiseApi.md) 41 | ### HTML/CSS 42 | - HTML5/CSS3新特性 43 | - 重绘、回流 44 | - [盒模型](./CSS/盒模型.md) 45 | - [BFC](./CSS/BFC.md) 46 | - [水平垂直居中](./CSS/水平垂直居中.md) 47 | - [两列布局(左固定、右自适应)](./CSS/两列布局.md) 48 | - [三列布局(圣杯布局、双飞翼布局)](./CSS/三列布局.md) 49 | - [定位五个属性](https://www.ruanyifeng.com/blog/2019/11/css-position.html) 50 | ### 网络协议 51 | - [OSI模型、TCP/IP模型分别由哪些分层组成](./网络协议/OSI模型TCPIP模型.md) 52 | - HTTP 53 | - [HTTP重点知识汇总](https://www.nowcoder.com/discuss/634359?channel=-1&source_id=profile_follow_post_nctrack) 54 | - [常见HTTP状态码](./网络协议/HTTP状态码.md) 55 | - [HTTP版本](./网络协议/HTTP版本.md) 56 | - [HTTP缓存机制](./网络协议/HTTP缓存.md) 57 | - [GEI 和 POST 有什么区别](./网络协议/GEI和POST有什么区别.md) 58 | - [HTTP 与 HTTPS 的区别及实现方式](./网络协议/HTTP与HTTPS的区别及实现方式.md) 59 | - HTTPS握手 60 | - 对称加密与非对称加密 61 | - TCP/UDP 62 | - [TCP 和 UDP 有什么区别](./网络协议/TCP和UDP有什么区别.md) 63 | - [TCP 三次握手和四次挥手机制以及原因](./网络协议/TCP三次握手和四次挥手机制以及原因.md) 64 | - [TCP 通过哪些方式来保证可靠性](./网络协议/TCP如何保证可靠性.md) 65 | - [TCP 流量控制](./网络协议/TCP流量控制.md) 66 | - [TCP 拥塞控制](./网络协议/TCP拥塞控制.md) 67 | - [流量控制和拥塞控制的区别](./网络协议/流量控制和拥塞控制的区别.md) 68 | ### 设计模式 69 | - [单例模式](./设计模式/单例模式.md) 70 | - [策略模式](./设计模式/策略模式.md) 71 | ### 数据结构与算法 72 | - 数据结构 73 | - [数组与链表区别和优缺点](./数据结构与算法/数组与链表区别和优缺点.md) 74 | - 算法 75 | - 排序 76 | - [冒泡排序](./数据结构与算法/冒泡排序.md) 77 | - [选择排序](./数据结构与算法/选择排序.md) 78 | - [插入排序](./数据结构与算法/插入排序.md) 79 | - [计数排序](./数据结构与算法/计数排序.md) 80 | - [归并排序](./数据结构与算法/归并排序.md) 81 | - [快速排序](./数据结构与算法/快速排序.md) 82 | - 查找 83 | - [二分查找](./数据结构与算法/二分查找.md) 84 | - 随机 85 | - [随机算法](./数据结构与算法/随机算法.md) 86 | 87 | ### 操作系统 88 | - [进程与线程区别](./操作系统/进程与线程的区别.md) 89 | - [死锁的产生](./操作系统/死锁的产生.md) 90 | - [处理死锁的方法](./操作系统/处理死锁的方法.md) 91 | 92 | 93 | 94 | #### node的事件循环 95 | node的事件循环 96 | 97 | #### Vue key 98 | Vue key 99 | https://juejin.cn/post/7102219080152645668 100 | 101 | #### diff算法 102 | https://github.com/chenhongdong/article/tree/develop/%E8%99%9A%E6%8B%9Fdom/dom-diff 103 | -------------------------------------------------------------------------------- /blog/iconfont-auto-import.md: -------------------------------------------------------------------------------- 1 | ### 插件介绍 2 | 基于puppeteer自动导入阿里巴巴矢量库的项目图标。iconfont.cn 3 | 【图片】 4 | 5 | ### 背景 6 | 7 | ### 功能 8 | - 下载并压缩图标文件到项目的指定位置 9 | - 设置保留文件 10 | - 修改文件内容 11 | - 保留图标压缩包 12 | - 配置puppeteer 13 | 14 | 具体使用方法请查看文档:https://github.com/chenjiezi/iconfont-auto-import#readme 15 | 16 | 17 | ### 实现思路 18 | 19 | ### 总结 -------------------------------------------------------------------------------- /剑指offer/01 二维数组中查找.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 题目描述 3 | * 在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。 4 | * 请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。 5 | * 6 | * 测试案例 7 | * 5,[[1,2,8,9],[2,4,9,12],[4,7,10,13],[6,8,11,15]] 8 | * 6,[[1,2,8,9],[2,4,9,12],[4,7,10,13],[6,8,11,15]] 9 | */ 10 | 11 | // 第一种做法(最优解) 12 | function Find (target, array) { 13 | const n = array.length, 14 | m = array[0].length; 15 | let row = n - 1, 16 | rol = 0; 17 | if (n === 0 && m === 0) { 18 | return false; 19 | } 20 | while (row >= 0 && rol <= m - 1) { 21 | if (target < array[row][rol]) { 22 | row--; 23 | } else if (target > array[row][rol]) { 24 | rol++; 25 | } else { 26 | return true; 27 | } 28 | } 29 | return false; 30 | } 31 | // 第二种做法:两层for循环 32 | function Find(target, array) 33 | { 34 | const rowNum = array.length; 35 | if (!rowNum) { 36 | return false; 37 | } 38 | const colNum = array[0].length; 39 | for(let i = 0; i < rowNum; i++) { 40 | for(let j = 0; j < colNum; j++) { 41 | if (array[i][j] === target) { 42 | return true; 43 | } 44 | } 45 | } 46 | return false; 47 | } 48 | // 第三种做法:二分查找 49 | function Find(target, array) 50 | { 51 | if (!array.length) { 52 | return false; 53 | } 54 | 55 | for(let i = 0; i < array.length; i++) { 56 | let low = 0; 57 | let high = array[i].length - 1; 58 | while (low <= high) { 59 | let mid = (low + high) / 2; 60 | if (target > array[i][mid]) { 61 | low = mid + 1; 62 | } else if (target < array[i][mid]) { 63 | high = mid - 1; 64 | } else { 65 | return true; 66 | } 67 | } 68 | } 69 | return false; 70 | } 71 | -------------------------------------------------------------------------------- /剑指offer/02 替换空格.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 题目描述: 3 | * 请实现一个函数,将一个字符串中的每个空格替换成“%20”。 4 | * 例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。 5 | */ 6 | 7 | // 第一种做法:利用正则表达式(最优解) 8 | function replaceSpace(str) 9 | { 10 | return str.replace(/\s/g, '%20'); 11 | } 12 | 13 | //第二种做法 14 | function replaceSpace(str) 15 | { 16 | let strLen = str.length, 17 | result = ''; 18 | for(let i = 0; i < strLen; i++) { 19 | result += (str[i] !== ' ') ? str[i] : '%20'; 20 | } 21 | return result; 22 | } -------------------------------------------------------------------------------- /剑指offer/README.md: -------------------------------------------------------------------------------- 1 | # 剑指offer 2 | 记录剑指offer中编程题的答案。 -------------------------------------------------------------------------------- /操作系统/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenjz08/FE-SelfCheckList/e8fb6f83951f19e077b2a2a264b63bad21b5b447/操作系统/1.png -------------------------------------------------------------------------------- /操作系统/处理死锁的方法.md: -------------------------------------------------------------------------------- 1 | 1. 预防死锁 2 | - 事先采取各种限制措施,破坏死锁产生条件中的一个或者几个,从而预防产生死锁。 3 | 4 | - **破坏请求和保持条件**。有两种方式。 5 | 6 | - 方式1:所有进程在运行之前,必须一次性地申请其在整个运行过程中所需的全部资源。这样进程在运行中,便不会再提出资源请求了,从而破坏了“请求”条件。系统在分配资源时,只要有一种资源不能满足进程的要求,即使其它所需的各项资源都空闲也不分配给该进程,而让该进程等待。由于该进程在等待期间未占有任何资源,于是破坏了“保持”条件。 7 | 8 | - 这种方式简单易行,但是资源被严重浪费了,因为进程一开始就一次性地占用了整个运行过程所需的全部资源,而其中有些资源可能仅在运行初期或运行快结束时才可能被使用。 9 | 10 | - 这种方式也会使得进程经常发生饥饿现象。如果个别资源长期被其它进程占用,而等待该资源的进程就迟迟不能开始运行,而个别资源可能仅在进程运行到最后才需要,比如打印机。那么就没有必要一开始就申请打印机这个资源。 11 | 12 | - 方式2:这种方式是对第一种方式的改进,它允许一个进程只获得运行初期所需的资源后就开始运行。程运行过程中再逐步释放已分配给自己的、且已用完的全部资源,然后再请求新的所需资源。这种方式提高了设备的利用率,还能减少进程发生饥饿的机率。 13 | 14 | **破坏“不可抢占”条件** 15 | 16 | 当一个已经保持了某些不可被抢占资源的进程,提出新的资源请求而不能得到满足时,它必须释放已经保持的所有资源,待以后需要时再重新申请。这种方式可能会导致以前的工作失效。 17 | 18 | **破坏循环等待条件** 19 | 20 | 把系统中所有资源顺序编号, 各进程按资源编号递增次序申请资源,通过这种方式不会出现环路。 21 | 22 | 2. 避免死锁 23 | 24 | - 避免死锁和预防死锁相比,所施加的限制条件较弱;但是要进行安全性检查。它是在资源的动态分配过程中,用某种方式防止系统进入不安全的状态。 25 | 26 | - 该方法中把系统的状态分为**安全状态**和**不安全状态**,只要使系统始终都处于安全状态,便可避免发生死锁。当系统处于不安全状态时,则可能进入死锁状态。所谓**安全状态**,是指系统能按某种进程顺序(P1,P2,…,Pn),来为每个进程Pi分配其所需资源,直至满足每个进程对资源的最大需求,使每个进程都可顺利地完成。如果系统无法找到这样一个安全推进的进程序列,则称系统处于不安全状态。 27 | 28 | - 避免死锁的基本思想就是确保系统始终处于安全状态,它允许进程动态地申请资源,当有进程请求一个可用资源时,应先计算此次资源分配的安全性。如果此次分配不会导致系统进入不安全的状态,才可以将资源分配给系统的进程。否则就让进程等待。 29 | 30 | - 最具代表性的避免死锁的办法是银行家算法。 31 | 32 | 3. 死锁的检测和解除 33 | - 检测死锁:保存有关资源的的请求和分配信息,再提供一种算法,它利用这些信息来检测系统是否已经进入死锁状态。利用**死锁定理**。我们可以画一个资源分配图,在资源分配图中进程结点和资源结点,有请求边和分配边。首先在图中找出既不阻塞又不孤立的进程结点,在顺利的情况下,这个节点可获得所需的资源直到运行完毕,然后释放占有的资源,消除掉该结点的请求边和分配边,使之成为孤立的结点。然后以此类推,对资源分配图进行一系列的简化,如果能消除图中所有的边,说明这个图是可以完全简化的,否则是不可完全简化的。变成死锁的充分条件是资源分配图是不可完全简化的。这就是死锁定理,用来检测是否发生了死锁。 34 | - 解除死锁有两种方式: 35 | - 资源剥夺法。挂起(暂时放到外存上)某些死锁进程,并抢占它的资源,将这些资源分配给其他的死锁进程,以解除死锁状态。 36 | - 终止或撤销进程。 37 | - 终止所有死锁进程:代价大,被终止进程的前期工作必须放弃 38 | - 按照某种顺序逐个终止进程,直至有足够的资源以打破循环等待,解除死锁为止。 39 | 40 | ![死锁的检测和解除](./1.png) -------------------------------------------------------------------------------- /操作系统/死锁的产生.md: -------------------------------------------------------------------------------- 1 | - 死锁指的是多个进程在运行过程中因争夺资源而形成的一种僵局。进程在竞争不可抢占资源时会引 2 | 起死锁,另外因为进程具有异步性的特征,它以不可预知的速度向前推进,当进程推进顺序不正确时也 3 | 会引起死锁。 4 | 5 | - 虽然进程在执行过程中可能会发生死锁,但是产生死锁是必须具备四个必要条件的: 6 | 7 | - **互斥条件**:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只供一个进程占 8 | 用。如果此时其他进程请求该资源,请求者只能等待。 9 | - **请求和保持条件**:指进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源又已 10 | 被其它进程占用,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。 11 | - **不可抢占条件**:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己 12 | 释放。 13 | - **循环等待条件**:指在发生死锁时,必然存在一个进程请求资源,等待资源的循环链。 -------------------------------------------------------------------------------- /操作系统/进程与线程的区别.md: -------------------------------------------------------------------------------- 1 | # 线程与进程的区别 2 | - 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程依赖于进 程而存在。 3 | - 进程是资源分配的基本单位,线程是CPU调度和分派的基本单位。 4 | - 进程在执行过程中拥有独立的地址空间和资源,同一进程的不同线程并发执行,共享该进程的 内存地址空间和资源。 5 | - 由于在创建或撤消进程时,系统都要为之分配或回收资源,并且进程切换时系统开销较大。在 同一个进程中,线程的切换不会引起进程的切换。切换线程的开销比较小。 6 | - 一个线程挂掉将导致整个进程挂掉,而进程间不会相互影响。 -------------------------------------------------------------------------------- /数据结构与算法/二分查找.md: -------------------------------------------------------------------------------- 1 | # 二分查找: 2 | 3 | - (1) 选择数组的中间值。 4 | 5 | - (2) 如果选中值是待搜索值,那么算法执行完毕(值找到了)。 6 | 7 | - (3) 如果待搜索值比选中值要小,则返回步骤 1 并在选中值左边的子数组中寻找(较小)。 8 | 9 | - (4) 如果待搜索值比选中值要大,则返回步骤 1 并在选种值右边的子数组中寻找(较大)。 10 | 11 | ```js 12 | function binarySearch (arr, value) { 13 | let low = 0; 14 | let high = arr.length - 1; 15 | while (low <= high) { 16 | let mid = Math.floor((low + high) / 2); 17 | let element = arr[mid]; 18 | if (element < value) { 19 | low = mid + 1; 20 | } else if (element > value) { 21 | high = mid - 1; 22 | } else { 23 | return mid; 24 | } 25 | } 26 | return -1; 27 | } 28 | 29 | console.log('binarySearch([1,2,3,4,5,6]), 2:', binarySearch([1,2,3,4,5,6], 2)); // 1 30 | console.log('binarySearch([1,2,3,4,5,6]), 7:', binarySearch([1,2,3,4,5,6], 7)); // -1 31 | ``` -------------------------------------------------------------------------------- /数据结构与算法/冒泡排序.md: -------------------------------------------------------------------------------- 1 | # 冒泡排序:遍历数组,相邻两个比较交换 2 | 3 | ```js 4 | // 冒泡排序第一版 5 | function bubbleSort1 () { 6 | let arr = [6, 5, 4, 3, 2, 1]; 7 | let count = 0; 8 | console.log('排序前:', arr) 9 | for (let i = 0; i < arr.length; i++) { 10 | for (let j = 0; j < arr.length - 1; j++) { 11 | count++; 12 | if (arr[j] > arr[j + 1]) { 13 | [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]; 14 | } 15 | } 16 | } 17 | console.log('排序后:', arr); 18 | console.log('循环次数1:', count); 19 | } 20 | 21 | // 冒泡排序第二版 22 | function bubbleSort2 () { 23 | let arr = [6, 5, 4, 3, 2, 1]; 24 | let count = 0; 25 | console.log('排序前:', arr) 26 | for (let i = 0; i < arr.length; i++) { 27 | for (let j = 0; j < arr.length - 1 - i; j++) { 28 | count++; 29 | if (arr[j] > arr[j + 1]) { 30 | [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]; 31 | } 32 | } 33 | } 34 | console.log('排序后:', arr); 35 | console.log('循环次数2:', count); 36 | } 37 | 38 | // 冒泡排序第三版 39 | function bubbleSort3 () { 40 | let arr = [6, 5, 4, 3, 2, 1]; 41 | let count = 0; 42 | console.log('排序前:', arr) 43 | for (let i = 0; i < arr.length - 1; i++) { 44 | for (let j = i + 1; j < arr.length; j++) { 45 | count++; 46 | if (arr[i] > arr[j]) { 47 | [arr[i], arr[j]] = [arr[j], arr[i]]; 48 | } 49 | } 50 | } 51 | console.log('排序后:', arr); 52 | console.log('循环次数3:', count); 53 | } 54 | 55 | bubbleSort1(); // 循环次数 30 56 | bubbleSort2(); // 循环次数 15 57 | bubbleSort3(); // 循环次数 15 58 | // 第二、三种版本比第一种版本减少很多没必要的内循环比较 59 | ``` -------------------------------------------------------------------------------- /数据结构与算法/归并排序.md: -------------------------------------------------------------------------------- 1 | # 归并排序 2 | 3 | - 归并排序是一种分而治之算法。其思想是将原始数组切分成较小的数组,直到每个小数组只有一个位置,接着将小数组归并成较大的数组,直到最后只有一个排序完毕的大数组。 4 | 5 | - 由于是分治法,归并排序也是递归的。我们要将算法分为两个函数:第一个负责将一个大数组分为多个小数组并调用用来排序的辅助函数。我们来看看在这里声明的主要函数。 6 | 7 | ```js 8 | function merge (left, right) { 9 | let i = 0, 10 | j = 0; 11 | const result = []; 12 | 13 | while (i < left.length && j < right.length) { 14 | result.push(left[i] > right[j] ? right[j++] : left[i++]); 15 | } 16 | 17 | return result.concat(i < left.length ? left.slice(i) : right.slice(j)); 18 | } 19 | 20 | function mergeSort (arr) { 21 | if (arr.length > 1) { 22 | const { length } = arr; 23 | const middle = Math.floor(length / 2); 24 | const left = mergeSort(arr.slice(0, middle)); 25 | const right = mergeSort(arr.slice(middle, length)); 26 | return merge(left, right); 27 | } 28 | return arr; 29 | } 30 | console.log('mergeSort([6,5,4,3,2,1]):', mergeSort([6,5,4,3,2,1])) 31 | ``` -------------------------------------------------------------------------------- /数据结构与算法/快速排序.md: -------------------------------------------------------------------------------- 1 | 快速排序 2 | 3 | - **核心思想**:快排最重要的思想就是分而治之:从整个数组中随机挑选一个元素作为基准,数组中小于基准的元素全部移到左边,大于的移到右边,即以该基准作为分界点。接着便可以按照此思想,递归的处理左边部分数组和右边部分数组。 4 | 5 | - 思路: 6 | 7 | - (1) 首先,从数组中选择一个值作为主元(pivot),也就是数组中间的那个值。 8 | - (2) 创建两个指针(引用),左边一个指向数组第一个值,右边一个指向数组最后一个值。移动左指针直到我们找到一个比主元大的值,接着,移动右指针直到找到一个比主元小的值,然后交换它们,重复这个过程,直到左指针超过了右指针。这个过程将使得比主元小的值都排在主元之前,而比主元大的值都排在主元之后。这一步叫作划分(partition)操作。 9 | - (3) 接着,算法对划分后的小数组(较主元小的值组成的子数组,以及较主元大的值组成的子数组)重复之前的两个步骤,直至数组已完全排序。 10 | 11 | ```js 12 | function quickSort (arr) { 13 | return quick(arr, 0, arr.length - 1); 14 | } 15 | 16 | function quick (arr, left, right) { 17 | let index; 18 | if (arr.length > 1) { 19 | index = partition(arr, left, right); 20 | if (left < index - 1) { 21 | quick(arr, left, index - 1); 22 | } 23 | if (index < right) { 24 | quick(arr, index, right); 25 | } 26 | } 27 | return arr; 28 | } 29 | 30 | function partition (arr, left, right) { 31 | let povit = arr[Math.floor((left + right) / 2)]; 32 | let i = left; 33 | let j = right; 34 | 35 | while (i <= j) { 36 | while (arr[i] < povit) { 37 | i++; 38 | } 39 | while (arr[j] > povit) { 40 | j--; 41 | } 42 | if (i <= j) { 43 | [arr[i], arr[j]] = [arr[j], arr[i]]; 44 | i++; 45 | j--; 46 | } 47 | } 48 | 49 | return i; 50 | } 51 | 52 | console.log('quickSort([6,5,4,3,2,1]):', quickSort([6,5,4,3,2,1])) 53 | console.log('quickSort([1,2,2,3,4,5]):', quickSort([1,2,2,3,4,5])) 54 | ``` -------------------------------------------------------------------------------- /数据结构与算法/插入排序.md: -------------------------------------------------------------------------------- 1 | # 插入排序:排序小型数组时,此算法比选择排序和冒泡排序性能要好。 2 | 3 | ```js 4 | function insertionSort () { 5 | const arr = [6, 5, 4, 3, 2, 1]; 6 | console.log('排序前:', arr); 7 | 8 | for (let i = 1; i < arr.length; i++) { 9 | let j = i, temp = arr[i]; 10 | 11 | while (j > 0 && arr[j - 1] > temp) { 12 | arr[j] = arr[j - 1]; 13 | j--; 14 | } 15 | arr[j] = temp; 16 | } 17 | 18 | console.log('排序后:', arr); 19 | } 20 | 21 | insertionSort(); 22 | ``` -------------------------------------------------------------------------------- /数据结构与算法/数组与链表区别和优缺点.md: -------------------------------------------------------------------------------- 1 | 数组和链表是两种基本的数据结构,他们在内存存储上的表现不一样,所以也有各自的特点。 2 | 3 | 链表中各结点在内存中的存放位置是任意的。 4 | 5 | ### 链表与数组的主要区别 6 | 7 | (1)数组的元素个数是固定的,而组成链表的结点个数可按需要增减; 8 | 9 | (2)数组元素的存诸单元在数组定义时分配,链表结点的存储单元在程序执行时动态向系统申请: 10 | 11 | (3)数组中的元素顺序关系由元素在数组中的位置(即下标)确定,链表中的结点顺序关系由结点所包含的指针来体现。 12 | 13 | (4)对于不是固定长度的列表,用可能最大长度的数组来描述,会浪费许多内存空间。 14 | 15 | (5)对于元素的插人、删除操作非常频繁的列表处理场合,用数组表示列表也是不适宜的。若用链表实现,会使程序结构清晰,处理的方法也较为简便。 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 | 57 | - 可能浪费内存 58 | 59 | - 内存空间要求高,必须有足够的连续内存空间。 60 | 61 | - 链表的优点 62 | 63 | - 插入删除速度快 64 | 65 | - 内存利用率高,不会浪费内存 66 | 67 | - 大小没有固定,拓展很灵活。 68 | 69 | - 链表的缺点 70 | 71 | - 不能随机查找,必须从第一个开始遍历,查找效率低 72 | 73 | ### 文献:https://blog.csdn.net/weibo1230123/article/details/82011889 -------------------------------------------------------------------------------- /数据结构与算法/计数排序.md: -------------------------------------------------------------------------------- 1 | # 计数排序:空间换时间 2 | 3 | ```js 4 | function countingSort (arr) { 5 | if (arr.length < 2) return arr; 6 | 7 | const maxValue = Math.max(...arr); 8 | const counts = new Array(maxValue + 1); 9 | 10 | arr.forEach(element => { 11 | if (!counts[element]) { 12 | counts[element] = 0; 13 | } 14 | counts[element]++; 15 | }); 16 | 17 | let sortedIndex = 0; 18 | counts.forEach((element, i) => { 19 | while (element > 0) { 20 | arr[sortedIndex++] = i; 21 | element--; 22 | } 23 | }); 24 | 25 | return arr; 26 | } 27 | 28 | console.log('countingSort([6,5,4,3,2,1]):', countingSort([6,5,4,3,2,1])) 29 | ``` -------------------------------------------------------------------------------- /数据结构与算法/选择排序.md: -------------------------------------------------------------------------------- 1 | # 选择排序:找到数组中最小值放在第一位,然后再找数组中第二小值放在第二位,以此类推。 2 | 3 | ```js 4 | function selectionSort () { 5 | const arr = [6, 5, 4, 3, 2, 1]; 6 | console.log('排序前:', arr); 7 | const { length } = arr; 8 | let count = 0; 9 | let minIndex; 10 | for (let i = 0; i < length - 1; i++) { 11 | minIndex = i; 12 | for (let j = i + 1; j < length; j++) { 13 | if (arr[minIndex] > arr[j]) { 14 | count++; 15 | minIndex = j; 16 | } 17 | } 18 | if (minIndex !== i) { 19 | [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]]; 20 | } 21 | } 22 | console.log('循环次数:', count); 23 | console.log('排序前:', arr); 24 | } 25 | 26 | selectionSort(); // 循环次数 6 27 | ``` -------------------------------------------------------------------------------- /数据结构与算法/随机算法.md: -------------------------------------------------------------------------------- 1 | # 随机算法: 2 | 3 | ```js 4 | for (let i = arr.length - 1; i > 0; i--) { 5 | let randomIndex = Math.floor(Math.random() * (i + 1)); 6 | [arr[randomIndex], arr[i]] = [arr[i], arr[randomIndex]]; 7 | } 8 | ``` -------------------------------------------------------------------------------- /网络协议/GEI和POST有什么区别.md: -------------------------------------------------------------------------------- 1 | - get 是从指定的资源请求数据,post 是向指定的资源提交要处理的数据 2 | - get 请求可以被缓存,post 请求不会被缓存 3 | - get 请求传输的数据有长度限制,一般为 2048 字符,post 请求传输的数据没有大小限制 4 | - get 请求的数据一般追加在 URL 的末尾,post 请求的数据在 http 请求体中 5 | -------------------------------------------------------------------------------- /网络协议/HTTP与HTTPS的区别及实现方式.md: -------------------------------------------------------------------------------- 1 | # HTTP与HTTPS的区别及实现方式 2 | 1. HTTP 是超文本传输协议,信息是明文传输,存在安全风险的问题。HTTPS 则解决HTTP 不安全的缺陷,在 TCP 和 HTTP 之间加入了 SSL/TLS 安全协议,使得 3 | 报文能够加密传输。 4 | 2. HTTP :TCP 三次握手之后便可进行 HTTP 的报文传输。而 HTTPS 在 TCP三次握手之后,还需进行 SSL/TLS 的握手过程,才可进入加密报文传输。 5 | 3. HTTP 的端口号是 80,HTTPS 的端口号是 443。 6 | 4. HTTPS 协议需要向 CA(证书权威机构)申请数字证书,来保证服务器的身份是可信的。 7 | -------------------------------------------------------------------------------- /网络协议/HTTP版本.md: -------------------------------------------------------------------------------- 1 | - HTTP/0.9 2 | - 只支持 GET 请求方法 3 | - 只能发送 HTML 格式的文档 4 | - HTTP/1.0 5 | - 新增了请求头和响应头 6 | - 支持 GET、POST、HEAD 请求方法 7 | - 支持多种类型的文件 8 | - 提供了缓存机制(Expires 和 Last-Modified) 9 | - HTTP/1.1 10 | - 新增 OPTIONS、PUT、DELETE、TRACE、CONNECT 请求方法 11 | - 引入持久连接(keep-alive) 12 | - 缓存机制:在1.0基础上新增了新特性(Cache-Control 和 Etag) 13 | - 请求消息和响应消息都支持Host头域:在HTTP1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个IP地址。因此,Host头的引入就很有必要了。 14 | - HTTP/2.0 15 | - HTTP/3.0 16 | - 基于 UDP 传输协议(前四个版本是基于 TCP 传输协议) 17 | - QUIC协议:基于 UDP 实现了类似 TCP 的多路数据流、传输可靠性等功能 18 | - 实现了类似 TCP 的流量控制、传输可靠性的功能 19 | - 集成了 TLS 加密功能 20 | - 实现了 HTTP/2.0 中多路复用功能 21 | - 不同点是 QUIC 实现了在同一物理连接上有多个独立的逻辑数据流,实现了数据流的单独传输,解决了TCP中队头阻塞的问题 22 | - 实现了快速握手功能 23 | - QUIC 是基于 UDP 的,所以 QUIC 可以实现使用 0-RTT 或者 1-RTT 来建立连接 24 | - 目前没有广泛应用的原因 25 | - 服务器和浏览器都没有对 HTTP/3.0 提供比较完成的支持 26 | - 部署 HTTP/3.0 也存在非常大的问题,因为系统对 UDP 优化还不是很好 27 | - 中间设备僵化问题,这些设备对 UDP 的优化程序远低于 TCP 28 | 29 | - 文献 30 | - [六张图从HTTP/0.9进化到HTTP3.0](https://juejin.cn/post/6856036933723521032) 31 | - [HTTP协议几个版本的比较](https://www.jianshu.com/p/bcff7c252114) 32 | - [http协议各个版本](https://blog.csdn.net/qq_22238021/article/details/81197157) -------------------------------------------------------------------------------- /网络协议/HTTP状态码.md: -------------------------------------------------------------------------------- 1 | - 2XX 请求成功 2 | 3 | - 200 OK 请求成功 4 | - 204 No Content 请求成功,但没资源返回 5 | - 206 Partial Content 请求只返回一部分资源(响应报文中,设置Content-Range字段指定范围的实体内容) 6 | 7 | - 3XX 重定向(304灰常重要) 8 | 9 | - 301 Moved Permanently 永久性重定向,该状态码表示请求的资源已被分配了新的URI,以后应使用Location指定的URI,如果已经保存为书签的,这时应该更新书签。 10 | 11 | - 302 Found 临时性重定向,和301类似,但是表示资源是临时性移动,已移动资源对应的URI将来还可能发生改变,因此不需要更新书签。 12 | 13 | - 303 See Other 和302类似,但是标准明确规定客户端应使用get请求中Location字段指定的URI。 14 | 15 | - 304 Not Modified 是用来表示客户端所请求的资源和上次所请求时没有发生改变,这样服务端就不用重新发送资源的内容,从而减少了网络的负担;( 304跟重定向没任何关系) 16 | 17 | - 307 Temporary Redirect 相当于302,由于浏览器对于302标准并不遵守,因此定义307来代替302。post请求不会改变为get请求。 18 | 19 | - 4XX 客户端错误 20 | 21 | - 400 Bad Request 报文中存在语法错误 22 | - 401 Unauthrized 未经授权 23 | - 403 Forbidden 请求的资源被服务器拒绝访问 24 | - 404 Not Found 请求的资源找不到 25 | 26 | **ps: 401是要求验证、403是验证没通过** 27 | 28 | - 5XX 服务端错误 29 | 30 | - 500 Internal Server Error 后端出现bug或者某些临时的故障 31 | - 503 Service Unavailable 服务器暂时处于**超负载**或正在进行**停机维护** -------------------------------------------------------------------------------- /网络协议/HTTP缓存.md: -------------------------------------------------------------------------------- 1 | - 文献 2 | - [HTTP----HTTP缓存机制](https://juejin.cn/post/6844903517702848526) 3 | - [从前端角度理解缓存](https://juejin.cn/post/6844903762918637576) 4 | - [浏览器HTTP缓存机制](https://juejin.cn/post/6844903554587574285) -------------------------------------------------------------------------------- /网络协议/OSI模型TCPIP模型.md: -------------------------------------------------------------------------------- 1 | - OSI模型 2 | - 应用层 3 | - 表示层 4 | - 会话层 5 | - 传输层 6 | - 网络层 7 | - 数据链路层 8 | - 物理层 9 | - TCP/IP模型 10 | - 应用层 11 | - 传输层 12 | - 网络层 13 | - 数据链路层 14 | -------------------------------------------------------------------------------- /网络协议/TCP三次握手和四次挥手机制以及原因.md: -------------------------------------------------------------------------------- 1 | - 三次握手 2 | - 一开始,客户端和服务端都处于 `CLOSED` 状态。先是服务端主动监听某个端口,处于 `LISTEN` 状态 3 | - 第一次握手: 客户端将 `SYN ` 报文发送给服务端,表示向服务端发起连接,之后客户端处于(同步发送) `SYN-SENT` 状态。 4 | - 第二次握手:服务端收到客户端的 `SYN` 报文后,接着把 `SYN` 和 `ACK` 报文发给客户端,之后服务端处于(同步接收) `SYN-RCVD` 状态。 5 | - 第三次握手:客户端收到服务端报文后,还要向服务端回应最后一个应答报文,最后把`ACK` 报文发送给服务端,之后客户端处于 (建立)`ESTABLISHED` 状态。 6 | - 服务器收到客户端的应答报文后,也进入 (建立)`ESTABLISHED` 状态。 7 | - 四次挥手 8 | - 第一次挥手:客户端打算关闭连接,此时会发送一个 TCP 首部 `FIN` 标志位被置为 `1` 的报文,也即 `FIN` 报文,之后客户端进入 `FIN_WAIT_1` 状态。 9 | 10 | - 第二次挥手:服务端收到该报文后,就向客户端发送 `ACK` 应答报文,接着服务端进入 `CLOSED_WAIT` 状态。客户端收到服务端的 `ACK` 应答报文后,之后进入 `FIN_WAIT_2` 状态。 11 | - 第三次挥手:等待服务端处理完数据后,也向客户端发送 `FIN` 报文,之后服务端进入 `LAST_ACK` 状态。 12 | 13 | - 第四次挥手:客户端收到服务端的 `FIN` 报文后,回一个 `ACK` 应答报文,之后进入 `TIME_WAIT` 状态。 14 | 15 | 服务器收到了 `ACK` 应答报文后,就进入了 `CLOSE` 状态,至此服务端已经完成连接的关闭。 16 | 17 | 客户端在经过 `2MSL` 一段时间后,自动进入 `CLOSE` 状态,至此客户端也完成连接的关闭。 18 | - 建立连接为什么要三次握手?为什么不是两次? 19 | - 因为TCP是全双工通信,双方需要先确认彼此都可以发送和接收到对方的信息,确认完才可以进行数据传输; 20 | - 如果建立连接时两次握手的话,遇到网络堵塞时,Client 以为没有连接上,发送多次请求,而 Server 会为前面多次无效请求创建连接,造成资源浪费; 21 | - 为什么连接的时候是三次握手,关闭的时候却是四次握手? 22 | - 因为当 Server 端收到 Client 端的 SYN 连接请求报文后,可以直接发送 SYN+ACK 报文。(其中ACK 报文是用来应答的, SYN 报文是用来同步的) 23 | - 但是关闭连接时,当 Server 端收到 FIN 报文时,很可能并不会立即关闭 SOCKET,所以只能先回复一个 ACK 报文,告诉 CLient 端,“你发的 FIN 报文我收到了”。只有等到我 Server 端所有的报文都发送完了,我才能发送 FIN 报文,因此不能一起发送。所以需要四步握手。 24 | - 为什么 TIME_WAIT 状态需要经过2 MSL(最大报文段生存时间)才能到 CLOSE 状态? 25 | - 虽然按道理,四个报文都发送完毕,我们可以直接 进入 CLOSE 状态了,但是网络传输是不可靠的,有可能最后一个 ACK 丢失了。所以TIME_WAIT状态就是用来重发可能丢失的 ACK 报文。 26 | -------------------------------------------------------------------------------- /网络协议/TCP和UDP有什么区别.md: -------------------------------------------------------------------------------- 1 | 1. TCP是面向连接的,udp是无连接的即发送数据前不需要先建立链接。 2 | 3 | 2. TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付。 并且因为tcp可靠,面向连接,不会丢失数据因此适合大数据量的交换。 4 | 5 | 3. TCP是面向字节流,UDP面向报文,并且网络出现拥塞不会使得发送速率降低(因此会出现丢包,对实时的应用比如IP电话和视频会议等)。 6 | 7 | 4. TCP只能是1对1的,UDP支持1对1,1对多。 8 | 9 | 5. TCP的首部较大为20字节,而UDP只有8字节。 10 | 11 | 6. TCP是面向连接的可靠性传输,而UDP是不可靠的。 -------------------------------------------------------------------------------- /网络协议/TCP如何保证可靠性.md: -------------------------------------------------------------------------------- 1 | - 校验和 2 | - 序列号 3 | - 确认应答 4 | - 超时重传 5 | - 连接管理 6 | - 流量控制 7 | - 拥塞控制 8 | - 详细讲解文献:[网络基础:TCP协议-如何保证传输可靠性](https://blog.csdn.net/liuchenxia8/article/details/80428157) 9 | -------------------------------------------------------------------------------- /网络协议/TCP拥塞控制.md: -------------------------------------------------------------------------------- 1 | - 拥塞控制的方法 2 | - 慢启动 3 | 1. 慢开始不是指 cwnd 的增长速度慢(指数增长),而是指 TCP 开始发送设置 cwnd=1。 4 | 2. 思路:不要一开始就发送大量的数据,先探测一下网络的拥塞程度,也就是说由小到大逐渐增加拥塞窗口的大小。这里用报文段的个数的拥塞窗口大小举例说明慢开始算法,实时拥塞窗口大小是以字节为单位的。 5 | 3. 为了防止 cwnd 增长过大引起网络拥塞,设置一个慢开始门限(ssthresh 状态变量) 6 | 当 cnwd<ssthresh,使用慢开始算法 7 | 当 cnwd=ssthresh,既可使用慢开始算法,也可以使用拥塞避免算法 8 | 当 cnwd>ssthresh,使用拥塞避免算法 9 | 10 | - 拥塞避免 11 | 1. 拥塞避免并非完全能够避免拥塞,是说在拥塞避免阶段将拥塞窗口控制为按线性规律增长,使网络比较不容易出现拥塞。 12 | 2. 思路:让拥塞窗口 cwnd 缓慢地增大,即每经过一个往返时间 RTT 就把发送方的拥塞控制窗口加一。 13 | 14 | 无论是在慢开始阶段还是在拥塞避免阶段,只要发送方判断网络出现拥塞(其根据就是没有收到确认,虽然没有收到确认可能是其他原因的分组丢失,但是因为无法判定,所以都当做拥塞来处理),就把慢开始门限设置为出现拥塞时的发送窗口大小的一半。然后把拥塞窗口设置为 1,执行慢开始算法。 15 | 16 | 17 | - 快重传 18 | 1. 快重传要求接收方在收到一个失序的报文段后就立即发出重复确认(为的是使发送方及早知道有报文段没有到达对方)而不要等到自己发送数据时捎带确认。快重传算法规定,发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段,而不必继续等待设置的重传计时器时间到期。 19 | 2. 由于不需要等待设置的重传计时器到期,能尽早重传未被确认的报文段,能提高整个网络的吞吐量。 20 | 21 | - 快恢复 22 | 1. 当发送方连续收到三个重复确认时,就执行“乘法减小”算法,把 ssthresh 门限减半。但是接下去并不执行慢开始算法。 23 | 2. 考虑到如果网络出现拥塞的话就不会收到好几个重复的确认,所以发送方现在认为网络可能没有出现拥塞。所以此时不执行慢开始算法,而是将 cwnd 设置为 ssthresh 的大小,然后执行拥塞避免算法。 -------------------------------------------------------------------------------- /网络协议/TCP流量控制.md: -------------------------------------------------------------------------------- 1 | - **流量控制**就是让发送方的发送速率不要太快,要让接收方来得及接收; 2 | - 接收方会通过 **接收窗口(rwnd)** 告诉发送方,它还能接收多少字节;也就是说,**发送方的发送窗口不能超过接收方给出的接收窗口的数值**; 3 | - **特殊情况**:接收方给发送方发送零窗口的报文段,过了一会缓存又有了一些存储空间。于是接收方向放送方发送了 rwnd=400 的报文段。然而这个报文段在传送过程中丢失了。放送方一直等待接收方发送非零窗口的通知,而接收方也一直等待发送方发送的数据。如果没有其他措施,这种互相等待的死锁局面将一直延续下去; 4 | - 为了解决这个问题,当发送方收到零窗口的通知后,就启动**持续计时器**,每隔一段时间就发送一个**零窗口探测报文段**(仅携带1字节的数据),对方就在确认这个探测报文段后给出现在的窗口值。 5 | -------------------------------------------------------------------------------- /网络协议/流量控制和拥塞控制的区别.md: -------------------------------------------------------------------------------- 1 | - 流量控制指的是点对点通信量的控制,是个端到端的问题(接收端控制发送端)。流量控制所要做的就是控制发送端发送数据的速率,以便使接收端来得及接收; 2 | - 拥塞控制考虑的是全局性的过程,拥塞控制所要做的都有一个前提,就是**网络能承受现有的网络负荷**,也就是防止过多的数据注入到网络中,这样可以使网络中的路由器或者链路不至于过载。 3 | -------------------------------------------------------------------------------- /设计模式/单例模式.md: -------------------------------------------------------------------------------- 1 | # 单例模式 2 | 单例模式的定义是:保证一个类仅有一个实例,并提供一个访问它的全局访问点 3 | 当我们单击登录按钮的时候,页面中会出现一个登录浮窗,而这个登录浮窗是唯一的,无论单击多少次登录按钮,这个浮窗只会被创建一次,那么这个登录浮窗就适合用单例模式来创建。 4 | 5 | ## 实现 6 | ### 惰性单例 7 | ```js 8 | const singleton = function (fn) { 9 | let result; 10 | 11 | return function () { 12 | if (!result) { 13 | result = fn.apply(this, arguments); 14 | } 15 | return result; 16 | } 17 | } 18 | ``` 19 | ## 应用 20 | ```js 21 | // 创建登录浮窗 22 | const createLoginLayer = function(){ 23 | const div = document.createElement( 'div' ); 24 | div.innerHTML = '我是登录浮窗'; 25 | div.style.display = 'none'; 26 | document.body.appendChild( div ); 27 | return div; 28 | }; 29 | // 通过单例模式确保浮窗仅被创建一次 30 | const createSingleLoginLayer = singleton( createLoginLayer ); 31 | 32 | document.getElementById( 'loginBtn' ).onclick = function(){ 33 | const loginLayer = createSingleLoginLayer(); 34 | loginLayer.style.display = 'block'; 35 | }; 36 | ``` -------------------------------------------------------------------------------- /设计模式/策略模式.md: -------------------------------------------------------------------------------- 1 | # 策略模式 2 | 3 | ## 策略模式的优缺点 4 | 5 | ## 表单校验 - 案例 6 | 7 | ```html 8 | 9 | 10 | 11 |
12 | 请输入用户名: 13 | 请输入密码: 14 | 请输入手机号码: 15 | 16 |
17 | 94 | 95 | 96 | 97 | ``` -------------------------------------------------------------------------------- /读书笔记:HTTP图解/01 了解web及网络基础.md: -------------------------------------------------------------------------------- 1 | # 01 了解web及网络基础 2 | 3 | ## 1.1 使用HTTP协议访问Web 4 | Web使用一种名为HTTP(HyperText Transfer Protocol,超文本传输协议)的协议作为规范,完成从客户端到服务端等一系列运作流程。而协议是只规则的约定。可以说,Web是建立在HTTP协议上通信的。 5 | ## 1.2 HTTP的诞生 6 | ### 1.2.1 为知识共享而规划Web 7 | CERN(欧洲核子研究组织)的蒂姆·巴纳斯-李(Tim Berners-Lee)博士提出了一种能让远隔两地的研究者们共享知识的设想。 8 | 最初设想的基本理念是:借助多文档之间相互关联形成的超文本,连成可互相参阅的WWW(World Wide Web,万维网)。 9 | 现在已提出了3项WWW构建技术,分别是: 10 | - 把SGML(Standard Generalized Markup Language)作为页面的文本标记语言HTML(HyperText Markup Language); 11 | - 作为文档传递协议的HTTP; 12 | - 指定文档所在地址的URL(Uniform Resource Locator, 统一资源定位符)。 13 | ### 1.2.2 Web成长时代 14 | - 日本第一个主页 15 | - HTML1.0 16 | - NCSA Mosaic bounce page 17 | - The NCSA HTTPd Home Page(存档) 18 | ### 1.2.3 驻足不前的HTTP 19 | HTTP有三个版本: 20 | - HTTP/0.9 21 | - HTTP/1.0 22 | RFC1945 - HyperText Transfer Protocol -- HTTP/1.0 23 | - HTTP/1.1 24 | RFC2616 - HyperText Transfer Protocol -- HTTP/1.1 25 | 作为Web文档传输协议的HTTP,它的版本几乎没有更新。新一代HTTP/2.0正在制订中,但要达到较高的覆盖率,仍需假以时日。 26 | 当年HTTP协议出现主要为了解决文本传输问题。由于协议本身非常简单,于是再此基础上设想了很多应用方法并投入了实际使用。现在HTTP协议已经长处了Web 这个框架的局限,被运用到了各种场景里。 27 | ## 1.3 网络基础TCP/IP 28 | 通常使用的网络(包括互联网)是在TCP/IP协议族的基础上运作的。而HTTP属于它内部的子集。 29 | ### 1.3.1 TCP/IP协议族 30 | 不同的硬件、操作系统之间的通信,所有的这一切都需要一种规则。而我们就把这种规则成为协议(protocol)。 31 | TCP/IP是互联网相关的各类协议族的总称:TCP、IP、HTTP、FTP、DNS、UDP、PPPoE、SNMP、IEEE 802.3、FDDI、ICMP 32 | ### 1.3.2 TCP/IP的分层管理 33 | TCP/IP协议族按层次分别分为以下4层:应用层、传输层、网络层和数据链路层。 34 | 把TCP/IP层次化是有好处的,比如,如果互联网只由一个协议统筹,某个地方需要改变设计时,就必须把所有部分整体换掉。而分层之后只需把变动的层替换掉即可。把各层之间的接口部分规划好之后,每个层次内部的设计就能够自由改动了。 35 | TCP/IP协议族各层的作用如下: 36 | - 应用层 37 | 应用层决定了向用户提供应用服务时通信的活动。 38 | TCP/IP协议族内预存了各类通用的应用服务。比如:FTP、DNS和HTTP 39 | - 传输层 40 | 传输层对上层应用层,提供处于网络连接中的两台计算机之间的数据传输。 41 | 在传输层有两个性质不同的协议:TCP和UDP。 42 | - 网络层(又名为互连层) 43 | 网络层用来处理在网络上流动的数据包。数据包是网络传输的最小数据单位。该层规定了通过怎样的路径(所谓的传输路线)到达对方计算机,并把数据包传送给对方。 44 | 与对方计算机之间通过多台计算机或网络设备进行传输时,网络层所起的作用就是在众多的选项内选择一条传输路线。 45 | - 链路层(又名数据链路层,网络接口层) 46 | 用来处理连接网络的硬件部分。包括控制操作系统、硬件的设备驱动、NIC(Network Interface Card,网络适配器,即网卡),及光纤等物理可见部分(包括连接器等以切传输媒介)。硬件上的范畴均在链路层的作用范围之内。 47 | ### 1.3.3 TCP/IP通信传输流 48 | ![图片](./img/1.3.3.png) 49 | 利用 TCP/IP 协议族进行网络通信时,会通过分层顺序与对方进行通 信。发送端从应用层往下走,接收端则往应用层往上走。 50 | 我们用 HTTP 举例来说明,首先作为发送端的客户端在应用层 (HTTP 协议)发出一个想看某个 Web 页面的 HTTP 请求。 51 | 接着,为了传输方便,在传输层(TCP 协议)把从应用层处收到的数 据(HTTP 请求报文)进行分割,并在各个报文上打上标记序号及端 口号后转发给网络层。 52 | 在网络层(IP 协议),增加作为通信目的地的 MAC 地址后转发给链 路层。这样一来,发往网络的通信请求就准备齐全了。 53 | 接收端的服务器在链路层接收到数据,按序往上层发送,一直到应用 层。当传输到应用层,才能算真正接收到由客户端发送过来的 HTTP 请求。 54 | 发送端在层与层之间传输数据时,每经过一层时必定会被打上一个该 层所属的首部信息。反之,接收端在层与层传输数据时,每经过一层 时会把对应的首部消去。 55 | 这种把数据信息包装起来的做法称为封装(encapsulate)。 56 | ## 1.4 与HTTP关系密切的协议:IP、TCP和DNS 57 | ### 1.4.1 负责传输的IP协议 58 | ![图片](./img/1.4.1.png) 59 | ### 1.4.2 确保可靠性的TCP协议 60 | ![图片](./img/1.4.2.png) 61 | ## 1.5 负责域名解析的DNS服务 62 | 用户通常使用主机名或域名来访问对方的计算机,而不是直接通过 IP 地址访问。因为与 IP 地址的一组纯数字相比,用字母配合数字的表 示形式来指定计算机名更符合人类的记忆习惯。 63 | 但要让计算机去理解名称,相对而言就变得困难了。因为计算机更擅 长处理一长串数字。 64 | 为了解决上述的问题,DNS 服务应运而生。DNS 协议提供通过域名 查找 IP 地址,或逆向从 IP 地址反查域名的服务。 65 | ![图片](./img/1.5.png) 66 | ## 1.6 各种协议与HTTP协议的关系 67 | ![图片](./img/1.6.png) 68 | ## 1.7 URI和URL 69 | -------------------------------------------------------------------------------- /读书笔记:HTTP图解/img/1.3.3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenjz08/FE-SelfCheckList/e8fb6f83951f19e077b2a2a264b63bad21b5b447/读书笔记:HTTP图解/img/1.3.3.png -------------------------------------------------------------------------------- /读书笔记:HTTP图解/img/1.4.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenjz08/FE-SelfCheckList/e8fb6f83951f19e077b2a2a264b63bad21b5b447/读书笔记:HTTP图解/img/1.4.1.png -------------------------------------------------------------------------------- /读书笔记:HTTP图解/img/1.4.2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenjz08/FE-SelfCheckList/e8fb6f83951f19e077b2a2a264b63bad21b5b447/读书笔记:HTTP图解/img/1.4.2.png -------------------------------------------------------------------------------- /读书笔记:HTTP图解/img/1.5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenjz08/FE-SelfCheckList/e8fb6f83951f19e077b2a2a264b63bad21b5b447/读书笔记:HTTP图解/img/1.5.png -------------------------------------------------------------------------------- /读书笔记:HTTP图解/img/1.6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenjz08/FE-SelfCheckList/e8fb6f83951f19e077b2a2a264b63bad21b5b447/读书笔记:HTTP图解/img/1.6.png --------------------------------------------------------------------------------