Skip to content

Latest commit

 

History

History
641 lines (515 loc) · 40.6 KB

吹出一片天.md

File metadata and controls

641 lines (515 loc) · 40.6 KB

1.说一下cdn的原理: 资源上传cdn之后,当用户访问cdn的资源地址之后会经历下面的步骤:

首先经过本地的dns解析,请求cname指向的那台cdn专用的dns服务器。 dns服务器返回全局负载均衡的服务器ip给用户 用户请求全局负载均衡服务器,服务器根据ip返回所在区域的负载均衡服务器ip给用户 用户请求区域负载均衡服务器,负载均衡服务器根据用户ip选择距离近的,并且存在用户所需内容的,负载比较合适的一台缓存服务器ip给用户。当没有对应内容的时候,会去上一级缓存服务器去找,直到找到资源所在的源站服务器,并且缓存在缓存服务器中。用户下一次在请求该资源,就可以就近拿缓存了。

注意: 因为cdn的负载均衡和就近选择缓存都是根据用户的ip来的,服务器只能拿到local dns的ip,也就是网络设置中设置的dns ip,如果这个设置的不合理,那么可能起不到加速的效果。可能就近找到的缓存服务器实际离得很远。 cdn的原理主要答出负载均衡和缓存再就是dns解析这三部分就行了吧,通过dns解析到全局负载均衡服务器,然后再到区域的负载均衡,之后根据一些条件来找合适的缓存服务器,如果第一次访问就从源站拿过来缓存。 需要注意的是一切都是根据请求的ip来的,如果ip不合理,那么可能起不到加速效果。缓存和负载均衡的思想在减轻服务器压力方面其实是很常见的。

2.如何监控网页崩溃? // 基于 Service Worker 的崩溃统计方案 // 随着 PWA 概念的流行,大家对 Service Worker 也逐渐熟悉起来。基于以下原因,我们可以使用 Service Worker 来实现网页崩溃的监控:

// Service Worker 有自己独立的工作线程,与网页区分开,网页崩溃了,Service Worker 一般情况下不会崩溃;
// Service Worker 生命周期一般要比网页还要长,可以用来监控网页的状态;
// 网页可以通过navigator.serviceWorker.controller.postMessage API 向掌管自己的 SW 发送消息。

// 基于以上几点,我们可以实现一种基于心跳检测的监控方案:

// p1:网页加载后,通过postMessageAPI 每5s给 sw 发送一个心跳,表示自己的在线,sw 将在线的网页登记下来,更新登记时间;
// p2:网页在beforeunload时,通过postMessageAPI 告知自己已经正常关闭,sw 将登记的网页清除;
// p3:如果网页在运行的过程中 crash 了,sw 中的running状态将不会被清除,更新时间停留在奔溃前的最后一次心跳;
// sw:Service Worker 每10s查看一遍登记中的网页,发现登记时间已经超出了一定时间(比如 15s)即可判定该网页 crash 了。

3.介绍下 npm 模块安装机制,为什么输入 npm install 就可以自动安装对应的模块?
1. npm 模块安装机制:

发出npm install命令 查询node_modules目录之中是否已经存在指定模块 若存在,不再重新安装 若不存在 npm 向 registry 查询模块压缩包的网址 下载压缩包,存放在根目录下的.npm目录里 解压压缩包到当前项目的node_modules目录 2. npm 实现原理 输入 npm install 命令并敲下回车后,会经历如下几个阶段(以 npm 5.5.1 为例): 执行工程自身 preinstall 当前 npm 工程如果定义了 preinstall 钩子此时会被执行。 确定首层依赖模块 首先需要做的是确定工程中的首层依赖,也就是 dependencies 和 devDependencies 属性中直接指定的模块(假设此时没有添加 npm install 参数)。 工程本身是整棵依赖树的根节点,每个首层依赖模块都是根节点下面的一棵子树,npm 会开启多进程从每个首层依赖模块开始逐步寻找更深层级的节点。 获取模块 获取模块是一个递归的过程,分为以下几步: 获取模块信息。在下载一个模块之前,首先要确定其版本,这是因为 package.json 中往往是 semantic version(semver,语义化版本)。此时如果版本描述文件(npm-shrinkwrap.json 或 package-lock.json)中有该模块信息直接拿即可,如果没有则从仓库获取。如 packaeg.json 中某个包的版本是 ^1.1.0,npm 就会去仓库中获取符合 1.x.x 形式的最新版本。 获取模块实体。上一步会获取到模块的压缩包地址(resolved 字段),npm 会用此地址检查本地缓存,缓存中有就直接拿,如果没有则从仓库下载。 查找该模块依赖,如果有依赖则回到第1步,如果没有则停止。 模块扁平化(dedupe) 上一步获取到的是一棵完整的依赖树,其中可能包含大量重复模块。比如 A 模块依赖于 loadsh,B 模块同样依赖于 lodash。在 npm3 以前会严格按照依赖树的结构进行安装,因此会造成模块冗余。 从 npm3 开始默认加入了一个 dedupe 的过程。它会遍历所有节点,逐个将模块放在根节点下面,也就是 node-modules 的第一层。当发现有重复模块时,则将其丢弃。 这里需要对重复模块进行一个定义,它指的是模块名相同且 semver 兼容。每个 semver 都对应一段版本允许范围,如果两个模块的版本允许范围存在交集,那么就可以得到一个兼容版本,而不必版本号完全一致,这可以使更多冗余模块在 dedupe 过程中被去掉。 比如 node-modules 下 foo 模块依赖 lodash@^1.0.0,bar 模块依赖 lodash@^1.1.0,则 ^1.1.0 为兼容版本。 而当 foo 依赖 lodash@^2.0.0,bar 依赖 lodash@^1.1.0,则依据 semver 的规则,二者不存在兼容版本。会将一个版本放在 node_modules 中,另一个仍保留在依赖树里。 举个例子,假设一个依赖树原本是这样: node_modules -- foo ---- lodash@version1 -- bar ---- lodash@version2 假设 version1 和 version2 是兼容版本,则经过 dedupe 会成为下面的形式: node_modules -- foo -- bar -- lodash(保留的版本为兼容版本) 假设 version1 和 version2 为非兼容版本,则后面的版本保留在依赖树中: node_modules -- foo -- lodash@version1 -- bar ---- lodash@version2 安装模块 这一步将会更新工程中的 node_modules,并执行模块中的生命周期函数(按照 preinstall、install、postinstall 的顺序)。 执行工程自身生命周期 当前 npm 工程如果定义了钩子此时会被执行(按照 install、postinstall、prepublish、prepare 的顺序)。 最后一步是生成或更新版本描述文件,npm install 过程完成。

