2021初前端面试

203 阅读14分钟

css

js基础

weakmap和map区别

  • Set 成员唯一、无序且不重复
    [value, value],键值与键名是一致的(或者说只有键值,没有键名)
    可以遍历,方法有:add、delete、has
  • WeakSet 成员都是对象
    成员都是弱引用,可以被垃圾回收机制回收,可以用来保存DOM节点,不容易造成内存泄漏
    不能遍历,方法有add、delete、has
  • Map 本质上是键值对的集合,类似集合
    可以遍历,方法很多可以跟各种数据格式转换
  • WeakMap 只接受对象作为键名(null除外),不接受其他类型的值作为键名
    键名是弱引用,键值可以是任意的,键名所指向的对象可以被垃圾回收,此时键名是无效的
    不能遍历,方法有get、set、has、delete

apply、call、bind区别

apply 、 call 、bind 三者都是用来改变函数的this对象的指向的;
apply 、 call 、bind 三者第一个参数都是this要指向的对象,也就是想指定的上下文;apply 、 call 、bind 三者都可以利用后续参数传参;
bind是返回对应函数,便于稍后调用;apply、call则是立即调用。
call 需要把参数按顺序传递进去,而 apply 则是把参数放在数组里。
某个函数的参数是明确知道数量时用 call ; 而不确定的时候用 apply,然后把参数 push进数组传递进去。当参数数量不确定时,函数内部也可以通过 arguments 这个数组来遍历所有的参数
func.call(this, arg1, arg2);
func.apply(this, [arg1, arg2])

原型、继承

javascript中的继承是通过原型链来体现的。
每个对象都有一个__proto__属性,指向构造函数的prototype。
访问一个对象的属性时,先在基本属性中查找,如果没有,再沿着__proto__这条链向上找,这就是原型链。

  • class继承 uper代表的是父类构造函数,但是返回的是子类的实例。比如A是B的父类,那么super的功能相当于A.prototype.constructor.call(this)。
  • 原型链继承、构造函数继承、组合继承、原型式继承、寄生式继承、组合式寄生式继承

判断数据类型

Object.prototype.toString.call()

防抖节流

/**
 * 节流
 * @param func 
 * @param wait 
 */
function throttle(func: Function, wait: number) {
  let timer: number = 0;
  return (...args) => {
    if (timer) { return }
    timer = window.setTimeout(() => {
      func(...args)
      timer = 0
    }, wait)
  }
}
/**
 * 防抖
 * @param func 
 * @param wait 
 */
function debounce(func: ()=>void, wait: number) {
  let timer: number = 0
  return (...args) => {
    clearTimeout(timer)
    timer = window.setTimeout(() => {
      func(...args)
      timer = 0; // 必须么??
    }, wait)
  }
}

业务场景

http

浏览器缓存

  • 强缓存 服务器通知浏览器一个缓存时间,在缓存时间内,下次请求直接使用缓存,不在时间内,执行比较缓存策略;
  1. Expires 优点:
    HTTP 1.0产物,可以在HTTP 1.0和1.1中使用,简单、易用。
    以时刻标识失效时间。
    缺点:
    时间是由服务器发送的,如果服务器时间和客户端时间不一致,可能会出现问题。
    存在版本问题,到期之前的修改客户端是不可知的。

  2. Cache-Control
    private(默认值):客户端可以缓存,代理服务器不能缓存
    public:客户端和代理服务器都可缓存
    no-cache:在发布缓存副本之前,强制要求缓存把请求提交给原始服务器进行验证(协商缓存验证)
    max-age:设置缓存存储的最大周期,超过这个时间缓存被认为过期(单位秒)
    no-store:缓存不应该存储有关客户端请求或服务器响应的任何内容,即使不使用任何缓存

  • 协商缓存 让客户端与服务器之间能实现缓存文件是否更新的验证、提升缓存复用率,将缓存信息中的Etag和Last-Modified通过请求发送给服务器,由服务器校验,返回304状态码时,浏览器直接使用缓存;
  1. Last-Modified/If-Modified-since 优点:
    不存在版本问题,每次请求都会去服务器进行校验。服务器对比最后修改时间如果相同则返回304,不同返回200及资源内容。
    如果返回的是 304,返回的仅仅是一个状态码而已,并没有实际的文件内容,因此在响应体的体积上节省是很好的优点
    缺点:
    只要资源发生了修改,无论内容是否发生了实质性的改变,都会将该资源返回客户端。例如周期性重写,但这种情况下资源包含的数据实质是一样的。
    以时刻作为标识,无法识别一秒内多次修改的情况。如果资源更新的速度是秒以下的单位,那么该缓存是不能被使用的,因为它的时间最低单位是秒。
    某些服务器不能精确的得到文件最后修改时间。
    如果文件是服务器动态生成的,那么该方法的更新时间永远是生成的时间,尽管文件可能并没有变化,所以也起不到缓存的作用。
  2. Etag/If-None-match 为了解决上述问题,出现了一组新的字段Etag/In-None-Match。
    Etag是上一次加载资源时,服务器返回的。它的作用是唯一用来标识资源是否有变化 浏览器在下一次发起请求时,会将上一次返回的Etag值赋值给If-None-Match并添加在 Request Header 中。服务端匹配传入的值与上次是否一致,如果一致返回304,浏览器则读取本地缓存,否则返回200和更新后的资源及新的Etag
    优点:
    可以更加精确的判断资源是否被修改,可以识别一秒内多次修改的情况 不存在版本问题,每次请求都会去服务器进行校验
    缺点:
    计算Etag值需要性能损耗
    分布式服务器存储情况下下,计算Etag的算法如果不一致,会导致浏览器从一个服务器上获取得页面内容后到另一台服务器上进行验证时出现Etag不匹配的情况

