能够理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)。javascript
浏览器为了可以使得JS内部(macro)task与DOM任务可以有序的执行,会在一个(macro)task执行结束后, 在下一个(macro)task 执行开始前,对页面进行从新渲染,流程以下:css
(macro)task->渲染->(macro)task->... (macro)task主要包含:script(总体代码)、setTimeout、setInterval、I/O、UI交互事件、postMessage、MessageChannel、setImmediate(Node.js 环境)html
能够理解是在当前 task 执行结束后当即执行的任务。也就是说,在当前task任务后,下一个task以前,在渲染以前。前端
因此它的响应速度相比setTimeout(setTimeout是task)会更快,由于无需等渲染。也就是说,在某一个macrotask执行完后,就会将在它执行期间产生的全部microtask都执行完毕(在渲染前)。html5
microtask主要包含:Promise.then、MutaionObserver、process.nextTick(Node.js 环境)java
在事件循环中,每进行一次循环操做称为 tick,每一次 tick 的任务处理模型是比较复杂的,但关键步骤以下:android
执行一个宏任务(栈中没有就从事件队列中获取) 执行过程当中若是遇到微任务,就将它添加到微任务的任务队列中 宏任务执行完毕后,当即执行当前微任务队列中的全部微任务(依次执行) 当前宏任务执行完毕,开始检查渲染,而后GUI线程接管渲染 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)ios
dom tree + css tree = render tree
完了之后回流 肯定每一个元素的几何位置 重绘肯定每一个元素的像素nginx
节点的几何属性发生改变或者因为样式发生改变而不会影响布局的,称为重绘, 例如outline, visibility, color、background-color等,重绘的代价是高昂的,由于浏览器必须验证DOM树上其余节点元素的可见性。web
回流是布局或者几何属性须要改变就称为回流。回流是影响浏览器性能的关键因素,由于其变化涉及到部分页面(或是整个页面)的布局更新。 一个元素的回流可能会致使了其全部子元素以及DOM中紧随其后的节点、祖先节点元素的随后的回流。 回流一定会发生重绘,重绘不必定会引起回流。
<meta name='viewport' content='width=device-width,initial-scale=0.5'>
复制代码
Vue2.x:使用Object.defineProperty实现双向绑定,其实现的功能是数据劫持,在观察者订阅者模式当中是观察者的角色 此外,当观察者发现变化时能够告知manager,manager告知订阅者,订阅者对相应的作出反应(compile)。 p.s.:对于某些数组操做失灵,其采用的方法是,改写某个数组的原型链,使其指向改造过的方法原型。 Vue3.x:使用Proxy来代替Object.defineProperty,优势总结就是,劫持能力比前者强大,能够监控整个对象,并且不会对 数组的某些操做失灵。
四个关键方法:pathch函数分为两种状况初始化时,将全部的虚拟dom实现再插入到container上
第二次当虚拟dom改变时,会使用diff算法找出不一样,使用新dom替换旧dom(同层比较,不移动
说到渲染,有个h函数,做用是将虚拟dom渲染成为真实dom,本质是一个递归函数,当不是叶子节点时会不断的
向下调用h函数,参数有标签名,属性,子节点;叶子节点的调用是,标签名,属性,text
同层递归顺序比较 只比较同层的dom结构,比较是依次深度递归比较的,顺序是先看此节点还在不在,再看节点属性是否改变,再看文本内容,节点是否被替换
能够配置默认的url、transformRequest、transformResponse对请求体和响应体作对应的处理、headers自定义
timeout自定义、onUploadProgres onDownloadProgress、proxy设置代理、cancelToken用于取消响应
axios.get('/user/12345', {
cancelToken: new CancelToken(function executor(c) {
// executor 函数接收一个 cancel 函数做为参数
cancel = c;
})
});
// 取消请求
cancel();
复制代码
事件绑定:w3c事件委托 addEventListener,removeEventListener 第三个参数是false则为冒泡 true是捕获
事件捕获与冒泡:捕获是先触发父元素再触发子元素 阻止冒泡:e.stopPropagation()、e.cancelBubble = true 阻止默认事件:e.preventDefault()、e.returnValue = false; 事件委托:将事件绑定在父元素上能够减小绑定的数量以及动态增减子元素都无所谓 好比 focus、blur 之类的事件自己没有事件冒泡机制,因此没法委托; mousemove、mouseout 这样的事件,虽然有事件冒泡,可是只能不断经过位置去计算定位,对性能消耗高,所以也是不适合于事件委托的;
任何能够输入的地方都有可能引发,包括URL XSS 常见的注入方法:
防护:客户端求情参数:包括用户输入,url参数、post参数。
csrf是让用户住不知情的状况下,冒用其身份发起了一个请求
在后台接收到请求的时候,能够经过请求头中的Referer请求头来判断请求来源 一般来讲 CSRF 是由 XSS 实现的,CSRF 时常也被称为 XSRF(CSRF 实现的方式还能够是直接经过命令行发起请求等)。 本质上讲,XSS 是代码注入问题,CSRF 是 HTTP 问题。 XSS 是内容没有过滤致使浏览器将攻击者的输入当代码执行。CSRF 则是由于浏览器在发送 HTTP 请求时候自动带上 cookie,而通常网站的 session 都存在 cookie里面(Token验证能够避免)。
——因为IEEE 754标准的缘由 解决办法能够将结果乘1000再除1000进行一个截尾操做 parseFloat((0.1 + 0.2).toFixed(10))
方法仅仅返回匹配指定选择器的第一个元素。若是你须要返回全部的元素,请使用 querySelectorAll() 方法替代。 因为querySelector是按css规范来实现的,因此它传入的字符串中第一个字符不能是数字. 最后再根据查询的资料总结一下: query选择符选出来的元素及元素数组是静态的,而getElement这种方法选出的元素是动态的。 静态的就是说选出的全部元素的数组,不会随着文档操做而改变. 在使用的时候getElement这种方法性能比较好,query选择符则比较方便.
Boolean([]) //true
Boolean(undefined) // false
Boolean(null) // false
Boolean(0) // false
Boolean(NaN) // false
Boolean('') // false
Number([]) // 0
Number({}) // NaN。
复制代码
前者支持动态导入,也就是 require(${path}/xx.js),后者目前不支持,可是已有提案
前者是同步导入,由于用于服务端,文件都在本地,同步导入即便卡住主线程影响也不大。然后者是异步导入,由于用于浏览器,须要下载文件,若是也采用同步导入会对渲染有很大影响
前者在导出时都是值拷贝,就算导出的值变了,导入的值也不会改变,因此若是想更新值,必须从新导入一次。可是后者采用实时绑定的方式,导入导出的值都指向同一个内存地址,因此导入值会跟随导出值变化
后者会编译成 require/exports 来执行的
--浏览器搜索自身的DNS缓存: 首先浏览器会去搜索自身的DNS缓存,看缓存有没有过时,过时的话缓存的解析就结束了(chrome缓存的时间只有一分钟,查看chrome的缓存可打开:chrome:/net-internals/#dns )。 --搜索操做系统自身的DNS缓存: 若是浏览器没有找到缓存或者缓存过时失效,浏览器就会搜索操做系统自身的缓存,没有找到或者失效,解析结束(操做系统的缓存:window系统是一天,mac系统严格根DNS协议中的TTL)。 --读取本地的hosts文件: 若操做系统的缓存也没有找到或失效,浏览器就会去读取本地的hosts文件(Hosts文件也能够创建域名到IP地址的绑定关系,能够经过编辑Hosts文件来达到名称解析的的。 例如,咱们须要屏蔽某个域名时,就能够将其地址指向一个不存在IP地址,以达到屏蔽的效果)。 --浏览器发起一个DNS的系统调用: hosts中没有找到对应的配置项的话,浏览器发起一个DNS的调用(向本地主控DNS服务,通常来讲是你的运营商提供的)。
--经过 DNS 查询 IP 地址的操做称为域名解析,负责执行解析这一操做的就叫解析器。 解析器其实是一段程序,它包含在操做系统的 Socket 库中 --调用解析器后,解析器会向 DNS 服务器(运营商提供的)发送查询消息。 --运营商服务会先查找自身缓存找到对应条目,没有过时,解析成功,若没找到对应条目,主控服务器会代替浏览器发起一个迭代的DNS解析的请求,先查找根域的), 运营商服务器拿到域名的IP,返回给操做系统的内核,同时缓存在了本身的缓存区,操做系统内核从DNS服务商拿来的IP地址返回给浏览器。 --浏览器再向 Web 服务器发送消息时,只要从该内存地址取出 IP地址,将它与 HTTP 请求消息一块儿交给操做系统 .
--首先运营商服务从已经配置好的信息中拿到根域名的IP地址(这里假设根域只有一个,实际是想13个根域发起请求),而后像根域发起请求群问:"请问http:/www.lab.glasscom.com的IP地址是多少?",根域名查询记录数据后没有找到,回答:"我不知道它的IP地址,不过我知道.com的权威服务器(ns)的地址,它xxx.xxx.xxx.xxx,你去问它吧"。运营商服务运营商服务拿到.com的IP地址,根据IP地址发起另外一个请求去询问.com服务器问:"请问 http:/www.lab.glasscom.com的ns的IP地址是多少?",.com域服务器查找自身记录数据后回答:“我不知道,我只知道.http://glasscom.com的IP地址”。 以此类推,只要重复前面的步骤,就能够顺藤摸瓜找到目标DNS服务器,只要向目标DNS 服务器发送查询消息,就可以获得咱们须要的答案,也就是 http:/www.lab.glasscom.com 的 IP 地址了。
--而HTTP传输时延对web的访问速度的影响很大,在绝大多数状况下是起决定性做用的,这是由TCP/IP协议的一些特色决定的。物理层上的缘由是光速有限、信道有限,协议上的缘由有丢包、慢启动、拥塞控制等。 要提升访问速度,最简单的作法固然就是多设置几个服务器,让终端用户离服务器“更近”。典型的例子是各种下载网站在不一样地域不一样运营商设置镜像站,或者是像Google那样设置多个数据中心。可是多设几个服务器的问题也很多,一是多地部署时的困难,二是一致性无法保障,三则是管理困难、成本很高。实际上,在排除多地容灾等特殊需求的状况下,对大多数公司这种作法是不太可取的。固然,这种方案真正作好了,甚至是比后续所说的使用CDN要好的。
CDN是一种公共服务,他自己有不少台位于不一样地域、接入不一样运营商的服务器,而所谓的使用CDN实质上就是让CDN做为网站的门面,用户访问到的是CDN服务器,而不是直接访问到网站。因为CDN内部对TCP的优化、对静态资源的缓存、预取,加上用户访问CDN时,会被智能地分配到最近的节点,下降大量延迟,让访问速度能够获得很大提高。
--原理:CDN作了两件事,一是让用户访问最近的节点,二是从缓存或者源站获取资源 CDN有个源站的概念,源站就是提供内容的站点(网站的真实服务器), 从源站取内容的过程叫作回源。 1)、用户向浏览器提供要访问的域名; 2)、浏览器调用域名解析库对域名进行解析,因为CDN对域名解析过程进行了调整,因此解析函数库通常获得的是该域名对应的CNAME记录,为了获得实际IP地址, 浏览器须要再次对得到的CNAME域名进行解析以获得实际的IP地址;在此过程当中,使用的全局负载均衡DNS解析, 如根据地理位置信息解析对应的IP地址,使得用户能就近访问。 3)、这次解析获得CDN缓存服务器的IP地址,浏览器在获得实际的IP地址之后,向缓存服务器发出访问请求; 4)、缓存服务器根据浏览器提供的要访问的域名,经过Cache内部专用DNS解析获得此域名的实际IP地址,再由缓存服务器向此实际IP地址提交访问请求; 5)、缓存服务器从实际IP地址得获得内容之后,一方面在本地进行保存,以备之后使用,另外一方面把获取的数据返回给客户端,完成数据服务过程; 6)、客户端获得由缓存服务器返回的数据之后显示出来并完成整个浏览的数据请求过程。 经过以上的分析咱们能够获得,为了实现既要对普通用户透明(即加入缓存之后用户客户端无需进行任何设置,直接使用被加速网站原有的域名便可访问, 又要在为指定的网站提供加速服务的同时下降对ICP的影响,只要修改整个访问过程当中的域名解析部分,以实现透明的加速服务。
ES6 新增的一种新的数据结构,相似于数组,但成员是惟一且无序的,没有重复的值。 Set 对象容许你储存任何类型的惟一值,不管是原始值或者是对象引用。 向 Set 加入值的时候,不会发生类型转换,因此5和"5"是两个不一样的值。Set 内部判断两个值是否不一样,使用的算法叫作“Same-value-zero equality”, 它相似于精确相等运算符(===),主要的区别是NaN等于自身,而精确相等运算符认为NaN不等于自身。 add(value):新增,至关于 array里的push
delete(value):存在即删除集合中value
has(value):判断集合中是否存在 value
clear():清空集合
Array.from 方法能够将 Set 结构转为数组
遍历方法(遍历顺序为插入顺序) keys():返回一个包含集合中全部键的迭代器
values():返回一个包含集合中全部值得迭代器
entries():返回一个包含Set对象中全部元素得键值对迭代器
forEach(callbackFn, thisArg):用于对集合成员执行callbackFn操做,若是提供了 thisArg 参数,回调中的this会是这个参数,没有返回值
WeakSet 与 Set 的区别:
WeakSet 只能储存对象引用,不能存放值,而 Set 对象均可以 WeakSet 对象中储存的对象值都是被弱引用的,即垃圾回收机制不考虑 WeakSet 对该对象的应用,若是没有其余的变量或属性引用这个对象值, 则这个对象将会被垃圾回收掉(不考虑该对象还存在于 WeakSet 中),因此,WeakSet 对象里有多少个成员元素,取决于垃圾回收机制有没有运行, 运行先后成员个数可能不一致,遍历结束以后,有的成员可能取不到了(被垃圾回收了),WeakSet 对象是没法被遍历的(ES6 规定 WeakSet 不可遍历), 也没有办法拿到它包含的全部元素
集合 与 字典 的区别:
共同点:集合、字典 能够储存不重复的值 不一样点:集合 是以 [value, value]的形式储存元素,字典 是以 [key, value] 的形式储存
const m = new Map()
const o = {p: 'haha'}
m.set(o, 'content')
m.get(o) // content
m.has(o) // true
m.delete(o) // true
m.has(o) // false
复制代码
set(key, value):向字典中添加新元素 get(key):经过键查找特定的数值并返回 has(key):判断字典中是否存在键key delete(key):经过键 key 从字典中移除对应的数据 clear():将这个字典中的全部元素删除
Keys():将字典中包含的全部键名以迭代器形式返回 values():将字典中包含的全部数值以迭代器形式返回 entries():返回全部成员的迭代器 forEach():遍历字典的全部成员
const map = new Map([[1, 1], [2, 2], [3, 3]])
console.log([...map]) // [[1, 1], [2, 2], [3, 3]]
复制代码
const map = new Map([[1, 1], [2, 2], [3, 3]])
console.log(map) // Map {1 => 1, 2 => 2, 3 => 3}
复制代码
由于 Object 的键名都为字符串,而Map 的键名为对象,因此转换的时候会把非字符串键名转换为字符串键名。
function mapToObj(map) {
let obj = Object.create(null)
for (let [key, value] of map) {
obj[key] = value
}
return obj
}
const map = new Map().set('name', 'An').set('des', 'JS')
mapToObj(map)
复制代码
function objToMap(map) {
let map = new Map()
for (let key of Object.keys(obj)) {
map.set(key, obj[key])
}
return map
}
objToMap({'name': 'An', 'des': 'JS'})
复制代码
function mapToJson(map) {
return JSON.stringify([...map])
}
let map = new Map().set('name', 'An').set('des', 'JS')
mapToJson(map) // [["name","An"],["des","JS"]]
复制代码
function jsonToMap(jsonStr) {
return objToMap(JSON.parse(jsonStr));
}
jsonToStrMap('{"name": "An", "des": "JS"}')
复制代码
WeakMap 对象是一组键值对的集合,其中的键是弱引用,其中,键必须是对象,而值能够是任意。
注意,WeakMap 弱引用的只是键名,而不是键值。键值依然是正常引用。
WeakSet 中,每一个键对本身所引用对象的引用都是弱引用,在没有其余引用和该键引用同一对象,这个对象将会被垃圾回收(相应的key则变成无效的),因此,WeakSet 的 key 是不可枚举的。
has(key):判断是否有 key 关联对象 get(key):返回key关联对象(没有则则返回 undefined) set(key):设置一组key关联对象 delete(key):移除 key 的关联对象
成员惟1、无序且不重复 [value, value],键值与键名是一致的(或者说只有键值,没有键名) 能够遍历,方法有:add、delete、has
成员都是对象 成员都是弱引用,能够被垃圾回收机制回收,能够用来保存DOM节点,不容易形成内存泄漏 不能遍历,方法有add、delete、has
本质上是键值对的集合,相似集合 能够遍历,方法不少能够跟各类数据格式转换
只接受对象最为键名(null除外),不接受其余类型的值做为键名 键名是弱引用,键值能够是任意的,键名所指向的对象能够被垃圾回收,此时键名是无效的 不能遍历,方法有get、set、has、delete
// ES6
class Super {}
class Sub extends Super {}
const sub = new Sub();
Sub.__proto__ === Super;
// ES5
function Super() {}
function Sub() {}
Sub.prototype = new Super();
Sub.prototype.constructor = Sub;
var sub = new Sub();
Sub.__proto__ === Function.prototype;
复制代码
function MyES5Array() {
Array.call(this, arguments);
}
// it's useless
const arrayES5 = new MyES5Array(3); // arrayES5: MyES5Array {}
class MyES6Array extends Array {}
// it's ok
const arrayES6 = new MyES6Array(3); // arrayES6: MyES6Array(3) []
复制代码
负责收集并维护由全部声明的标识符(变量、函数)组成的一系列查询,并实施一套很是严格的规则,肯定当前执行的代码对这些标识符的访问权限。
L和R分别表明一个赋值操做的左侧和右侧,当变量出如今赋值操做的左侧时进行LHS查询,出如今赋值操做的非左侧时进行RHS查询。
LHS查询(左侧):找到变量的容器自己,而后对其赋值 RHS查询(非左侧):查找某个变量的值,能够理解为 retrieve his source value,即取到它的源值
ReferenceError和做用域判别失败相关,TypeError表示做用域判别成功了,可是对结果的操做是非法或不合理的。
RHS查询在做用域链中搜索不到所需的变量,引擎会抛出ReferenceError异常。 非严格模式下,LHS查询在做用域链中搜索不到所需的变量,全局做用域中会建立一个具备该名称的变量并返还给引擎。 严格模式下(ES5开始,禁止自动或隐式地建立全局变量),LHS查询失败会抛出ReferenceError异常 在RHS查询成功状况下,对变量进行不合理的操做,引擎会抛出TypeError异常。(好比对非函数类型的值进行函数调用,或者引用null或undefined类型的值中的属性)
将内部函数传递到所在的词法做用域之外,它都会持有对原始定义做用域的引用,不管在何处执行这个函数都会使用闭包。
app.use()——挂载中间件 在app.listen()方法中compose函数调用了中间件对其进行处理,以后就是this.handleRequest
存储:以数组形式存储中间件。 状态管理:全部的状态变动,都交给ctx对象,无需跨中间件传递参数。 流程控制:以递归的方式进行中间件的执行,将下一个中间件的执行权交给正在执行的中间件,即洋葱圈模型。 异步方案:用Promise包裹中间件的返回结果,以支持在上一个中间件内部实现Await逻辑。
在 HTTP/1 中,每次请求都会创建一次TCP链接,也就是咱们常说的3次握手4次挥手,这在一次请求过程当中占用了至关长的时间,即便开启了 Keep-Alive ,解决了屡次链接的问题,可是依然有两个效率上的问题:
第一个:串行的文件传输。当请求a文件时,b文件只能等待,等待a链接到服务器、服务器处理文件、服务器返回文件,这三个步骤。咱们假设这三步用时都是1秒,那么a文件用时为3秒,b文件传输完成用时为6秒,依此类推。(注:此项计算有一个前提条件,就是浏览器和服务器是单通道传输) 第二个:链接数过多。咱们假设Apache设置了最大并发数为300,由于浏览器限制,浏览器发起的最大请求数为6(Chrome),也就是服务器能承载的最高并发为50,当第51我的访问时,就须要等待前面某个请求处理完成。
HTTP2采用二进制格式传输,取代了HTTP1.x的文本格式,二进制格式解析更高效。 多路复用代替了HTTP1.x的序列和阻塞机制,全部的相同域名请求都经过同一个TCP链接并发完成。在HTTP1.x中,并发多个请求须要多个TCP链接,浏览器为了控制资源会有6-8个TCP链接都限制。 HTTP2中 同域名下全部通讯都在单个链接上完成,消除了因多个 TCP 链接而带来的延时和内存消耗。 单个链接上能够并行交错的请求和响应,之间互不干扰
REST即Representational State Transfer的缩写,可译为"表现层状态转化”。REST最大的几个特色为:资源、统一接口、URI和无状态。
即在数据发送前将数据进行哈希或使用公钥加密。若是数据被中间人获取,拿到的则再也不是明文。 固然还有其余一些优势:例如避免后端等打印日志直接暴露明文密码,还能够避免明文撞库等.
浏览器会先检查是否有相应的cookie,有则自动添加在request header中的cookie字段中。这些是浏览器自动帮咱们作的,并且每一次http请求浏览器都会自动帮咱们作。这个特色很重要,由于这关系到“什么样的数据适合存储在cookie中”。 存储在cookie中的数据,每次都会被浏览器自动放在http请求中,若是这些数据并非每一个请求都须要发给服务端的数据,浏览器这设置自动处理无疑增长了网络开销;但若是这些数据是每一个请求都须要发给服务端的数据(好比身份认证信息),浏览器这设置自动处理就大大免去了重复添加操做。因此对于那种设置“每次请求都要携带的信息(最典型的就是身份认证信息)”就特别适合放在cookie中,其余类型的数据就不适合了。
不一样的浏览器存放的cookie位置不同,也是不能通用的。 cookie的存储是以域名形式进行区分的,不一样的域下存储的cookie是独立的。 咱们能够设置cookie生效的域(当前设置cookie所在域的子域),也就是说,咱们可以操做的cookie是当前域以及当前域下的全部子域 一个域名下存放的cookie的个数是有限制的,不一样的浏览器存放的个数不同,通常为20个。 每一个cookie存放的内容大小也是有限制的,不一样的浏览器存放大小不同,通常为4KB。 cookie也能够设置过时的时间,默认是会话结束的时候,当时间到期自动销毁。