面试自查手册

257 阅读33分钟

前端面试题汇总

1.事件循环

(macro)task(又称之为宏任务)

可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)。

浏览器为了能够使得JS内部(macro)task与DOM任务能够有序的执行,会在一个(macro)task执行结束后, 在下一个(macro)task 执行开始前,对页面进行重新渲染,流程如下:

(macro)task->渲染->(macro)task->... (macro)task主要包含:script(整体代码)、setTimeout、setInterval、I/O、UI交互事件、postMessage、MessageChannel、setImmediate(Node.js 环境)

microtask(又称为微任务)

可以理解是在当前 task 执行结束后立即执行的任务。也就是说,在当前task任务后,下一个task之前,在渲染之前。

所以它的响应速度相比setTimeout(setTimeout是task)会更快,因为无需等渲染。也就是说,在某一个macrotask执行完后,就会将在它执行期间产生的所有microtask都执行完毕(在渲染前)。

microtask主要包含:Promise.then、MutaionObserver、process.nextTick(Node.js 环境)

tick

在事件循环中,每进行一次循环操作称为 tick,每一次 tick 的任务处理模型是比较复杂的,但关键步骤如下:

执行一个宏任务(栈中没有就从事件队列中获取) 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行) 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)

async函数中在await之前的代码是立即执行的,遇到了await时,会将await后面的表达式执行一遍,将await后面的代码也就是加入到microtask中的Promise队列

2.渲染过程

dom tree + css tree = render tree
完了以后回流 确定每个元素的几何位置 重绘确定每个元素的像素

重绘

节点的几何属性发生改变或者由于样式发生改变而不会影响布局的,称为重绘, 例如outline, visibility, color、background-color等,重绘的代价是高昂的,因为浏览器必须验证DOM树上其他节点元素的可见性。

回流

回流是布局或者几何属性需要改变就称为回流。回流是影响浏览器性能的关键因素,因为其变化涉及到部分页面(或是整个页面)的布局更新。 一个元素的回流可能会导致了其所有子元素以及DOM中紧随其后的节点、祖先节点元素的随后的回流。 回流必定会发生重绘,重绘不一定会引发回流。

优化

  1. Dom操作
  2. 位置信息获取
  3. style、class
  4. display:hidden再操作

优化:

  1. 少获取元素的位置信息 每获取一次都会触发回流
  2. 对元素样式进行统一修改 e.g.在style.cssText上一次性修改,更改出class名
  3. 批量修改dom,可以将元素设置为none,将其脱离文档流以后再更改,全部插到一个dom里再插入这个dom
  4. 使用treansform等方法触发gpu加速 使得css动画不会触发回流

3.移动端1px问题

原因:window.devicePixelRatio查看,移动端DPR=2 就是设备物理像素与视觉像素的比值

解决:

  1.  <meta name='viewport' content='width=device-width,initial-scale=0.5'>
    
  2. 微元素+transform 本元素position:relative:after position:absolute 宽度百分百 高度1px;background设个颜色 transform:scaleY(0.5);
  3. 如果是全边框 宽度高度都为百分之两百 缩小0.5 边框设置1px

4.双向绑定问题:

Vue2.x:使用Object.defineProperty实现双向绑定,其实现的功能是数据劫持,在观察者订阅者模式当中是观察者的角色 此外,当观察者发现变化时可以告知manager,manager告知订阅者,订阅者对相应的做出反应(compile)。 p.s.:对于某些数组操作失灵,其采用的方法是,改写某个数组的原型链,使其指向改造过的方法原型。 Vue3.x:使用Proxy来代替Object.defineProperty,优点总结就是,劫持能力比前者强大,可以监控整个对象,而且不会对 数组的某些操作失灵。

5.虚拟dom:

