3月web前端面试小结 | 掘金技术征文

4,052 阅读25分钟

说一下box-sizing的应用场景

  1. box-sizing的属性值分为两个,border-box和content-box,其中,
    border-box:width=content+padding+border
    content-box: width=content

应用场景
2. border-box属性在form上的使用
当我们在要做一个登陆页面的时候,这时候就需要表单和按钮这些元素
首先我们在div中设置两个表单,一个用来输入用户名,一个用来输入密码,同时还有一个登录按钮, 当我们想让这两个表单和一个登录按钮的长度相同时,我们试着把她们三个的width的值设置为100%
但她们的长度并不一致,表单和按钮的padding,border值不统一,这时我们给表单的属性中添加一个box-sizing:border-box,此时表单和按钮的长度保持一致
当不给表单添加box-sizing:border-box时,而是添加一个padding值会发现表单的长度都有所增加
当给按钮添加padding时,她的长度并不会改变,由此可以看出表单默认为content-box,按钮submit默认为border-box,button的默认值也为border-box

  1. border-box属性在盒子中的使用
    当我们设置一个宽度为500px的盒子,在里面放入四个盒子,分别为上(width:100%),中左(width:60%),中右(width:40%),下(width: 100%),此时在大盒子里面四个盒子排列的很整齐
    这是我们给上盒子设置一个padding或者border为5px,这时上盒子的长度就会超出大盒子的宽度,此时我们给上盒子添加box-sizing:border-box,就可以使他的宽度不超出

  2. 说到这里你应该就能看到,box-sizing:border-box这个属性值可以使dom元素的padding和border属性值作用于自身,而不对同级的兄弟元素造成影响

参考链接 box-sizing的使用场景

说一下你了解的弹性flex布局

  1. flex布局是什么?
    flex布局意为弹性布局,任何一个容器都可以指定为flex布局(display: flex),行内元素也可以使用flex布局(display: inline-flex),webkit内核的浏览器,必须加上(diaplay: -webkit-flex),设为flex布局后,子元素的float,clear,vertical-align属性将失效

  2. 基本概念
    采用flex布局的元素,成为flex容器,她的所有子元素自动成为容器成员,称为flex项目
    容器默认存在两根轴,水平的主轴(main axis)和垂直的交叉轴(cross axis),默认沿主轴排列,单个项目占据的主轴空间叫做main size

  3. 容器的属性
    这6个属性设置在容器上,flex-direction,flex-wrap, flex-flow, justify-content, align-items, align-content

    • flex-direction决定主轴的方向,即项目排列的方向
      flex-direction:row | row-reserve | column | column-reserve

    • flex-wrap决定项目在一条轴线上排不下时,如何换行
      flex-wrap: nowrap | wrap | wrap-reserve

    • flex-flow是flex-direction属性和flex-wrap属性的简写,默认为row nowrap

    • justify-content决定项目在主轴上的对齐方式
      justify-content: flex-start | flex-end | center | space-between | space-around

    • align-items决定项目在交叉轴上如何对齐
      align-items:flex-start | flex-end | center | baseline | stretch

    • ailgn-content决定多根轴线的对齐方式,如果项目只有一根轴线,该属性不起作用
      align-content: flex-start | flex-end | center | space-between | space-around | stretch

  4. 项目的属性 这6个属性设置在项目上,order,flex-grow,flex-shrink,flex-basis,flex,algin-self

    • order定义项目的排列顺序,数值越小,排列越靠前,默认为0
      order:<integer>

    • flex-grow定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大
      flex-grow:<number>
      如果所有项目的flex-grow属性都为1,则他们将等分剩余空间(如果有的话)

    • flex-shrink定义项目的缩小比例,默认为1,即如果空间不足,该项目将缩小
      flex-shrink: <number>
      如果一个项目的flex-shrink属性为0,其他项目为1,则空间不足时,前者不缩小

    • flex-basis定义项目在分配多余空间之前,项目占据的主轴空间,默认为auto,可以设置为width或height一样的值
      flex-basis:<length> | auto

    • flex属性是flex-grow,flex-shrink,flex-basis的简写,默认为0 1 auto
      默认存在两个快捷值auto(1,1,auto)和none(0,0,auto)

    • align-self允许单个项目和其他项目不一样的对齐方式,可覆盖align-items属性,默认值为auto
      align-self:auto | flex-start | flex-end | center | baseline | stretch

