《前端100问》70

介绍下如何实现 token 加密

JWT(JSON Web Token)举例:

  1. 需要一个 secret (随机数)
  2. 后端利用 secret 和加密算法(如:SHA-256)对 payload (如:账号、密码)生成一个字符串(token),返回前端
  3. 前端每次 request 都在 header 中 带上 token
  4. 后端用同样算法解密

redux 为什么要把 reducer 设计成纯函数

纯函数:

  1. 如果函数的调用参数相同,则永远返回相同的结果。它不依赖于程序执行期间函数外部任何状态或数据的变化,必须只依赖于其输入参数。
  2. 该函数不会产生任何可观察的副作用,例如网络请求,输入和输出设备或数据突变(mutation)。

什么是纯函数?

  1. 相同的输入永远返回相同的输出 (参数为对象,返回值也为对象)
  2. 不修改函数的输入值
  3. 不依赖外部环境状态
  4. 无任何副作用 

在 Redux 源码中 var nextStateForKey = reducer(previousStateForKey, action)

nextStateForKey 就是通过 reducer 执行后返回的结果(state),然后通过hasChanged = hasChanged || nextStateForKey !== previousStateForKey 来比较新旧两个对象是否一致。

此比较法,比较的是两个对象的存储位置,也就是浅比较法,所以,当我们 reducer 直接返回旧的 state 对象时,Redux 认为没有任何改变,从而导致页面没有更新。

为什么 Redux 会这样设计?

因为比较两个 JavaScript 对象中所有的属性是否完全相同,唯一的办法就是深比较。

然而,深比较在真实的应用中代码是非常大的,非常耗性能的,需要比较的次数特别多。

所以一个有效的解决方案就是做一个规定,当无论发生任何变化时,开发者都要返回一个新的对象,没有变化时,开发者返回旧的对象,这也就是 redux 为什么要把 reducer 设计成纯函数的原因。

如何设计实现无缝轮播

简单来说,无缝轮播的核心是制造一个连续的效果。

最简单的方法就是复制一个轮播的元素,当复制元素将要滚到目标位置后,把原来的元素进行归位的操作,以达到无缝的轮播效果。

模拟实现一个 Promise.finally

Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
  );
};

MDN 对于 finally 的定义:

finally() 方法返回一个Promise。在 promise 结束时,无论结果是 fulfilled 或者是 rejected,都会执行指定的回调函数。

a.b.c.da['b']['c']['d'],哪个性能更高?

.[] 性能更高

在语法分析转换成 AST时,[] 是含计算的,而 . 只是字符串字面量。

天然的[] 会消耗更多的计算成本,时间更长。

ES6 代码转成 ES5 代码的实现思路是什么

以 Babel 的实现举例:

  • 将代码字符串解析成抽象语法树,即所谓的 AST
  • 对 AST 进行处理,在这个阶段可以对 ES6 代码进行相应转换,即转成 ES5 代码
  • 根据处理后的 AST 再生成代码字符串

不用工具的话就要编写各种 polyfill。

题外话:Babel6.0 开始,就不再对代码进行转换。Babel 只负责 parse 和 generate 流程,专注于解析和生成阶段。转换代码的 transform 过程有 Babel 的插件完成

.babelrc 文件就是用来配置 Babel 处理的,常有 pluginspresets 配置项,当他们同时存在的时候,先执行 plugins 从上到下,在执行 presets 从左到右。

数组编程题

随机生成一个长度为 10 的整数类型的数组,例如 [2, 10, 3, 4, 5, 11, 10, 11, 20],将其排列成一个新数组,要求新数组形式如下,例如 [[2, 3, 4, 5], [10, 11], [20]]

/**
 * @param {*} min 生成随机数最小范围
 * @param {*} max 生成随机数最大范围
 * @param {*} arrLength 生成数组长度
 */
const randomArr = (min, max, arrLength) => {
  //区间取整
  min = Math.ceil(min)
  max = Math.floor(max)
  //Array.from() 可以通过以下方式来创建数组对象:
  //伪数组对象(拥有一个 length 属性和若干索引属性的任意对象)
  //可迭代对象(可以获取对象中的元素,如 Map和 Set 等)
  //Aarray 第二个参数:如果指定了该参数,新数组中的每个元素会执行该回调函数。
  return Array.from(
    { length: arrLength },
    () => Math.floor(Math.random() * (max - min + 1)) + min
  ) //含最大值,含最小值
}

// 排列数组函数
const arrConvert = arr => {
  // 去重 排序
  arr = Array.from(new Set(arr)).sort((a, b) => a - b)
  let index = 0
  //作为临时数组
  let tep = []
  //结果数组
  const result = []
  // 先推进去第一个数
  tep.push(arr[index])
  while (index < arr.length) {
    if (arr[index + 1] - arr[index] === 1) {
      tep.push(arr[index + 1])
    } else {
      result.push(tep)
      tep = [arr[index + 1]]
    }
    index++
  }
  return result
}
// console.log(arrConvert(data))
console.log(arrConvert(randomArr(0, 100, 10)))