四个关键方法:pathch函数分为两种情况初始化时,将所有的虚拟dom实现再插入到container上
第二次当虚拟dom改变时,会使用diff算法找出不同,使用新dom替换旧dom(同层比较,不移动
说到渲染,有个h函数,作用是将虚拟dom渲染成为真实dom,本质是一个递归函数,当不是叶子节点时会不断的
向下调用h函数,参数有标签名,属性,子节点;叶子节点的调用是,标签名,属性,text

6.diff算法:

同层递归顺序比较 只比较同层的dom结构,比较是依次深度递归比较的,顺序是先看此节点还在不在,再看节点属性是否改变,再看文本内容,节点是否被替换

key的作用

  1. key的作用是为了在diff算法执行时更快的找到对应的节点,提高diff速度。
  2. 在数据变化时强制更新组件,以避免“原地复用”带来的副作用。
  3. 主要是为了提升diff【同级比较】的效率。自己想一下自己要实现前后列表的diff,如果对列表的每一项增加一个key,即唯一索引, 那就可以很清楚的知道两个列表谁少了谁没变。而如果不加key的话,就只能一个个对比了。
  4. 会根据新节点的key去对比旧节点数组中的key,从而找到相应旧节点(这里对应的是一个key => index 的map映射)。 如果没找到就认为是一个新增节点。而如果没有key,那么就会采用一种遍历查找的方式去找到对应的旧节点。一种一个map映射, 另一种是遍历查找。相比而言。map映射的速度更快。

7.axois相关配置:

可以配置默认的url、transformRequest、transformResponse对请求体和响应体做对应的处理、headers自定义
timeout自定义、onUploadProgres onDownloadProgress、proxy设置代理、cancelToken用于取消响应

axios.get('/user/12345', {
  cancelToken: new CancelToken(function executor(c) {
    // executor 函数接收一个 cancel 函数作为参数
    cancel = c;
  })
});
// 取消请求
cancel(); 

8.事件机制:

事件绑定:w3c事件委托 addEventListener,removeEventListener 第三个参数是false则为冒泡 true是捕获
事件捕获与冒泡:捕获是先触发父元素再触发子元素 阻止冒泡:e.stopPropagation()、e.cancelBubble = true 阻止默认事件:e.preventDefault()、e.returnValue = false; 事件委托:将事件绑定在父元素上可以减少绑定的数量以及动态增减子元素都无所谓 比如 focus、blur 之类的事件本身没有事件冒泡机制,所以无法委托; mousemove、mouseout 这样的事件,虽然有事件冒泡,但是只能不断通过位置去计算定位,对性能消耗高,因此也是不适合于事件委托的;

9.前端安全:

xss-script标签

任何可以输入的地方都有可能引起,包括URL XSS 常见的注入方法:

  1. 在 HTML 中内嵌的文本中,恶意内容以 script 标签形成注入。
  2. 在内联的 JavaScript 中,拼接的数据突破了原本的限制(字符串,变量,方法名等)。
  3. 在标签属性中,恶意内容包含引号,从而突破属性值的限制,注入其他属性或者标签。
  4. 在标签的 href、src 等属性中,包含 javascript: (伪协议)等可执行代码。
  5. 在 onload、onerror、onclick 等事件中,注入不受控制代码。
  6. 在 style 属性和标签中,包含类似 background-image:url("javascript:..."); 的代码(新版本浏览器已经可以防范)。
  7. 在 style 属性和标签中,包含类似 expression(...) 的 CSS 表达式代码(新版本浏览器已经可以防范)。 --储存型:数据库返还数据未经转义直接渲染 --反射型:将用户输入的存在XSS攻击的数据,发送给后台,后台并未对数据进行存储,也未经过任何过滤,直接返回给客户端。被浏览器渲染。就可能导致XSS攻击;

防御:客户端求情参数:包括用户输入,url参数、post参数。

  1. 输入:参数变量类型限制, 如果拼接 HTML 是必要的,就需要对于引号,尖括号,斜杠进行转义,但这还不是很完善.想对 HTML 模板各处插入点进行充分的转义,就需要采用合适的转义库. 一般是用于对于输入格式的检查,例如:邮箱,电话号码,用户名,密码……等,按照规定的格式输入。 不仅仅是前端负责,后端也要做相同的过滤检查。因为攻击者完全可以绕过正常的输入流程,直接利用相关接口向服务器发送设置。
  2. 输出:
  3. httpOnly: 在 cookie 中设置 HttpOnly 属性后,js脚本将无法读取到 cookie 信息。

CSRF 跨站请求伪造

csrf是让用户住不知情的情况下,冒用其身份发起了一个请求

流程图
在三方网站中,利用图片的url来执行get请求,表单执行post请求

防范

  1. Get 请求不对数据进行修改
  2. 不让第三方网站访问到用户 Cookie ——可以对 Cookie 设置 SameSite 属性,该属性设置 Cookie 不随着跨域请求发送,不是都兼容
  3. 阻止第三方网站请求接口
  4. 请求时附带验证信息,比如验证码或者 token
  5. 验证 Referer对于需要防范 CSRF 的请求,我们可以通过验证 Referer 来判断该请求是否为第三方网站发起的。

与XSS的差别

在后台接收到请求的时候,可以通过请求头中的Referer请求头来判断请求来源 通常来说 CSRF 是由 XSS 实现的,CSRF 时常也被称为 XSRF(CSRF 实现的方式还可以是直接通过命令行发起请求等)。 本质上讲,XSS 是代码注入问题,CSRF 是 HTTP 问题。 XSS 是内容没有过滤导致浏览器将攻击者的输入当代码执行。CSRF 则是因为浏览器在发送 HTTP 请求时候自动带上 cookie,而一般网站的 session 都存在 cookie里面(Token验证可以避免)。

iframe插件——可以通过配置沙盒属性来控制粒度权限、

点击劫持通过iframez-index显示在某小游戏上面,iframe设置为透明——通过X-Frame—Option:Deny、

错误的内容推断,使用假的文件格式隐藏脚本内容,再次请求上传的文件时,由于内容推断错误而导致脚本执行——X-Content-Type-Options来阻止浏览器自行推断文件类型

npm第三方包可能存在xss漏洞

前端本地储存的数据可能会因为xss漏洞被读取

cdn劫持,当分散的脚本被劫持时,可能会加载后出现问题,可以通过hash加密验证匹配不匹配

10. 0.2 + 0.1 = 0.300000000004

——由于IEEE 754标准的原因 解决办法可以将结果乘1000再除1000进行一个截尾操作 parseFloat((0.1 + 0.2).toFixed(10))

11.querySelector()

方法仅仅返回匹配指定选择器的第一个元素。如果你需要返回所有的元素,请使用 querySelectorAll() 方法替代。 由于querySelector是按css规范来实现的,所以它传入的字符串中第一个字符不能是数字. 最后再根据查询的资料总结一下: query选择符选出来的元素及元素数组是静态的,而getElement这种方法选出的元素是动态的。 静态的就是说选出的所有元素的数组,不会随着文档操作而改变. 在使用的时候getElement这种方法性能比较好,query选择符则比较方便.

12.“==”运算符比较“喜欢”Number 先toString再valueOf

Boolean([]) //true

Boolean(undefined) // false

Boolean(null) // false 

Boolean(0) // false 

Boolean(NaN) // false 

Boolean('') // false

Number([]) // 0

Number({}) // NaN。

13.对于 CommonJS 和 ES6 中的模块化的两者区别是:

前者支持动态导入,也就是 require(${path}/xx.js),后者目前不支持,但是已有提案

前者是同步导入,因为用于服务端,文件都在本地,同步导入即使卡住主线程影响也不大。而后者是异步导入,因为用于浏览器,需要下载文件,如果也采用同步导入会对渲染有很大影响

前者在导出时都是值拷贝,就算导出的值变了,导入的值也不会改变,所以如果想更新值,必须重新导入一次。但是后者采用实时绑定的方式,导入导出的值都指向同一个内存地址,所以导入值会跟随导出值变化

后者会编译成 require/exports 来执行的

14.DNS:域名服务系统

--浏览器搜索自身的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 地址了。

15.CDN:内容分发网络

--而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的影响,只要修改整个访问过程中的域名解析部分,以实现透明的加速服务。

16. ['1', '2', '3'].map(parseInt)

answer[1,NaN,NaN]。

why? 因为parseInt第二个参数是默认进制,map使用时默认传入第二个参数是index,当第二个参数是0时默认是十进制,2大于一进制最大值,3大于二进制最大值

17. Set、WeakSet

Set

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

WeakSet 与 Set 的区别:

WeakSet 只能储存对象引用,不能存放值,而 Set 对象都可以 WeakSet 对象中储存的对象值都是被弱引用的,即垃圾回收机制不考虑 WeakSet 对该对象的应用,如果没有其他的变量或属性引用这个对象值, 则这个对象将会被垃圾回收掉(不考虑该对象还存在于 WeakSet 中),所以,WeakSet 对象里有多少个成员元素,取决于垃圾回收机制有没有运行, 运行前后成员个数可能不一致,遍历结束之后,有的成员可能取不到了(被垃圾回收了),WeakSet 对象是无法被遍历的(ES6 规定 WeakSet 不可遍历), 也没有办法拿到它包含的所有元素

18.Map、WeakMap

集合 与 字典 的区别:

共同点:集合、字典 可以储存不重复的值 不同点:集合 是以 [value, value]的形式储存元素,字典 是以 [key, value] 的形式储存

Map

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():遍历字典的所有成员

转换方法

  1. Map 转 Array
const map = new Map([[1, 1], [2, 2], [3, 3]])
console.log([...map])	// [[1, 1], [2, 2], [3, 3]]
  1. Array 转 Map
const map = new Map([[1, 1], [2, 2], [3, 3]])
console.log(map)	// Map {1 => 1, 2 => 2, 3 => 3}
  1. Map 转 Object

因为 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)
  1. Object 转 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'})
  1. Map 转 JSON
