腾讯面试之web安全很重要

539 阅读18分钟

前言

一年前端,腾讯wetest部门面经,话不多说,先看题(算法题已过滤,腾讯考察的算法不多,1-3道简单-中等leetcode)。 备注:大佬们可以只看题目,题目后面的解答都是我自己对题目的理解,不是标准答案,我也不知道标准答案。

一面

先做笔试题,大概半小时,题目少,很基础,就是数据类型的判断(考察typeof instanceof 以及它们的缺陷)、this指向问题、变量提升问题、继承问题、promise、async await、手写拓展一些数组方法。在自我介绍后,面试官会针对笔试题提出一些追问,以下是回忆版:

1、看你做过电视端项目,平常怎么做页面适配处理的?

目前的在电视端的项目仍然属于浏览器环境,但是机顶盒的内核不同,有的就是很老旧的低配linux内核,小米机顶盒、电信的IPTV、联通的NTV对JS语言的支持度不同,部分NTV对标IE8以下,不支持Canvas。在适配方面全靠css和浏览元标签。下面总结以下 元标签常用属性。

- width - viewport的宽度
- height - viewport的高度
- initial-scale - 初始的缩放比例
- minimum-scale - 允许用户缩放到的最小比例
- maximum-scale - 允许用户缩放到的最大比例
- user-scalable - 用户是否可以手动缩放

meta标签大全:http://segmentfault.com/blog/ciaocc/1190000002407912

2、手写一个方法获取字符串的所有字节长度

/** 获取字符串的所有字节长度 ,需要指定编码方式 */
function getByteLength(str, charset) {
    var total, charCode, i, len;
    charset = charset ? charset.toLowerCase() : "";
    if (charset == 'utf-16' || charset == 'utf16') {
        for (var i = 0, len = str.length; i < len; i++) {
            total = str.charCodeAt(i) <= Oxffff ? (total+2) : (total+4)
        }
    } else {
        for (i = 0, len = str.length; i < len; i++) {
            if (charCode <= 0x007f) {
                total += 1
            } else if (charCode <= 0x07ff) {
                totla += 2
            } else if (charCode <= 0xffff) {
                total += 3
            } else {
                totla += 4
            }
        }
    }
    return total
}

4、比如笔试题上面typeof null 输出object,如何正确输出Null的类型。

instanceof 可以正确的判断对象的类型,因为内部机制是通过判断对象的原型链中是不是可以找到类型的prototype,此外还有typeof。但是 typeof null输出object是因为在JS的最初版本中,使用的是32位系统,为了性能考虑使用了地位存储了变量的类型信息,000开头表示对象,而null是全零,导致它被错位的判定为object。可以使用 Object.prototype.toString.call(x)的方式获取变量正确的类型。

5、前端路由的意义,一版JS框架路由使用了哪些设计模式?

前端路由本质就是监听 URL 的变化,然后匹配路由规则, 显示相应的页面,并且无须刷新。目前单页面使用的路由就只有两种实现方式: hash 模式、 history 模式。 前端路由的设计主要是编译器模式与责任链模式,主要有三种思路。

onpopstate, 加在window上, 这是HTML5 HistoryAPI
onhashchange,加在window上, IE8开始 支持
onpropertychange, 加在document上,用于对付IE67

针对IE6-7需要重写iframe的文档来产生历史记录。这里可以通过定时器监听iframe。还需要加一个全局的点击事件,劫持 所有链接,当用户点击时,会导致地址栏发生变化。有些地址只是改变了一些参数与hash,我们就阻止它跳转,让它进入路由系统。并且手动修改地址栏。但地址栏发生变化时,对应的回调就会触发。然后在回调里面截取地址栏中的有效数据,比如onpopstate是抽取location的pathname,而其他情况是抽取hash。这些最好自己用正则来处理,还要decodeURIComponent, 然后进入匹配正则阶段,一开始要定义许多路由规则,比如

 router.add("https://juejin.im/editor/:bbb", cb1)
 router.add("https://juejin.im/editor/drafts/:bbb", cb2)
 router.add("https://juejin.im/ddd/:ddd", cb3)

