浏览器、 http
1、浏览器输入地址回车发生了什么
- 解析URL是否合法
- 浏览器查看缓存,如果请求资源在缓存中并且新鲜,跳转到转码步骤
- 如果资源未缓存,发起新请求
- 如果已缓存,检验是否足够新鲜,足够新鲜直接提供给客户端,否则与服务器进行验证。
- 检验新鲜通常有两个HTTP头进行控制Expires和Cache-Control:
- HTTP1.0提供Expires,值为一个绝对时间表示缓存新鲜日期
- HTTP1.1增加了Cache-Control: max-age=,值为以秒为单位的最大新鲜时间
- 浏览器解析URL获取协议,主机,端口,path
- 浏览器组装一个HTTP(GET)请求报文
- 浏览器获取主机ip地址,过程如下:
- 浏览器缓存
- 本机缓存,存储在系统运行内存多的缓存
- hosts文件
- 路由器缓存
- 都没有,再去域名服务器上查找,直到找到顶级DNS服务器
- 与目标服务器建立TCP链接,TCP三次握手:
- 客户端发送一个TCP的SYN=1,Seq=X的包到服务器
- 服务器收到包之后,发回SYN=1, ACK=X+1, Seq=Y的响应包
- 客户端收到包,将状态切换为发送ACK=Y+1, Seq=Z
- TCP链接建立后发送HTTP请求
- 服务器接受请求并解析
- 服务器检查HTTP请求头是否包含缓存验证信息如果验证缓存新鲜,返回304等对应状态码
- 处理程序读取完整请求并准备HTTP响应,可能需要查询数据库等操作
- 服务器将响应报文通过TCP连接发送回浏览器
- 浏览器接收HTTP响应
- 为了避免服务器与客户端双方的资源占用和损耗,当双方没有请求或响应传递时,任意一方都可以发起关闭请求。与创建TCP连接的3次握手类似,关闭TCP连接,需要4次握手。
- 主动方发送Fin=1, Ack=Z, Seq= X报文
- 被动方发送ACK=X+1, Seq=Z报文
- 被动方发送Fin=1, ACK=X, Seq=Y报文
- 主动方发送ACK=Y, Seq=X报文
- 浏览器检查响应状态吗:是否为1XX,3XX, 4XX, 5XX,这些情况处理与2XX不同
- 如果资源可缓存,进行缓存
- 对响应进行解码(例如gzip压缩)
- 根据资源类型决定如何处理(假设资源为HTML文档)
- 解析HTML文档,构件DOM树,下载资源,构造CSSOM树,执行js脚本,这些操作没有严格的先后顺序,以下分别解释
- 构建DOM树:
- Tokenizing:根据HTML规范将字符流解析为标记
- Lexing:词法分析将标记转换为对象并定义属性和规则
- DOM construction:根据HTML标记关系将对象组成DOM树
- 解析过程中遇到图片、样式表、js文件,启动下载
- 构建CSSOM树:
- Tokenizing:字符流转换为标记流
- Node:根据标记创建节点
- CSSOM:节点创建CSSOM树
- 根据DOM树和CSSOM树构建渲染树:
- 从DOM树的根节点遍历所有可见节点,不可见节点包括:1)script,meta这样本身不可见的标签。2)被css隐藏的节点,如display: none
- 对每一个可见节点,找到恰当的CSSOM规则并应用
- 发布可视节点的内容和计算样式
- js解析如下:
- 浏览器创建Document对象并解析HTML,将解析到的元素和文本节点添加到文档中,此时document.readystate为loading
- HTML解析器遇到没有async和defer的script时,将他们添加到文档中,然后执行行内或外部脚本。这些脚本会同步执行,并且在脚本下载和执行时解析器会暂停。这样就可* 以用document.write()把文本插入到输入流中。同步脚本经常简单定义函数和注册事件处理程序,他们可以遍历和操作script和他们之前的文档内容
- 当解析器遇到设置了async属性的script时,开始下载脚本并继续解析文档。脚本会在它下载完成后尽快执行,但是解析器不会停下来等它下载。异步脚本禁止使用* document.write(),它们可以访问自己script和之前的文档元素
- 当文档完成解析,document.readState变成interactive
- 所有defer脚本会按照在文档出现的顺序执行,延迟脚本能访问完整文档树,禁止使用document.write()
- 浏览器在Document对象上触发DOMContentLoaded事件
- 此时文档完全解析完成,浏览器可能还在等待如图片等内容加载,等这些内容完成载入并且所有异步脚本完成载入和执行,document.readState变为complete,window触* 发load事件
- 显示页面(HTML解析过程中会逐步显示页面)
2、http1.0,1.1,2.0区别
-
缓存处理
- 强缓存
- 1.0
Expires过期时间受限于本地时间,如果修改了本地时间,可能会造成缓存失效Expires: Wed, 22 Oct 2018 08:41:00 GMT - 1.1
Cache-Control优先级高于 Expires 。该属性表示资源会在 30 秒后过期Cache-control: max-age=30
- 1.0
- 协商缓存
- 1.0
Last-Modified和If-Modified-Since本地文件最后修改日期, 但是如果在本地打开缓存文件,就会造成 Last-Modified 被修改,所以在 HTTP / 1.1 出现了 ETag - 1.1
ETag和If-None-MatchETag/If-None-Match的值是一串 hash 码,代表的是一个资源的标识符,当服务端的文件变化的时候,它的 hash码会随之改变,通过请求头中的 If-None-Match 和当前文件的 hash 值进行比较,如果相等则表示命中协商缓存。ETag 又有强弱校验之分,如果 hash 码是以 "W/" 开头的一串字符串,说明此时协商缓存的校验是弱校验的,只有服务器上的文件差异(根据 ETag 计算方式来决定)达到能够触发 hash 值后缀变化的时候,才会真正地请求资源,否则返回 304 并加载浏览器缓存。
- 1.0
- 强缓存
-
长链接
- 1.0 每次请求都要创建连接
- 1.1 增加了长链接支持,默认开启keep-alive,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟
-
带宽优化及充分利用网络连接
- 1.1中新增了24个错误状态响应码,例如
410(Gone)表示服务器上的某个资源被永久性的删除
- 1.1中新增了24个错误状态响应码,例如
-
Host头处理
- 1.0 中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个IP地址。
- 1.1 请求消息和响应消息都应支持Host头域,且请求消息中如果没有Host头域会报告一个错误
-
HTTP 2.0
- 二进制传输
- 1x 版本中,我们是通过文本的方式传输数据
- 2.0 所有传输的数据都会被分割,并采用二进制格式编码
- 多路复用
- 1x 线头阻塞
- 2.0 一个 TCP 连接中可以发送多个请求
- header压缩
- 1x 使用文本的形式传输 header,在 header 携带 cookie 的情况下,可能每次都需要重复传输几百到几千的字节
- 2.0 使用了 HPACK 压缩格式对传输的 header 进行编码,减少了 header 的大小。并在两端维护了索引表,用于记录出现过的 header ,后面在传输过程中就可以传输已经记录过的 header 的键名,对端收到数据后就可以通过键名找到对应的值。
- 服务端推送
- 1x 服务端推送可能需要轮询或者浏览器兼容的情况下使用
prefetch - 2.0 服务端可以在客户端某个请求后,主动推送其他资源
- 1x 服务端推送可能需要轮询或者浏览器兼容的情况下使用
- 二进制传输
3、浏览器缓存(你们项目中用到了吗? 具体干了些什么?)
- localStorage sessionStorage - 5M
- cookie - 4K
- indexDB - 无限制
- 强缓存和协商缓存
4、工作原理
- 处理 HTML 并构建 DOM 树。
- 处理 CSS 构建 CSSOM 树。
- 将 DOM 与 CSSOM 合并成一个渲染树。
- 根据渲染树来布局,计算每个节点的位置。
- 调用 GPU 绘制,合成图层,显示在屏幕上。
5、跨域解决
- jsonp
- 服务器反向代理
- 服务器设置 Access-Control-Allow-Origin 值为 * 或者根据 Referer、origin头 针对性的设置白名单
6、重绘重排
- 重绘: 当节点需要更改外观而不会影响布局的,比如改变 color 就叫称为重绘
- 重排\回流: 布局或者几何属性需要改变
- 回流必定会发生重绘,重绘不一定会引发回流。回流所需的成本比重绘高的多,改变深层次的节点很可能导致父节点的一系列回流。
7、前端安全(XSS,CSP, CSRF)如果防范
——————xss——————
XSS 跨网站指令码(英语:Cross-site scripting,通常简称为:XSS)是一种网站应用程式的安全漏洞攻击,是代码注入的一种。它允许恶意使用者将程式码注入到网页上,其他使用者在观看网页时就会受到影响。这类攻击通常包含了 HTML 以及使用者端脚本语言
- 如何攻击?
XSS 通过修改 HTML 节点或者执行 JS 代码来攻击网站。
例如通过 URL 获取某些参数
<!-- http://www.domain.com?name=<script>alert(1)</script> -->
<div>{{name}}</div>
上述 URL 输入可能会将 HTML 改为
,这样页面中就凭空多了一段可执行脚本。这种攻击类型是反射型攻击,也可以说是 DOM-based 攻击。也有另一种场景,比如写了一篇包含攻击代码 的文章,那么可能浏览文章的用户都会被攻击到。这种攻击类型是存储型攻击,也可以说是 DOM-based 攻击,并且这种攻击打击面更广。
- 如何防御?
最普遍的做法是转义输入输出的内容,对于引号,尖括号,斜杠进行转义
——————CSP——————
内容安全策略 (CSP) 是一个额外的安全层,用于检测并削弱某些特定类型的攻击,包括跨站脚本 (XSS) 和数据注入攻击等。无论是数据盗取、网站内容污染还是散发恶意软件,这些攻击都是主要的手段。
我们可以通过 CSP 来尽量减少 XSS 攻击。CSP 本质上也是建立白名单,规定了浏览器只能够执行特定来源的代码。
通常可以通过 HTTP Header 中的 Content-Security-Policy 来开启 CSP
- 只允许加载本站资源
Content-Security-Policy: default-src ‘self’
- 只允许加载 HTTPS 协议图片
Content-Security-Policy: img-src https://*
- 允许加载任何来源框架
Content-Security-Policy: child-src 'none'
——————CSRF——————
跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已登录的 Web 应用程序上执行非本意的操作的攻击方法。[1] 跟跨網站指令碼(XSS)相比,XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。
- 利用用户的登录态发起恶意请求
- 如何攻击?
假设网站中有一个通过 Get 请求提交用户评论的接口,那么攻击者就可以在钓鱼网站中加入一个图片,图片的地址就是评论接口
<img src="http://www.domain.com/xxx?comment='attack'" />
Post请求可以用表单提交接口
<form action="http://www.domain.com/xxx" id="CSRF" method="post">
<input name="comment" value="attack" type="hidden" />
</form>
- 如何防御?
- Get 请求不对数据进行修改
- 不让第三方网站访问到用户 Cookie
- 阻止第三方网站请求接口
- 请求时附带验证信息,比如验证码或者 toke
- 验证 Referer
- 使用token
8、路由原理(哈希路由和history模式)
9、浏览器事件循环
- 微任务(microtasks)
| ¥ | 浏览器 | node |
|---|---|---|
Promise |
✔️ | ✔ |
process.nextTick |
❌ | ✔ |
MutationObserver |
✔ | ❌ |
宏任务(macrotasks)
| ¥ | 浏览器 | node |
|---|---|---|
I/O |
✔️ | ✔ |
setTimeout |
✔ | ✔ |
setInterval |
✔ | ✔ |
setImmediate |
❌ | ✔ |
UI Render |
✔ | ❌ |
-
规则
- 1:先执行主任务,然后是微任务(promise),最后是宏任务(setTimeout)
- 2:执行宏任务的时候碰到微任务,先将其推到task队列,执行完当前宏任务后,去清空微任务队列
- 3: 宏任务、微任务先进先出
- 4:new Promise 会立即执行 但 resolve是一个异步回调
- 5:async 返回的是一个Promise
- 6:await 会跳出当前执行的 async 函数体(await 让出线程)接着执行
- 7:当await操作符后面的表达式是一个Promise的时候,它的返回值,实际上就是Promise的回调函数resolve的参数
- 8:谨记上面 分析完下面三道题 就无敌了
题1
Promise.resolve().then(function promise1 () {
console.log('promise1');
})
setTimeout(function setTimeout1 (){
console.log('setTimeout1')
Promise.resolve().then(function promise2 () {
console.log('promise2');
})
}, 0)
Promise.resolve().then(function promise1 () {
console.log('promise3');
})
setTimeout(function setTimeout2 (){
console.log('setTimeout2')
}, 0)
题2
setTimeout(() => {
console.log(1)
})
log()
function log() {
setTimeout(() => {
console.log(2)
setTimeout(() => {
console.log(3)
})
})
}
var a = new Promise(resolve => {
setTimeout(() => {
console.log(4)
})
resolve(() => {
setTimeout(() => {
console.log(5)
setTimeout(() => {
console.log(6)
})
})
})
})
setTimeout(() => {
console.log(7)
})
a.then((fn: any) => {
fn()
setTimeout(() => {
console.log(8)
})
})
题3
console.log('1')
async function async1() {
await async2()
console.log('2')
}
async function async2() {
await async3()
console.log('3')
}
async function async3() {
console.log('4')
}
async1()
setTimeout(function () {
console.log('5')
}, 0)
new Promise(resolve => {
console.log('6')
resolve()
}).then(function () {
console.log('7')
return new Promise(resolve => {
console.log('8')
resolve()
})
}).then(function () {
console.log('9')
})
console.log('10')
vue
1、父组件中想用子组件里面的方法,有哪几种方式
- 子组件可以在自己的created钩子里面用 $emit 把this开放出去
- 在子组件上添加 ref 指令 拿到当前子组件的实例对象
- 其实在当前写的项目中,使用了类似angualr2的依赖注入的机制,我们将所有的页面行为操作,和业务逻辑分别写到了action.ts、helper.ts里面了,如果让这两个模块用 @injectable 装饰器注入到实例列表里面,外界就可以轻松使用该实例的任何方法了(但不能为了使用方法而故意注入,这样做也有它的缺点)、
2、vue是怎样实现数据更新的
- observe Object.defineProperty 数据劫持
- watcher 告诉订阅值的地方 数据更新了
- compile 收到更新通知 更新状态
3、vue和angualr2的数据更新机制的区别
4、虚拟dom
5、vue3.0数据监测
6、vuex原理
vuex、redux、react-redux、react hooks简单原理比较
7、https和http的区别
React
1、 生命周期
2、 setState同步还是异步?我想拿到异步后的数据怎么办?react为什么把setstate设计成这样?
3、虚拟dom的优势、原理
4、diff算法在虚拟dom中如何比较的新旧树的差异
5、错误边界
6、讲讲react hooks
7、useState保持状态的原理
8、redux原理
vuex、redux、react-redux、react hooks简单原理比较
9、React.memo()、React.PureComponent区别?
- React.memo()适用函数组件,仅仅浅比较props
- React.PureComponent类组件,浅比较props和state,根据结果决定是否重新渲染组件
多提一句, 为什么React.memo(不比较一次state而只比较props,因为函数组件每次render 都有独立的变量和Effects, 有可能面试官接着问react hooks的 Capture 特性了。。。
js
1、 ["1", "2", "3"].map(parseInt)
[1,NaN,NaN]
2、冒泡、快排、深度递归
3、jsonp
4、深层复制
let a = {
name: '刘',
age: 18,
say: function() {}
}
let b = JSON.parse(JSON.stringify(a));
b.age //18
b.age = 20;
b.age // 20
a.age //18
b // {name:"刘",age: 20}
- 这种方式可以实现深层复制,但是复制的对象有函数是不行的。
- 如果对象是一个重复引用的特性的(自己的属性里面有原对象信息,无限的自己引用自己),也不适用
5、es6新语法
6、js原型 能手画出来
7、bind、call、apply
8、隐式转换
+'23' + 1 // 24
9、数组降维
let a = [[1,2],[4,5],[77,88]];
[].concat.apply([], a)
a.flatMap(item => item)
10、promise、async、await、generator生成器
11、发布订阅模式、观察者模式、单例模式
12、面向对象的特点、以及用面向对象编程需要注意的点
13、0.1 + 0.2 !=0.3 为什么?
14、正则匹配三个连续的数字(常用正则)
15、 es6 Proxy
16、怎样区分function、 数组、 对象
17、快速生成一个 0到N的数组,然后要求在不生成新数组的情况下再打乱成随机数组
let a = [...(new Array(n)).keys()];
let b = Array.from({length:n},(v, k) => k);
let c = (n) => Array.from({length:n}).map((v,k) => k);
// 利用sort
arr.sort((v1, v2) => {
return 0.5 - Math.random() > 0? 1: -1
})
// 随机项调换位置
for(let i = 0; i < arr.length; i++) {
let curr = i;
let randomIndex = Math.floor(Math.random() * arr.length);
let currItem = arr[i];
arr[i] = arr[randomIndex];
arr[randomIndex] = currItem;
}
18、 广度优先递归一个二叉树
19、 查看两个单链表是否有交叉(有重复值)
20、 时间复杂度
21、 js对象和Map的区别
22、 斐波那切数列 (分析下 函数被调用了多少次)
23、 for in for of 的区别
24、 一个请求在 delay函数延迟过后发送,结果可能成功也可能失败,如果失败就重复发送,重复n次过后还是失败,结束发送返回错误
- 请求函数 query, 成功{succ: true}, 失败{succ: false}
- delay函数 本身返回一个promise
function delay(t) {
console.log('延迟执行开始')
return new Promise(resolve => {
setTimeout(() => {
console.log('延迟完毕')
resolve()
}, t)
})
}
function query() {
console.log('请求开始')
return new Promise(resolve => {
setTimeout(() => {
console.log('1秒失败,请求完毕')
resolve({succ: false})
},1000)
})
}
async function repeatQuery(delayTime, n) {
await delay(delayTime)
let res = await query().then(data => data);
console.log(12312312, res)
if(res.succ) {
return res
} else {
if (n <= 0) {
return
}
return await repeatQuery(delayTime, --n)
}
}
repeatQuery(2000, 5).then(res => {
console.log(111, res)
})
25、使用Promise自身特性,实现每隔两秒顺序打印数组
- 正常思路都是在一个
async的方法里面用for循环await - 这里运用 Promise自身可链式调用的特性
const list: number[] = [1, 2, 3]
function a(i: number): ProNum {
return new Promise((resolve) => {
setTimeout(() => {
console.log(i)
resolve(i)
}, 2000)
})
}
var p: ProNum = Promise.resolve(0)
function test (i = 0) {
if (i === list.length) return
p = p.then(() => a(list[i]))
test(i+1)
}
test(0)
type ProNum = Promise<number>
26、Set、Map、WeakSet 和 WeakMap 的区别?
-
Set
- 成员不能重复
- 只有健值,没有健名,有点类似数组。
- 可以遍历,方法有add, delete,has
-
weakSet
- 成员都是对象
- 成员都是弱引用,随时可以消失。 可以用来保存DOM节点,不容易造成内存泄漏
- 不能遍历,方法有add, delete,has
-
Map
- 本质上是健值对的集合,类似集合
- 可以遍历,常用方法set, get, delete, has, forEach
- {} 这种正常对象遍历是无序的,而Map的遍历是有序的
-
weakMap
- 直接受对象作为健名(null除外),不接受其他类型的值作为健名
- 健名所指向的对象都是弱引用,不计入垃圾回收机制
- 不能遍历,方法同get,set,has,delete
- vue3.0的数据监测,就用了它来保存,被检测对象和该对象响应数据
ts
1、怎样表示下面结构
let a = [
[Box,Box],
[Box,Box],
...
]
Array<Box[]>
2、装饰器
3、反射
4、接口和抽象类的区别
5、反射实现依赖注入(为了解决什么问题,实现过程)
css布局
1、css权重
2、c3新选择器
3、less的特性
4、记得小东西 多使用伪类
5、吸顶实现
6、内容过长footer跟随内容,内容很少footer就在屏幕下面 (flex实现)
7、flex: 1;是那几个属性的缩写,这几个属性分别有什么作用
项目
1、项目的架构
2、项目中遇到问题怎样解决
3、项目优化、首屏优化, 骨架屏
- 网络相关
- DNS预解析
- DNS 解析也是需要时间的,可以通过预解析的方式来预先获得域名所对应的 IP。
<link rel="dns-prefetch" href="//baidu.com" />
- 缓存
- 强缓存
Expires、Cache-control - 协商缓存
Last-Modified 和 If-Modified-Since、ETag 和 If-None-Match
- 强缓存
- 预加载
- 在开发中,可能会遇到这样的情况。有些资源不需要马上用到,但是希望尽早获取,这时候就可以使用预加载。预加载其实是声明式的 fetch, 强制浏览器请求资源,并且不会阻塞 onload 事件,可以使用以下代码开启预加载
- 预加载可以一定程度上降低首屏的加载时间,因为可以将一些不影响首屏但重要的文件延后加载,唯一缺点就是兼容性不好
<link rel="preload" href="http://example.com" />
- 预渲染
- 可以通过预渲染将下载的文件预先在后台渲染,可以使用以下代码开启预渲染
- 预渲染虽然可以提高页面的加载速度,但是要确保该页面百分百会被用户在之后打开,否则就白白浪费资源去渲染
<link rel="prerender" href="http://example.com" />
- DNS预解析
- 渲染过程
- 懒加载
- 懒加载就是将不关键的资源延后加载。
- 例如图片的懒加载,对于没有出现在屏幕的图片,可以将图片链接放到自定义属性里面,当进入可视区之后,再赋值到
src属性里面
- 懒执行
- 懒执行就是将某些逻辑延迟到使用时再计算。该技术可以用于首屏优化,对于某些耗时逻辑并不需要在首屏就使用的,就可以使用懒执行。懒执行需要唤醒,一般可以通过定时器或者事件的调用来唤醒
- 懒加载
- 文件
- 图片
- 用icon字体图标代替图片
- 压缩图片,减少其像素点
- 小图使用base64
- 雪碧图\精灵图
- 对于能够显示 WebP 格式的浏览器尽量使用 WebP 格式。因为 WebP 格式具有更好的图像数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量,缺点就是兼容性并不好
- 小图使用 PNG,其实对于大部分图标这类图片,完全可以使用 SVG 代替
- 其他文件
- CSS 文件放在 head 中,js放在body之后
- script 标签放在任意位置然后加上 defer ,表示该文件会并行下载,但是会放到 HTML 解析完成后顺序执行。对于没有任何依赖的 JS 文件可以加上 async ,表示加载和渲染后续文档元素的过程将和 JS 文件的加载与执行并行无序进行。
- 服务端开启
g-zip文件压缩功能
- 使用 CDN
- 静态资源尽量使用 CDN 加载,由于浏览器对于单个域名有并发请求上限,可以考虑使用多个 CDN 域名。对于 CDN 加载静态资源需要注意 CDN 域名要与主站不同,否则每次请求都会带上主站的 Cookie。
- 图片
- webpack
- production环境,开启代码压缩
- 给打包后的文件添加哈希,配合浏览器缓存
- 按路由拆分代码,实现模块的按需加载
- 设置图片打包,不超过设定值使用base64写入文件
- 启动 tree shaking
- 组件库的按需加载
- 打包缓存
- 如果项目过大,打包的时间会相当的长,如果频繁更新上线则会不断对代码进行编译打包,浪费很多时间。这时我们便可以将那些不常更新的框架和库(如vue.js等)进行单独的编译打包,这样每次开发上线就只需要对我们的开发文件进行编译打包,这样可以极大地省去不必要的打包时间。而这种方法需要DLLPlugin和DLLReferencePlugin两个插件的配合来完成。
- 浏览器本地存储
- 使用缓存存储比较大的请求数据或者计算数据
- 代码层面
- 标签语义化
- 组件抽象
- 逻辑复用
- 计算属性
- vue的computed
- react的useMemo
- 减少组件渲染次数
- useMemo
- React.memo()
- React.PureComponent
- eslint
- ts
4、提交了几个commit, 线上紧急bug如何处理(考Git)
状态管理工具
1、vuex, redux, vuex和redux的区别
2、谈谈你对rxjs的理解
webpack
1、为什么要用loader,它解析原理
2、优化
3、那些常用的配置(入口,出口,loader,plugin,devserve等)
node
1、事件循环机制 eventloop
算法题
1、 千分位(隔三位插个逗号)
- var a = '123456789'; => a = '123,456,789,'
function toThounsandInsertStr(str: string): string {
let result: string = '';
let rule: RegExp = /\d{3}$/;
while (rule.test(str)) {
result = RegExp.lastMatch + result;
if (str != RegExp.lastMatch) {
result = ',' + result;
}
str = RegExp.leftContext;
}
return str + result;
}
toThounsandInsertStr('1234567') // 1,234,567
toThounsandInsertStr('123') // 123
toThounsandInsertStr('1') //1
2、 碰到a就剔除字符串里面的 a 和 a前面的数字
- var a = '12
3a456a789aa'; => a = '12457'
let _arr = a.split('');
for(let i = 0; i < _arr.length; i++) {
let start = 0; let n = 2;
if(_arr[i] == 'a') {
start = i -1 < 0 ? 0 : i -1;
n = i - 1 < 0 ? 1 : 2;
_arr.splice(start, n);
i = n == 2 ? i - 2 : 0;
}
}
console.log(_arr.join(''))
3、数组转树
var a = [
{id: 1, pid: 0, name: '上海市' },
{id: 2, pid: 1, name: '宝山'},
{id: 3, pid: 1, name: '普陀'},
{id: 4, pid: 0, name: '北京'},
{id: 5, pid: 4, name: '朝阳'},
{id: 6, pid: 4, name: '五环'},
{id: 7, pid: 2, name: '镇平'},
{id: 8, pid: 2, name: '宝山'},
]
//转成递归树形式展示
- 思路整理
- 第一次遍历节点的时候,会发现遍历的子节点很有可能还没有父亲节点
- 所以利用第一次遍历,找出根节点,并把所有子节点统一放到Map中,形式为(pid, [pid对应的子节点...])
- 然后用根节点的id去匹配Map中的pid对应的子节点,匹配到的结果数组也即是当前节点的children
- 如果有children,把children当做下一次要匹配的元素找每一个children元素的children
function arrTotree(arr) {
let parentMap = new Map();
// let nodeMap = new Map();
arr.forEach(item => {
// nodeMap.set(item.id, item);
if(parentMap.has(item.pid)) {
let arr = parentMap.get(item.pid);
arr.push(item)
} else {
parentMap.set(item.pid, [item])
}
})
let res = [];
res = parentMap.get(0);
(function loopAddChild(res) {
res.forEach(item => {
if (parentMap.has(item.id)) {
item.children = parentMap.get(item.id);
loopAddChild(item.children)
}
})
})(res)
return res;
}
// 简化版
function arrToTree(arr) {
let map = new Map();
arr.forEach(item => {
if(!map.has(item.pid)) {
map.set(item.pid, [item])
} else {
let arr = map.get(item.pid);
arr.push(item)
}
})
// 都是指针 第二次遍历原数组 匹配id和pid 一样的话 map取出来当做当前遍历元素的children
arr.forEach(item => {
if (map.has(item.id)) {
item.children = map.get(item.id);
}
})
// 把顶级的数组取出来 返回
return map.get(0)
}
4、 数组去重(两种方式, 如果有for循环,时间复杂度不能是o(n^2))
var a = [1,2,3,3,2,1,4,5,6,6];
Array.from(new Set([...a]))
// ----
[...new Set([...a])]
// for循环 - 空间换时间
let arr= []
let b = {};
for (let i = 0; i < a.length; i++) {
let curr = a[i];
if (b[curr] >= 0) {
continue
}
b[curr] = i;
arr.push(a[i]);
}
console.log(arr)
5、一个数组中有一系列的整数 例如 [1,2,-3,4,5,-3,4,-6],利用一个for循环在数组中找出连续相加和最大的一段 [4,5,-3,4] 和为 10
var a = [6, -3, -2, 7, -15, 1, 2, 2];
var b = [1, -2, 2, -1, 4, 5, -3, 4, -1, -2];
function findSum(arr) {
if (arr.length < 0) return 0;
let maxSum = 0;
let temSum = 0;
let resArr = []
let originIndex = 0
let spliceIndex = 0
for (let i = 0; i < arr.length; i++) {
temSum += arr[i];
resArr.push(arr[i])
if (temSum > maxSum) {
maxSum = temSum;
spliceIndex = i - originIndex
}
if(temSum < 0) {
temSum = 0;
resArr = []
originIndex = i
}
}
resArr.splice(spliceIndex, resArr.length - 1)
return {
maxSum,
resArr
}
}
// 简单测了下 好像是对的...
console.log(findSum(a));
console.log(findSum(b));
6、一个牧场,有一个母牛, 2年后母牛会生一头母牛和一头公牛,3年只生一头公牛,5年母牛会死,4年公牛也会死,N年后这个牧场里面有多少只牛
7、 如何让__if(a == 1 && a == 2 && a == 3 )__ 为true
8、用js打印杨辉三角形
9、微信抢红包逻辑
10、找出一个单词在字典中所有字母一样的单词, 如 god -> dog (查字典), 给出思路
11、实现一个无线可调用的函数用来做加法运算 add(1)(2)(3)() ---- 6
function add(num, prevSum) {
let result = prevSum || 0
return num ? function (nextNum) {
result = result + num
return add(nextNum, result)
} : result
}
let a = add(1)(2)(3)()
console.log(a)
// 如果非要求柯里化,可以用arguments
12、 合并两个有序数组
function mergeTwoOrderArr(...chunk: number[][]): number[] {
const [arr1, arr2] = chunk;
let tem = Array.from(arr1)
let i = arr1.length - 1;
let j = arr2.length - 1;
let tail = arr1.length + arr2.length - 1;
while (j >= 0) {
if (arr1[i] > arr2[j]) {
tem[tail] = arr1[i]
i--
} else {
tem[tail] = arr2[j]
j--
}
tail --
}
return tem
}