function mapToJson(map) {
    return JSON.stringify([...map])
}

let map = new Map().set('name', 'An').set('des', 'JS')
mapToJson(map)	// [["name","An"],["des","JS"]]
  1. JSON 转 Map
function jsonToMap(jsonStr) {
  return objToMap(JSON.parse(jsonStr));
}

jsonToStrMap('{"name": "An", "des": "JS"}')

WeakMap

WeakMap 对象是一组键值对的集合,其中的键是弱引用,其中,键必须是对象,而值可以是任意。

注意,WeakMap 弱引用的只是键名,而不是键值。键值依然是正常引用。

WeakSet 中,每个键对自己所引用对象的引用都是弱引用,在没有其他引用和该键引用同一对象,这个对象将会被垃圾回收(相应的key则变成无效的),所以,WeakSet 的 key 是不可枚举的。

方法:

has(key):判断是否有 key 关联对象 get(key):返回key关联对象(没有则则返回 undefined) set(key):设置一组key关联对象 delete(key):移除 key 的关联对象

Summary

Set

成员唯一、无序且不重复 [value, value],键值与键名是一致的(或者说只有键值,没有键名) 可以遍历,方法有:add、delete、has

WeakSet

成员都是对象 成员都是弱引用,可以被垃圾回收机制回收,可以用来保存DOM节点,不容易造成内存泄漏 不能遍历,方法有add、delete、has

