2023.03.09 - 2023.03.15 更新收集面试问题(45道题)【第1部分】
获取更多面试问题可以访问
github 地址: https://github.com/pro-collection/interview-question/issues
gitee 地址: https://gitee.com/yanleweb/interview-question/issues

目录:

  • 中级开发者相关问题【共计 6 道题】

    • 95.实现:setObjectValue(obj: object, keys: string[], value: any) 方法, 支持安全设置对象的值【JavaScript】
    • 96.一个 tcp 连接能发几个 http 请求?【网络】
    • 97.JS 中 this 有哪些使用场景?【JavaScript】
    • 98.HTTP 1.0 和 HTTP 1.1 有以下区别?【网络】
    • 99.了解 BFC 吗?【CSS】
    • 103.箭头函数和普通函数的区别?【JavaScript】
  • 高级开发者相关问题【共计 7 道题】

    • 88.从浏览器地址栏输入url到显示页面所经历的流程有哪些(以HTTP为例)?【浏览器】
    • 89.从哪些方面可以对网站性能优化?【浏览器】
    • 90.redux-thunk 和 redux 是什么关系?【web框架】
    • 91.Iterator 和 for…of 了解多少?【JavaScript】
    • 94.纯 TS 项目工程中, 如何使用 alias path?【JavaScript】
    • 100.flex 布局了解多少?【CSS】
    • 102.map 和 weakMap 的区别 ?【JavaScript】
  • 资深开发者相关问题【共计 2 道题】

    • 92.如何给自己团队的大型前端项目设计单元测试?【JavaScript】
    • 93.如何做一个前端项目工程的自动化部署, 有哪些规范和流程设计?【JavaScript】

95.实现:setObjectValue(obj: object, keys: string[], value: any) 方法, 支持安全设置对象的值【JavaScript】

可以使用递归实现安全设置对象的值。以下是一个实现setObjectValue方法的例子:

 1function setObjectValue(obj: object, keys: string[], value: any) {
 2  const key = keys.shift();
 3  if (!key) {
 4    return;
 5  }
 6
 7  if (keys.length === 0) {
 8    obj[key] = value;
 9    return;
10  }
11
12  if (!obj[key]) {
13    obj[key] = {};
14  }
15
16  setObjectValue(obj[key], keys, value);
17}

这个方法接受三个参数:要设置值的对象,一个字符串数组表示对象的键的路径,和要设置的值。例如,如果要设置对象useraddress字段的city属性为"New York",可以调用方法:

 1const user = {};
 2setObjectValue(user, ['address', 'city'], 'New York');

在这个例子中,keys数组的第一个元素是"address",所以我们检查user对象是否有一个名为"address"的属性。如果没有,我们创建一个新对象并将其分配给user.address属性。然后我们继续递归地调用setObjectValue方法,将新对象作为第一个参数传递,将keys数组的剩余部分作为第二个参数传递,将最终的值作为第三个参数传递。最终,我们将"New York"分配给user.address.city属性。

这个方法确保在设置对象值时不会引发TypeError异常,即使对象的某些部分尚未定义。

96.一个 tcp 连接能发几个 http 请求?【网络】

如果是 HTTP 1.0 版本协议,一般情况下,不支持长连接,因此在每次请求发送完毕之后,TCP 连接即会断开,因此一个 TCP 发送一个 HTTP 请求,但是有一种情况可以将一条 TCP 连接保持在活跃状态,那就是通过 Connection 和 Keep-Alive 首部,在请求头带上 Connection: Keep-Alive,并且可以通过 Keep-Alive 通用首部中指定的,用逗号分隔的选项调节 keep-alive 的行为,如果客户端和服务端都支持,那么其实也可以发送多条,不过此方式也有限制,可以关注《HTTP 权威指南》4.5.5 节对于 Keep-Alive 连接的限制和规则。

而如果是 HTTP 1.1 版本协议,支持了长连接,因此只要 TCP 连接不断开,便可以一直发送 HTTP 请求,持续不断,没有上限; 同样,如果是 HTTP 2.0 版本协议,支持多用复用,一个 TCP 连接是可以并发多个 HTTP 请求的,同样也是支持长连接,因此只要不断开 TCP 的连接,HTTP 请求数也是可以没有上限地持续发送

97.JS 中 this 有哪些使用场景?【JavaScript】