它们会转换成正则,然后这些正则与后面回调全部变成一个个对象,放到数组中。再用刚才的字符串,逐个匹配,符合条件的就触发。这里可能会有许多处理。如抽取queryString什么的,queryString的匹配。这就是责任链模式。

5、为什么JS需要异步,谈谈你对JS事件循环的理解

因为没有锁机制,所以JS 是门非阻塞单线程语言。JS 在执行的过程中会产生执行环境,这些执行环境会被顺序的加入到执行栈中。 如果遇到异步的代码,会被挂起并加入到 Task(有多种 task) 队列中。一旦执行栈 为空,Event Loop 就会从 Task 队列中拿出需要执行的代码并放入执行栈中执行,所 以本质上来说 JS 中的异步还是同步行为。事件循环是协调执行栈顺序执消息队列的一种机制,抛开Node环境不提,只谈在浏览器环境下,正确的一次 Event loop 顺序是:①执行同步代码(宏任务);②执行栈为空,查询是否有微任务需要执行;③执行所有微任务;④必要的话渲染 UI;⑤然后开始下一轮 Event loop,执行宏任务中的异步代码。

6、谈谈对ES7中的async 和 await的使用和理解

一个函数如果加上 async ,那么该函数就会返回一个 Promise,可以把 async 看成将函数返回值使用 Promise.resolve() 包裹了下,并且 await 只能在 async 函数中使用。async 和 await 相比直接使用 Promise 来说,优势在于处理 then 的调用链,能 够更清晰准确的写出代码。

它也有很大的局限性:

  • 因为是顺序执行,假设有三个请求,那么这里并没有很好的利用到异步带来的止损(再包装一个Promise.all)
  • 如果要捕获异常,需要去包try catch
  • 缺少控制流程,比如progress(进度)pause,resume等周期性的方法
  • 没有打断的功能

7、常用的数组方法都知道吧,Map常用吗,如何实现数组降维?

Map 作用是生成一个新数组,遍历原数组,将每个元素拿出来做一些变换然后 append 到新的数组中,Map 有三个参数,分别是当前索引元素,索引,原数组,而且FlapMap 方法 和 map 的作用几乎是相同的,但是对于多维数组来说,会将原数组降维。 可以将 FlapMap 看成是 map + flatten ,目前该函数在浏览器中还不支持。如果想将一个多维数组彻底的降维,可以这样实现:

8、 JS 实现事件分发系统,要求包含三个最基本功能 on (监听事件), off (移出事件), emit (触发事件)

9、如何理解重绘和回流

重绘和回流是渲染步骤中的一小节,但是这两个步骤对于性能影响很大。重绘是当节点需要更改外观而不会影响布局的,比如改变 color 就叫称为重绘;回流是布局或者几何属性需要改变就称为回流。 回流必定会发生重绘,重绘不一定会引发回流。回流所需的成本比重绘高的多,改 变深层次的节点很可能导致父节点的一系列回流。

10、call, apply, bind 区别

call 和 apply 都是为了解决改变 this 的指向。作用都是相同的,只是传参的方 式不同。 除了第一个参数外,call 可以接收一个参数列表,apply 只接受一个参数数组。需要注意,对于call 和 apply,如果不传入第一个参数,那么默认为 window。

11、谈谈Vue中的Proxy 与 Obeject.defineProperty 对比,并使用Proxy 来实现一个数据绑定和监听

Vue框架内部使用了 Obeject.defineProperty() 来实现双向绑定,通过这个函数可以 监听到 set 和 get 的事件,并在适当的时候给属性添加发布订阅,核心思路就是手动触发一次属性的 getter 来实 现发布订阅的添加。Obeject.defineProperty 虽然已经能够实现双向绑定了,但是他还是有缺陷的,只能对属性进行数据劫持,所以需要深度遍历整个对象,对于数组不能监听到数据的变化,虽然 Vue 中确实能检测到数组数据的变化,但是其实是使用了 hack 的办法,并且也是有缺陷的。反观 Proxy 就没以上的问题,原生支持监听数组变化,并且可以直接对整个对象 进行拦截,所以 Vue3 将使用 Proxy 替换 Obeject.defineProperty。

12、在Javascript中什么是伪数组?如何将伪数组转化为标准数组?