Map

本质上是键值对的集合,类似集合 可以遍历,方法很多可以跟各种数据格式转换

WeakMap

只接受对象最为键名(null除外),不接受其他类型的值作为键名 键名是弱引用,键值可以是任意的,键名所指向的对象可以被垃圾回收,此时键名是无效的 不能遍历,方法有get、set、has、delete

19.Array

Are U An Array

  1. Array.isArray()
  2. Obeject.prototype.toString.call() //这种方法对于所有基本的数据类型都能进行判断,即使是 null 和 undefined 。
  3. instanceof // instanceof 只能用来判断对象类型,原始类型不可以。并且所有对象类型 instanceof Object 都是 true。

数组扁平化 [[[1,2,3]]]

  1. arr.split(',')
  2. arr.flat(Infinity);

20.ES6与ES5类的区别

  1. class会类似变量提升,造成暂时性死区
  2. ES5的类中不会自动开启严格模式,ES6会
  3. ES6的方法不可枚举,ES5可以
  4. ES6中的每一个方法都不可以作为构造函数new一个对象
  5. ES5中可以重写类名,ES6不可以

21.ES6与ES5继承的区别

  1. 原型链指向的区别
// 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;
  1. this生成顺序的区别 ES5 和 ES6 子类 this 生成顺序不同。ES5 的继承先生成了子类实例,再调用父类的构造函数修饰子类实例, ES6 的继承先生成父类实例,再调用子类的构造函数修饰父类实例。这个差别使得 ES6 可以继承内置对象。所以需要使用super
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) []

