关于javascript:年底前端面试题总结下

35次阅读

共计 16631 个字符,预计需要花费 42 分钟才能阅读完成。

Chrome 关上一个页面须要启动多少过程?别离有哪些过程?

关上 1 个页面至多须要 1 个网络过程、1 个浏览器过程、1 个 GPU 过程以及 1 个渲染过程,共 4 个;最新的 Chrome 浏览器包含:1 个浏览器(Browser)主过程、1 个 GPU 过程、1 个网络(NetWork)过程、多个渲染过程和多个插件过程。

  • 浏览器过程:次要负责界面显示、用户交互、子过程治理,同时提供存储等性能。
  • 渲染过程:外围工作是将 HTML、CSS 和 JavaScript 转换为用户能够与之交互的网页,排版引擎 Blink 和 JavaScript 引擎 V8 都是运行在该过程中,默认状况下,Chrome 会为每个 Tab 标签创立一个渲染过程。出于平安思考,渲染过程都是运行在沙箱模式下。
  • GPU 过程:其实,Chrome 刚开始公布的时候是没有 GPU 过程的。而 GPU 的应用初衷是为了实现 3D CSS 的成果,只是随后网页、Chrome 的 UI 界面都抉择采纳 GPU 来绘制,这使得 GPU 成为浏览器广泛的需要。最初,Chrome 在其多过程架构上也引入了 GPU 过程。
  • 网络过程:次要负责页面的网络资源加载,之前是作为一个模块运行在浏览器过程外面的,直至最近才独立进去,成为一个独自的过程。
  • 插件过程:次要是负责插件的运行,因插件易解体,所以须要通过插件过程来隔离,以保障插件过程解体不会对浏览器和页面造成影响。

XSS 和 CSRF

1. XSS

涉及面试题:什么是 XSS 攻打?如何防备 XSS 攻打?什么是 CSP

  • XSS 简略点来说,就是攻击者想尽一切办法将能够执行的代码注入到网页中。
  • XSS 能够分为多种类型,然而总体上我认为分为两类:长久型和非长久型。
  • 长久型也就是攻打的代码被服务端写入进数据库中,这种攻打危害性很大,因为如果网站访问量很大的话,就会导致大量失常拜访页面的用户都受到攻打。

举个例子,对于评论性能来说,就得防备长久型 XSS 攻打,因为我能够在评论中输出以下内容

  • 这种状况如果前后端没有做好进攻的话,这段评论就会被存储到数据库中,这样每个关上该页面的用户都会被攻打到。
  • 非长久型相比于前者危害就小的多了,个别通过批改 URL 参数的形式退出攻打代码,诱导用户拜访链接从而进行攻打。

举个例子,如果页面须要从 URL 中获取某些参数作为内容的话,不通过过滤就会导致攻打代码被执行

<!-- http://www.domain.com?name=<script>alert(1)</script> -->
<div>{{name}}</div>                                                  

然而对于这种攻击方式来说,如果用户应用 Chrome 这类浏览器的话,浏览器就能主动帮忙用户进攻攻打。然而咱们不能因而就不进攻此类攻打了,因为我不能确保用户都应用了该类浏览器。

对于 XSS 攻打来说,通常有两种形式能够用来进攻。

  1. 转义字符

首先,对于用户的输出应该是永远不信赖的。最广泛的做法就是本义输入输出的内容,对于引号、尖括号、斜杠进行本义

