十万字前端面试问题总结 - 第1部分

963 阅读31分钟

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

目录:

  • 中级开发者相关问题【共计 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】

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

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

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

typescriptCopy codefunction setObjectValue(obj: object, keys: string[], value: any) {
  const key = keys.shift();
  if (!key) {
    return;
  }

  if (keys.length === 0) {
    obj[key] = value;
    return;
  }

  if (!obj[key]) {
    obj[key] = {};
  }

  setObjectValue(obj[key], keys, value);
}

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

typescriptCopy codeconst user = {};
setObjectValue(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 等。

99.了解 BFC 吗?【CSS】

小提示:先聊一聊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 关键字来定义生成器函数,而普通函数可以。

高级开发者相关问题【共计 7 道题】

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解析过程中会逐步显示页面)

89.从哪些方面可以对网站性能优化?【浏览器】

  • 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的认识

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

1、{type:”ADD”}
2、{type:”ADD”,key1:”“,key2:”“}

Reducer的认识

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

function count(state = 0, action) {
    switch (action.type) {
        case 'ADD':
            return state + 1;
        case 'REDUCER':
            return state - 1;
        default:
            return state;
    }
}

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

Store的认识

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

上面三者的使用案例

'use strict';
import {createStore} from 'redux';
function count(state = 0, action) {
    switch (action.type) {
        case 'ADD':
            return state + 1;
        case 'REDUCER':
            return state - 1;
        default:
            return state
    }
}

let store = createStore(count);

let currentValue = store.getState();
console.log('当前的值:', currentValue);

//定义一个监听的方法
let listener = () => {
    const previosValue = currentValue;
    currentValue = store.getState();
    console.log('上一个值:', previosValue, '当前值:', currentValue)
};
//创建一个监听
store.subscribe(listener);
//分发任务
store.dispatch({type:"ADD"});
store.dispatch({type:"ADD"});
store.dispatch({type:"ADD"});
store.dispatch({type:"REDUCER"});

Action创建函数

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

function add(){
    return{
        type:"ADD",
    }
}
function reducer() {
    return{
        type:"REDUCER",
    }
}

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

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

redux-thunk中间件的认识

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

function add() {
    return {
        type: 'ADD',
    }
}
function addIfOdd() {
    return (dispatch, getState) => {
        const currentValue = getState();
        if (currentValue % 2 == 0) {
            return false;
        }
        //分发一个任务
        dispatch(add())
    }
}

参考文章

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

Iterator 和 for...of 循环

1、Iterator 的概念

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属性是一个布尔值,表示遍历是否结束。

2、数据结构的默认 Iterator 接口

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

实例:

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

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

3、调用 Iterator 接口的场合

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

3.1、解构赋值

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

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

3.2、扩展运算符

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

    //  例一
    var str = 'hello';
    [...str] // ['h','e','l','l','o']
    //  例二
    let arr = ['b', 'c'];
    ['a', ...arr, 'd']
    // ['a', 'b', 'c', 'd']

3.3、yield*

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

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

3.4、其他场合

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

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

4、Iterator 接口与 Generator 函数

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

    var myIterable = {};
    myIterable[Symbol.iterator] = function* () {
        yield 1;
        yield 2;
        yield 3;
    };
    [...myIterable] // [1, 2, 3]
    
    //  或者采用下面的简洁写法
    let obj = {
        * [Symbol.iterator]() {
            yield 'hello';
            yield 'world';
        }
    };
    for (let x of obj) {
        console.log(x);
    }
    // hello
    // world

5、for...of 循环 - 重点!!!

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

5.1、数组

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

    const arr = ['red', 'green', 'blue'];
    let iterator = arr[Symbol.iterator]();
    
    for(let v of arr) {
        console.log(v); // red green blue
    }
    
    for(let v of iterator) {
        console.log(v); // red green blue
    }

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

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

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

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

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

5.2、Set 和 Map 结构

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

    var engines = new Set(["Gecko", "Trident", "Webkit", "Webkit"]);
    for (var e of engines) {
        console.log(e);
    }
    // Gecko
    // Trident
    // Webkit
    
    var es6 = new Map();
    es6.set("edition", 6);
    es6.set("committee", "TC39");
    es6.set("standard", "ECMA-262");
    for (var [name, value] of es6) {
        console.log(name + ": " + value);
    }
    4、通信类
    // committee: TC39
    // standard: ECMA-262

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

    let map = new Map().set('a', 1).set('b', 2);
    for (let pair of map) {
        console.log(pair);
    }
    // ['a', 1]
    // ['b', 2]
    
    for (let [key, value] of map) {
        console.log(key + ' : ' + value);
    }
    // a : 1
    // b : 2

5.3、计算生成的数据结构

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

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

实例:

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

5.4、对象

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

    var es6 = {
        edition: 6,
        committee: "TC39",
        standard: "ECMA-262"
    };
    
    for (e in es6) {
        console.log(e);
    }
    // edition
    // committee
    // standard
    
    for (e of es6) {
        console.log(e);
    }
    // TypeError: es6 is not iterable

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

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

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

    function* entries(obj) {
        for (let key of Object.keys(obj)) {
            yield [key, obj[key]];
        }
    }
    for (let [key, value] of entries(obj)) {
        console.log(key, "->", value);
    }
    // a -> 1
    // b -> 2
    // 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 的

语法:

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

Arrays(数组)

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

// array-example.js
const iterable = ['mini', 'mani', 'mo'];
 
for (const value of iterable) {
  console.log(value);
}
 
// Output:
// mini
// mani
// mo

Maps(映射)

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

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

Set(集合)

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

// set-example.js
const iterable = new Set([1, 1, 2, 2, 1]);
 
for (const value of iterable) {
  console.log(value);
}
// Output:
// 1
// 2

String(字符串)

// string-example.js
const iterable = 'javascript';
 
for (const value of iterable) {
  console.log(value);
}
 
// Output:
// "j"
// "a"
// "v"
// "a"
// "s"
// "c"
// "r"
// "i"
// "p"
// "t"

Arguments Object(参数对象)

// arguments-example.js
function args() {
  for (const arg of arguments) {
    console.log(arg);
  }
}
 
args('a', 'b', 'c');
// Output:
// a
// b
// c

Generators(生成器)

// generator-example.js
function* generator(){ 
  yield 1; 
  yield 2; 
  yield 3; 
}
 
for (const g of generator()) { 
  console.log(g); 
}
 
// Output:
// 1
// 2
// 3

退出迭代

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

const iterable = ['mini', 'mani', 'mo'];
 
for (const value of iterable) {
  console.log(value);
  break;
}
 
// Output:
// mini

普通对象不可迭代

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

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

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

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

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

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

For…of vs For…in

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

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

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

TODO 待补充内容

可以看这个文档: www.miganzi.com/tech/typesc…

100.flex 布局了解多少?【CSS】

flex 布局的学习

容器属性

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

flex-direction
flex-wrap
flex-flow
justify-content
align-items
align-content

flex-direction属性

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

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

flex-wrap属性

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

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

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

flex-flow

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

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

justify-content属性

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

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

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

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

align-items属性

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

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

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

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

align-content属性

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

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

项目的属性

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

order
flex-grow
flex-shrink
flex-basis
flex
align-self

order属性

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

.item {
  order: <integer>;
}

flex-grow

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

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

flex-shrink属性

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

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

flex-basis

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

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

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

flex属性

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

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

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

align-self属性

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

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

该属性可能取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。

资深开发者相关问题【共计 2 道题】

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

基本原则和步骤

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

参考文档

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

// TODO 待补充