301(永久重定向)和302(临时重定向)的区别

  1. 定义 301:被请求的资源已永久移动到新位置,并且将来任何对此资源的引用都应该使用本响应返回的若干个 URI 之一。如果可能,拥有链接编辑功能的客户端应当自动把请求的地址修改为从服务器反馈回来的地址。除非额外指定,否则这个响应也是可缓存的。

302:请求的资源现在临时从不同的 URI 响应请求。由于这样的重定向是临时的,客户端应当继续向原有地址发送以后的请求。只有在Cache-Control或Expires中进行了指定的情况下,这个响应才是可缓存的。

两者都是一个POST请求经过 301/302 后会被浏览器转为GET请求

  1. 缓存 定义上已经给出,对于301请求,浏览器是默认给一个很长的缓存。而302是不缓存的。

  2. 搜索引擎 301: 旧地址A的资源不可访问了(永久移除), 重定向到网址B,搜索引擎会抓取网址B的内容,同时将网址保存为B网址。

302: 旧地址A的资源仍可访问,这个重定向只是临时从旧地址A跳转到B地址,这时搜索引擎会抓取B网址内容,但是会将网址保存为A的。

  1. 安全 尽量使用301跳转,以防止网址劫持!

假如,A -> B。大部分的搜索引擎在大部分情况下,当收到302 重定向时,有的时候搜索引擎,尤其是Google,并不能总是抓取目标网址。比如说,有的时候A 网址很短,但是它做了一个302 重定向到B 网址,而B 网址是一个很长的乱七八糟的URL 网址,甚至还有可能包含一些问号之类的参数。很自然的,A 网址更加用户友好,而B 网址既难看,又不用户友好。这时Google 很有可能会仍然显示网址A。由于搜索引擎排名算法只是程序而不是人,在遇到302 重定向的时候,并不能像人一样的去准确判定哪一个网址更适当,这就造成了网址URL 劫持的可能性。也就是说,一个不道德的人在他自己的网址A 做一个302 重定向到你的网址B,出于某种原因, Google 搜索结果所显示的仍然是网址A,但是所用的网页内容却是你的网址B 上的内容,这种情况就叫做网址URL 劫持。你辛辛苦苦所写的内容就这样被别人偷走了。302 重定向所造成的网址URL 劫持现象,已经存在一段时间了。不过到目前为止,似乎也没有什么更好的解决方法。在正在进行的谷歌大爸爸数据中心转换中,302 重定向问题也是要被解决的目标之一。从一些搜索结果来看,网址劫持现象有所改善,但是并没有完全解决。

简单来说就是:有个坏人把他的电话来电转移到了一个明星那,让大家都以为他的电话是那个明星的。他的手机号成名后,就可以拉个微信群,大胆的假装明星,实现他的微shang梦,从此走上人生巅峰。

react

setState

当setState被调用时,新的state会进入一个状态队列,相同的操作被合并,这里的合并可以理解成是React做了一次浅拷贝合成的新值,

Object.assign(
    previousState,
    {count : state.count + 1},
    {count : state.count + 1}
)

复制代码 之后调用enqueueUpdate方法决定是否采用批量更新方式更新组件,判断条件是isBatchingUpdates
setState 并非真异步,只是看上去像异步。在源码中,通过 isBatchingUpdates 来判断 setState 是先存进 state 队列还是直接更新,如果值为 true 则执行异步操作,为 false 则直接更新。
场景
在 React 可以控制的地方,就为 true,比如在 React 生命周期事件和合成事件中,都会走合并操作,延迟更新的策略。 但在 React 无法控制的地方,比如原生事件,具体就是在 addEventListener、setTimeout、setInterval 等事件中,就只能同步更新。