参考链接 flex布局教程:语法篇
参考链接 flex布局教程:实战篇

说一下一个未知宽高元素怎么上下左右垂直居中

  1. position+absolute
position: absolute;
top: 50%;
left: 50%;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
  1. flex
display: flex;
align-items: center;
justify-content: center;
  1. table-cell
display: table;(父)

display: table-cell;(子)
text-align: center;
vertical-align: middle;
  1. position+margin
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;

参考链接 未知宽高元素水平居中

说一下原型链,对象,构造函数之间的一些联系

  1. javascript函数的new关键字到底是干什么的
    不用创建临时对象,因为new会帮你做
    不用绑定原型,因为new会帮你做
    不用return临时对象,因为new会帮你做
    不要给原型想名字了,因为new指定名字为prototype

  2. 对象与原型链(__proto__和prototype)
    每个JS对象一定对应一个原型对象,并从原型对象继承属性和方法
    对象__proto__属性的值就是他所对应的原型对象
    只有函数才有prototype属性,当你创建函数时,JS会为这个函数自动添加prototype属性,值是一个有constructor属性的对象,不是空对象,而一旦你把这个函数当作构造函数调用时,那么JS就会帮你创建该构造函数的实例,实例继承构造函数prototype的所有属性和方法
    对象的__proto__指向自己构造函数的prototype
    Object.prototype是原型链的顶端,Object本身是构造函数,继承了Function.prototype,Function也是对象,继承了Object.prototype
    Object.prototype.proto === null,说明原型链到Object.prototype终止
    null表示‘没有对象’,即此处不该有值
    Function本身就是函数,Function.__proto__是标准的内置对象Function.prototype,而Function.prototype.__proto__是标准的内置对象Object.prototype

  3. 构造函数和原型
    原型:每一个JS对象(除null外)在创建的时候就会与之关联另一个对象,这个对象就是我们说的原型,每个对象都会从原型继承属性
    proto:每一个JS对象(除null外)都具有的一个属性,叫__proto__,这个属性会指向该对象的原型
    constructor:每一个原型都有一个constructor属性指向关联的构造函数

参考链接 js的new到底是干什么的
参考链接 从__proto__和prototype来深入理解JS对象和原型链
参考链接 javascript深入之从原型到原型链
参考链接 从探究Function.proto===Function.prototype过程中的一些收获

DOM事件绑定的几种方式

  1. html中直接绑定
    html中绑定事件叫做内联绑定事件,不利于分离

  2. js中直接绑定
    js中直接绑定称为赋值绑定函数,缺点是只能绑定一次

  3. addEventListener
    target.addEventListener(type, listener[, useCapture])
    target表示要监听事件的目标对象,可以是一个文档上的元素DOM本身,Window或者XMLHttpRequest
    type表示事件类型的字符串
    listener为当指定的事件类型发生时被通知到的一个对象
    useCapture为设置事件的捕获或者冒泡
    true为捕获,false为冒泡(默认)
    addEventListener可以给同一个dom元素绑定多个函数,并且执行顺序按照绑定顺序执行,且执行顺序与useCapture无关
    给一个dom元素绑定同一个函数,最多只能绑定useCapture类型不同的两次
    addEventListener只支持到IE9,为兼容性考虑,在兼容IE8及一下浏览器可以用attachEvent函数,和addEventListener函数表现一样,但它绑定函数的this会指向全局

  4. 事件的解绑
    通过dom的on***属性设置的事件,可以用dom.onclick = null来解绑
    通过addEventListener绑定的事件可以使用removeEventListener来解绑,接受参数一样
    对于使用removeEventListener函数解绑事件,需要传入的listener,useCapture和addEventListener完全一致才可以解绑事件

  5. 事件冒泡
    事件开始时由最具体的元素接受,然后逐级向上传播到较为不具体的节点

  6. 事件捕获
    事件捕获的思想是不太具体的DOM节点应该更早接收到事件,而最具体的节点应该最后接收到事件,与事件冒泡顺序相反

  7. DOM事件流
    DOM事件流包括三个阶段,事件捕获阶段,处于目标阶段,事件冒泡阶段,首先发生的是事件捕获,为截获事件提供机会,然后是实际的目标接受事件,最后一个阶段是事件冒泡阶段,可以在这个阶段对事件作出响应

  8. stopPropagation()和stopImmediatePropagation()
    stopPropagation()既可以阻止事件冒泡,也可以阻止事件捕获,也可以阻止处于目标阶段
    stopImmediatePropagation()既可以阻止事件冒泡,也可以阻止事件捕获,还会阻止该元素其他事件的发生