this 是执行上下文中的一个属性,它指向最后一次调用这个方法的对象。在实际开发中,this 的指向可以通过四种调用模式来判断。

  • 第一种是函数调用模式,当一个函数不是一个对象的属性时,直接作为函数来调用时,this 指向全局对象。

  • 第二种是方法调用模式,如果一个函数作为一个对象的方法来调用时,this 指向这个对象。

  • 第三种是构造器调用模式,如果一个函数用 new 调用时,函数执行前会新创建一个对象,this 指向这个新创建的对象。

  • 第四种是 apply 、 call 和 bind 调用模式,这三个方法都可以显示的指定调用函数的 this 指向。其中 apply 方法接收两个参数:一个是 this 绑定的对象,一个是参数数组。call 方法接收的参数,第一个是 this 绑定的对象,后面的其余参数是传入函数执行的参数。也就是说,在使用 call() 方法时,传递给函数的参数必须逐个列举出来。bind 方法通过传入一个对象,返回一个 this 绑定了传入对象的新函数。这个函数的 this 指向除了使用 new 时会被改变,其他情况下都不会改变。

这四种方式,使用构造器调用模式的优先级最高,然后是 apply、call 和 bind 调用模式,然后是方法调用模式,然后是函数调用模式。

98.HTTP 1.0 和 HTTP 1.1 有以下区别?【网络】

  • 连接方面,http1.0 默认使用非持久连接,而 http1.1 默认使用持久连接。http1.1 通过使用持久连接来使多个 http 请求复用同一个 TCP 连接,以此来避免使用非持久连接时每次需要建立连接的时延。

  • 资源请求方面,在 http1.0 中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,http1.1 则在请求头引入了 range 头域,它允许只请求资源的某个部分,即返回码是 206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。

  • 缓存方面,在 http1.0 中主要使用 header 里的 If-Modified-Since、Expires 来做为缓存判断的标准,http1.1 则引入了更多的缓存控制策略,例如 Etag、If-Unmodified-Since、If-Match、If-None-Match 等更多可供选择的缓存头来控制缓存策略。

  • http1.1 中新增了 host 字段,用来指定服务器的域名。http1.0 中认为每台服务器都绑定一个唯一的 IP 地址,因此,请求消息中的 URL 并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机,并且它们共享一个IP地址。因此有了 host 字段,这样就可以将请求发往到同一台服务器上的不同网站。

  • http1.1 相对于 http1.0 还新增了很多请求方法,如 PUT、HEAD、OPTIONS 等。

小提示:先聊一聊BFC是什么; 然后再说说触发BFC的条件; 再说一说BFC的一些作用

BFC是块级格式化上下文,是一个独立的渲染区域,让处于 BFC 内部的元素与外部的元素相互隔离,使内外元素的定位不会相互影响。(俗称:脱离文档流)

触发条件

  • position: absolute/fixed:绝对定位
  • display: inline-block / table / flex
  • float 设置除none以外的值;(只要设置了浮动,当前元素就创建了BFC)
  • ovevflow !== visible (可为:hidden、auto、scroll)

特性和应用

  • 阻止margin重叠:同一个 BFC 下外边距(margin)会发生折叠
  • 清除浮动 :清除内部浮动(清除浮动的原理是两个div都位于同一个 BFC 区域之中)
  • 自适应两栏布局:左float+右BFC,是利用了BFC 的区域不会与 float 的元素区域重叠的机制

103.箭头函数和普通函数的区别?【JavaScript】

箭头函数和普通函数是 JavaScript 中两种不同的函数定义方式,它们有以下的区别:

  • 语法不同:箭头函数使用箭头 => 来定义函数,而普通函数使用 function 关键字来定义函数。

  • 箭头函数没有自己的 this,它会继承其所在作用域的 this 值。而普通函数的 this 则由函数调用时的上下文所决定,可以通过 call、apply、bind 方法来改变。

  • 箭头函数没有自己的 arguments 对象,它可以通过 rest 参数语法来接收不定数量的参数。而普通函数则有自己的 arguments 对象,它可以接收任意数量的参数。

  • 箭头函数不能作为构造函数使用,不能使用 new 来实例化,因为它没有自己的 this,而普通函数可以用 new 来创建新的对象。

  • 箭头函数不能使用 yield 关键字来定义生成器函数,而普通函数可以。