4.cookie 和 token 都存放在 header 中,为什么不会劫持 token? 首先token不是防止XSS的,而是为了防止CSRF的; CSRF攻击的原因是浏览器会自动带上cookie,而浏览器不会自动带上token

5.为什么 Vuex 的 mutation 和 Redux 的 reducer 中不能做异步操作 因为更改state的函数必须是纯函数,纯函数既是统一输入就会统一输出,没有任何副作用;如果是异步则会引入额外的副作用,导致更改后的state不可预测; 事实上在 vuex 里面 actions 只是一个架构性的概念,并不是必须的,说到底只是一个函数,你在里面想干嘛都可以,只要最后触发 mutation 就行。异步竞态怎么处理那是用户自己的事情。vuex 真正限制你的只有 mutation 必须是同步的这一点(在 redux 里面就好像 reducer 必须同步返回下一个状态一样)。

同步的意义在于这样每一个 mutation 执行完成后都可以对应到一个新的状态(和 reducer 一样),这样 devtools 就可以打个 snapshot 存下来,然后就可以随便 time-travel 了。如果你开着 devtool 调用一个异步的 action,你可以清楚地看到它所调用的 mutation 是何时被记录下来的,并且可以立刻查看它们对应的状态。其实我有个点子一直没时间做,那就是把记录下来的 mutations 做成类似 rx-marble 那样的时间线图,对于理解应用的异步状态变化很有帮助。

6.在 Vue 中,子组件为何不可以修改父组件传递的 Prop,如果修改了,Vue 是如何监控到属性的修改并给出警告的。 #60 原因很简单,一个父组件下不只有你一个子组件。 同样,使用这份 prop 数据的也不只有你一个子组件。 如果每个子组件都能修改 prop 的话,将会导致修改数据的源头不止一处。

所以我们需要将修改数据的源头统一为父组件,子组件像要改 prop 只能委托父组件帮它。从而保证数据修改源唯一

7:模拟实现一个 Promise.finally

  1. a.b.c.d 和 a['b']['c']['d'],哪个性能更高 应该是 a.b.c.d 比 a['b']['c']['d'] 性能高点,后者还要考虑 [ ] 中是变量的情况,再者,从两种形式的结构来看,显然编译器解析前者要比后者容易些,自然也就快一点。 下图是两者的 AST 对比:Advanced-Frontend/Daily-Interview-Question#111

9.如何解决移动端 Retina 屏 1px 像素问题

第 93 题:给定两个大小为 m 和 n 的有序数组 nums1 和 nums2。请找出这两个有序数组的中位数。要求算法的时间复杂度为 O(log(m+n))。

React 和 Vue 的 diff 时间复杂度从 O(n^3) 优化到 O(n) ,那么 O(n^3) 和 O(n) 是如何计算出来的?

