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);
}
这个方法接受三个参数:要设置值的对象,一个字符串数组表示对象的键的路径,和要设置的值。例如,如果要设置对象user的address字段的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为例)?【浏览器】
- 在浏览器地址栏输入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树和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解析过程中会逐步显示页面)
89.从哪些方面可以对网站性能优化?【浏览器】
-
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的认识
简单点说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】
基本原则和步骤
- 调研基本的前端测试框架有哪些, 有哪些优劣
- 需要分别针对 utils 静态方法测试与 UI 交互测试(2E2测试)
- 针对测试, 产出衡量指标与实践规范
- 针对项目产出最佳实践
- 通过 CI 工具进行测试检查、覆盖、定时回归
参考文档
93.如何做一个前端项目工程的自动化部署, 有哪些规范和流程设计?【JavaScript】
// TODO 待补充