88.从浏览器地址栏输入url到显示页面所经历的流程有哪些(以HTTP为例)?【浏览器】

  1. 在浏览器地址栏输入URL

  2. 浏览器查看缓存,如果请求资源在缓存中并且新鲜,跳转到转码步骤

  3. 如果资源未缓存,发起新请求

  4. 如果已缓存,检验是否足够新鲜,足够新鲜直接提供给客户端,否则与服务器进行验证。

  5. 检验新鲜通常有两个HTTP头进行控制ExpiresCache-Control: - HTTP1.0提供Expires,值为一个绝对时间表示缓存新鲜日期 - HTTP1.1增加了Cache-Control: max-age=,值为以秒为单位的最大新鲜时间

  6. 浏览器解析URL获取协议,主机,端口,path

  7. 浏览器组装一个HTTP(GET)请求报文

  8. 浏览器获取主机ip地址,过程如下:

  9. 浏览器缓存

  10. 本机缓存

  11. hosts文件

  12. 路由器缓存

  13. ISP DNS缓存

  14. DNS递归查询(可能存在负载均衡导致每次IP不一样)

  15. 打开一个socket与目标IP地址,端口建立TCP链接,三次握手如下:

  16. 客户端发送一个TCP的SYN=1,Seq=X的包到服务器端口

  17. 服务器发回SYN=1, ACK=X+1, Seq=Y的响应包

  18. 客户端发送ACK=Y+1, Seq=Z

  19. TCP链接建立后发送HTTP请求

  20. 服务器接受请求并解析,将请求转发到服务程序,如虚拟主机使用HTTP Host头部判断请求的服务程序

  21. 服务器检查HTTP请求头是否包含缓存验证信息如果验证缓存新鲜,返回304等对应状态码

  22. 处理程序读取完整请求并准备HTTP响应,可能需要查询数据库等操作

  23. 服务器将响应报文通过TCP连接发送回浏览器

  24. 浏览器接收HTTP响应,然后根据情况选择关闭TCP连接或者保留重用,关闭TCP连接的四次握手如下

  25. 主动方发送Fin=1, Ack=Z, Seq= X报文

  26. 被动方发送ACK=X+1, Seq=Z报文

  27. 被动方发送Fin=1, ACK=X, Seq=Y报文

  28. 主动方发送ACK=Y, Seq=X报文

  29. 浏览器检查响应状态吗:是否为1XX,3XX, 4XX, 5XX,这些情况处理与2XX不同

  30. 如果资源可缓存,进行缓存

  31. 对响应进行解码(例如gzip压缩)

  32. 根据资源类型决定如何处理(假设资源为HTML文档)

  33. 解析HTML文档,构件DOM树,下载资源,构造CSSOM树,执行js脚本,这些操作没有严格的先后顺序,以下分别解释

  34. 构建DOM树

  35. Tokenizing:根据HTML规范将字符流解析为标记

  36. Lexing:词法分析将标记转换为对象并定义属性和规则

  37. DOM construction:根据HTML标记关系将对象组成DOM树

  38. 解析过程中遇到图片、样式表、js文件,启动下载

  39. 构建CSSOM树

  40. Tokenizing:字符流转换为标记流

  41. Node:根据标记创建节点

  42. CSSOM:节点创建CSSOM树

  43. 根据DOM树和CSSOM树构建渲染树:

  44. 从DOM树的根节点遍历所有可见节点,不可见节点包括:1)script,meta这样本身不可见的标签。2)被css隐藏的节点,如display: none

  45. 对每一个可见节点,找到恰当的CSSOM规则并应用

  46. 发布可视节点的内容和计算样式

  47. js解析如下

  48. 浏览器创建Document对象并解析HTML,将解析到的元素和文本节点添加到文档中,此时document.readystate为loading

  49. HTML解析器遇到没有async和defer的script时,将他们添加到文档中,然后执行行内或外部脚本。 这些脚本会同步执行,并且在脚本下载和执行时解析器会暂停。 这样就可以用document.write()把文本插入到输入流中。 同步脚本经常简单定义函数和注册事件处理程序,他们可以遍历和操作script和他们之前的文档内容

  50. 当解析器遇到设置了async属性的script时,开始下载脚本并继续解析文档。 脚本会在它下载完成后尽快执行,但是解析器不会停下来等它下载。 异步脚本禁止使用document.write(),它们可以访问自己script和之前的文档元素

  51. 当文档完成解析,document.readState变成interactive

  52. 所有defer脚本会按照在文档出现的顺序执行,延迟脚本能访问完整文档树,禁止使用document.write()

  53. 浏览器在Document对象上触发DOMContentLoaded事件

  54. 此时文档完全解析完成,浏览器可能还在等待如图片等内容加载, 等这些内容完成载入并且所有异步脚本完成载入和执行,document.readState变为complete,window触发load事件

  55. 显示页面(HTML解析过程中会逐步显示页面)

  • content方面

    1. 减少HTTP请求:合并文件、CSS精灵、inline Image
    2. 减少DNS查询:DNS查询完成之前浏览器不能从这个主机下载任何任何文件。方法:DNS缓存、将资源分布到恰当数量的主机名,平衡并行下载和DNS查询
    3. 避免重定向:多余的中间访问
    4. 使Ajax可缓存
    5. 非必须组件延迟加载
    6. 未来所需组件预加载
    7. 减少DOM元素数量
    8. 将资源放到不同的域下:浏览器同时从一个域下载资源的数目有限,增加域可以提高并行下载量
    9. 减少iframe数量
    10. 不要404
  • Server方面

    1. 使用CDN
    2. 添加Expires或者Cache-Control响应头
    3. 对组件使用Gzip压缩
    4. 配置ETag
    5. Flush Buffer Early
    6. Ajax使用GET进行请求
    7. 避免空src的img标签
  • Cookie方面

    1. 减小cookie大小
    2. 引入资源的域名不要包含cookie
  • css方面

    1. 将样式表放到页面顶部
    2. 不使用CSS表达式
    3. 使用不使用@import
    4. 不使用IE的Filter
  • Javascript方面

    1. 将脚本放到页面底部
    2. 将javascript和css从外部引入
    3. 压缩javascript和css
    4. 删除不需要的脚本
    5. 减少DOM访问
    6. 合理设计事件监听器
  • 图片方面

    1. 优化图片:根据实际颜色需要选择色深、压缩
    2. 优化css精灵
    3. 不要在HTML中拉伸图片
    4. 保证favicon.ico小并且可缓存
  • 移动方面

    1. 保证组件小于25k
    2. Pack Components into a Multipart Document