考虑到性能问题,如何快速从一个巨大的数组中随机获取部分元素 快速生成一个巨大数组 使用Array.from() 通过Set特性,存放随机数,这里需要注意的是,没有就add,有就递归, 总之要保证遍历的每一项都要找到一个唯一随机值,如果有就跳过就不能保证最后能获取到10k个值。 const randomNumHandle = (len, randomNum) => { // 快速生成一个有len个元素的巨大数组 let originArr = Array.from({length: len}, (v, i) => i); let resultSet = new Set()

// 快速选取randomNum个元素 for(let i = 0; i < randomNum; i++) { addNum(resultSet, originArr) }

function addNum () { let luckDog = Math.floor(Math.random() * (len - 1))

if(!resultSet.has(originArr[luckDog])) {
  resultSet.add(originArr[luckDog])
} else {
  addNum()
}

}

return Array.from(resultSet) }

// 比如有个数组有100K个元素,从中不重复随机选取10K个元素 console.log(randomNumHandle(100000, 10000))

数组里面有10万个数据,取第一个元素和第10万个元素的时间相差多少 JavaScript 没有真正意义上的数组,所有的数组其实是对象,其“索引”看起来是数字,其实会被转换成字符串,作为属性名(对象的 key)来使用。所以无论是取第 1 个还是取第 10 万个元素,都是用 key 精确查找哈希表的过程,其消耗时间大致相同。 @lvtraveler 请帮忙测试下稀松数组。

在输入框中如何判断输入的是一个正确的网址 function searchUrl(url) { try { if (new URL(url) && (new URL(url).protocol === "http:" || new URL(url).protocol === "https:") && url.match(new RegExp(new URL(url).protocol + "//")).index === 0) return true } catch (err) { console.log("不是一个正确的网址"); } };

为什么 HTTP1.1 不能实现多路复用(腾讯) HTTP/1.1 不是二进制传输,而是通过文本进行传输。由于没有流的概念,在使用并行传输(多路复用) 传递数据时,接收端在接收到响应后,并不能区分多个响应分别对应的请求, 所以无法将多个响应的结果重新进行组装,也就实现不了多路复用。

第 156 题:求最终 left、right 的宽度

<style> * { padding: 0; margin: 0; } .container { width: 600px; height: 300px; display: flex; } .left { flex: 1 2 300px; background: red; } .right { flex: 2 1 200px; background: blue; } </style>

flex-grow 指定剩余空间以什么样的比例(增长系数 / 增长系数总和)分配给元素 子元素的 flex-grow 的值分别为 1, 2 剩余空间:600 - 300 - 200 = 100 两个元素的宽度分别为 300 + 100 * 1 / 3 = 333.33px 200 + 100 * 2 / 3 = 266.67px

第 155 题:求最终 left、right 的宽度

<style> * { padding: 0; margin: 0; } .container { width: 600px; height: 300px; display: flex; } .left { flex: 1 2 500px; background: red; } .right { flex: 2 1 400px; background: blue; } </style>

子元素的 flex-shrink 的值分别为 2,1 溢出:500+400 - 600 = 300。 总权重为 2 * 500+ 1 * 400 = 1400 两个元素分别收缩: 300 * 2(flex-shrink) * 500(width) / 1400= 214.28 300 * 1(flex-shrink) * 400(width) / 1400= 85.72 三个元素的最终宽度分别为: 500 - 214.28 = 285.72 400 - 85.72 = 314.28

第 153 题:实现一个批量请求函数 multiRequest(urls, maxNum) 要求如下: 要求最大并发数 maxNum 每当有一个请求返回,就留下一个空位,可以增加新的请求 所有请求完成后,结果按照 urls 里面的顺序依次打出 Advanced-Frontend/Daily-Interview-Question#378

谈一谈 nextTick 的原理

Vue 中的 computed 是如何实现的

//老鼠试毒 //鸡蛋掉落 //64匹马、8赛道,至少多少轮比赛找出速度最快的4匹马?

第 145 题:前端项目如何找出性能瓶颈(阿里)

如何实现骨架屏,说说你的思路

第 135 题:算法题(盛大) 在一个字符串数组中有红、黄、蓝三种颜色的球,且个数不相等、顺序不一致,请为该数组排序。使得排序后数组中球的顺序为:黄、红、蓝。 例如:红蓝蓝黄红黄蓝红红黄红,排序后为:黄黄黄红红红红红蓝蓝蓝。Advanced-Frontend/Daily-Interview-Question#266

第 49 题:为什么通常在发送数据埋点请求的时候使用的是 1x1 像素的透明 gif 图片? 能够完成整个 HTTP 请求+响应(尽管不需要响应内容) 触发 GET 请求之后不需要获取和处理数据、服务器也不需要发送数据 跨域友好 执行过程无阻塞 相比 XMLHttpRequest 对象发送 GET 请求,性能上更好 GIF的最低合法体积最小(最小的BMP文件需要74个字节,PNG需要67个字节,而合法的GIF,只需要43个字节)