22.作用域链

作用域

负责收集并维护由所有声明的标识符(变量、函数)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。

变量查找

  1. 作用域嵌套 引擎从当前的执行作用域开始查找变量,如果找不到就向上一级继续查找。当抵达最外层的全局作用域时,无论找到还是没有找到,查找过程都会停止。
  2. 作用域查找会在找到第一个匹配的标识符时停止。
  3. 在多层嵌套作用域中可以定义同名的标识符,内部的标识符会“遮蔽”外部的标识符。
  4. 全局变量会自动变成全局对象的属性
  5. 词法作用域只由函数被声明时所处的位置决定

LHS、RHS

L和R分别代表一个赋值操作的左侧和右侧,当变量出现在赋值操作的左侧时进行LHS查询,出现在赋值操作的非左侧时进行RHS查询。

LHS查询(左侧):找到变量的容器本身,然后对其赋值 RHS查询(非左侧):查找某个变量的值,可以理解为 retrieve his source value,即取到它的源值

ERROR

ReferenceError和作用域判别失败相关,TypeError表示作用域判别成功了,但是对结果的操作是非法或不合理的。

RHS查询在作用域链中搜索不到所需的变量,引擎会抛出ReferenceError异常。 非严格模式下,LHS查询在作用域链中搜索不到所需的变量,全局作用域中会创建一个具有该名称的变量并返还给引擎。 严格模式下(ES5开始,禁止自动或隐式地创建全局变量),LHS查询失败会抛出ReferenceError异常 在RHS查询成功情况下,对变量进行不合理的操作,引擎会抛出TypeError异常。(比如对非函数类型的值进行函数调用,或者引用null或undefined类型的值中的属性)

函数作用域

  1. 属于这个函数的全部变量都可以在整个函数的范围内使用及复用————闭包原理、规避冲突、模块原理
  2. 函数声明的提升与函数表达式的提升区别————一个普通块内部的函数声明通常会被提升到所在作用域的顶部,不会被条件判断所控制。尽量避免在普通块内部声明函数。

块作用域

  1. try、catch catch中会创建
  2. let——不会有变量提升、用于for循环可以绑定每次循环的作用域
  3. const

闭包

将内部函数传递到所在的词法作用域以外,它都会持有对原始定义作用域的引用,无论在何处执行这个函数都会使用闭包。

动态作用域——函数执行上下文——this

23.Koa的中间件机制

app.use()——挂载中间件 在app.listen()方法中compose函数调用了中间件对其进行处理,之后就是this.handleRequest

如何执行

  1. compose函数对中间件处理,返回fnMiddleware函数
  2. fnMiddleware的执行过程(context,next)两个参数, 其执行中标识了变量index,表示上次执行到了哪个函数
  3. 确定目前要执行哪个函数(dispach(i+1))以后对其执行结果进行promise包装

特点

存储:以数组形式存储中间件。 状态管理:所有的状态变更,都交给ctx对象,无需跨中间件传递参数。 流程控制:以递归的方式进行中间件的执行,将下一个中间件的执行权交给正在执行的中间件,即洋葱圈模型。 异步方案:用Promise包裹中间件的返回结果,以支持在上一个中间件内部实现Await逻辑。

24.HTTPS

区别

  1. 这是身披SSL的HTTP运行基于TCP的SSL上,添加了加密和认证机制
  2. 端口在443而非80
  3. 需要证书、共享密钥加密和公开密钥加密并用的混合加密机制

25.HTTP2多路复用

在 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 连接而带来的延时和内存消耗。 单个连接上可以并行交错的请求和响应,之间互不干扰

26.Restful接口

REST即Representational State Transfer的缩写,可译为"表现层状态转化”。REST最大的几个特点为:资源、统一接口、URI和无状态。

  1. 每一个URI代表一种资源;
  2. 客户端和服务器之间,传递这种资源的某种表现层;
  3. 客户端通过四个HTTP动词,对服务器端资源进行操作,实现"表现层状态转化"。
    Restful接口的流程与优点