90.redux-thunk 和 redux 是什么关系?【web框架】

理解redux和redux的中间件redux-thunk

目录

简单点说Action就是一个对象,一个必须带key为type的对象[value是自己定义的],其他的key就根据用户自己喜好自己定义:
以下都是action的定义

 11、{type:”ADD”}
 22、{type:”ADD”,key1:”“,key2:”“} 

别主观意识就是类似数组中的reduce,也不是只能定义reducer,它仅仅是一个称呼,纯函数, 函数名次自己随便定义都可以,但是函数的参数只能是state与action, 可以简单的理解为一个工厂函数,传递一个旧的state通过加工后产出一个新的state: 简单的代码如下:

 1function count(state = 0, action) {
 2    switch (action.type) {
 3        case 'ADD':
 4            return state + 1;
 5        case 'REDUCER':
 6            return state - 1;
 7        default:
 8            return state;
 9    }
10}

如果当state是对象的时候上面的代码是错误的:
redux里面规定state是不能修改的。
在javascript中对象是引用数据类型,当你修改了state的时候,变化前后的两个state将会指向同一个地址的,react-redux就会以为这两个相同的state,因为不会执行渲染
解决办法,我们用Object.assign去处理,如有不清楚Object.assign,请参考Object.assign文档

store是一个全局对象,将action和reducer以及state联系在一起,主要职责:
维护应用的state
提供getState()方法获取state
提供dispatch(action)方法更新state
通过subscribe(方法)注册监听器

 1'use strict';
 2import {createStore} from 'redux';
 3function count(state = 0, action) {
 4    switch (action.type) {
 5        case 'ADD':
 6            return state + 1;
 7        case 'REDUCER':
 8            return state - 1;
 9        default:
10            return state
11    }
12}
13
14let store = createStore(count);
15
16let currentValue = store.getState();
17console.log('当前的值:', currentValue);
18
19//定义一个监听的方法
20let listener = () => {
21    const previosValue = currentValue;
22    currentValue = store.getState();
23    console.log('上一个值:', previosValue, '当前值:', currentValue)
24};
25//创建一个监听
26store.subscribe(listener);
27//分发任务
28store.dispatch({type:"ADD"});
29store.dispatch({type:"ADD"});
30store.dispatch({type:"ADD"});
31store.dispatch({type:"REDUCER"});

上面我们说的action是一个对象,只是含有type的key的对象
action创建函数的意思就是创建一个action的函数,函数返回一个对象

 1function add(){
 2    return{
 3        type:"ADD",
 4    }
 5}
 6function reducer() {
 7    return{
 8        type:"REDUCER",
 9    }
10}

使用的时候直接store.dispatch(add());就可以

action创建函数的意义:
action创建函数表面是返回一个对象
真正的意义在于逻辑的封装

redux-thunk中间件可以让action创建函数先不返回一个action对象,而是返回一个函数, 函数传递两个参数(dispatch,getState),在函数体内进行业务逻辑的封装

 1function add() {
 2    return {
 3        type: 'ADD',
 4    }
 5}
 6function addIfOdd() {
 7    return (dispatch, getState) => {
 8        const currentValue = getState();
 9        if (currentValue % 2 == 0) {
10            return false;
11        }
12        //分发一个任务
13        dispatch(add())
14    }
15}

91.Iterator 和 for…of 了解多少?【JavaScript】

JavaScript 原有的表示 “ 集合 ” 的数据结构,主要是数组( Array )和对象( Object ), ES6 又添加了 Map 和 Set 。 这样就有了四种数据集合,用户还可以组合使用它们,定义自己的数据结构,比如数组的成员是 Map , Map 的成员是对象。 这样就需要一种统一的接口机制,来处理所有不同的数据结构。
遍历器( Iterator )就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。 任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令for…of循环, Iterator 接口主要供for…of消费。