HTTPS 握手过程中,客户端如何验证证书的合法性 (1)首先浏览器读取证书中的证书所有者、有效期等信息进行校验,校验证书的网站域名是否与证书颁发的域名一致,校验证书是否在有效期内 (2)浏览器开始查找操作系统中已内置的受信任的证书发布机构CA,与服务器发来的证书中的颁发者CA比对,用于校验证书是否为合法机构颁发 (3)如果找不到,浏览器就会报错,说明服务器发来的证书是不可信任的。 (4)如果找到,那么浏览器就会从操作系统中取出颁发者CA 的公钥(多数浏览器开发商发布 版本时,会事先在内部植入常用认证机关的公开密钥),然后对服务器发来的证书里面的签名进行解密 (5)浏览器使用相同的hash算法计算出服务器发来的证书的hash值,将这个计算的hash值与证书中签名做对比 (6)对比结果一致,则证明服务器发来的证书合法,没有被冒充

  1. 三栏布局的实现及优缺点: 布局方案 实现 优点 缺点

Float布局 左右中三列,左列左浮动,右列右浮动,中间列设置左右margin 比较简单,兼容性也比较好 浮动元素脱离文档流,使用的时候只需要注意一定要清除浮动。

Position布局 左中右三列(无顺序),根据定位属性去直接设置各个子元素位置 快捷,设置很方便 元素脱离了文档流,后代元素也脱离了文档流,高度未知的时候,会有问题,有效性和可使用性比较差

Table布局 左中右三列,父元素display: table;子元素display: table-cell;居中子元素不设宽度 使用起来方便,兼容性也不存在问题 ①无法设置栏边距;②对seo不友好;③当其中一个单元格高度超出的时候,两侧的单元格也是会跟着一起变高的

Flex布局 左中右三列,父元素display: flex;两侧元素设宽;居中子元素flex: 1; 比较完美 存在IE上兼容性问题,只能支持到IE9以上

Grid布局 左中右三列,父元素display: grid;利用网格实现 最强大和最简单 兼容性不好,IE10+上支持,而且也仅支持部分属性

  1. 文字单行显示/三行显示

单行文本溢出隐藏变为...

p { /* 隐藏元素溢出内容 / overflow: hidden; / 单行显示 / white-space: nowrap; / 溢出显示省略号 */ text-overflow: ellipsis; } 复制代码 多行文本溢出隐藏变为...

p { overflow: hidden; /* 将对象作为弹性伸缩盒子模型显示 。 / display: -webkit-box; / 限制在一个块元素显示的文本的行数,即行数设置 / -webkit-line-clamp: 3; / 规定框从上向下垂直排列子元素 */ -webkit-box-orient: vertical; }

Vue路由传参--------params和query的区别 背景:项目中需要跨页面传值,如试题id,遇到了刷新后,传的值消失,所以研究了以下两者的区别

params只能用name来引入路由,query用path来引入 params类似于post,query更加类似于我们ajax中get传参,说的再简单一点,前者在浏览器地址栏中不显示参数,后者显示,所以params传值相对安全一些。 取值用法类似分别是this.route.query.name。 params传值一刷新就没了,query传值刷新还存在

this.$router.push({ path:"/detail", params:{ name:'nameValue', code:10011 } }); this.$router.push({ path:'/xxx' query:{ id:id } }) 复制代码总结:少量参数可以用此方法,如果有大量公共数据,可以采用Vuex或本地存储的方式。

diff算法的缺点

nextTick中的waiting是什么时候变为true的呢

Get和Post 幂等性的问题 王大叔爱编程 发布于 2014/09/29 14:58 阅读 1K+ 收藏 0 答案 5 幂等意思就是说,一个方法无论执行多少次,结果都会是一样,说Get是幂等的,Post不是幂等的,但是我就有一个问题了,Get同样可以更新服务器,例如我可以在我的doGet方法里面增加一个update 某张表某条记录的操作,这样就更新了服务器,返回的结果肯定就不一样了,那为什么还要说Get是幂等的呢?高手指点一下迷津,谢谢。

Get就一定是幂等的吗

null为什么被typeof错误的判断为了'object' 为什么会出现这种情况呢?因为在 JS 的最初版本中,使用的是 32 位系统,为了性能考虑使用低位存储了变量的类型信息,000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object 。虽然现在的内部类型判断代码已经改变了,但是对于这个 Bug 却是一直流传下来。

移动端适配1px的问题 产生原因:与DPR(devicePixelRatio)设备像素比有关,它表示默认缩放为100%的情况下,设备像素和CSS像素的比值:物理像素 /CSS像素。 目前主流的屏幕DPR=2 (iPhone 8),或者3 (iPhone 8 Plus)。拿2倍屏来说,设备的物理像素要实现1像素,而DPR=2,所以css 像素只能是 0.5。一般设计稿是按照750来设计的,它上面的1px是以750来参照的,而我们写css样式是以设备375为参照的,所以我们应该写的0.5px就好了啊! 试过了就知道,iOS 8+系统支持,安卓系统不支持。 我常用的解决方案是 使用伪元素,为伪元素设置绝对定位,并且和父元素左上角对齐。将伪元素的长和宽先放大2倍,然后再设置一个边框,以左上角为中心,缩放到原来的0.5倍 除了这个方法之外还有使用图片代替,或者使用box-shadow代替,但是没有伪元素效果好。