优点

  1. 前后端分离,减少流量
  2. 安全问题集中在接口上,由于接受json格式,防止了注入型等安全问题
  3. 前端无关化,后端只负责数据处理,前端表现方式可以是任何前端语言(android,ios,html5)
  4. 前端和后端人员更加专注于各自开发,只需接口文档便可完成前后端交互,无需过多相互了解
  5. 服务器性能优化:由于前端是静态页面,通过nginx便可获取,服务器主要压力放在了接口上
  6. 轻量,直接基于http,不在需要任何别的诸如消息协议。get/post/put/delete为CRUD操作
  7. 面向资源,一目了然,具有自解释性。
  8. 数据描述简单,一般以xml,json做数据交换

27.前端加密

即在数据发送前将数据进行哈希或使用公钥加密。如果数据被中间人获取,拿到的则不再是明文。 当然还有其他一些优点:例如避免后端等打印日志直接暴露明文密码,还可以避免明文撞库等.

缺点:

  1. 密文被hacker获取以后仍然可以直接使用密文冒用用户登录
  2. 前端加密算法容易被hacker获取到,使得加密无意义

28.Cookie

使用

浏览器会先检查是否有相应的cookie,有则自动添加在request header中的cookie字段中。这些是浏览器自动帮我们做的,而且每一次http请求浏览器都会自动帮我们做。这个特点很重要,因为这关系到“什么样的数据适合存储在cookie中”。 存储在cookie中的数据,每次都会被浏览器自动放在http请求中,如果这些数据并不是每个请求都需要发给服务端的数据,浏览器这设置自动处理无疑增加了网络开销;但如果这些数据是每个请求都需要发给服务端的数据(比如身份认证信息),浏览器这设置自动处理就大大免去了重复添加操作。所以对于那种设置“每次请求都要携带的信息(最典型的就是身份认证信息)”就特别适合放在cookie中,其他类型的数据就不适合了。

特征

不同的浏览器存放的cookie位置不一样,也是不能通用的。 cookie的存储是以域名形式进行区分的,不同的域下存储的cookie是独立的。 我们可以设置cookie生效的域(当前设置cookie所在域的子域),也就是说,我们能够操作的cookie是当前域以及当前域下的所有子域 一个域名下存放的cookie的个数是有限制的,不同的浏览器存放的个数不一样,一般为20个。 每个cookie存放的内容大小也是有限制的,不同的浏览器存放大小不一样,一般为4KB。 cookie也可以设置过期的时间,默认是会话结束的时候,当时间到期自动销毁。

用途

  1. 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
  2. 个性化设置(如用户自定义设置、主题等)
  3. 浏览器行为跟踪(如跟踪分析用户行为等)

设置选项

  1. expires、max-age来设置cookie的过期时间
  2. domain与path限制cookie可以被访问的域与目录——domain的默认值为设置该cookie的网页所在的域名,path默认值为设置该cookie的网页所在的目录。
  3. secure document.cookie = "username=cfangxu; secure" 把cookie设置为secure,只保证 cookie 与服务器之间的数据传输过程加密,而保存在本地的 cookie文件并不加密。就算设置了secure 属性也并不代表他人不能看到你机器本地保存的 cookie 信息。 机密且敏感的信息绝不应该在 cookie 中存储或传输,因为 cookie 的整个机制原本都是不安全的 注意:如果想在客户端即网页中通过 js 去设置secure类型的 cookie,必须保证网页是https协议的。在http协议的网页中是无法设置secure类型cookie的。
  4. httpOnly 在客户端是不能通过js代码去设置一个httpOnly类型的cookie的,这种类型的cookie只能通过服务端来设置。 原因:如果任何 cookie 都能被客户端通过document.cookie获取会发生什么可怕的事情。当我们的网页遭受了 XSS 攻击,有一段恶意的script脚本插到了网页中。这段script脚本做的事情是:通过document.cookie读取了用户身份验证相关的 cookie,并将这些 cookie 发送到了攻击者的服务器。攻击者轻而易举就拿到了用户身份验证信息,于是就可以摇摇大摆地冒充此用户访问你的服务器了(因为攻击者有合法的用户身份验证信息,所以会通过你服务器的验证)。