Iterator 的遍历过程是这样的。

  • ( 1 )创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
  • ( 2 )第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
  • ( 3 )第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
  • ( 4 )不断调用指针对象的next方法,直到它指向数据结构的结束位置。

每一次调用next方法,都会返回数据结构的当前成员的信息。 具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。

Iterator 接口的目的,就是为所有数据结构,提供了一种统一的访问机制,即for…of循环(详见下文)。当使用for…of循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口。
在 ES6 中,有三类数据结构原生具备 Iterator 接口:数组、某些类似数组的对象、 Set 和 Map 结构。

实例:

 1    let arr = ['a', 'b', 'c'];
 2    let iter = arr[Symbol.iterator]();
 3    iter.next() // { value: 'a', done: false }
 4    iter.next() // { value: 'b', done: false }
 5    iter.next() // { value: 'c', done: false }
 6    iter.next() // { value: undefined, done: true }

上面提到,原生就部署 Iterator 接口的数据结构有三类,对于这三类数据结构,不用自己写遍历器生成函数,for…of循环会自动遍历它们。除此之外,其他数据结构(主要是对象)的 Iterator 接口,都需要自己在Symbol.iterator属性上面部署,这样才会被for…of循环遍历。

有一些场合会默认调用 Iterator 接口(即Symbol.iterator方法),除了下文会介绍的for…of循环,还有几个别的场合。

对数组和 Set 结构进行解构赋值时,会默认调用Symbol.iterator方法。
实例1:

 1    let set = new Set().add('a').add('b').add('c');
 2    let [x,y] = set;
 3    // x='a'; y='b'
 4    let [first, ...rest] = set;
 5    // first='a'; rest=['b','c'];

扩展运算符( … )也会调用默认的 iterator 接口。
实例2:

 1    //  例一
 2    var str = 'hello';
 3    [...str] // ['h','e','l','l','o']
 4    //  例二
 5    let arr = ['b', 'c'];
 6    ['a', ...arr, 'd']
 7    // ['a', 'b', 'c', 'd']

yield* 后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
实例3:

 1    let generator = function* () {
 2        yield 1;
 3        yield* [2,3,4];
 4        yield 5;
 5    };
 6    var iterator = generator();
 7    iterator.next() // { value: 1, done: false }
 8    iterator.next() // { value: 2, done: false }
 9    iterator.next() // { value: 3, done: false }
10    iterator.next() // { value: 4, done: false }
11    iterator.next() // { value: 5, done: false }
12    iterator.next() // { value: undefined, done: true }

由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口。下面是一些例子。

  • for…of
  • Array.from()
  • Map(), Set(), WeakMap(), WeakSet() (比如new Map([[‘a’,1],[‘b’,2]]))
  • Promise.all()
  • Promise.race()

4、Iterator 接口与 Generator 函数

Symbol.iterator方法的最简单实现,还是使用下一章要介绍的 Generator 函数。
实例:

 1    var myIterable = {};
 2    myIterable[Symbol.iterator] = function* () {
 3        yield 1;
 4        yield 2;
 5        yield 3;
 6    };
 7    [...myIterable] // [1, 2, 3]
 8    
 9    //  或者采用下面的简洁写法
10    let obj = {
11        * [Symbol.iterator]() {
12            yield 'hello';
13            yield 'world';
14        }
15    };
16    for (let x of obj) {
17        console.log(x);
18    }
19    // hello
20    // world

ES6 借鉴 C++ 、 Java 、 C# 和 Python 语言,引入了for…of循环,作为遍历所有数据结构的统一的方法。一个数据结构只要部署了Symbol.iterator属性,就被视为具有 iterator 接口,就可以用for…of循环遍历它的成员。也就是说,for…of循环内部调用的是数据结构的Symbol.iterator方法。
for…of 循环可以使用的范围包括数组、 Set 和 Map 结构、某些类似数组的对象(比如 arguments 对象、 DOM NodeList 对象)、后文的 Generator 对象,以及字符串。

数组原生具备 iterator 接口,for…of循环本质上就是调用这个接口产生的遍历器,可以用下面的代码证明。
实例1:

 1    const arr = ['red', 'green', 'blue'];
 2    let iterator = arr[Symbol.iterator]();
 3    
 4    for(let v of arr) {
 5        console.log(v); // red green blue
 6    }
 7    
 8    for(let v of iterator) {
 9        console.log(v); // red green blue
10    }

JavaScript 原有的for…in循环,只能获得对象的键名,不能直接获取键值。 ES6 提供for…of循环,允许遍历获得键值。
实例2:

 1    var arr = ['a', 'b', 'c', 'd'];
 2    
 3    for (let a in arr) {
 4        console.log(a); // 0 1 2 3
 5    }
 6    
 7    for (let a of arr) {
 8        console.log(a); // a b c d
 9    }