居中为什么要使用transform(为什么不使用marginLeft/Top) transform transform 属于合成属性(composite property),对合成属性进行 transition/animation 动画将会创建一个合成层(composite layer),这使得被动画元素在一个独立的层中进行动画。通常情况下,浏览器会将一个层的内容先绘制进一个位图中,然后再作为纹理(texture)上传到 GPU,只要该层的内容不发生改变,就没必要进行重绘(repaint),浏览器会通过重新复合(recomposite)来形成一个新的帧。 margin top / left top/left属于布局属性,该属性的变化会导致重排(reflow/relayout),所谓重排即指对这些节点以及受这些节点影响的其它节点,进行CSS计算->布局->重绘过程,浏览器需要为整个层进行重绘并重新上传到 GPU,造成了极大的性能开销。

大文件分片上传,断点续传 思路:核心是利用 Blob.prototype.slice 方法,和数组的 slice 方法相似,调用的 slice 方法可以返回原文件的某个切片 这样我们就可以根据预先设置好的切片最大数量将文件切分为一个个切片,然后借助 http 的可并发性,调用Promise.all同时上传多个切片,这样从原本传一个大文件,变成了同时传多个小的文件切片,可以大大减少上传时间 另外由于是并发,传输到服务端的顺序可能会发生变化,所以我们还需要给每个切片记录顺序。 当全部分片上传成功,通知服务端进行合并。当有一个分片上传失败时,提示“上传失败”。在重新上传时,通过文件 MD5 得到文件的上传状态,当服务器已经有该 MD5 对应的切片时,代表该切片已经上传过,无需再次上传,当服务器找不到该 MD5 对应的切片时,代表该切片需要上传,用户只需上传这部分切片,就可以完整上传整个文件,这就是文件的断点续传。

Async/Await内部实现 async 做了什么 async 函数会返回一个 Promise 对象,如果在函数中 return 一个直接量,async 会把这个直接量通过 Promise.resolve() 封装成 Promise 对象。 await 在等啥 await 可以用于等待一个 async 函数的返回值,注意到 await 不仅仅用于等 Promise 对象,它可以等任意表达式的结果,所以,await 后面实际是可以接普通函数调用或者直接量的。 await 等到了要等的,然后呢 如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。 如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。 async/await 优势在哪? async/await 的优势在于处理 then 链,尤其是每一个步骤都需要之前每个步骤的结果时,async/await 的代码对比promise非常清晰明了,简直像同步代码一样。 实现原理 async/await 就是 Generator 的语法糖,使得异步操作变得更加方便。async 函数就是将 Generator 函数的星号(*)替换成 async,将 yield 替换成await。 不一样的是:

async函数内置执行器,函数调用之后,会自动执行,输出最后结果。而Generator需要调用next。 返回值是Promise,async函数的返回值是 Promise 对象,Generator的返回值是 Iterator(迭代器),Promise 对象使用起来更加方便。

React Fiber react在进行组件渲染时,从setState开始到渲染完成整个过程是同步的(“一气呵成”)。如果需要渲染的组件比较庞大,js执行会占据主线程时间较长,会导致页面响应度变差,使得react在动画、手势等应用中效果比较差。 卡顿原因:Stack(原来的算法)的工作流程很像函数的调用过程。父组件里调子组件,可以类比为函数的递归。在setState后,react会立即开始从父节点开始遍历,以找出不同。将所有的Virtual DOM遍历完成后,才能给出当前需要修改真实DOM的信息,并传递给renderer,进行渲染,然后屏幕上才会显示此次更新内容。对于特别庞大的vDOM树来说,这个过程会很长(x00ms),在这期间,主线程是被js占用的,因此任何交互、布局、渲染都会停止,给用户的感觉就是页面被卡住了。 为了解决这个问题,react团队经过两年的工作,重写了react中核心算法。并在v16版本中发布了这个新的特性,简称为Fiber。 Fiber实现了自己的组件调用栈,它以链表的形式遍历组件树,可以灵活的暂停、继续和丢弃执行的任务。实现方式是使用了浏览器的requestIdleCallback这一 API。官方的解释是这样的:

window.requestIdleCallback()会在浏览器空闲时期依次调用函数,这就可以让开发者在主事件循环中执行后台或低优先级的任务,而且不会对像动画和用户交互这些延迟触发但关键的事件产生影响。函数一般会按先进先调用的顺序执行,除非函数在浏览器调用它之前就到了它的超时时间。

因为浏览器是单线程,它将GUI描绘,时间器处理,事件处理,JS执行,远程资源加载统统放在一起。当做某件事,只有将它做完才能做下一件事。如果有足够的时间,浏览器是会对我们的代码进行编译优化。只有让浏览器休息好,他才能跑的更快。