参考链接 从一个事件绑定说起-DOM

有没有了解http2,websocket,https,说一下你的理解以及你了解的特性

  1. http2.0和http1.1的区别

    • 多路复用
      多路复用允许单一的http2连接同时发起多重的请求-响应信息
      http性能优化的关键并不在于高带宽,而是低延迟,TCP连接会随着时间进行自我调谐,起初会限制连接的最大速度,如果数据成功传输,会随着时间的推移提高传输的速度,这种调谐则称之为TCP慢启动,由于这种原因,让原本就具有突发性和短时性的http连接变得十分低效
      http2通过让所有数据流共用同一个连接,可以更有效的使用TCP连接,让高带宽也能真正服务于http的性能提升
      小总结:多路复用技术,单连接多资源的方式,减少服务端的链接压力,内存占用更少,连接吞吐量更大,由于减少TCP慢启动时间,提高传输的速度

    因为所有的http2的请求都在一个TCP连接上,所以在http1中的自动化合并文件和Sprite合图等资源合并减少请求的优化手段对于http2来说是没有效果的

    • 二进制分帧
      http2在应用层和传输层之间增加一个二进制分帧层,http2会将所有传输的信息分割成更小的消息和帧,并对他们采用二进制格式的编码,其中http1的首部信息会被封装成Headers帧,而我们的request body则封装到Data帧里面

    • 首部压缩
      http请求和响应都是由状态行,请求/响应头部,消息主题三部分组成,一般而言,消息主题都会经过gzip压缩,或者本身传输的就是压缩后的二进制文件,但状态行和头部却没有经过任何压缩,直接以纯文本传输,浪费流量资源
      原理:头部压缩需要在支持http2的浏览器和服务端之间,维护一份相同的静态字典,包含常见的头部名称与值的组合,维护一份相同的动态字典,可以动态的添加内容,支持基于静态哈夫曼码表的哈夫曼编码

    • http2支持服务器推送
      服务端推送是一种在客户端请求之前发送数据的机制,当代网页使用了许多资源:html,样式表,脚本等,在http1.x中这些资源每一个都必须明确的请求,这可能是一个很慢的过程,因为服务器必须等待浏览器做的每一个请求,网络经常是空闲和未充分使用的
      为了改善延迟,http2引入了server push,它允许服务端推送资源给浏览器,在浏览器明确请求之前,一个服务器经常知道一个页面需要更多的附加资源,在他响应浏览器第一个请求时,可以开始推送这些资源,这允许服务端去完全充分利用一个可能空闲的网络,改善页面加载的时间
      有了http2的服务端推送,http1时代的内嵌资源的优化手段也变得没有意义了,使用服务端推送更高效,因为客户端可以缓存起来,甚至可以不同页面之间共享

    • 并行双向字节流的请求和响应
      在http2上,客户端和服务端可以把http消息分解成回不依赖的帧,然后乱序发送,最后再在另一端把她们重新组合起来,同一链接上可以有多个不同方向上的数据在传输,客户端可以一边乱序发送stream,也可以一边接收着服务端的响应,在服务端同理
      把http消息分解为独立的帧,交错发送,然后在另一端重新组装是http2最重要的一项增强,这个机制会在整个web技术栈中引发一系列的连锁反应,从而带来巨大的性能提升,因为

      1. 可以并行交错的发送请求,请求之间互不影响
      2. 可以并行交错的发送响应,响应之间互不干扰
      3. 只使用同一个连接即可并行的发送多个请求和响应
      4. 消除不必要的延迟,从而减少页面加载的时间
        也就是说‘域名分区’的优化手段对于http2来说是无用的,因为资源都是并行交错发送,且没有限制,不需要额外的多域名并行下载
    • http2的请求优先级
      每个http2流里面有个优先值,这个优先值确定着客户端和服务端处理不同的流采取不同的优先级策略,高优先级的流应该优先发送,但又不是绝对的准守,可能又会引入首队阻塞的问题,高优先级的请求慢导致阻塞其他文件的交付,分配处理资源和客户端与服务器间的带宽,不同优先级的混合是必须的

  2. https
    http协议传输的数据都是未加密的,也就是明文的,因此使用http协议传输隐私信息非常不安全,为了保证这些隐私数据能加密传输,于是网景公司设计了SSL协议用于对http协议传输的数据进行加密,从而诞生了https,现在的https使用的都是TSL协议

    https在传输数据之前需要客户端和服务端之间进行一次握手,在握手的过程中将确立双方加密传输数据的密码信息,TSL/SSL协议不仅仅是一套加密传输的协议,TSL/SSL中使用了非对称加密,对称加密以及hash算法

    握手过程:

    • 浏览器将自己支持的一套加密规则发送给网站

    • 网站从中选出一组加密算法和hash算法,并将自己的身份信息以证书的形式发回给浏览器,证书里面包含了网站地址,加密公钥,以及证书的颁发机构等信息

    • 获得网站证书后浏览器要做以下工作

      1. 验证证书的合法性(颁发证书的机构是否合法,证书中包含的网站地址是否与正在访问的地址一致)如果证书受信任,则浏览器栏里会显示一个小锁头,否则会给出证书不受信的提示
      2. 如果证书受信任,或者是用户接受了不受信的证书,浏览器会生成一串随机数的密码,并用证书中提供的公钥加密
      3. 使用约定好的hash计算握手信息,并使用生成的随机数对消息进行加密,最后将之前生成的所有信息发送给网站
    • 网站接收浏览器发来的数据之后要做以下工作

      1. 使用自己的私钥将信息解密取出密码,使用密码解密浏览器发来的握手信息,并验证hash是否与浏览器发来的一致
      2. 使用密码加密一段握手信息,发送给浏览器
    • 浏览器解密并计算握手信息的hash,如果与服务端发来的hash一致,此时握手过程结束,之后所有的通信数据将由之前浏览器生成的随机密码并利用对称加密算法进行加密

    这里浏览器与网站互相发送加密的握手信息并验证,目的是为了保证双发都获得了一致的密码,并且可以正常的加密解密数据

    其中非对称加密算法用于在握手过程中加密生成的密码,对称加密算法用于对真正传输的数据进行加密,而hash算法用于验证数据的完整性

    由于浏览器生成的密码是整个数据加密的关键,因此在传输的时候使用了非对称加密算法对其进行加密,非对称加密算法会生成公钥和私钥,公钥只能用于加密数据,因此可以随意传输,而网站的私钥用于对数据进行解密,所以网站都会非常小心的保管自己的私钥,防止泄漏

    TSL握手的过程中如果有任何错误,都会使加密连接断开,从而阻止了隐私数据的传输,正是由于https非常的安全,攻击者无法从中找到下手的地方,于是更多的是采用了假证书的手法来欺骗客户端,从而获取明文信息

  3. webSocket概述
    http协议是一种无状态的协议,要实现有状态的会话必须借助一些外部机制如session和cookie,这或多或少或带来一些不便,尤其是服务端和客户端需要实时交换数据的时候
    webSocket允许服务器和客户端进行全双工通信,传统的http是单工通信的,它只允许客户端向服务端发出请求,服务端被动返回数据,而不能主动向客户端传递数据
    webSocket的请求头部

    Connection: Upgrade             //通信协议提升
    Upgrade: websocket              //传输协议升级为websocket
    Sec-WebSocket-Key: **********   //握手协议密钥,base64位编码的16字节的随机字符串
    

    webSocket的响应头部

    Connection: Upgrade                 //通信协议提升
    Upgrade: websocket                  //传输协议升级为websocket
    Sec-WebSocket-Accept: **********    //将客户上报的Sec-WebSocket-Key和一段GUID(258EAFA5-E914-47DA-95CA-C5AB0DC85B11)进行拼接,再将这个拼接的字符串做SHA-1 hash计算,然后再把得到的结果通过base64加密,最后再返回给客户端
    

    WebSocket,ajax轮询和long poll