伪数组(类数组):无法直接调用数组方法或期望length属性有什么特殊的行为,但仍可以对真正数组遍历方法来遍历它们。典型的是函数的argument参数,还有像调用getElementsByTagName,document.childNodes之类的,它们都返回NodeList对象都属于伪数组。可以使用Array.prototype.slice.call(fakeArray)将数组转化为真正的Array对象。

13、ajax的readyState有哪几个状态,含义分别是什么?

ajax的readyState共有5个状态,分别是0-4,其中每个数字的含义分别是0代表还没调用open方法,1代表的是未调用send方法,也就是还没发送数据给服务器 2代表的是还没有接收到响应,3代表的是开始接收到了部分数据,4代表的是接收完成,数据可以在客户端使用了。

14、谈谈V8引擎下的GC机制

V8 实现了准确式 GC,GC 算法采用了分代式垃圾回收机制。因此,V8 将内存(堆) 分为新生代和老生代两部分。 新生代算法,新生代中的对象一般存活时间较短,使用 Scavenge GC 算法。 在新生代空间中,内存空间分为两部分,分别为 From 空间和 To 空间。在这两 个空间中,必定有一个空间是使用的,另一个空间是空闲的。新分配的对象会被放入 From 空间中,当 From 空间被占满时,新生代 GC 就会启动了。算法会检查 From 空 间中存活的对象并复制到 To 空间中,如果有失活的对象就会销毁。当复制完成后将 From 空间和 To 空间互换,这样 GC 就结束了。 老生代算法,老生代中的对象一般存活时间较长且数量也多,使用了两个算法,分别是标记清除 算法和标记压缩算法。

15、平常针对web安全做过哪些措施?什么是XSS

是一种网站应用程式的安全漏洞攻击,是代码注入的一种。它允许恶意使用者将程式码注入到网页上,其他使用者在观看网页时就会受到影响。这类攻击通常包含了 HTML 以及使用者端脚本语言。XSS 分为三种:反射型,存储型和 DOM-based。

16、XSS注入有哪些方式

①在 HTML 中内嵌的文本中,恶意内容以 script 标签形成注入。 ②在内联的 JavaScript 中,拼接的数据突破了原本的限制(字符串,变量,方法名等)。 ③在标签属性中,恶意内容包含引号,从而突破属性值的限制,注入其他属性或者标签。 ④在标签的 href、src 等属性中,包含 javascript: 等可执行代码。 ⑤在 onload、onerror、onclick 等事件中,注入不受控制代码。 ⑥在 style 属性和标签中,包含类似 background-image:url("javascript:..."); 的代码(新版本浏览器已经可以防范)。 ⑦在 style 属性和标签中,包含类似 expression(...) 的 CSS 表达式代码(新版本浏览器已经可以防范)。

17、从前端角度来谈XSS攻击的一些预防手段

