阅读前提示
这个面试提示纯粹方便个人回忆一些碎片知识,会有点杂乱,也不一定正确,可以作为提纲看看,漏了哪些知识要复习的。
http:
post/get差别
get没有body,get无副作用,能被缓存,post有副作用
状态码
- 201/post
- 204可以被缓存,常见option请求
- 301永久重定向,302临时重定向(同见307,308针对post),浏览器会缓存301的重定向结果。
- 304 协商缓存,见缓存章节
- 400 bad request表示http语法错误
- 401 身份未验证
- 403 禁止,身份已验证也可能因为权限不足而禁止
- 405 方法被禁止,其中get、head不允许返回405
- 502 bad gateway通常网关层接收上游服务器的错误响应(nginx)
- 503 服务不可访问(常见服务启动期间)
缓存机制
- 强缓存:Expires cache control:no stored(强制不缓存)no cached(强制协商缓存)max-age指定过期时间 public/private(共有私有)
- 协商缓存:e-tag、vary、If-Modified-Since、If-Match等if开头的头部
cors
浏览器定义的跨域资源共享机制(浏览器独有,非http规范,服务器实际能收到请求,浏览器拦截响应),本规范要求跨域请求浏览器应当先发起option请求
- 使用Access-Control-*的一系列头部控制资源获取
- Access-Control-Allow-Credentials 标注是否能发送cookie
cookie
cookie用途一般是存储个性化设置/会话管理,cookie可以指定过期时间(在set-cookie头部配置),否则将在浏览器关闭后清除(部分浏览器会缓存)。
cookie可以通过设置secure字段,只允许在https请求中传输,可以通过设置http-only禁止脚本访问cookie(为了防止可能发生的xss)
cookie可以通过domain和path设置作用域,domain默认是本域名(不包含子域名)
cookie安全相关:
- xss相关,被注入脚本,窃取cookie(包含个人敏感信息),需要设置http-only
- csrf相关,钓鱼网站伪造一个发向服务器恶意请求会自动携带cookie
http2相关
http2自带多路复用,自http1.1后可以使用keep-alive头部保持tcp链接不关闭,但是http请求依然要排队发送,如果前面有个长请求将导致阻塞(浏览器在http1下最多开4~6个tcp链接,意思同时并发最高为6)
- 多路复用,使用帧的形式实现,每个请求拆分成多帧,带上id,最后server端组装
- server push,用的比较少,服务端推送,主要应用内联资源的延迟推送
- 头部压缩/复用,实际就是服务端和客服端保存一些字典
dns相关
dns其实也是一个应用层协议和http一样,通过tcp、udp协议都能实现dns,dns本质就是将域名翻译成ip,但dns的一些特性成为实现cdn的核心。
每次域名访问都伴随着dns请求,同一个域名可以映射到多个ip,同时也可能存在多个dns服务器保存该域名的ip。
每个域名会分层:www.baidu.com.root(.root一般省略)从右到左分别是跟域名、顶级域名、次级域名、主机名,dns会先寻找跟域名的服务器地址,跟域名的地址会返回顶级域名的地址,依次类推,每次查询会有个ttl属性代表缓存时间。
每个域名还能配置cname,相当于一个别名,cname具体的值为另外一个域名,从而达到无感知域名跳转的效果,最后返回的ip将会是cname配置的域名的地址。
cdn和dns的关系:cdn就是依靠cname配置,将域名解析映射到实际的cdn专属dns服务器上,通过负载均衡/分布式部署等工程技术返回一个合理的ip地址。
tcp、udp、tls、ssl相关
tcp三次握手过程
- [syn] seq x;[syn ack] ack: x + 1 seq:y;[ack] ack: y + 1 seq: x + 1;
- seq = last ack; ack(总包大小) = last seq + last length(信号syn和fin占一个seq);
四次挥手过程
- [client fin] seq x ack y;
- [server ack] seq y ack x + 1(收到fin信号,不再接受新的包)
- [server ack fin] seq y ack x + 1(处理完成缓存的所有包后发送)
- [ack] seq x + 1 ack y + 1(发送收到,并关闭链接)
流量控制
滑动窗口机制,每次发送包都会有receiver window来限制下次对方传过来包的大小
拥塞控制
- 慢启动(包大小有小到大传输,确认网络环境)
- 快重传(收到序号错误的包,立刻发出重复确认,而不是等带自己要发包再捎带)
udp
udp相对于tcp的特点,能够1对1,1对多传输,每个udp server端只负责发报文,不管报文是否发送成功,没有拥塞控制,所以对报文大小要把握比较好,udp头部只有八字节(端口号,报文长度,报文校验),比tcp的20字节要小很多。
tls ssl握手过程
tls和ssl是实现https的套接层协议,既不属于应用层,也不属于传输层。tls握手过程的本质是,用非对称加密协商出对称加密的秘钥,并使用秘钥加密通信。
- client 发送 client hello,并附带randow number1,并附带客服端支持的加密算法列表
- server 发送 server hello,并附带randow number2,选定加密算法。
- server 发送证书给 client
- server key exchange(部分算法需要发送一些加密算法的参数/条件)
- server 发送 server hello done
- client 收到 server 这些信息后,会向证书签发机构验证证书的合法性,取出证书公钥,并生成random number3,用公钥加密random3生成pre master key
- server 收到 premaster key后要私钥解密出random3,此时client和server都有3个随机数,并且协商了加密算法,之后server和client将使用这3个随机数生成密钥进行通信。
- client 通知 server 后续将使用密钥通信
- 后续client和server会分别发送一条使用密钥加密的信息,双方都能识别意味着链接建立
- application data
前端安全
编码相关
- url使用ascii字符进行编码,具体为 % 后加十六进制编码如: !编码后为%20,由于 ascii 并没有规定中文编码,所以url中的中文编码由浏览器自行实现,一般为%加上utf-8编码
- HTML规定了部分占位符来代表特殊符号,如:> 编码后为 >
- 浏览器文本资源的编码一般有请求过程中的content-type决定,常为utf-8
- JavaScript的number底层为double类型,遵循 IEEE-754,64位表示,1位符号位,11位指数位,52位小数位,这种表示形式导致了浮点数的不精确。
xss相关
xss主要发生于用户的输入构成了有意义的指令,xss大多发生在decode期间,解析JavaScript脚本、CSS样式解析、HTML文本解析、JsonDecode、UriDecode。 一些解决方案:
- csp(浏览器自带的内容安全策略)
- cors (浏览器自带的跨域资源共享策略)
- 用户输入过滤,xss-filter
简易的xss-filter实现:
// 加上要过滤的敏感字符
var replacer = {
'<': '<',
'>': '>',
'&': '&'
}
var xssFilter = (htmlStr) => {
return htmlStr.replace(/[<>&]/g, (matcher) => {
return replacer[matcher]
})
}
CSRF跨域请求伪造
通过伪造合法的请求,窃取用户信息、破坏系统等。一般来说通过钓鱼网站,让用户发起了一些请求,这些请求携带了用户的信息从而导致破坏效果。csrf-token可以有效解决这类问题,像是提供第三方服务的考虑oauth。cookie可以携带same-site标志,禁止跨域携带。
为什么使用TypeScript?
表达能力比较强,写代码本质上是给人看,而JavaScript的表达能力比较鸡肋,过于自由的结果就是写的代码表达的含义只能通过命名来区别,抽象能力比较低,typescript提供类型/接口这种将数据结构抽象的能力,要求开发者在写代码之前,设计出合理的结构,而不是写一步看一步,有效提高代码的可读性,和方便后期维护。
TypeScript带来的额外优势:代码提示(JS的文档注释也可以做到,但后期维护并不方便)、减少因为类型导致的代码错误。
非得JavaScript不可嘛?并非如此,JavaScript也能写出优秀的代码,只是对人有要求,TypeScript付出一点学习成本可以有效降低人的要求(优秀的人才是最昂贵的而不是类型)
前端持久化相关
- sessionStorage同步访问,会话级别,5mb
- localStorage同步访问,永久有效,5mb
- indexDB异步访问,支持事务,大小没有限制
存满了,再写入数据都会报错。
react相关
前沿Concurrent 模式、Suspense
解决渲染不能中断的问题,防抖、节流目标是把UI渲染放到合适的时间点,减少不必要的渲染;Concurrent 模型是能渲染就渲染,但高优先级的任务可以中断当前渲染,可以让react同时持有多个状态,类型layer层。
Concurrent 模式下react拥有多个平行世界(官方说法...平行世界应该代表一颗虚拟dom树),每个平行世界都做着自己的任务,fiber孜孜不倦的调度这些任务,但是这些任务来自不同世界,只有主世界的任务会展现在dom上,其余分世界的任务需要满足开发者设定的条件才能合并入主世界。
fiber解读
fiber是react从16.3开始启用的协调器,替换掉了之前的栈协调器,特点是时间分片,不阻塞渲染的情况下完成协调。
特点:
- forceupdate/render/setstate返回一个work,并加入更新队列
- 每个work拥有自己的优先级expriation timer,work会按照优先级执行,但是状态依然会按照work入队时间累加(保证状态的时序)
- 同一个work可能会分布在多个帧之间执行,除非work被指定了同步执行,超时和首次渲染都会导致work同步执行
- 每个组件对比完成后会检查空闲时间,如果空闲时间不足,会暂存任务,将控制权返回给浏览器
- 在新的一帧里面,优先级低的任务可能会被高优先级的任务替换,导致低优先级任务执行结果丢弃,也导致了部分生命周期可能会重复执行。
- commit side effect是同步
- fiber使用了保存了镜像树,让低优先级任务有机会复用高优先级任务的成果
Vue相关
$nextTick实现流程
优先使用Promise,然后MutationObserver,之后setImmediate,最后setTimeout;前两者为microtask,后两者为macrotask。
nextTick使用了变量pending,防止流程的重入(即多次调用nextTick的场景)
vue初始化相关
vue的整体架构设计基于mixin和委托,利用这种模式划分处理模块。
- Vue脚本加载期间,挂载静态全局方法,并通过混入的方式挂载原型方法,这期间干了这些事。
- 将原型上的$data,$prop属性的访问请求委托到相应的实例对象上。
- 还有其余Vue的原型方法,基本都是这个时期挂上去的($set、$watch、$on)。
- Vue实例化流程
- 合并配置,配置可能来自Vue.extend添加的配置,new Vue传入的配置,父实例传给子组件的配置等等。
- 初始化生命周期相关的属性,维护父子跟组件关系
- 维护原生事件和组件间的关系
- 初始化渲染函数,创建了虚拟dom的构建函数,将实例属性$attr、$listeners设置为响应式$attr包含父作用域传进来的原生属性,$listeners则是除了native外的事件。
- 触发beforeCreate钩子
- 按顺序初始化injection、props、data、provider(响应式)
- 触发create钩子
- 执行mount
- Vue响应式原理
- 响应式核心方法defineProperty,响应式的属性触发getter时候,会导致初始化时创建的dep将当前活跃的watcher添加到dep中。
- 触发setter时,会通知dep中的watcher
- watcher初始化的时候,除了标为lazy外的watcher,会调用自己的get方法,将当前活跃的watcher标志位自身,并访问对应的响应式属性(render也是个watcher)。
- Vue挂载流程
- $mount的方法按照几个不同的包实现方法不一样,只描述web-runtime版本
- 执行beforeMount钩子
- 将渲染函数构建成一个watcher,该watcher的get方法
// get方法大概长这样,render返回vnode () => updates(render())
- 内部使用vm.$createElement构建虚拟dom,就是下面那个h,最后返回vnode树
render(h) { return h('h1', '我是标题')}
- _update方法负责将vnode dispatch到真实dom上,无论初始化渲染还是更新都是这个方法,后续属于snabbdom。
- 更新流程
- _update方法实际最终调用__patch__方法。
vm._vnode = vnode // Vue.prototype.__patch__ is injected in entry points // based on the rendering backend used. if (!prevVnode) { // initial render vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */) } else { // updates vm.$el = vm.__patch__(prevVnode, vnode) }
- 如果是首次挂载,直接把vnode传递给createElm生成dom,否则会有个对比的过程
- diff的过程大概就是比较类型、比较key、递归调用进行比较子组件
JavaScript基础相关
作用域相关
- 全局声明const let class作用域挂在一个名为scope的作用域上,而不是window
- "use strict",var声明的变量也不会挂在window上
- var const let class函数声明都存在作用域提升,但let、const、class不允许初始化前使用(暂时性死区)。
- 访问或者使用typeof检测在暂时性死区的变量会报ReferenceError
- 严格模式下不允许在块作用域中声明函数
- 块作用域中的函数,函数体提升到块作用域的顶部,但是变量本身会提升到全局作用域,块作用域中不允许使用var声明已经声明过得函数。
// SyntaxError
if(true) {
var a = 0;
function a() {}
}
// ok
typeof a // undefined
if(true) {
typeof a // function
function a() {}
function a() {}
}
类型相关
a instanceof b检测b的原型是否在a的原型链上。
function A() {};
var a = new A();
a instanceof A; // true
a.constructor = A;
其他类型相关参考:[] == ![] !? 浅析JS的类型系统和隐式类型转换
操作符相关
- 逗号操作符 - 对它的每个操作数求值(从左到右),并返回最后一个操作数的值(exp1, exp2)
- in操作符会检索原型链上的属性
- for..in会遍历所有可枚举的属性,包括原型链上的,但是原生方法一般不可枚举
- for..of使用迭代器遍历
- 操作符执行优先级
- 20 - 圆括号(永远先执行圆括号内的运算)
- 19 - 成员访问、需计算的成员访问、无参数的new调用、函数调用。
- 17 - i++,i--
- 16 - ! ~ +i -i ++i --i typeof void delete await
- 15 - **
- 14 - * / %
- 13 - + -
- 12 - >> << >>>
- 11 - > < >= <= instanceof
- 10 - == != === !==
- 9 ~ 4 - & ^ | && || ? :
- 3 - 赋值操作
- 2 - yield
- 1 - 展开运算符
- 0 - 逗号
// 之前很火的面试题,因为成员访问运算高于赋值运算
var a = { b: { d: 2 } }
var c = a;
a.b = a = { f: 3 };
console.log(a, c)
// { f: 3 } { b: { d: 2 } }
class相关
- 类中的所有方法是不可枚举的,自定义的原型方法需要手动设置
- 继承类会继承超类的静态方法和实例方法,继承类的this在调用super后创建。
// es5继承的实现
function Bar() {
this.bar = 'bar'
}
Bar.prototype.getBar = function() {
return this.bar
}
function Foo() {
this.foo = 'foo'
Bar.call(this)
}
Foo.prototype = Object.create(Bar.prototype);
Foo.prototype.constructor = Foo;
Foo.prototype.getFoo = function() {
return this.foo;
}
箭头函数相关
- 箭头函数没有prototype属性,使用new调用会报错typeerror
- 箭头函数使用call、apply、bind会忽略传入的this
- 箭头函数的this继承父级
文件相关
- File对象除了由开发构建,还能来自于input[type="file"]和drop事件的event.dataTransfer.files。
- File继承于Blob(本质应该是文件描述符)
- 现代浏览器ajax都可以直接发送File/Blob(binary)
- URL.createObjectURL可以为File/Blob/MediaSource等数据结构生成url链接(可以用于本地上传文件的预览,如PDF预览/图片预览),使用完后需要手动调用revokeObjectURL释放引用。
- FileReader接口主要用于将File/Blob转化为array buffer/dataurl(base64)/text(根据对象的mine-type进行编码)
- Blob.slice方法主要用于大文件的分片传输
nodejs相关
nodejs架构
- Javascript
- C/C++ Binding/Addons(C++插件)
- libuv(异步调度)/v8(JS执行引擎)/其他系统底层工具(http等)
nodejs事件模型与浏览器的event loop有比较大的差距
- Timer(setTimeout setInterval)
- pending Task(部分系统回调,例如tcp error)
- idle,prepare(内部使用)
- poll(大部分异步回调都在这个阶段执行,且这个阶段会阻塞,直到下一个timer或者存在setImmediate的回调)
- check(setImmediate)
- close callback(关闭的回调在这执行,tcp.on('close'))
每种任务结束都会执行,proccess.nextTick和其他微任务。
内存相关
- 经常泄露内存的场景
- 大量永不决议的promise
- 框架context之外的引用情况
- js引用dom没有释放的场景
- 单例引用已经销毁的句柄(Component/Incomingmessage)
- 还有闭包的引用问题
- v8内存分配情况
- Buffer不属于v8的内存分配,使用C++申请内存
- v8堆内存分为新生代和老生代,新生代使用Scavenge算法分配,每次gc将from区域的内存复制到to区域,对于已经经历过该算法的内存或者to区域空闲率超过25%,会被移动到老生代
- 老生代使用标记-清楚算法gc。
以下为个人备忘录部分
个人介绍
17年毕业,当年3月开始进行前端开发到现在应该三年左右,目前在一家做银行资产管理系统的公司任职前端开发工程师,主要负责交易和风控相关的业务,业务特点是复杂表单交互,业务之外还参与一些前端工程化的工作和前沿的分享(比如:组件库/脚手架的维护/),之前一家公司做一些小项目的前端负责人包含(PC/小程序/h5)。技术特点是:熟悉react/vue/mobx,读过部分核心源码,了解大多特性的实现原理,熟悉JavaScript大部分特性,大概有一年的typescript使用经验。nodejs比较熟悉koa和eggjs,对nodejs的一些核心模块也有一定理解。
项目中能讲的点:推动typescript、微服务化、dashboard模式
微服务化
业务背景
业务特点:业务专业性较强,各个业务虽然互相依赖,但是开发人员通常只理解自己那块业务。
启动产品化开发,组织架构变动,由之前十多人的组拆分成多个业务小组+架构组,方便人才的定向培养。
原有UI工程在一个项目内(还有一些不在同个项目的UI工程也需要合并进来),本来同一个组还能由组长统一协同,现在分散到各组,协同成本增加,各个业务线需求紧急度和发版频率也不一致,每个业务线也拥有业务相关的公用组件,导致发生把代码切分成多个项目单独维护的想法,线上通过代码聚合的组织方式。
具体实施
第一阶段先使用iframe的方式进行快速的切分,外部菜单通过菜单的业务属性展示不同的iframe,完成第一部的切分。
iframe的缺点:存在跨域的问题,跨业务模块的组件通信有很大的限制,对于业务模块的布局模式也有很大的限制,本身iframe的开销也很大。
动态加载编译好的js,返回react.element,动态挂载,内部使用memory route,存在的问题:和下面的dashboard模式有冲突,不能灵活组合,
自应用跟节点增加needToRenderAtPortal属性,保存对应的路径和挂载的元素。
dashboard模式
业务背景
资产管理平台这种类型的产品会涉及到多个使用角色:基金经理、操作员等等,每个角色对信息的关注不一样,但是每种人总是关注某几个类型的界面;于是乎需要频繁的切换界面;于是乎我们抽象出工作台这么一种模式,将页面打散,可供用户自由组装成自己的工作台。从技术上来讲工作台就是提供可供拖拽组合的界面,并能够记住用户的个性化设置。
具体实施
本质上没有什么技术上的难度,难度在于产品抽象上,比如工作台使用平铺还是层级关系,前者可以扩展几个组件之间的联动关系,后者方便用户操作。
组件注册成为可拖拽组合的组件,使用写死的URL的模式,因为使用装饰器注册对懒加载不友好。
最后选择了平铺的方式,平铺的方式如果拓展组件间的交互方式又是一个问题,不可能每个组件之间都有个性化的交互,所以这里有需要对组件本身进行类型的交互。 比如:searchBar可以提交一个search update事件,所有的可搜索的组件会响应这个事件,调用自身实现的相应方法进行更新。
成果
在对客户进行poc的过程中,客户对我们这种交互模式表示特别的关心
命令式的控制文档更新,精准原子化,语法可以使用类似sql