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}
这个方法接受三个参数:要设置值的对象,一个字符串数组表示对象的键的路径,和要设置的值。例如,如果要设置对象user
的address
字段的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为例)?【浏览器】
-
在浏览器地址栏输入URL
-
浏览器查看缓存,如果请求资源在缓存中并且新鲜,跳转到转码步骤
-
如果资源未缓存,发起新请求
-
如果已缓存,检验是否足够新鲜,足够新鲜直接提供给客户端,否则与服务器进行验证。
-
检验新鲜通常有两个HTTP头进行控制
Expires
和Cache-Control
: - HTTP1.0提供Expires,值为一个绝对时间表示缓存新鲜日期 - HTTP1.1增加了Cache-Control: max-age=,值为以秒为单位的最大新鲜时间 -
浏览器解析URL获取协议,主机,端口,path
-
浏览器组装一个HTTP(GET)请求报文
-
浏览器获取主机ip地址,过程如下:
-
浏览器缓存
-
本机缓存
-
hosts文件
-
路由器缓存
-
ISP DNS缓存
-
DNS递归查询(可能存在负载均衡导致每次IP不一样)
-
打开一个socket与目标IP地址,端口建立TCP链接,三次握手如下:
-
客户端发送一个TCP的SYN=1,Seq=X的包到服务器端口
-
服务器发回SYN=1, ACK=X+1, Seq=Y的响应包
-
客户端发送ACK=Y+1, Seq=Z
-
TCP链接建立后发送HTTP请求
-
服务器接受请求并解析,将请求转发到服务程序,如虚拟主机使用HTTP Host头部判断请求的服务程序
-
服务器检查HTTP请求头是否包含缓存验证信息如果验证缓存新鲜,返回304等对应状态码
-
处理程序读取完整请求并准备HTTP响应,可能需要查询数据库等操作
-
服务器将响应报文通过TCP连接发送回浏览器
-
浏览器接收HTTP响应,然后根据情况选择关闭TCP连接或者保留重用,关闭TCP连接的四次握手如下:
-
主动方发送Fin=1, Ack=Z, Seq= X报文
-
被动方发送ACK=X+1, Seq=Z报文
-
被动方发送Fin=1, ACK=X, Seq=Y报文
-
主动方发送ACK=Y, Seq=X报文
-
浏览器检查响应状态吗:是否为1XX,3XX, 4XX, 5XX,这些情况处理与2XX不同
-
如果资源可缓存,进行缓存
-
对响应进行解码(例如gzip压缩)
-
根据资源类型决定如何处理(假设资源为HTML文档)
-
解析HTML文档,构件DOM树,下载资源,构造CSSOM树,执行js脚本,这些操作没有严格的先后顺序,以下分别解释
-
构建DOM树:
-
Tokenizing:根据HTML规范将字符流解析为标记
-
Lexing:词法分析将标记转换为对象并定义属性和规则
-
DOM construction:根据HTML标记关系将对象组成DOM树
-
解析过程中遇到图片、样式表、js文件,启动下载
-
构建CSSOM树:
-
Tokenizing:字符流转换为标记流
-
Node:根据标记创建节点
-
CSSOM:节点创建CSSOM树
-
从DOM树的根节点遍历所有可见节点,不可见节点包括:1)
script
,meta
这样本身不可见的标签。2)被css隐藏的节点,如display: none
-
对每一个可见节点,找到恰当的CSSOM规则并应用
-
发布可视节点的内容和计算样式
-
js解析如下:
-
浏览器创建Document对象并解析HTML,将解析到的元素和文本节点添加到文档中,此时document.readystate为loading
-
HTML解析器遇到没有async和defer的script时,将他们添加到文档中,然后执行行内或外部脚本。 这些脚本会同步执行,并且在脚本下载和执行时解析器会暂停。 这样就可以用document.write()把文本插入到输入流中。 同步脚本经常简单定义函数和注册事件处理程序,他们可以遍历和操作script和他们之前的文档内容
-
当解析器遇到设置了async属性的script时,开始下载脚本并继续解析文档。 脚本会在它下载完成后尽快执行,但是解析器不会停下来等它下载。 异步脚本禁止使用document.write(),它们可以访问自己script和之前的文档元素
-
当文档完成解析,document.readState变成interactive
-
所有defer脚本会按照在文档出现的顺序执行,延迟脚本能访问完整文档树,禁止使用document.write()
-
浏览器在Document对象上触发DOMContentLoaded事件
-
此时文档完全解析完成,浏览器可能还在等待如图片等内容加载, 等这些内容完成载入并且所有异步脚本完成载入和执行,document.readState变为complete,window触发load事件
-
显示页面(HTML解析过程中会逐步显示页面)
-
content方面
- 减少HTTP请求:合并文件、CSS精灵、inline Image
- 减少DNS查询:DNS查询完成之前浏览器不能从这个主机下载任何任何文件。方法:DNS缓存、将资源分布到恰当数量的主机名,平衡并行下载和DNS查询
- 避免重定向:多余的中间访问
- 使Ajax可缓存
- 非必须组件延迟加载
- 未来所需组件预加载
- 减少DOM元素数量
- 将资源放到不同的域下:浏览器同时从一个域下载资源的数目有限,增加域可以提高并行下载量
- 减少iframe数量
- 不要404
-
Server方面
- 使用CDN
- 添加Expires或者Cache-Control响应头
- 对组件使用Gzip压缩
- 配置ETag
- Flush Buffer Early
- Ajax使用GET进行请求
- 避免空src的img标签
-
Cookie方面
- 减小cookie大小
- 引入资源的域名不要包含cookie
-
css方面
- 将样式表放到页面顶部
- 不使用CSS表达式
- 使用不使用@import
- 不使用IE的Filter
-
Javascript方面
- 将脚本放到页面底部
- 将javascript和css从外部引入
- 压缩javascript和css
- 删除不需要的脚本
- 减少DOM访问
- 合理设计事件监听器
-
图片方面
- 优化图片:根据实际颜色需要选择色深、压缩
- 优化css精灵
- 不要在HTML中拉伸图片
- 保证favicon.ico小并且可缓存
-
移动方面
- 保证组件小于25k
- 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】
- 1、Iterator 的概念
- 2、数据结构的默认 Iterator 接口
- 3、调用 Iterator 接口的场合
- 4、Iterator 接口与 Generator 函数
- 5、for…of 循环 - 重点!!!
- 6、对比JS中的几种遍历:for forEach for…in for…of
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】
- 调研基本的前端测试框架有哪些, 有哪些优劣
- 需要分别针对 utils 静态方法测试与 UI 交互测试(2E2测试)
- 针对测试, 产出衡量指标与实践规范
- 针对项目产出最佳实践
- 通过 CI 工具进行测试检查、覆盖、定时回归
93.如何做一个前端项目工程的自动化部署, 有哪些规范和流程设计?【JavaScript】
// TODO 待补充