redux、react-redux、redux-saga

  • redux
    简单来说就是view层触发一个action到dispatcher,dispatcher将aciton传入reducer进行计算,得到新的state后传入view层进行状态更新。
  • react-redux
    redux与react一起使用时,我们需要手动在组件里通过subscribe监听state的变化并更新组件,为了解决这样的问题,redux官方提供了react-redux的库,通过connect的方式连接state和react组件,达到自动监听的效果。
  • redux-saga
    提到redux-saga,通常会提到redux-thunk,两者都是redux的中间件,都是对异步场景的处理。 redux-thunk的大概流程: redux-thunk虽然支持了异步场景,但其存在的缺点也很明显:
    1、使用回调的方式来实现异步,容易形成层层回调的面条代码
    2、异步逻辑散落在各个action中,难以进行统一管理
    因此,出现了redux-saga更强大的异步管理方案,可以代替redux-thunk使用。
    redux-saga的大概流程:
    其主要特点
    1、使用generator的方式实现,更加符合同步代码的风格; 2、统一监听action,当命中action时,执行对应的saga任务,并且支持各个saga之间的互相调用,使得异步代码更方便统一管理。 在saga中,出现了新的概念,其中effect指一个普通的js对象,描述一个指定的动作,saga指一个generator函数。

虚拟dom、diff算法

  • 什么是虚拟DOM 虚拟DOM是一颗以JavaScript对象(node节点)作为基础的树,用对象属性来描述节点,他是对真实DOM的抽象,通过一些列操作使这棵树映射到真实环境上。
    虚拟dom就是能代表DOM树的对象,通常含有标签名,标签上的属性,事件监听和子元素们以及其它属性
  • 为什么要有虚拟DOM这个东西 减少DOM的操作
    跨平台渲染
  • diff算法的三个步骤
  1. 用 JS 对象的方式来表示 DOM 树的结构,然后根据这个对象构建出真实的 DOM 树,插到文档中
  2. 当状态变化的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树的差异
  3. 最后把所记录的差异应用到所构建的真正的DOM树上,进行视图更新
  • DOM diff 在做比较时分为了三个层级
    1.Tree Diff(层级比较)
    先进行树结构的层级比较,对同一个父节点下的所有子节点进行比较;
    接着看节点是什么类型的,是组件就做 Component Diff;
    如果节点是标签或者元素,就做 Element Diff;
    2.component Diff (组件比较)
    若组件类型相同,则继续按照层级比较其虚拟 DOM的结构;
    如果组件类型不同,则替换整个组件的所有内容
    3.Element Diff (元素比较)
    如果节点是原生标签,则看标签名做比较是否相同来决定替换还是更新属性
    然后进入标签后代递归 Tree Diff

安全

http网络劫持与DNS劫持原理及预防

网络劫持的原理
DNS 劫持

一般而言,用户上网的DNS服务器都是运营商分配的,所以在这个节点上,运营商可以为所欲为。 例如,访问jiankang.qq.com/index.html,…
HTTP劫持
在运营商的路由器节点上,设置协议检测,一旦发现是HTTP请求,而且是html类型请求,则拦截处理。 常见有两种: 类似DNS劫持返回302让用户浏览器跳转到另外的地址。(钓鱼网站就是这么干) 在服务器返回的HTML数据中插入js或dom节点(广告)。(比较常见) 被劫持怎么办?
在html 上加上 http-equiv="Cache-Control" content="no-transform " /> 百度官方给的禁止转码声明。最有用的方式,使用HTTPS ,不让数据那么明显的裸奔。 https 加了SSL协议,会对数据进行加密。 在开发的网页中加入代码过滤,大概思路就是用JavaScript代码检查所有的外链是否属于白名单。

各种劫持的手段都有:

  1. 直接返回一个带广告的HTML
  2. 在原html中插入js,再通过js脚本安插广告;
  3. iframe展示原来正常网页。 js实际对抗

在window 监听 DOMNodeInserted 事件,上报插入的dom、分析插入的dom 信息。(通常匹配所有的url,逐个比较是否白名单域名,如果不是,则判定为劫持,上报的同时,移除dom.parentNode.removeChild(dom)); 刚插入的dom。小心误伤。比较稳的操作是做监测统计,再决策预防。

ul.addEventListener('DOMNodeInserted',function (e) {
    console.log(e.srcElement)
    console.log(ul.childElementCount)
})
ul.addEventListener('DOMNodeRemoved',function (e) {
    console.log(e.srcElement)
    console.log(ul.childElementCount)
})\