面试整理

466 阅读15分钟

浏览器、 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 Last-ModifiedIf-Modified-Since 本地文件最后修改日期, 但是如果在本地打开缓存文件,就会造成 Last-Modified 被修改,所以在 HTTP / 1.1 出现了 ETag
      • 1.1 ETagIf-None-Match ETag/If-None-Match 的值是一串 hash 码,代表的是一个资源的标识符,当服务端的文件变化的时候,它的 hash码会随之改变,通过请求头中的 If-None-Match 和当前文件的 hash 值进行比较,如果相等则表示命中协商缓存。ETag 又有强弱校验之分,如果 hash 码是以 "W/" 开头的一串字符串,说明此时协商缓存的校验是弱校验的,只有服务器上的文件差异(根据 ETag 计算方式来决定)达到能够触发 hash 值后缀变化的时候,才会真正地请求资源,否则返回 304 并加载浏览器缓存。
  • 长链接

    • 1.0 每次请求都要创建连接
    • 1.1 增加了长链接支持,默认开启keep-alive,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟
  • 带宽优化及充分利用网络连接

    • 1.1中新增了24个错误状态响应码,例如 410(Gone)表示服务器上的某个资源被永久性的删除
  • 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 服务端可以在客户端某个请求后,主动推送其他资源

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" />
    • 缓存
      • 强缓存 ExpiresCache-control
      • 协商缓存 Last-Modified 和 If-Modified-SinceETag 和 If-None-Match
    • 预加载
      • 在开发中,可能会遇到这样的情况。有些资源不需要马上用到,但是希望尽早获取,这时候就可以使用预加载。预加载其实是声明式的 fetch, 强制浏览器请求资源,并且不会阻塞 onload 事件,可以使用以下代码开启预加载
      • 预加载可以一定程度上降低首屏的加载时间,因为可以将一些不影响首屏但重要的文件延后加载,唯一缺点就是兼容性不好
      • <link rel="preload" href="http://example.com" />
    • 预渲染
      • 可以通过预渲染将下载的文件预先在后台渲染,可以使用以下代码开启预渲染
      • 预渲染虽然可以提高页面的加载速度,但是要确保该页面百分百会被用户在之后打开,否则就白白浪费资源去渲染
      • <link rel="prerender" href="http://example.com" />
  • 渲染过程
    • 懒加载
      • 懒加载就是将不关键的资源延后加载。
      • 例如图片的懒加载,对于没有出现在屏幕的图片,可以将图片链接放到自定义属性里面,当进入可视区之后,再赋值到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就剔除字符串里面的 aa前面的数字

  • var a = '123a456a789aa'; => 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
}

13、数组全排列

其他

1、未来几年的职业计划

2、为什么选择前端

3、学习前端技术的途径

4、自身优缺点

5、对未来公司有什么要求

6、开发项目中遇到了什么让你感觉最骄傲的事情

7、你对我们公司有什么想要了解的

8、为什么离职

9、讲一下你最近在做的项目