React 优化 代码分割 import() lazy + Suspense

你学BFF和Serverless了吗

用 Vue 编写抽象组件 weixin_34341229 2019-05-06 03:45:03 85 收藏 展开 看过 Vue 源码的同学可以知道,、、等组件 组件的实现是一个对象,注意它有一个属性 abstract 为 true,表明是它一个抽象组件。 Vue 的文档没有提这个概念,在抽象组件的生命周期过程中,我们可以对包裹的子组件监听的事件进行拦截,也可以对子组件进行 Dom 操作,从而可以对我们需要的功能进行封装,而不需要关心子组件的具体实现。

之前在网络通信和通用数据交换等应用场景中经常使用的技术是 JSON 或 XML,而在最近的开发中接触到了 Google 的 ProtoBuf。

Plugin 的特点

是一个独立的模块 模块对外暴露一个 js 函数 函数的原型 (prototype) 上定义了一个注入 compiler 对象的 apply方法 apply 函数中需要有通过 compiler 对象挂载的 webpack 事件钩子,钩子的回调中能拿到当前编译的 compilation 对象,如果是异步编译插件的话可以拿到回调 callback 完成自定义子编译流程并处理 complition 对象的内部数据 如果异步编译插件的话,数据处理完成后执行 callback 回调。

网易有道: 第一个场景问题 比如直播的场景,你应该知道吧,你需要实现一个这样子的场景,比如某个老师点击某个地方,比如U盘,你这个时候需要展示U盘的动画效果,比如这个时候,老师点击这个电脑屏幕,你需要展示一个小电脑的动画效果,向上述这样子,需要在特定的时间点,完成特定的动画效果。

嗯,这个问题,我的想法是,动画是例外加上去的,如果说是直接后期处理的话,那应该跟我们前端的关系不大了,所以我们接下来的问题,就是如何去处理,时间同步的问题,怎么在具体的时间点,开始展示动画呢 第二个问题,假设我们可以获取到某个时间点的动画,那么接下来,小哥哥,给我们提出了一个新的问题,就是当你的网络拥塞时,比如有延迟的时候,这个时候,出现卡顿的效果,原本需要5秒播放完的,可能需要7秒,那么你是如何去解决动画同步的?

嗯,我没有做过这种类似的问题,所以回答起来感觉很吃力,有了解的小伙伴可以评论留下你们的答案,我虚心学习。 第二个场景问题 有一个场景,在一个输入框输入内容,怎么更加高效的去提示用户你输入的信息,举个例子,你输入天猫,那么对应的提示信息是天猫商城,天猫集团,这个信息如何最快的获取,有没有不需要发请求的方式来实现?

比如数据请求的时候,尽量发的请求少,那么可以做防抖和节流处理 接下来的小哥哥,给了新的场景,当你的服务器挂了,数据取不到,那如何设计一个小型的本地数据库 接下来问题就是如何设计一个本地的数据,优化的点,就是尽可能的快,每次查询数据尽可能的快 我的第一个思路,是key-value这样子去设计,但是随后就被小哥哥给质疑出问题了,举个例子,如果按照你的想法,如果关键字为 天,天猫,这样子设计数据的话,就会存在被数据重复,这样子显然是不合理的。 想了很久,这个时候,既然有前缀重合的情况,那么是不是有一种数据结构可以解决这个问题呢?👇 字典树,我们可以在本地建立一个预读的字典树,这样子的话,根据用户输入的内容,查字典树,这个时间复杂度大概就是O(m+n),所以很大程度上加快了查找效率。

当然了,有更好的解决办法的话,可以留下你们的答案,看看你们是如何解决问题的。 第三个场景问题 Git版本工具你使用过吧,那你能不能实现一个这样子的效果,完成Git Diff算法,比较两个文件的不同,然后说一说具体的思路,这个过程怎么去比较的?两个文件不同的位置如何标注出来,如何找出这个不同,具体说一说思路。

一开始我想的是diff算法,比如是vue中的虚拟dom算法,但是感觉不对啊,diff是做了优化的,这里很明显不合理,于是这个方法就不合理了。 那么两个文件,该如何快速的找到对应的两者文件的差别呢?这个问题想了好久,嗯,当时自己好像是口胡了一些思路,比如去逐行逐行的比较,这样子的话,其实也不是很合理的,仔细想一想不行 小哥哥提示了我,我们是不是要去找最长的公共子串,这个是时候,我应该想起来这个是两个串的LCS,应该就是经典的动态规划问题,最后一个问题,确实没有想到这个,可能就是很久没有接触这类动态规划问题了。 核心应该是动态规划解决LCS,以及后续的处理,可以去看看有些文章,写的很不错,我这里就不张开啦。