上面代码表明,for…in循环读取键名,for…of循环读取键值。如果要通过for…of循环,获取数组的索引,可以借助数组实例的entries方法和keys方法,参见《数组的扩展》章节。

实例3:for…of循环调用遍历器接口,数组的遍历器接口只返回具有数字索引的属性。这一点跟for…in循环也不一样。

 1    let arr = [3, 5, 7];
 2    arr.foo = 'hello';
 3    
 4    for (let i in arr) {
 5        console.log(i); // "0", "1", "2", "foo"
 6    }
 7    
 8    for (let i of arr) {
 9        console.log(i); // "3", "5", "7"
10    }

Set 和 Map 结构也原生具有 Iterator 接口,可以直接使用for…of循环。
实例1:基本使用

 1    var engines = new Set(["Gecko", "Trident", "Webkit", "Webkit"]);
 2    for (var e of engines) {
 3        console.log(e);
 4    }
 5    // Gecko
 6    // Trident
 7    // Webkit
 8    
 9    var es6 = new Map();
10    es6.set("edition", 6);
11    es6.set("committee", "TC39");
12    es6.set("standard", "ECMA-262");
13    for (var [name, value] of es6) {
14        console.log(name + ": " + value);
15    }
16    4通信类
17    // committee: TC39
18    // standard: ECMA-262

Set 结构遍历时,返回的是一个值,而 Map 结构遍历时,返回的是一个数组,该数组的两个成员分别为当前 Map 成员的键名和键值。
实例2:

 1    let map = new Map().set('a', 1).set('b', 2);
 2    for (let pair of map) {
 3        console.log(pair);
 4    }
 5    // ['a', 1]
 6    // ['b', 2]
 7    
 8    for (let [key, value] of map) {
 9        console.log(key + ' : ' + value);
10    }
11    // a : 1
12    // b : 2

有些数据结构是在现有数据结构的基础上,计算生成的。比如, ES6 的数组、 Set 、 Map 都部署了以下三个方法,调用后都返回遍历器对象。

  • entries() 返回一个遍历器对象,用来遍历[ 键名 , 键值 ]组成的数组。对于数组,键名就是索引值;对于 Set ,键名与键值相同。 Map 结构的iterator 接口,默认就是调用 entries 方法。
  • keys() 返回一个遍历器对象,用来遍历所有的键名。
  • values() 返回一个遍历器对象,用来遍历所有的键值。

实例:

 1    let arr = ['a', 'b', 'c'];
 2    
 3    for (let pair of arr.entries()) {
 4        console.log(pair);
 5    }
 6    // [0, 'a']
 7    // [1, 'b']
 8    // [2, 'c']

对于普通的对象,for…of结构不能直接使用,会报错,必须部署了 iterator 接口后才能使用。但是,这样情况下,for…in循环依然可以用来遍历键名。
实例:

 1    var es6 = {
 2        edition: 6,
 3        committee: "TC39",
 4        standard: "ECMA-262"
 5    };
 6    
 7    for (e in es6) {
 8        console.log(e);
 9    }
10    // edition
11    // committee
12    // standard
13    
14    for (e of es6) {
15        console.log(e);
16    }
17    // TypeError: es6 is not iterable

一种解决方法是,使用Object.keys方法将对象的键名生成一个数组,然后遍历这个数组。

 1    for (var key of Object.keys(someObject)) {
 2        console.log(key + ": " + someObject[key]);
 3    }

另一个方法是使用 Generator 函数将对象重新包装一下。

 1    function* entries(obj) {
 2        for (let key of Object.keys(obj)) {
 3            yield [key, obj[key]];
 4        }
 5    }
 6    for (let [key, value] of entries(obj)) {
 7        console.log(key, "->", value);
 8    }
 9    // a -> 1
10    // b -> 2
11    // c -> 3

6、对比JS中的几种遍历:for forEach for…in for…of

理解 JavaScript 中的 for…of 循环

for…of 语句创建一个循环来迭代可迭代的对象。 在 ES6 中引入的 for…of 循环,以替代 for…in 和 forEach() ,并支持新的迭代协议。 for…of 允许你遍历 Arrays(数组), Strings(字符串), Maps(映射), Sets(集合)等可迭代的数据结构等。
对象数据结构是不可以用于for…of 的

语法:

 1for (variable of iterable) {
 2    statement
 3}
  • variable:每个迭代的属性值被分配给该变量。
  • iterable:一个具有可枚举属性并且可以迭代的对象。