function escape(str) {str = str.replace(/&/g, '&')
  str = str.replace(/</g, '<')
  str = str.replace(/>/g, '>')
  str = str.replace(/"/g,'&quto;')
  str = str.replace(/'/g,'&#39;')
  str = str.replace(/`/g, '&#96;')
  str = str.replace(/\//g, '&#x2F;')
  return str
}

通过本义能够将攻打代码 <script>alert(1)</script> 变成

// -> <script>alert(1)<&#x2F;script>
escape('<script>alert(1)</script>')

然而对于显示富文本来说,显然不能通过下面的方法来本义所有字符,因为这样会把须要的格局也过滤掉。对于这种状况,通常采纳白名单过滤的方法,当然也能够通过黑名单过滤,然而思考到须要过滤的标签和标签属性切实太多,更加举荐应用白名单的形式

const xss = require('xss')
let html = xss('<h1 id="title">XSS Demo</h1><script>alert("xss");</script>')
// -> <h1>XSS Demo</h1><script>alert("xss");</script>
console.log(html)

以上示例应用了 js-xss 来实现,能够看到在输入中保留了 h1 标签且过滤了 script标签

  1. CSP

CSP 实质上就是建设白名单,开发者明确通知浏览器哪些内部资源能够加载和执行。咱们只须要配置规定,如何拦挡是由浏览器本人实现的。咱们能够通过这种形式来尽量减少 XSS 攻打。

通常能够通过两种形式来开启 CSP

  • 设置 HTTP Header 中的 Content-Security-Policy
  • 设置 meta 标签的形式 <meta http-equiv="Content-Security-Policy">

这里以设置 HTTP Header 来举例

只容许加载本站资源

Content-Security-Policy: default-src‘self’

只容许加载 HTTPS 协定图片

Content-Security-Policy: img-src https://*

容许加载任何起源框架

Content-Security-Policy: child-src 'none'

当然能够设置的属性远不止这些,你能够通过查阅 文档 (opens new window)的形式来学习,这里就不过多赘述其余的属性了。

对于这种形式来说,只有开发者配置了正确的规定,那么即便网站存在破绽,攻击者也不能执行它的攻打代码,并且 CSP 的兼容性也不错。

2 CSRF

跨站申请伪造(英语:Cross-site request forgery),也被称为 one-click attack或者 session riding,通常缩写为 CSRF 或者 XSRF,是一种挟制用户在以后已登录的 Web 应用程序上执行非本意的操作的攻打办法

CSRF 就是利用用户的登录态发动歹意申请

如何攻打

假如网站中有一个通过 Get 申请提交用户评论的接口,那么攻击者就能够在钓鱼网站中退出一个图片,图片的地址就是评论接口

<img src="http://www.domain.com/xxx?comment='attack'"/>

res.setHeader('Set-Cookie', `username=poetry2;sameSite = strict;path=/;httpOnly;expires=${getCookirExpires()}`)

在 B 网站,危险网站向 A 网站发动申请

<!DOCTYPE html>
<html>
  <body>
  <!-- 利用 img 主动发送申请 -->
    <img src="http://localhost:8000/api/user/login" />
  </body>
</html>

会带上 A 网站的 cookie

// 在 A 网站下发 cookie 的时候,加上 sameSite=strict,这样 B 网站在发送 A 网站申请,不会主动带上 A 网站的 cookie,保障了平安


// NAME=VALUE    赋予 Cookie 的名称及对应值
// expires=DATE  Cookie 的有效期
// path=PATH     赋予 Cookie 的名称及对应值
// domain= 域名   作为 Cookie 实用对象的域名(若不指定则默认为创立 Cookie 的服务器的域名)(个别不指定)
// Secure        仅在 HTTPS 平安通信时才会发送 Cookie
// HttpOnly      加以限度,使 Cookie 不能被 JavaScript 脚本拜访
// SameSite      Lax|Strict|None  它容许您申明该 Cookie 是否仅限于第一方或者同一站点上下文

res.setHeader('Set-Cookie', `username=poetry;sameSite=strict;path=/;httpOnly;expires=${getCookirExpires()}`)

如何进攻

  • Get 申请不对数据进行批改
  • 不让第三方网站拜访到用户 Cookie
  • 阻止第三方网站申请接口
  • 申请时附带验证信息,比方验证码或者 token
  • SameSite Cookies: 只能以后域名的网站收回的 http 申请,携带这个Cookie。当然,因为这是新的 cookie 属性,在兼容性上必定会有问题

CSRF 攻打,仅仅是利用了 http 携带 cookie 的个性进行攻打的,然而攻打站点还是无奈失去被攻打站点的 cookie。这个和 XSS 不同,XSS 是间接通过拿到 Cookie 等信息进行攻打的

在 CSRF 攻打中,就 Cookie 相干的个性:

  • http 申请,会主动携带 Cookie。
  • 携带的 cookie,还是 http 申请所在域名的 cookie。

3 明码平安

加盐

对于明码存储来说,必然是不能明文存储在数据库中的,否则一旦数据库泄露,会对用户造成很大的损失。并且不倡议只对明码单纯通过加密算法加密,因为存在彩虹表的关系

  • 通常须要对明码加盐,而后进行几次不同加密算法的加密
// 加盐也就是给原明码增加字符串,减少原明码长度
sha256(sha1(md5(salt + password + salt)))

然而加盐并不能阻止他人盗取账号,只能确保即便数据库泄露,也不会裸露用户的实在明码。一旦攻击者失去了用户的账号,能够通过暴力破解的形式破解明码。对于这种状况,通常应用验证码减少延时或者限度尝试次数的形式。并且一旦用户输出了谬误的明码,也不能间接提醒用户输错明码,而应该提醒账号或明码谬误

前端加密

尽管前端加密对于平安防护来说意义不大,然而在遇到中间人攻打的状况下,能够防止明文明码被第三方获取

4. 总结

  • XSS:跨站脚本攻打,是一种网站应用程序的安全漏洞攻打,是代码注入的一种。常见形式是将恶意代码注入非法代码里暗藏起来,再诱发恶意代码,从而进行各种各样的非法活动

防备:记住一点“所有用户输出都是不可信的”,所以得做输出过滤和本义

  • CSRF:跨站申请伪造,也称 XSRF,是一种挟制用户在以后已登录的 Web 应用程序上执行非本意的操作的攻打办法。与 XSS 相比,XSS利用的是用户对指定网站的信赖,CSRF利用的是网站对用户网页浏览器的信赖。

防备:用户操作验证(验证码),额定验证机制(token应用)等

公布订阅模式和观察者模式

1. 公布 / 订阅模式

  • 公布 / 订阅模式

    • 订阅者
    • 发布者
    • 信号核心

咱们假设,存在一个 ” 信号核心 ”,某个工作执行实现,就向信号核心 ” 公布 ”(publish)一个信 号,其余工作能够向信号核心 ” 订阅 ”(subscribe)这个信号,从而晓得什么时候本人能够开始执 行。这就叫做 ” 公布 / 订阅模式 ”(publish-subscribe pattern)

Vue 的自定义事件

let vm = new Vue()
vm.$on('dataChange', () => {console.log('dataChange')})
vm.$on('dataChange', () => {console.log('dataChange1')
}) 
vm.$emit('dataChange')

兄弟组件通信过程

// eventBus.js
// 事件核心
let eventHub = new Vue()

// ComponentA.vue
// 发布者
addTodo: function () {// 公布音讯(事件)
  eventHub.$emit('add-todo', { text: this.newTodoText}) 
  this.newTodoText = ''
}
// ComponentB.vue
// 订阅者
created: function () {// 订阅音讯(事件)
  eventHub.$on('add-todo', this.addTodo)
}

模仿 Vue 自定义事件的实现

class EventEmitter {constructor(){// { eventType: [ handler1, handler2] }
    this.subs = {}}
  // 订阅告诉
  $on(eventType, fn) {this.subs[eventType] = this.subs[eventType] || []
    this.subs[eventType].push(fn)
  }
  // 公布告诉
  $emit(eventType) {if(this.subs[eventType]) {this.subs[eventType].forEach(v=>v())
    }
  }
}

// 测试
var bus = new EventEmitter()

// 注册事件
bus.$on('click', function () {console.log('click')
})

bus.$on('click', function () {console.log('click1')
})

// 触发事件 
bus.$emit('click')

2. 观察者模式

  • 观察者(订阅者) — Watcher

    • update(): 当事件产生时,具体要做的事件
  • 指标(发布者) — Dep

    • subs 数组: 存储所有的观察者
    • addSub(): 增加观察者
    • notify(): 当事件产生,调用所有观察者的 update() 办法
  • 没有事件核心
// 指标(发布者) 
// Dependency
class Dep {constructor () {
    // 存储所有的观察者
    this.subs = []}
  // 增加观察者
  addSub (sub) {if (sub && sub.update) {this.subs.push(sub)
    }
  }
  // 告诉所有观察者
  notify () {this.subs.forEach(sub => sub.update())
  }
}

// 观察者(订阅者)
class Watcher {update () {console.log('update')
  }
}

// 测试
let dep = new Dep()
let watcher = new Watcher()
dep.addSub(watcher) 
dep.notify()

3. 总结

  • 观察者模式 是由具体指标调度,比方当事件触发,Dep 就会去调用观察者的办法,所以观察者模 式的订阅者与发布者之间是存在依赖的
  • 公布 / 订阅模式 由对立调度核心调用,因而发布者和订阅者不须要晓得对方的存在

说一说 keep-alive 实现原理

keep-alive组件承受三个属性参数:includeexcludemax

  • include 指定须要缓存的 组件 name汇合,参数格局反对 String, RegExp, Array。 当为字符串的时候,多个组件名称以逗号隔开。
  • exclude 指定不须要缓存的 组件 name汇合,参数格局和 include 一样。
  • max 指定最多可缓存组件的数量, 超过数量删除第一个。参数格局反对 String、Number。

原理

keep-alive实例会缓存对应组件的VNode, 如果命中缓存,间接从缓存对象返回对应VNode

LRU(Least recently used) 算法依据数据的历史拜访记录来进行淘汰数据,其核心思想是“如果数据最近被拜访过,那么未来被拜访的几率也更高”。(墨菲定律:越放心的事件越会产生)

参考前端进阶面试题具体解答

TCP 的牢靠传输机制

TCP 的牢靠传输机制是基于间断 ARQ 协定和滑动窗口协定的。

TCP 协定在发送方维持了一个发送窗口,发送窗口以前的报文段是曾经发送并确认了的报文段,发送窗口中蕴含了曾经发送但 未确认的报文段和容许发送但还未发送的报文段,发送窗口当前的报文段是缓存中还不容许发送的报文段。当发送方向接管方发 送报文时,会顺次发送窗口内的所有报文段,并且设置一个定时器,这个定时器能够了解为是最早发送但未收到确认的报文段。如果在定时器的工夫内收到某一个报文段的确认答复,则滑动窗口,将窗口的首部向后滑动到确认报文段的后一个地位,此时如 果还有已发送但没有确认的报文段,则从新设置定时器,如果没有了则敞开定时器。如果定时器超时,则从新发送所有曾经发送 但还未收到确认的报文段,并将超时的距离设置为以前的两倍。当发送方收到接管方的三个冗余的确认应答后,这是一种批示,阐明该报文段当前的报文段很有可能产生失落了,那么发送方会启用疾速重传的机制,就是以后定时器完结前,发送所有的已发 送但确认的报文段。

接管方应用的是累计确认的机制,对于所有按序达到的报文段,接管方返回一个报文段的必定答复。如果收到了一个乱序的报文 段,那么接方会间接抛弃,并返回一个最近的按序达到的报文段的必定答复。应用累计确认保障了返回的确认号之前的报文段都 曾经按序达到了,所以发送窗口能够挪动到已确认报文段的前面。

发送窗口的大小是变动的,它是由接管窗口残余大小和网络中拥塞水平来决定的,TCP 就是通过管制发送窗口的长度来管制报文 段的发送速率。

然而 TCP 协定并不齐全和滑动窗口协定雷同,因为许多的 TCP 实现会将失序的报文段给缓存起来,并且产生重传时,只会重 传一个报文段,因而 TCP 协定的牢靠传输机制更像是窗口滑动协定和抉择重传协定的一个混合体。

从输出 URL 到页面展现过程

1. DNS 域名解析

  • 根 DNS 服务器:返回顶级域 DNS 服务器的 IP 地址
  • 顶级域 DNS 服务器:返回权威 DNS 服务器的 IP 地址
  • 权威 DNS 服务器:返回相应主机的 IP 地址

DNS 的域名查找,在客户端和浏览器,本地 DNS 之间的查问形式是递归查问;在本地 DNS 服务器与根域及其子域之间的查问形式是迭代查问;

在客户端输出 URL 后,会有一个递归查找的过程,从 浏览器缓存中查找 -> 本地的 hosts 文件查找 -> 找本地 DNS 解析器缓存查找 -> 本地 DNS 服务器查找,这个过程中任何一步找到了都会完结查找流程。

如果本地 DNS 服务器无奈查问到,则依据本地 DNS 服务器设置的转发器进行查问。若未用转发模式,则迭代查找过程如下图:

联合起来的过程,能够用一个图示意:

在查找过程中,有以下优化点:

  • DNS 存在着多级缓存,从离浏览器的间隔排序的话,有以下几种: 浏览器缓存,零碎缓存,路由器缓存,IPS 服务器缓存,根域名服务器缓存,顶级域名服务器缓存,主域名服务器缓存
  • 在域名和 IP 的映射过程中,给了利用基于域名做负载平衡的机会,能够是简略的负载平衡,也能够依据地址和运营商做全局的负载平衡。

2. 建设 TCP 连贯

首先,判断是不是 https 的,如果是,则 HTTPS 其实是 HTTP + SSL / TLS 两局部组成,也就是在 HTTP 上又加了一层解决加密信息的模块。服务端和客户端的信息传输都会通过 TLS 进行加密,所以传输的数据都是加密后的数据

进行三次握手,建设 TCP 连贯。

  • 第一次握手:建设连贯。客户端发送连贯申请报文段
  • 第二次握手:服务器收到 SYN 报文段。服务器收到客户端的 SYN 报文段,须要对这个 SYN 报文段进行确认
  • 第三次握手:客户端收到服务器的 SYN+ACK 报文段,向服务器发送 ACK 报文段

SSL 握手过程

  • 第一阶段 建设平安能力 包含协定版本 会话 Id 明码构件 压缩办法和初始随机数
  • 第二阶段 服务器发送证书 密钥替换数据和证书申请,最初发送申请 - 相应阶段的完结信号
  • 第三阶段 如果有证书申请客户端发送此证书 之后客户端发送密钥替换数据 也能够发送证书验证音讯
  • 第四阶段 变更明码构件和完结握手协定

实现了之后,客户端和服务器端就能够开始传送数据

发送 HTTP 申请,服务器解决申请,返回响应后果

TCP 连贯建设后,浏览器就能够利用 HTTP/HTTPS 协定向服务器发送申请了。服务器承受到申请,就解析申请头,如果头部有缓存相干信息如if-none-match 与 if-modified-since,则验证缓存是否无效,若无效则返回状态码为304,若有效则从新返回资源,状态码为200

这里有产生的一个过程是 HTTP 缓存,是一个常考的考点,大抵过程如图:

3. 敞开 TCP 连贯

4. 浏览器渲染

依照渲染的工夫程序,流水线可分为如下几个子阶段:构建 DOM 树、款式计算、布局阶段、分层、栅格化和显示。如图:

  • 渲染过程将 HTML 内容转换为可能读懂 DOM 树结构。
  • 渲染引擎将 CSS 样式表转化为浏览器能够了解的 styleSheets,计算出 DOM 节点的款式。
  • 创立布局树,并计算元素的布局信息。
  • 对布局树进行分层,并生成分层树。
  • 为每个图层生成绘制列表,并将其提交到合成线程。合成线程将图层分图块,并栅格化将图块转换成位图。
  • 合成线程发送绘制图块命令给浏览器过程。浏览器过程依据指令生成页面,并显示到显示器上。

构建 DOM 树

  • 转码(Bytes -> Characters)—— 读取接管到的 HTML 二进制数据,按指定编码格局将字节转换为 HTML 字符串
  • Tokens 化(Characters -> Tokens)—— 解析 HTML,将 HTML 字符串转换为构造清晰的 Tokens,每个 Token 都有非凡的含意同时有本人的一套规定
  • 构建 Nodes(Tokens -> Nodes)—— 每个 Node 都增加特定的属性(或属性拜访器),通过指针可能确定 Node 的父、子、兄弟关系和所属 treeScope(例如:iframe 的 treeScope 与外层页面的 treeScope 不同)
  • 构建 DOM 树(Nodes -> DOM Tree)—— 最重要的工作是建设起每个结点的父子兄弟关系

款式计算

渲染引擎将 CSS 样式表转化为浏览器能够了解的 styleSheets,计算出 DOM 节点的款式。

CSS 款式起源次要有 3 种,别离是通过 link 援用的内部 CSS 文件、style 标签内的 CSS、元素的 style 属性内嵌的 CSS。

页面布局

布局过程,即 排除 script、meta 等功能化、非视觉节点,排除 display: none 的节点,计算元素的地位信息,确定元素的地位,构建一棵只蕴含可见元素布局树。如图:

其中,这个过程须要留神的是回流和重绘

生成分层树

页面中有很多简单的成果,如一些简单的 3D 变换、页面滚动,或者应用 z-indexing 做 z 轴排序等,为了更加不便地实现这些成果,渲染引擎还须要为特定的节点生成专用的图层,并生成一棵对应的图层树(LayerTree)

栅格化

合成线程会依照视口左近的图块来优先生成位图,理论生成位图的操作是由栅格化来执行的。所谓栅格化,是指将图块转换为位图

通常一个页面可能很大,然而用户只能看到其中的一部分,咱们把用户能够看到的这个局部叫做视口(viewport)。在有些状况下,有的图层能够很大,比方有的页面你应用滚动条要滚动良久能力滚动到底部,然而通过视口,用户只能看到页面的很小一部分,所以在这种状况下,要绘制出所有图层内容的话,就会产生太大的开销,而且也没有必要。

显示

最初,合成线程发送绘制图块命令给浏览器过程。浏览器过程依据指令生成页面,并显示到显示器上,渲染过程实现。

HTTPS是如何保障平安的?

先了解两个概念:

  • 对称加密:即通信的双⽅都使⽤同⼀个秘钥进⾏加解密,对称加密尽管很简略性能也好,然而⽆法解决⾸次把秘钥发给对⽅的问题,很容易被⿊客拦挡秘钥。
  • ⾮对称加密:
  • 私钥 + 公钥 = 密钥对
  • 即⽤私钥加密的数据, 只有对应的公钥能力解密, ⽤公钥加密的数据, 只有对应的私钥能力解密
  • 因为通信双⽅的⼿⾥都有⼀套⾃⼰的密钥对, 通信之前双⽅会先把⾃⼰的公钥都先发给对⽅
  • 而后对⽅再拿着这个公钥来加密数据响应给对⽅, 等到到了对⽅那⾥, 对⽅再⽤⾃⼰的私钥进⾏解密

⾮对称加密尽管安全性更⾼,然而带来的问题就是速度很慢,影响性能。

解决⽅案:

联合两种加密⽅式,将对称加密的密钥使⽤⾮对称加密的公钥进⾏加密,而后发送进来,接管⽅使⽤私钥进⾏解密失去对称加密的密钥,而后双⽅能够使⽤对称加密来进⾏沟通。

此时⼜带来⼀个问题,两头⼈问题:
如果此时在客户端和服务器之间存在⼀个两头⼈, 这个两头⼈只须要把本来双⽅通信互发的公钥, 换成⾃⼰的公钥, 这样两头⼈就能够轻松解密通信双⽅所发送的所有数据。

所以这个时候须要⼀个平安的第三⽅颁发证书(CA),证实身份的身份,防⽌被两头⼈攻打。证书中包含:签发者、证书⽤途、使⽤者公钥、使⽤者私钥、使⽤者的 HASH 算法、证书到期工夫等。

然而问题来了,如果两头⼈篡改了证书,那么身份证明是不是就⽆效了?这个证实就⽩买了,这个时候须要⼀个新的技术,数字签名。

数字签名就是⽤ CA ⾃带的 HASH 算法对证书的内容进⾏ HASH 失去⼀个摘要,再⽤ CA 的私钥加密,最终组成数字签名。当别⼈把他的证书发过来的时候, 我再⽤同样的 Hash 算法, 再次⽣成音讯摘要,而后⽤ CA 的公钥对数字签名解密, 失去 CA 创立的音讯摘要, 两者⼀⽐, 就晓得两头有没有被⼈篡改了。这个时候就能最⼤水平保障通信的平安了。

LRU 算法

实现代码如下:

//  一个 Map 对象在迭代时会依据对象中元素的插入程序来进行
// 新增加的元素会被插入到 map 的开端,整个栈倒序查看
class LRUCache {constructor(capacity) {this.secretKey = new Map();
    this.capacity = capacity;
  }
  get(key) {if (this.secretKey.has(key)) {let tempValue = this.secretKey.get(key);
      this.secretKey.delete(key);
      this.secretKey.set(key, tempValue);
      return tempValue;
    } else return -1;
  }
  put(key, value) {
    // key 存在,仅批改值
    if (this.secretKey.has(key)) {this.secretKey.delete(key);
      this.secretKey.set(key, value);
    }
    // key 不存在,cache 未满
    else if (this.secretKey.size < this.capacity) {this.secretKey.set(key, value);
    }
    // 增加新 key,删除旧 key
    else {this.secretKey.set(key, value);
      // 删除 map 的第一个元素,即为最长未应用的
      this.secretKey.delete(this.secretKey.keys().next().value);
    }
  }
}
// let cache = new LRUCache(2);
// cache.put(1, 1);
// cache.put(2, 2);
// console.log("cache.get(1)", cache.get(1))// 返回  1
// cache.put(3, 3);// 该操作会使得密钥 2 作废
// console.log("cache.get(2)", cache.get(2))// 返回 -1 (未找到)
// cache.put(4, 4);// 该操作会使得密钥 1 作废
// console.log("cache.get(1)", cache.get(1))// 返回 -1 (未找到)
// console.log("cache.get(3)", cache.get(3))// 返回  3
// console.log("cache.get(4)", cache.get(4))// 返回  4

React 有哪些优化性能的伎俩

类组件中的优化伎俩

  • 应用纯组件 PureComponent 作为基类。
  • 应用 shouldComponentUpdate 生命周期函数来自定义渲染逻辑。

办法组件中的优化伎俩

  • 应用 React.memo 高阶函数包装组件,React.memo 能够实现相似于 shouldComponentUpdate 或者 PureComponent 的成果
  • 应用 useMemo

    • 应用 React.useMemo 精细化的管控,useMemo 管制的则是是否须要反复执行某一段逻辑,而React.memo 管制是否须要重渲染一个组件
  • 应用 useCallBack

其余形式

  • 在列表须要频繁变动时,应用惟一 id 作为 key,而不是数组下标。
  • 必要时通过扭转 CSS 款式暗藏显示组件,而不是通过条件判断显示暗藏组件。
  • 应用 Suspense 和 lazy 进行懒加载,例如:
import React, {lazy, Suspense} from "react";

export default class CallingLazyComponents extends React.Component {render() {
    var ComponentToLazyLoad = null;

    if (this.props.name == "Mayank") {ComponentToLazyLoad = lazy(() => import("./mayankComponent"));
    } else if (this.props.name == "Anshul") {ComponentToLazyLoad = lazy(() => import("./anshulComponent"));
    }

    return (
      <div>
        <h1>This is the Base User: {this.state.name}</h1>
        <Suspense fallback={<div>Loading...</div>}>
          <ComponentToLazyLoad />
        </Suspense>
      </div>
    )
  }
}

对浏览器的缓存机制的了解

浏览器缓存的全过程:

  • 浏览器第一次加载资源,服务器返回 200,浏览器从服务器下载资源文件,并缓存资源文件与 response header,以供下次加载时比照应用;
  • 下一次加载资源时,因为强制缓存优先级较高,先比拟以后工夫与上一次返回 200 时的时间差,如果没有超过 cache-control 设置的 max-age,则没有过期,并命中强缓存,间接从本地读取资源。如果浏览器不反对 HTTP1.1,则应用 expires 头判断是否过期;
  • 如果资源已过期,则表明强制缓存没有被命中,则开始协商缓存,向服务器发送带有 If-None-Match 和 If-Modified-Since 的申请;
  • 服务器收到申请后,优先依据 Etag 的值判断被申请的文件有没有做批改,Etag 值统一则没有批改,命中协商缓存,返回 304;如果不统一则有改变,间接返回新的资源文件带上新的 Etag 值并返回 200;
  • 如果服务器收到的申请没有 Etag 值,则将 If-Modified-Since 和被申请文件的最初批改工夫做比对,统一则命中协商缓存,返回 304;不统一则返回新的 last-modified 和文件并返回 200;

    很多网站的资源前面都加了版本号,这样做的目标是:每次降级了 JS 或 CSS 文件后,为了避免浏览器进行缓存,强制扭转版本号,客户端浏览器就会从新下载新的 JS 或 CSS 文件,以保障用户可能及时取得网站的最新更新。

把握页面的加载过程

网页加载流程

  • 当咱们关上网址的时候,浏览器会从服务器中获取到 HTML 内容
  • 浏览器获取到 HTML 内容后,就开始从上到下解析 HTML 的元素
  • <head>元素内容会先被解析,此时 浏览器还没开始渲染页面

    • 咱们看到 <head> 元素里有用于形容页面元数据的 <meta> 元素,还有一些 <link> 元素波及内部资源(如 图片、CSS 款式 等),此时浏览器会去获取这些内部资源。除此之外,咱们还能看到 <head> 元素中还蕴含着不少的 <script> 元素,这些 <script> 元素通过 src 属性指向内部资源
  • 当浏览器解析到这里时(步骤 3),会暂停解析并下载 JavaScript 脚本
  • 当 JavaScript 脚本下载实现后,浏览器的控制权转交给 JavaScript 引擎。当脚本执行实现后,控制权会交回给渲染引擎,渲染引擎持续往下解析 HTML 页面
  • 此时 <body> 元素内容开始被解析,浏览器开始渲染页面
  • 在这个过程中,咱们看到 <head> 中搁置的 <script> 元素会阻塞页面的渲染过程:把 JavaScript 放在 <head> 里,意味着必须把所有 JavaScript 代码都 下载、解析和解释实现后,能力开始渲染页面
  • 如果内部脚本加载工夫很长(比方始终无奈实现下载),就会造成网页长时间失去响应,浏览器就会出现“假死”状态,用户体验会变得很蹩脚
  • 因而,对于对性能要求较高、须要疾速将内容出现给用户的网页,经常会将 JavaScript 脚本放在 <body> 的最初面。这样 能够防止资源阻塞,页面得以迅速展现 。咱们还能够应用defer/async/preload 等属性来标记 <script> 标签,来管制 JavaScript 的加载程序

提早加载的形式有哪些

js 的加载、解析和执行会阻塞页面的渲染过程,因而咱们心愿 js 脚本可能尽可能的提早加载,进步页面的渲染速度。

几种形式是:

  • 将 js 脚本放在文档的底部,来使 js 脚本尽可能的在最初来加载执行
  • 给 js 脚本增加 defer 属性,这个属性会让脚本的加载与文档的解析同步解析,而后在文档解析实现后再执行这个脚本文件,这样的话就能使页面的渲染不被阻塞。多个设置了 defer 属性的脚本按标准来说最初是程序执行的,然而在一些浏览器中可能不是这样
  • 给 js 脚本增加 async属性,这个属性会使脚本异步加载,不会阻塞页面的解析过程,然而当脚本加载实现后立刻执行 js 脚本,这个时候如果文档没有解析实现的话同样会阻塞。多个 async 属性的脚本的执行程序是不可预测的,个别不会依照代码的程序顺次执行
  • 动态创建 DOM 标签的形式,咱们能够对文档的加载事件进行监听,当文档加载实现后再动静的创立 script 标签来引入 js 脚本

怎么判断页面是否加载实现

  • Load 事件触发代表页面中的 DOMCSSJS,图片曾经全副加载结束。
  • DOMContentLoaded 事件触发代表初始的 HTML 被齐全加载和解析,不须要期待 CSSJS,图片加载

强类型语言和弱类型语言的区别

  • 强类型语言:强类型语言也称为强类型定义语言,是一种总是强制类型定义的语言,要求变量的应用要严格合乎定义,所有变量都必须先定义后应用。Java 和 C ++ 等语言都是强制类型定义的,也就是说,一旦一个变量被指定了某个数据类型,如果不通过强制转换,那么它就永远是这个数据类型了。例如你有一个整数,如果不显式地进行转换,你不能将其视为一个字符串。
  • 弱类型语言:弱类型语言也称为弱类型定义语言,与强类型定义相同。JavaScript 语言就属于弱类型语言。简略了解就是一种变量类型能够被疏忽的语言。比方 JavaScript 是弱类型定义的,在 JavaScript 中就能够将字符串 ’12’ 和整数 3 进行连贯失去字符串 ’123’,在相加的时候会进行强制类型转换。

两者比照:强类型语言在速度上可能略逊色于弱类型语言,然而强类型语言带来的严谨性能够无效地帮忙防止许多谬误。

代码输入后果

const promise = Promise.resolve().then(() => {return promise;})
promise.catch(console.err)

输入后果如下:

Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>

这里其实是一个坑,.then.catch 返回的值不能是 promise 自身,否则会造成死循环。

JavaScript 有哪些数据类型,它们的区别?

JavaScript 共有八种数据类型,别离是 Undefined、Null、Boolean、Number、String、Object、Symbol、BigInt。

其中 Symbol 和 BigInt 是 ES6 中新增的数据类型:

  • Symbol 代表创立后举世无双且不可变的数据类型,它次要是为了解决可能呈现的全局变量抵触的问题。
  • BigInt 是一种数字类型的数据,它能够示意任意精度格局的整数,应用 BigInt 能够平安地存储和操作大整数,即便这个数曾经超出了 Number 可能示意的平安整数范畴。

这些数据能够分为原始数据类型和援用数据类型:

  • 栈:原始数据类型(Undefined、Null、Boolean、Number、String)
  • 堆:援用数据类型(对象、数组和函数)

两种类型的区别在于 存储地位的不同:

  • 原始数据类型间接存储在栈(stack)中的简略数据段,占据空间小、大小固定,属于被频繁应用数据,所以放入栈中存储;
  • 援用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;援用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找援用值时,会首先检索其在栈中的地址,获得地址后从堆中取得实体。

堆和栈的概念存在于数据结构和操作系统内存中,在数据结构中:

  • 在数据结构中,栈中数据的存取形式为先进后出。
  • 堆是一个优先队列,是按优先级来进行排序的,优先级能够依照大小来规定。

在操作系统中,内存被分为栈区和堆区:

  • 栈区内存由编译器主动调配开释,寄存函数的参数值,局部变量的值等。其操作形式相似于数据结构中的栈。
  • 堆区内存个别由开发着调配开释,若开发者不开释,程序完结时可能由垃圾回收机制回收。

迭代查问与递归查问

实际上,DNS 解析是一个蕴含迭代查问和递归查问的过程。

  • 递归查问 指的是查问申请收回后,域名服务器代为向下一级域名服务器发出请求,最初向用户返回查问的最终后果。应用递归 查问,用户只须要收回一次查问申请。
  • 迭代查问 指的是查问申请后,域名服务器返回单次查问的后果。下一级的查问由用户本人申请。应用迭代查问,用户须要收回 屡次的查问申请。

个别咱们向本地 DNS 服务器发送申请的形式就是递归查问,因为咱们只须要收回一次申请,而后本地 DNS 服务器返回给我 们最终的申请后果。而本地 DNS 服务器向其余域名服务器申请的过程是迭代查问的过程,因为每一次域名服务器只返回单次 查问的后果,下一级的查问由本地 DNS 服务器本人进行。

line-height 的了解及其赋值形式

(1)line-height 的概念:

  • line-height 指一行文本的高度,蕴含了字间距,实际上是下一行基线到上一行基线间隔;
  • 如果一个标签没有定义 height 属性,那么其最终体现的高度由 line-height 决定;
  • 一个容器没有设置高度,那么撑开容器高度的是 line-height,而不是容器内的文本内容;
  • 把 line-height 值设置为 height 一样大小的值能够实现单行文字的垂直居中;
  • line-height 和 height 都能撑开一个高度;

(2)line-height 的赋值形式:

  • 带单位:px 是固定值,而 em 会参考父元素 font-size 值计算本身的行高
  • 纯数字:会把比例传递给后辈。例如,父级行高为 1.5,子元素字体为 18px,则子元素行高为 1.5 * 18 = 27px
  • 百分比:将计算后的值传递给后辈

闭包的利用场景

  • 柯里化 bind
  • 模块

px、em、rem 的区别及应用场景

三者的区别:

  • px 是固定的像素,一旦设置了就无奈因为适应页面大小而扭转。
  • em 和 rem 绝对于 px 更具备灵活性,他们是绝对长度单位,其长度不是固定的,更实用于响应式布局。
  • em 是绝对于其父元素来设置字体大小,这样就会存在一个问题,进行任何元素设置,都有可能须要晓得他父元素的大小。而 rem 是绝对于根元素,这样就意味着,只须要在根元素确定一个参考值。

应用场景:

  • 对于只须要适配少部分挪动设施,且分辨率对页面影响不大的,应用 px 即可。
  • 对于须要适配各种挪动设施,应用 rem,例如须要适配 iPhone 和 iPad 等分辨率差异比拟挺大的设施。

代码输入后果

Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)

输入后果如下:

1

看到这个题目,好多的 then,实际上只须要记住一个准则:.then.catch 的参数冀望是函数,传入非函数则会产生 值透传

第一个 then 和第二个 then 中传入的都不是函数,一个是数字,一个是对象,因而产生了透传,将resolve(1) 的值间接传到最初一个 then 里,间接打印出 1。

代码输入后果

setTimeout(function () {console.log(1);
}, 100);

new Promise(function (resolve) {console.log(2);
  resolve();
  console.log(3);
}).then(function () {console.log(4);
  new Promise((resove, reject) => {console.log(5);
    setTimeout(() =>  {console.log(6);
    }, 10);
  })
});
console.log(7);
console.log(8);

输入后果为:

2
3
7
8
4
5
6
1

代码执行过程如下:

  1. 首先遇到定时器,将其退出到宏工作队列;
  2. 遇到 Promise,首先执行外面的同步代码,打印出 2,遇到 resolve,将其退出到微工作队列,执行前面同步代码,打印出 3;
  3. 继续执行 script 中的代码,打印出 7 和 8,至此第一轮代码执行实现;
  4. 执行微工作队列中的代码,首先打印出 4,如遇到 Promise,执行其中的同步代码,打印出 5,遇到定时器,将其退出到宏工作队列中,此时宏工作队列中有两个定时器;
  5. 执行宏工作队列中的代码,这里咱们须要留神是的第一个定时器的工夫为 100ms,第二个定时器的工夫为 10ms,所以先执行第二个定时器,打印出 6;
  6. 此时微工作队列为空,继续执行宏工作队列,打印出 1。

做完这道题目,咱们就须要分外留神,每个定时器的工夫,并不是所有定时器的工夫都为 0 哦。

正文完
 0