①利用模板引擎,开启模板引擎自带的 HTML 转义功能。例如: 在 ejs 中,尽量使用 <%= data %> 而不是 <%- data %>; 在 doT.js 中,尽量使用 {{! data } 而不是 {{= data }; 在 FreeMarker 中,确保引擎版本高于 2.3.24,并且选择正确的 freemarker.core.OutputFormat。 ②避免内联事件,尽量不要使用 onLoad="onload('{{data}}')"、onClick="go('{{action}}')" 这种拼接内联事件的写法。在 JavaScript 中通过 .addEventlistener() 事件绑定会更安全。 ③避免拼接,HTML 前端采用拼接 HTML 的方法比较危险,如果框架允许,使用 createElement、setAttribute 之类的方法实现。或者采用比较成熟的渲染框架,如 Vue/React 等。 ④时刻保持警惕,在插入位置为 DOM 属性、链接等位置时,要打起精神,严加防范。 ⑤增加攻击难度,降低攻击后果 通过 CSP、输入长度配置、接口安全措施等方法,增加攻击的难度,降低攻击的后果。 ⑥主动检测和发现,可使用 XSS 攻击字符串和自动扫描工具寻找潜在的 XSS 漏洞。

18、CSRF的特点

①攻击一般发起在第三方网站,而不是被攻击的网站。被攻击的网站无法防止攻击发生。 ②攻击利用受害者在被攻击网站的登录凭证,冒充受害者提交操作;而不是直接窃取数据。 ③整个过程攻击者并不能获取到受害者的登录凭证,仅仅是“冒用”。 ④跨站请求可以用各种方式:图片URL、超链接、CORS、Form提交等等。部分请求方式可以直接嵌入在第三方论坛、文章中,难以进行追踪。 ⑤CSRF通常是跨域的,因为外域通常更容易被攻击者掌控。

19、常用的前端安全措施

①禁止加载外域代码,防止复杂的攻击逻辑。 ②禁止外域提交,网站被攻击后,用户的数据不会泄露到外域。 ③禁止内联脚本执行(规则较严格,目前发现 GitHub 使用)。 ④禁止未授权的脚本执行(新特性,Google Map 移动版在使用)。 ⑤合理使用上报可以及时发现 XSS,利于尽快修复问题。 ⑥HTTP-only Cookie: 禁止 JavaScript 读取某些敏感 Cookie,攻击者完成 XSS 注入后也无法窃取此 Cookie。 ⑦验证码:防止脚本冒充用户提交危险操作。

20、HTTPS的原理和缺点

HTTPS主要由有两部分组成:HTTP + SSL / TLS,也就是在HTTP上又加了一层处理加密信息的模块。服务端和客户端的信息传输都会通过TLS进行加密,所以传输的数据都是加密后的数据。 ①相同网络环境下,HTTPS协议会使页面的加载时间延长近50%,增加10%到20%的耗电。此外,HTTPS协议还会影响缓存,增加数据开销和功耗。 ②HTTPS协议的安全是有范围的,在黑客攻击、拒绝服务攻击、服务器劫持等方面几乎起不到什么作用。 ③最关键的,SSL 证书的信用链体系并不安全。特别是在某些国家可以控制 CA 根证书的情况下,中间人攻击一样可行。

21、fastclick解决点击穿透的原理

在document.body 上绑定 touchstart 和 touchend 其中,touchstart 用于记录当前点击元素的targetElement。 touchend 用于:

  • 阻止默认事件(屏蔽之后的click事件)
  • 合成click事件,并添加可跟踪属性forwardedTouchEvent
  • 在 targetElement 上触发 click 事件
  • targetElement 上绑定的事件立即执行!done

二面

1.简单讲解一下 http2 的多路复用

在 HTTP/1 中,每次请求都会建立一次HTTP连接,也就是我们常说的3次握手4次挥手,这个过程在一次请求过程中占用了相当长的时间,即使开启了 Keep-Alive ,解决了多次连接的问题,但是依然有两个效率上的问题: 第一个:串行的文件传输。当请求a文件时,b文件只能等待,等待a连接到服务器、服务器处理文件、服务器返回文件,这三个步骤。我们假设这三步用时都是1秒,那么a文件用时为3秒,b文件传输完成用时为6秒,依此类推。(注:此项计算有一个前提条件,就是浏览器和服务器是单通道传输)

第二个:连接数过多。我们假设Apache设置了最大并发数为300,因为浏览器限制,浏览器发起的最大请求数为6,也就是服务器能承载的最高并发为50,当第51个人访问时,就需要等待前面某个请求处理完成。

HTTP/2的多路复用就是为了解决上述的两个性能问题。 在 HTTP/2 中,有两个非常重要的概念,分别是帧(frame)和流(stream)。 帧代表着最小的数据单位,每个帧会标识出该帧属于哪个流,流也就是多个帧组成的数据流。多路复用,就是在一个 TCP 连接中可以存在多条流。换句话说,也就是可以发送多个请求,对端可以通过帧中的标识知道属于哪个请求。通过这个技术,可以避免 HTTP 旧版本中的队头阻塞问题,极大的提高传输性能。

2.Promise 构造函数是同步执行还是异步执行,那么 then 方法呢

Promise new 的时候会立即执行里面的代码,then 是微任务,会在本次任务执行完的时候执行 ;setTimeout是宏任务 会在下次任务执行的时候执行,then()当然是同步执行,只不过是.then的cb被放入了微任务队列,产生了异步执行。

const promise = new Promise((resolve, reject) => {
  console.log(1)
  resolve()
  console.log(2)
})

promise.then(() => {
  console.log(3)
})

console.log(4)
// 执行结果是:1243
// promise构造函数是同步执行的,then方法是异步执行的

3.为什么 Vuex 的 mutation 和 Redux 的 reducer 中不能做异步操作

Mutation 必须是同步函数 一条重要的原则就是要记住 mutation 必须是同步函数。为什么?请参考下面的例子: mutations: { someMutation (state) { api.callAsyncMethod(() => { state.count++ }) } } 现在想象,我们正在 debug 一个 app 并且观察 devtool 中的 mutation 日志。每一条 mutation 被记录,devtools 都需要捕捉到前一状态和后一状态的快照。然而,在上面的例子中 mutation 中的异步函数中的回调让这不可能完成:因为当 mutation 触发的时候,回调函数还没有被调用,devtools 不知道什么时候回调函数实际上被调用——实质上任何在回调函数中进行的状态的改变都是不可追踪的。

在组件中提交 Mutation

你可以在组件中使用 this.$store.commit('xxx') 提交 mutation,或者使用 mapMutations 辅助函数将组件中的 methods 映射为 store.commit 调用(需要在根节点注入 store)。

import { mapMutations } from 'vuex'

export default {
  // ...
  methods: {
    ...mapMutations([
      'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`

      // `mapMutations` 也支持载荷:
      'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
    ]),
    ...mapMutations({
      add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
    })
  }
}

4.为什么输入 npm install 就可以自动安装对应的模块

  1. npm 模块安装机制: 发出npm install命令

查询node_modules目录之中是否已经存在指定模块

若存在,不再重新安装

若不存在 --npm 向 registry 查询模块压缩包的网址 ---下载压缩包,存放在根目录下的.npm目录里 ---解压压缩包到当前项目的node_modules目录

  1. npm 实现原理 输入 npm install 命令并敲下回车后,会经历如下几个阶段(以 npm 5.5.1 为例):
  • 1.执行工程自身 preinstall 当前 npm 工程如果定义了 preinstall 钩子此时会被执行。

  • 2.确定首层依赖模块 首先需要做的是确定工程中的首层依赖,也就是 dependencies 和 devDependencies 属性中直接指定的模块(假设此时没有添加 npm install 参数)。

工程本身是整棵依赖树的根节点,每个首层依赖模块都是根节点下面的一棵子树,npm 会开启多进程从每个首层依赖模块开始逐步寻找更深层级的节点。

  • 3.获取模块 获取模块是一个递归的过程,分为以下几步: 获取模块信息。在下载一个模块之前,首先要确定其版本,这是因为 package.json 中往往是 semantic version(semver,语义化版本)。此时如果版本描述文件(npm-shrinkwrap.json 或 package-lock.json)中有该模块信息直接拿即可,如果没有则从仓库获取。如 packaeg.json 中某个包的版本是 ^1.1.0,npm 就会去仓库中获取符合 1.x.x 形式的最新版本。 获取模块实体。上一步会获取到模块的压缩包地址(resolved 字段),npm 会用此地址检查本地缓存,缓存中有就直接拿,如果没有则从仓库下载。查找该模块依赖,如果有依赖则回到第1步,如果没有则停止。
  • 4.模块扁平化(dedupe) 上一步获取到的是一棵完整的依赖树,其中可能包含大量重复模块。比如 A 模块依赖于 loadsh,B 模块同样依赖于 lodash。在 npm3 以前会严格按照依赖树的结构进行安装,因此会造成模块冗余。 从 npm3 开始默认加入了一个 dedupe 的过程。它会遍历所有节点,逐个将模块放在根节点下面,也就是 node-modules 的第一层。当发现有重复模块时,则将其丢弃。 这里需要对重复模块进行一个定义,它指的是模块名相同且 semver 兼容。每个 semver 都对应一段版本允许范围,如果两个模块的版本允许范围存在交集,那么就可以得到一个兼容版本,而不必版本号完全一致,这可以使更多冗余模块在 dedupe 过程中被去掉。 比如 node-modules 下 foo 模块依赖 lodash@^1.0.0,bar 模块依赖 lodash@^1.1.0,则 ^1.1.0 为兼容版本。 而当 foo 依赖 lodash@^2.0.0,bar 依赖 lodash@^1.1.0,则依据 semver 的规则,二者不存在兼容版本。会将一个版本放在 node_modules 中,另一个仍保留在依赖树里。
  • 5.安装模块 这一步将会更新工程中的 node_modules,并执行模块中的生命周期函数(按照 preinstall、install、postinstall 的顺序)。 6.执行工程自身生命周期 当前 npm 工程如果定义了钩子此时会被执行(按照 install、postinstall、prepublish、prepare 的顺序)。 最后一步是生成或更新版本描述文件,npm install 过程完成。

5.简述Webpack 4打包流程

① 根据传入的参数模式(development | production)来加载对应的默认配置; ② 在entry里配置的module开始递归解析entry所依赖的所有module; ③ 每一个module都会根据rules的配置项去寻找用到的loader,接受所配置的loader的处理; ④ 以entry中的配置对象为分组,每一个配置入口和其对应的依赖文件最后组成一个代码块文件(chunk)并输出; ⑤ 整个流程中webpack会在恰当的时机执行plugin的逻辑,来完成自定义的插件逻辑。

6、平常做过埋点吗,为什么通常在发送数据埋点请求的时候使用的是 1x1 像素的透明 gif 图

  • 能够完成整个 HTTP 请求+响应(尽管不需要响应内容)
  • 触发 GET 请求之后不需要获取和处理数据、服务器也不需要发送数据
  • 跨域友好
  • 执行过程无阻塞
  • 相比 XMLHttpRequest 对象发送 GET 请求,性能上更好
  • GIF的最低合法体积最小(最小的BMP文件需要74个字节,PNG需要67个字节,而合法的GIF,只需要43个字节)

7、强缓存与协商缓存

浏览器与服务器通信的方式为应答模式,即是:浏览器发起HTTP请求 – 服务器响应该请求,那么浏览器怎么确定一个资源该不该缓存,如何去缓存呢?浏览器第一次向服务器发起该请求后拿到请求结果后,将请求结果和缓存标识存入浏览器缓存,浏览器对于缓存的处理是根据第一次请求资源时返回的响应头来确定的。

  • 浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识
  • 浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中

以上两点结论就是浏览器缓存机制的关键,它确保了每个请求的缓存存入与读取,只要我们再理解浏览器缓存的使用规则,那么所有的问题就迎刃而解了,本文也将围绕着这点进行详细分析。为了方便大家理解,这里我们根据是否需要向服务器重新发起HTTP请求将缓存过程分为两个部分,分别是强缓存和协商缓存。

强制缓存优先于协商缓存进行,若强制缓存(Expires和Cache-Control)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified / If-Modified-Since和Etag / If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,返回200,重新返回资源和缓存标识,再存入浏览器缓存中;生效则返回304,继续使用缓存。

8、用户行为对浏览器缓存的影响

所谓用户行为对浏览器缓存的影响,指的就是用户在浏览器如何操作时,会触发怎样的缓存策略。主要有 3 种:

  • 打开网页,地址栏输入地址:查找 disk cache 中是否有匹配。如有则使用;如没有则发送网络请求。

  • 普通刷新 (F5):因为 TAB 并没有关闭,因此 memory cache 是可用的,会被优先使用(如果匹配的话)。其次才是 disk cache。

  • 强制刷新 (Ctrl + F5):浏览器不使用缓存,因此发送的请求头部均带有 Cache-control: no-cache(为了兼容,还带了 Pragma: no-cache),服务器直接返回 200 和最新内容。

9、既然内存缓存这么高效,可不可以让数据都存放在内存中?

这是不可能的。计算机中的内存一定比硬盘容量小得多,操作系统需要精打细算内存的使用,所以能让我们使用的内存必然不多。 当我们访问过页面以后,再次刷新页面,可以发现很多数据都来自于内存缓存。 内存缓存中有一块重要的缓存资源是preloader相关指令(例如)下载的资源。众所周知preloader的相关指令已经是页面优化的常见手段之一,它可以一边解析js/css文件,一边网络请求下一个资源。 需要注意的事情是,内存缓存在缓存资源时并不关心返回资源的HTTP缓存头Cache-Control是什么值,同时资源的匹配也并非仅仅是对URL做匹配,还可能会对Content-Type,CORS等其他特征做校验。

10、XSS预防,一些常用的标签转义怎么处理

最普遍的做法是转义输入输出的内容,对于引号,尖括号,斜杠进行转义。

// 举个粗俗的例子
function escape(str) {
  str = str.replace(/&/g, "&amp;");
  str = str.replace(/</g, "&lt;");
  str = str.replace(/>/g, "&gt;");
  str = str.replace(/"/g, "&quto;");
  str = str.replace(/'/g, "&##39;");
  str = str.replace(/`/g, "&##96;");
  str = str.replace(/\//g, "&##x2F;");
  return str
}

// 通过转义可以将攻击代码 <script>alert(1)</script> 变成

// -> &lt;script&gt;alert(1)&lt;&##x2F;script&gt;
escape('<script>alert(1)</script>')

但是需要注意,对于显示富文本来说,不能通过上面的办法来转义所有字符,因为这样会把需要的格式也过滤掉。这种情况通常采用白名单过滤的办法,当然也可以通过黑名单过滤,但是考虑到需要过滤的标签和标签属性实在太多,更加推荐使用白名单的方式。

// filter
var xss = require("xss");
var html = xss('<h1 id="title">XSS Demo</h1><script>alert("xss");</script>');
// js-xss
// -> <h1>XSS Demo</h1>&lt;script&gt;alert("xss");&lt;/script&gt;
console.log(html);

11、谈谈Redux-saga的作用和理解

Redux-saga是一个用于管理redux应用异步操作的中间件之一,redux-saga通过创建sagas将所有异步操作逻辑收集在一个地方集中处理,可以用来代替redux-thunk中间件。Redux-saga相对于其他异步流中间件,将异步处理单独放在一起,不需要修改action,action还是同步。同时redux-saga的异步控制流程也很强大,比如对于竞态的处理就通过takeLatest()来处理。 redux-saga 是一个用于管理应用程序 Side Effect(副作用,例如异步获取数据,访问浏览器缓存等)的 library,它的目标是让副作用管理更容易,执行更高效,测试更简单,在处理故障时更容易。可以想像为,一个 saga 就像是应用程序中一个单独的线程,它独自负责处理副作用。 redux-saga 是一个 redux 中间件,意味着这个线程可以通过正常的 redux action 从主应用程序启动,暂停和取消,它能访问完整的 redux state,也可以 dispatch redux action。

12、React 中 refs 的作用是什么

Refs 是 React 提供给我们的安全访问 DOM 元素或者某个组件实例的句柄。我们可以为元素添加 ref 属性然后在回调函数中接受该元素在 DOM 树中的句柄,该值会作为回调函数的第一个参数返回。

13、为什么建议传递给 setState 的参数是一个 callback 而不是一个对象?

因为 this.props 和 this.state 的更新可能是异步的,不能依赖它们的值去计算下一个 state。

14、createElement 和 cloneElement 有什么区别?

React.createElement():JSX 语法就是用 React.createElement()来构建 React 元素的。它接受三个参数,第一个参数可以是一个标签名。如 div、span,或者 React 组件。第二个参数为传入的属性。第三个以及之后的参数,皆作为组件的子组件。

15、Context两种方式,childContectType为什么不推荐去使用了?

这个组件它想要获取哪些上层组件提供的context,就必须要申明,不然拿不到,原因是它上层组件也许不止一个。弃用的原因,就是中间哪些不需要这些数据的组件,也会因为接收到这些数据而去二次渲染。

16、我想在A网址像B网址通信,如何实现?

对于同源页面,常见的方式包括:广播模式:Broadcast Channe / Service Worker / LocalStorage + StorageEvent共享存储模式:Shared Worker / IndexedDB / cookie口口相传模式:window.open + window.opener基于服务端:Websocket / Comet / SSE 等。

而对于非同源页面,则可以通过嵌入同源 iframe 作为“桥”,将非同源页面通信转换为同源页面

17、mysql用的多吗

18、平常有维护团队或自己的一些库之类的吗

19、参与过原生APP的开发,或者一些混合工程,你对跨平台的技术掌握多少

20、你还有什么要问的

略(知道自己要跪了,就没问啥了)