Arrays(数组)就是类列表(list-like)对象。数组原型上有各种方法,允许对其进行操作,比如修改和遍历等操作。 下面手在一个数组上进行的 for…of 操作:

 1// array-example.js
 2const iterable = ['mini', 'mani', 'mo'];
 3 
 4for (const value of iterable) {
 5  console.log(value);
 6}
 7 
 8// Output:
 9// mini
10// mani
11// mo

Map 对象就是保存 key-value(键值) 对。对象和原始值可以用作 key(键)或 value(值)。 Map 对象根据其插入方式迭代元素。换句话说, for…of 循环将为每次迭代返回一个 key-value(键值) 数组。

 1// map-example.js
 2const iterable = new Map([['one', 1], ['two', 2]]);
 3 
 4for (const [key, value] of iterable) {
 5  console.log(`Key: ${key} and Value: ${value}`);
 6}
 7 
 8// Output:
 9// Key: one and Value: 1
10// Key: two and Value: 2

Set(集合) 对象允许你存储任何类型的唯一值,这些值可以是原始值或对象。 Set(集合) 对象只是值的集合。 Set(集合) 元素的迭代基于其插入顺序。 Set(集合) 中的值只能发生一次。如果您创建一个具有多个相同元素的 Set(集合) ,那么它仍然被认为是单个元素

 1// set-example.js
 2const iterable = new Set([1, 1, 2, 2, 1]);
 3 
 4for (const value of iterable) {
 5  console.log(value);
 6}
 7// Output:
 8// 1
 9// 2
 1// string-example.js
 2const iterable = 'javascript';
 3 
 4for (const value of iterable) {
 5  console.log(value);
 6}
 7 
 8// Output:
 9// "j"
10// "a"
11// "v"
12// "a"
13// "s"
14// "c"
15// "r"
16// "i"
17// "p"
18// "t"
 1// arguments-example.js
 2function args() {
 3  for (const arg of arguments) {
 4    console.log(arg);
 5  }
 6}
 7 
 8args('a', 'b', 'c');
 9// Output:
10// a
11// b
12// c
 1// generator-example.js
 2function* generator(){ 
 3  yield 1; 
 4  yield 2; 
 5  yield 3; 
 6}
 7 
 8for (const g of generator()) { 
 9  console.log(g); 
10}
11 
12// Output:
13// 1
14// 2
15// 3

avaScript 提供了四种已知的终止循环执行的方法:break、continue、return 和 throw。让我们来看一个例子:

 1const iterable = ['mini', 'mani', 'mo'];
 2 
 3for (const value of iterable) {
 4  console.log(value);
 5  break;
 6}
 7 
 8// Output:
 9// mini

for…of 循环仅适用于迭代。 而普通对象不可迭代。 我们来看一下:

 1const obj = { fname: 'foo', lname: 'bar' };
 2 
 3for (const value of obj) { // TypeError: obj[Symbol.iterator] is not a function
 4    console.log(value);
 5}

在这里,我们定义了一个普通对象 obj ,并且当我们尝试 for…of 对其进行操作时,会报错:TypeError: obj[Symbol.iterator] is not a function。

我们可以通过将类数组(array-like)对象转换为数组来绕过它。该对象将具有一个 length 属性,其元素必须可以被索引。我们来看一个例子:

 1// object-example.js
 2const obj = { length: 3, 0: 'foo', 1: 'bar', 2: 'baz' };
 3 
 4const array = Array.from(obj);
 5for (const value of array) { 
 6    console.log(value);
 7}
 8// Output:
 9// foo
10// bar
11// baz

Array.from() 方法可以让我通过类数组(array-like)或可迭代对象来创建一个新的 Array(数组) 实例。

for…of 更多用于特定于集合(如数组和对象),但不包括所有对象。
注意:任何具有 Symbol.iterator 属性的元素都是可迭代的。

for…in 不考虑构造函数原型的不可枚举属性。它只需要查找可枚举属性并将其打印出来。

94.纯 TS 项目工程中, 如何使用 alias path?【JavaScript】

TODO 待补充内容

可以看这个文档: https://www.miganzi.com/tech/typescript-s-paths-config/

以下6个属性设置在容器上。

 1flex-direction
 2flex-wrap
 3flex-flow
 4justify-content
 5align-items
 6align-content 

属性决定主轴的方向(即项目的排列方向)。

 1.box {
 2  flex-direction: row | row-reverse | column | column-reverse;
 3}
  • row(默认值):主轴为水平方向,起点在左端。
  • row-reverse:主轴为水平方向,起点在右端。
  • column:主轴为垂直方向,起点在上沿。
  • column-reverse:主轴为垂直方向,起点在下沿。

默认情况下,项目都排在一条线(又称”轴线”)上。flex-wrap属性定义,如果一条轴线排不下,如何换行。

 1.box{
 2  flex-wrap: nowrap | wrap | wrap-reverse;
 3} 