参考链接 http2.0协议你应该准备的面试题
参考链接 面试时如何优雅的谈论http
参考链接 http2.0的奇妙日常 参考链接 浅谈WebSocket协议及其实现

  1. 说说对洗牌算法的理解和如何验证其正确性
    洗牌算法之前没了解过,刚面到的时候好蒙,闲话不多说,这里说下洗牌算法的js实现

    Fisher-Yates
    这是最经典的洗牌算法,其算法思想是从原数组中随机抽取一个新的元素到新数组中

    从还没处理的数组(假如还剩n个)中,产生一个[0,n]之间的随机数random

    从剩下的n个元素中把第random个元素取出到新数组中

    删除原数组第random个元素

    重复第2 3步直到所有的元素取完

    最终返回一个新的打乱的数组

    代码实现

    function shufle(arr){
        var result = [],
            random;
        while(arr.length > 0){
            random = Math.floor(Math.random() * arr.length);
            result.push(arr[random])
            arr.splice(random, 1)
        }
        return result;
    }
    

    这种算法的时间复杂度是O(n2)

参考链接 洗牌算法的js实现
参考链接 Fisher–Yates shuffle 洗牌算法

  1. 说一下你对事件委托和事件代理的理解?
    什么是事件委托?它还有一个名字叫事件代理,JavaScript高级程序设计上讲:事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件,当我们需要对很多元素添加事件的时候,可以通过事件添加到他们的父节点二将时间委托给父节点来触发处理函数

    为什么要使用事件委托?
    一般来说,dom需要有事件处理程序,我们都会直接给它设置事件处理程序就好了,那如果是很多的dom需要添加事件处理呢?比如我们这里有100个li,每个li都有相同的click事件,那么我们会用for循环的方法来遍历所有的li,然后给他们添加事件,那么这样会存在什么问题呢?
    在JavaScript中,添加到页面上的事件处理程序的数量将直接关联到页面整体的运行性能,因为需要不断的与dom节点进行交互,访问dom的次数越多,引起浏览器重绘与重排的次数就越多,就会延长整个页面交互就绪时间,这就是为什么性能优化的主要思想是减少dom操作的原因,如果使用事件委托,就会将所有的操作放到js程序里面,与dom的操作就只需要交互一次,这样就能大大的减少与dom的交互次数,提高性能
    每个函数都是一个对象,是对象就会占用内存,内存占用率就越大,自然性能就差了,比如上面的100个li,就要占用100个内存空间,如果是1000个,10000个呢,如果使用事件委托,那么我们就可以只对它的父级这一个对象(如果只有一个父级)进行操作,这样我们就需要一个内存空间就够了,是不是省了很多,自然性能就会更好

    事件委托的原理?
    事件委托是利用事件的冒泡机制来实现的,何为事件冒泡呢?这里介绍下浏览器dom事件处理的过程,dom2.0模型将事件流程分为三个阶段:事件捕获阶段,事件目标阶段,事件冒泡阶段。
    事件捕获:当某个元素触发某个事件,顶层对象document就会发出一个事件流,随着dom树的节点向目标元素节点流去,直到到达事件真正发生的目标元素,在这个过程中,事件相应的监听函数是不会被触发的
    事件目标:当到达目标元素之后,执行目标元素该事件相应的处理函数,如果没有绑定监听函数,那就不执行
    事件冒泡:从目标元素开始,往顶层元素传播,途中如果有节点绑定了相应的事件处理函数,这些函数都会被一次触发,如果想阻止事件冒泡,可以使用event.stopPropgation()或者event.cancelBubble=true来阻止事件的冒泡传播

    事件委托怎么实现:
    Event对象提供了一个属性叫target,可以返回事件的目标节点,我们称为事件源,也就是说,target就可以表示为当前事件操作的dom,但是不是真正操作的dom,当然,这个是有兼容性的,标准浏览器用event.target,IE浏览器用event.srcElement,此时知识获取了当前节点的位置,并不知道是什么节点名称,这里我们用nodeName来获取具体是什么标签名,这个返回的是一个大写的,一般转化为小写再进行比较
    如果你想将事件委托给父元素来处理,但每个子元素的事件内容又不相同时,这里我们可以给每个子元素添加一个唯一的key来作标识,然后在父元素中对其进行分别的处理

    const list = document.querySelector('#list)  
    const lists = list.querySelector('#list > li')
    for(let i=0; i<lists.length; i++){
        lists[i].dataset.key = 'list-' + i
    }
    list.addEventListener('click',function(e){
        const event = e || window.event
        const target = event.target || event.srcElement  
        if(target.nodeName.toLocaleLowerCase() === 'li'){
            switch(target.dataset.key){
                case 'list-1':
                    do something-1
                    break
                case 'list-2':
                    do something-2
                    break
                ...
                default:
                    do something-3
                    break
            }
        }
    })  
    

参考链接 JavaScrip事件代理和委托
参考链接 JS的事件委托和事件代理详解

  1. 说一下你对css重绘和重排的理解,两个有什么不同?
    这一题考的其实是网页性能的问题,我们先理解下对网页性能产生影响到因素,了解下网页是怎么生成的

    网页生成的过程,大致可以分为五步:

    • html代码转化为dom
    • css代码转化为cssom
    • 结合dom和cssom,生成一棵渲染树(包含每个节点的视觉信息)
    • 生成布局layout,即将所有的渲染树的节点进行平面合成
    • 将布局绘制paint在屏幕上

    在这五步里面,第一步和第三步都非常快耗时的是第四步和第五步
    其中生成布局flow和绘制paint这两步,合称为渲染render

    重绘和重排
    网页生成的时候,至少会渲染一次,用户访问的过程中,还会不断的重新渲染
    以下三种情况,会导致网页重新渲染

    • 修改dom
    • 修改样式表
    • 用户事件

    重新渲染,就需要重新生成布局和重新绘制,前者叫做重排reflow,后者交货重绘repaint
    需要注意的是,重绘不一定需要重排,比如改变某个网页元素的颜色,就只会触发重绘,不会触发重排,因为布局没有改变,但是,重排一定会导致重绘,比如改变一个网页元素的位置,就会同时触发重排和重绘,因为布局改变了

    对于性能的影响
    重绘和重排会不断触发,这是不可避免的,但是,他们是非常耗费资源的,是导致网页性能低下根本原因
    要提高网页性能,就是要降低重排和重绘的频率和成本,尽量少触发重新渲染

    • 一般来说,样式的写操作之后,如果有下面这些属性的读操作,都会引发浏览器立即重新渲染
      • offsetTop/offsetLeft/offsetWidth/offsetHeight
      • scrollTop/scrollLeft/scrollWidth/scrollHeight
      • clientTop/clientLeft/clientWidth/clientHeight
      • getComputeStyle()
    • 所以,从性能角度考虑,尽量不要把读操作和写操作,放在一个语句里,一般的规则是
      • 样式表越简单,重绘和重排就越快
      • 重排和重绘的dom层级越高,成本就越高
      • table元素的重绘和重排成本,要高于div元素

    提高性能的九个技巧

    • dom的多个读操作(或者多个写操作),应该放在一起,不要两个读操作之间,加入一个写操作
    • 如果某个样式是通过重排得到的,那么最好缓存结果,避免下一次用到的时候,浏览器又要重排
    • 不要一条条的改变样式,而是要通过改变class,或者csstext属性,一次性的改变样式
    • 尽量使用离线dom,而不是真正的网页dom,来改变元素样式,比如,使用cloneNode()方法,在克隆的节点上进行操作,然后再用克隆的节点替换原始节点
    • 先将原素设为display:none(需要一次重排和重绘),然后对这个节点进行100次操作,最后再恢复显示(需要一次重排和重绘),这样一来,你就用两次重新渲染,取代了可能高达100次的重新渲染
    • position属性为absolute或fixed的元素,重排的开销会比较小,因为用考虑他对其他元素的影响
    • 只有必要的时候,才会将元素的display属性设为课件,因为不可见的元素不影响重排和重绘,另外visibility:hidden的元素只会对重绘有影响,不会影响重排
    • 使用虚拟dom的脚本库,如react
    • 使用window.requestAnimationFrame(),window.requestIdleCallback()这两个方法调节重新渲染

参考链接 网页性能管理详解

因为懒而不想写的部分

  1. ajax原生实现
  2. ajax readyState值理解
  3. Event loop事件循环理解
  4. js事件循环机制
  5. react+redux理解
  6. js执行顺序
  7. 强缓存和协商缓存
  8. http缓存机制
  9. http缓存小结

大概就这些吧,从年后初八面试到现在,磕磕碰碰花了挺多精力的,前段时间过了腾讯三面,让我等hr电话,欣喜若狂的以为能进鹅厂了,等了一个多礼拜,杳无音信,到处找人问,得到的结果是等四月中下旬的校招再通知我面试,瞬间一盆冷水浇下来,我基础比较差,算法功底也不强,自知够不上大厂的门,认了,自己本事不够,怨不得别人,这段时间在v2ex这个社区收获挺多的,写篇自己的面经给大家看看,各位都是大佬,有什么写错的地方,多多指教,不喜勿喷,谢谢