hash(PS:得补习一下数据结构了,玩不转 hash)

如何解决移动端 Retina 屏 1px 像素问题

  1. 基于 media 查询判断不同的设备像素比给定不同的 border-image
  2. 使用 background-imageborder-image类似,准备一张符合条件的边框背景图,模拟在背景上。
  3. 伪类 + transform 基于 media 查询判断不同的设备像素比对线条进行缩放transformY:scale(.5)
  4. rem + viewport :在根元素 html 设置 font-sizepx 转换成 rem 通过 window.devicePixelRatio 拿到 dpr 再写 meta 设置 viewportscale : 1/dpr
  5. box-shadow

如何把一个字符串的大小写取反(大写变小写小写变大写),例如 ‘AbC’ 变成 ‘aBc’ 。

// 如何把一个字符串的大小写取反(大写变小写小写变大写),例如 'AbC' 变成 'aBc'
const str = 'AbC'
const testStr = 'ADasfads123!@$!@#'

const toggleCase = str => {
  const strArr = str.split('')
  const newArr = strArr.map(item =>
    item === item.toUpperCase() ? item.toLowerCase() : item.toUpperCase()
  )
  return newArr.join('')
}
//test
console.log(toggleCase(str))
console.log(toggleCase(testStr))

Webpack 热更新原理,是如何做到在不刷新浏览器的前提下更新页面的

好难🤯,只能明白的大概意思和原理… 希望能搞懂…在以后

来自官网文档:

模块热替换(HMR - hot module replacement)功能会在应用程序运行过程中,替换、添加或删除模块,而无需重新加载整个页面。主要是通过以下几种方式,来显著加快开发速度:

  • 保留在完全重新加载页面期间丢失的应用程序状态。
  • 只更新变更内容,以节省宝贵的开发时间。
  • 在源代码中 CSS/JS 产生修改时,会立刻在浏览器中进行更新,这几乎相当于在浏览器 devtools 直接更改样式。

这一切是如何运行的?

让我们从一些不同的角度观察,以了解 HMR 的工作原理……

在应用程序中

通过以下步骤,可以做到在应用程序中置换(swap in and out)模块:

  1. 应用程序要求 HMR runtime 检查更新。
  2. HMR runtime 异步地下载更新,然后通知应用程序。
  3. 应用程序要求 HMR runtime 应用更新。
  4. HMR runtime 同步地应用更新。

你可以设置 HMR,以使此进程自动触发更新,或者你可以选择要求在用户交互时进行更新。

在 compiler 中

除了普通资源,compiler 需要发出 “update”,将之前的版本更新到新的版本。”update” 由两部分组成:

  1. 更新后的 manifest (JSON)
  2. 一个或多个 updated chunk (JavaScript)

manifest 包括新的 compilation hash 和所有的 updated chunk 列表。每个 chunk 都包含着全部更新模块的最新代码(或一个 flag 用于表明此模块需要被移除)。

compiler 会确保在这些构建之间的模块 ID 和 chunk ID 保持一致。通常将这些 ID 存储在内存中(例如,使用 webpack-dev-server 时),但是也可能会将它们存储在一个 JSON 文件中。

在模块中

HMR 是可选功能,只会影响包含 HMR 代码的模块。举个例子,通过 style-loader 为 style 追加补丁。为了运行追加补丁,style-loader 实现了 HMR 接口;当它通过 HMR 接收到更新,它会使用新的样式替换旧的样式。

类似的,当在一个模块中实现了 HMR 接口,你可以描述出当模块被更新后发生了什么。然而在多数情况下,不需要在每个模块中强行写入 HMR 代码。如果一个模块没有 HMR 处理函数,更新就会冒泡(bubble up)。这意味着某个单独处理函数能够更新整个模块树。如果在模块树的一个单独模块被更新,那么整组依赖模块都会被重新加载。

在 runtime 中

对于模块系统运行时(module system runtime),会发出额外代码,来跟踪模块 parentschildren 关系。在管理方面,runtime 支持两个方法 checkapply

check 方法,发送一个 HTTP 请求来更新 manifest。如果请求失败,说明没有可用更新。如果请求成功,会将 updated chunk 列表与当前的 loaded chunk 列表进行比较。每个 loaded chunk 都会下载相应的 updated chunk。当所有更新 chunk 完成下载,runtime 就会切换到 ready 状态。

apply 方法,将所有 updated module 标记为无效。对于每个无效 module,都需要在模块中有一个 update handler,或者在此模块的父级模块中有 update handler。否则,会进行无效标记冒泡,并且父级也会被标记为无效。继续每个冒泡,直到到达应用程序入口起点,或者到达带有 update handler 的 module(以最先到达为准,冒泡停止)。如果它从入口起点开始冒泡,则此过程失败。

之后,所有无效 module 都会被(通过 dispose handler)处理和解除加载。然后更新当前 hash,并且调用所有 accept handler。runtime 切换回 idle 状态,一切照常继续。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!