它可能取三个值。
(1)nowrap(默认):不换行。
(2)wrap:换行,第一行在上方。
(3)wrap-reverse:换行,第一行在下方。

flex-flow属性是flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap。

 1.box {
 2  flex-flow: <flex-direction> || <flex-wrap>;
 3}

属性定义了项目在主轴上的对齐方式。

 1.box {
 2  justify-content: flex-start | flex-end | center | space-between | space-around;
 3} 

它可能取5个值,具体对齐方式与轴的方向有关。下面假设主轴为从左到右。

 1flex-start默认值):左对齐
 2flex-end右对齐
 3center居中
 4space-between两端对齐项目之间的间隔都相等
 5space-around每个项目两侧的间隔相等所以项目之间的间隔比项目与边框的间隔大一倍

定义项目在交叉轴上如何对齐。

 1.box {
 2  align-items: flex-start | flex-end | center | baseline | stretch;
 3} 

它可能取5个值。具体的对齐方式与交叉轴的方向有关,下面假设交叉轴从上到下。

 1flex-start交叉轴的起点对齐
 2flex-end交叉轴的终点对齐
 3center交叉轴的中点对齐
 4baseline: 项目的第一行文字的基线对齐
 5stretch默认值):如果项目未设置高度或设为auto将占满整个容器的高度

定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。

 1.box {
 2  align-content: flex-start | flex-end | center | space-between | space-around | stretch;
 3} 
 1flex-start与交叉轴的起点对齐
 2flex-end与交叉轴的终点对齐
 3center与交叉轴的中点对齐
 4space-between与交叉轴两端对齐轴线之间的间隔平均分布
 5space-around每根轴线两侧的间隔都相等所以轴线之间的间隔比轴线与边框的间隔大一倍
 6stretch默认值):轴线占满整个交叉轴

以下6个属性设置在项目上。

 1order
 2flex-grow
 3flex-shrink
 4flex-basis
 5flex
 6align-self 

定义项目的排列顺序。数值越小,排列越靠前,默认为0。

 1.item {
 2  order: <integer>;
 3} 

定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。

如果所有项目的flex-grow属性都为1,则它们将等分剩余空间(如果有的话)。 如果一个项目的flex-grow属性为2,其他项目都为1,则前者占据的剩余空间将比其他项多一倍。

flex-shrink属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。

如果所有项目的flex-shrink属性都为1,当空间不足时,都将等比例缩小。 如果一个项目的flex-shrink属性为0,其他项目都为1,则空间不足时,前者不缩小。

定义了在分配多余空间之前,项目占据的主轴空间(main size)。 浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。

 1.item {
 2  flex-basis: <length> | auto; /* default auto */
 3} 

它可以设为跟width或height属性一样的值(比如350px),则项目将占据固定空间。

是flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto。后两个属性可选。

 1.item {
 2  flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]
 3} 

该属性有两个快捷值:auto (1 1 auto) 和 none (0 0 auto)。 建议优先使用这个属性,而不是单独写三个分离的属性,因为浏览器会推算相关值。

align-self属性允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。 默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。

 1.item {
 2  align-self: auto | flex-start | flex-end | center | baseline | stretch;
 3} 

该属性可能取6个值,除了auto,其他都与align-items属性完全一致。

102.map 和 weakMap 的区别 ?【JavaScript】

Map 和 WeakMap 都是 JavaScript 中的键值对数据结构,它们的主要区别在于其键的存储方式和内存管理。

Map 对象中的键可以是任何类型的值,包括基本类型和引用类型,而 WeakMap 对象中的键必须是对象。在 Map 中,如果一个键不再被引用,它仍然会被 Map 对象保留,因为 Map 对象对其进行了强引用。这会导致内存泄漏的问题。而 WeakMap 对象只会对其键进行弱引用,也就是说,如果一个键不再被引用,它会被垃圾回收器回收,因此不会导致内存泄漏的问题。

此外,WeakMap 没有 Map 中的一些方法,比如 size、keys、values 和 forEach 等方法,因为 WeakMap 的键不是强引用,所以无法确定其大小。

总之,如果需要存储对象键值对,并且不需要保留键的引用,可以考虑使用 WeakMap,否则使用 Map。

92.如何给自己团队的大型前端项目设计单元测试?【JavaScript】

  1. 调研基本的前端测试框架有哪些, 有哪些优劣
  2. 需要分别针对 utils 静态方法测试与 UI 交互测试(2E2测试)
  3. 针对测试, 产出衡量指标与实践规范
  4. 针对项目产出最佳实践
  5. 通过 CI 工具进行测试检查、覆盖、定时回归

93.如何做一个前端项目工程的自动化部署, 有哪些规范和流程设计?【JavaScript】

// TODO 待补充

个人笔记记录 2021 ~ 2025