js函数注释规范:https://jsdoc.app/tags-param.html ts结合jsdoc规范:https://juejin.im/post/6862981984801521672 符号 用法 @param @param {类型} 参数名 描述 @return @return {类型} 描述 @author @author 作者 @version @version 版本号 /**

  • 测试
  • @param {number} num
  • @return {void} */ function test(num) { console.log(num); } 其他

@api: 提供给第三方使用的接口 @author: 标明作者 @param: 参数 @return: 返回值 @todo: 待办 @version: 版本号 @inheritdoc: 文档继承 @property: 类属性 @property-read: 只读属性 @property-write: 只写属性 @const: 常量 @deprecated: 过期方法 @example: 示例 @final: 标识类是终态, 禁止派生 @global: 指明引用的全局变量 @static: 标识类、方法、属性是静态的 @ignore: 忽略 @internal: 限内部使用 @license: 协议 @link: 链接,引用文档等 @see: 与 link 类似, 可以访问内部方法或类 @method: 方法 @package: 命名空间 @since: 从指定版本开始的变动 @throws: 抛出异常 @uses: 使用 @var: 变量 @copyright: 版权声明

// * 字段解析 来源网络 // 获取 performance 数据 var performance = { // memory 是非标准属性,只在 Chrome 有 memory: { usedJSHeapSize: 16100000, // JS 对象(包括V8引擎内部对象)占用的内存,一定小于 totalJSHeapSize totalJSHeapSize: 35100000, // 可使用的内存 jsHeapSizeLimit: 793000000 // 内存大小限制 },

//  哲学问题:我从哪里来?
navigation: {
    redirectCount: 0, // 如果有重定向的话,页面通过几次重定向跳转而来
    type: 0           // 0   即 TYPE_NAVIGATENEXT 正常进入的页面(非刷新、非重定向等)
                      // 1   即 TYPE_RELOAD       通过 window.location.reload() 刷新的页面
                      // 2   即 TYPE_BACK_FORWARD 通过浏览器的前进后退按钮进入的页面(历史记录)
                      // 255 即 TYPE_UNDEFINED    非以上方式进入的页面
},

timing: {
    // 在同一个浏览器上下文中,前一个网页(与当前页面不一定同域)unload 的时间戳,如果无前一个网页 unload ,则与 fetchStart 值相等
    navigationStart: 1441112691935,

    // 前一个网页(与当前页面同域)unload 的时间戳,如果无前一个网页 unload 或者前一个网页与当前页面不同域,则值为 0
    unloadEventStart: 0,

    // 和 unloadEventStart 相对应,返回前一个网页 unload 事件绑定的回调函数执行完毕的时间戳
    unloadEventEnd: 0,

    // 第一个 HTTP 重定向发生时的时间。有跳转且是同域名内的重定向才算,否则值为 0 
    redirectStart: 0,

    // 最后一个 HTTP 重定向完成时的时间。有跳转且是同域名内部的重定向才算,否则值为 0 
    redirectEnd: 0,

    // 浏览器准备好使用 HTTP 请求抓取文档的时间,这发生在检查本地缓存之前
    fetchStart: 1441112692155,

    // DNS 域名查询开始的时间,如果使用了本地缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等
    domainLookupStart: 1441112692155,

    // DNS 域名查询完成的时间,如果使用了本地缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等
    domainLookupEnd: 1441112692155,

    // HTTP(TCP) 开始建立连接的时间,如果是持久连接,则与 fetchStart 值相等
    // 注意如果在传输层发生了错误且重新建立连接,则这里显示的是新建立的连接开始的时间
    connectStart: 1441112692155,

    // HTTP(TCP) 完成建立连接的时间(完成握手),如果是持久连接,则与 fetchStart 值相等
    // 注意如果在传输层发生了错误且重新建立连接,则这里显示的是新建立的连接完成的时间
    // 注意这里握手结束,包括安全连接建立完成、SOCKS 授权通过
    connectEnd: 1441112692155,

    // HTTPS 连接开始的时间,如果不是安全连接,则值为 0
    secureConnectionStart: 0,

    // HTTP 请求读取真实文档开始的时间(完成建立连接),包括从本地读取缓存
    // 连接错误重连时,这里显示的也是新建立连接的时间
    requestStart: 1441112692158,

    // HTTP 开始接收响应的时间(获取到第一个字节),包括从本地读取缓存
    responseStart: 1441112692686,

    // HTTP 响应全部接收完成的时间(获取到最后一个字节),包括从本地读取缓存
    responseEnd: 1441112692687,

    // 开始解析渲染 DOM 树的时间,此时 Document.readyState 变为 loading,并将抛出 readystatechange 相关事件
    domLoading: 1441112692690,

    // 完成解析 DOM 树的时间,Document.readyState 变为 interactive,并将抛出 readystatechange 相关事件
    // 注意只是 DOM 树解析完成,这时候并没有开始加载网页内的资源
    domInteractive: 1441112693093,

    // DOM 解析完成后,网页内资源加载开始的时间
    // 在 DOMContentLoaded 事件抛出前发生
    domContentLoadedEventStart: 1441112693093,

    // DOM 解析完成后,网页内资源加载完成的时间(如 JS 脚本加载执行完毕)
    domContentLoadedEventEnd: 1441112693101,

    // DOM 树解析完成,且资源也准备就绪的时间,Document.readyState 变为 complete,并将抛出 readystatechange 相关事件
    domComplete: 1441112693214,

    // load 事件发送给文档,也即 load 回调函数开始执行的时间
    // 注意如果没有绑定 load 事件,值为 0
    loadEventStart: 1441112693214,

    // load 事件的回调函数执行完毕的时间
    loadEventEnd: 1441112693215
}

};

禁止使用iframe(阻塞父文档onload事件); *iframe会阻塞主页面的Onload事件; *搜索引擎的检索程序无法解读这种页面,不利于SEO; *iframe和主页面共享连接池,而浏览器对相同域的连接有限制,所以会影响页面的并行加载。

    使用iframe之前需要考虑这两个缺点。如果需要使用iframe,最好是通过javascript
    动态给iframe添加src属性值,这样可以绕开以上两个问题。

    目前谷歌浏览器的多进程架构

最新的谷歌浏览器包括了:1个浏览器(Browser)主进程,1个GPU进程,一个网络(NetWork)进程,多个渲染进程和多个插件进程。

浏览器进程:负责页面显示、用户交互、子进程管理、提供存储等功能 渲染进程:核心任务是将HTML、CSS和Js转换为可以与用户交互的网页,排版进程Blink和Js的V8引擎运行在该进程中,默认情况下,Chrome会为每一个新开标签创建一个新的渲染进程(还会受同一站点的影响,下一题解释),每一个渲染进程运行在安全沙箱下, GPU进程:实现3D CSS效果,绘制网页的UI界面 网络进程:负责页面的网络资源加载 插件进程:负责插件的运行,每一个插件对应一个线程。单独开一个线程主要是为了防止插件崩溃而对网页造成影响

client向server发送请求https://baidu.com,然后连接到server的443端口,发送的信息主要是随机值1和客户端支持的加密算法。 server接收到信息之后给予client响应握手信息,包括随机值2和匹配好的协商加密算法,这个加密算法一定是client发送给server加密算法的子集。 随即server给client发送第二个响应报文是数字证书。服务端必须要有一套数字证书,可以自己制作,也可以向组织申请。区别就是自己颁发的证书需要客户端验证通过,才可以继续访问,而使用受信任的公司申请的证书则不会弹出提示页面,这套证书其实就是一对公钥和私钥。传送证书,这个证书其实就是公钥,只是包含了很多信息,如证书的颁发机构,过期时间、服务端的公钥,第三方证书认证机构(CA)的签名,服务端的域名信息等内容。 客户端解析证书,这部分工作是由客户端的TLS来完成的,首先会验证公钥是否有效,比如颁发机构,过期时间等等,如果发现异常,则会弹出一个警告框,提示证书存在问题。如果证书没有问题,那么就生成一个随即值(预主秘钥)。 客户端认证证书通过之后,接下来是通过随机值1、随机值2和预主秘钥组装会话秘钥。然后通过证书的公钥加密会话秘钥。 传送加密信息,这部分传送的是用证书加密后的会话秘钥,目的就是让服务端使用秘钥解密得到随机值1、随机值2和预主秘钥。 服务端解密得到随机值1、随机值2和预主秘钥,然后组装会话秘钥,跟客户端会话秘钥相同。 客户端通过会话秘钥加密一条消息发送给服务端,主要验证服务端是否正常接受客户端加密的消息。 同样服务端也会通过会话秘钥加密一条消息回传给客户端,如果客户端能够正常接受的话表明SSL层连接建立完成了。

四则运算的表达式可以分为三种: 前缀表达式(prefix expression),又称波兰表达式 中缀表达式(infix expression) 后缀表达式(postfix expression),又称为逆波兰式表达式 如果将表达式的操作作为叶子节点,运算符作为父节点(假设只有四则运算),这些节点刚好可以组成一颗二叉树。 比如表达式:A / B + C * D - E ,如果对这颗二叉树进行遍历 前序遍历:- + / A B * C D E ,刚好就是前缀表达式(波兰表达式) 中序遍历:A / B + C * D - E,刚好就是中缀表达式 后序遍历:A B / C D * + E -,刚好就是后缀表达式(逆波兰表达式) https://www.cnblogs.com/tandaxia/p/11234454.html

Scavenge为新生代采用的算法,是一种采用复制的方式实现的垃圾回收算法。它将内存分为from和to两个空间。每次gc,会将from空间的存活对象复制到to空间。然后两个空间角色对换(又称反转)。 该算法是牺牲空间换时间,所以适合新生代,因为它的对象生存周期较短。

content-visibility——只需一行CSS代码,让长列表网页的渲染性能提升几倍以上!