0%

在前端面试和日常业务中,Promise.race 是一个非常常见但又容易被忽略的 API。它的规则很简单:

多个 Promise 同时“赛跑”,谁先落地(fulfilled 或 rejected),整体 Promise 就跟着落地。

这篇文章我们会:

  • 先讲清楚 Promise.race 的行为与典型使用场景
  • 再手写一个 myPromiseRace
  • 最后补充边界情况、优化点与面试常考问题

1. Promise.race 是什么?

Promise.race(iterable) 接收一个可迭代对象(通常是数组),返回一个新的 Promise:

  • 只要 任意一个 Promise 先 fulfilled,结果 Promise 就 fulfilled
  • 只要 任意一个 Promise 先 rejected,结果 Promise 就 rejected
  • 它不会等待全部结束,也不会“取消”其他 Promise(它们仍然会继续执行)

2. 常见使用场景

2.1 超时控制(最常见)

比如请求接口超过 3 秒就当作超时处理:

  • fetch(url) vs timeoutPromise(3000)
  • 谁先结束就用谁的结果

2.2 多源请求兜底

比如同一个资源从 CDN1/CDN2 同时拉取,谁先成功就用谁。

2.3 首屏竞速

首屏数据、降级数据、缓存数据同时读取,谁先返回就先渲染。

3. 手写 myPromiseRace

你这版实现非常接近原生行为:

  • 校验 iterable
  • 遍历输入
  • Promise.resolve 兼容非 Promise 值
  • 谁先 settle 就 resolve/reject
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function myPromiseRace(promises) {
return new Promise((resolve, reject) => {
// 检查输入是否为可迭代对象
if (!promises || typeof promises[Symbol.iterator] !== 'function') {
return reject(new TypeError('Argument is not iterable'));
}

// 遍历每个 Promise / 值
for (const promise of promises) {
// 用 Promise.resolve 包一层,确保 thenable / 普通值都能被处理
Promise.resolve(promise)
.then(resolve) // 谁先 fulfilled,整体就 fulfilled
.catch(reject); // 谁先 rejected,整体就 rejected
}
});
}

4. 关键点拆解(为什么要 Promise.resolve?)

Promise.race 的参数里不一定都是 Promise,可能是:

  • 普通值:Promise.race([1, 2, 3])
  • thenable:{ then(resolve) { resolve(123) } }

使用 Promise.resolve(x) 的好处是:

  • 普通值会立刻变成 fulfilled Promise
  • thenable 会被吸收并按 Promise 规则执行

这也是符合原生 Promise.race 的行为。

5. 更完整的版本:处理空 iterable

原生 Promise.race([]) 会返回一个永远 pending 的 Promise。

你现在的实现遇到空数组也会返回 pending(因为 for 循环不执行),这符合原生。

如果你想更明确一点,可以加个计数,但不是必须。

6. 更多测试用例

6.1 测试(先 reject)

1
2
3
4
5
6
7
const p1 = new Promise(res => setTimeout(() => res('p1'), 1000));
const p2 = new Promise(res => setTimeout(() => res('p2'), 500));
const p3 = new Promise((res, rej) => setTimeout(() => rej('p3 error'), 300));

myPromiseRace([p1, p2, p3])
.then(console.log)
.catch(console.error); // 输出: p3 error

6.2 普通值参与竞速(会立刻赢)

1
2
3
4
myPromiseRace([Promise.resolve('A'), 123, new Promise(res => setTimeout(() => res('B'), 10))])
.then(console.log)
.catch(console.error);
// 输出:123(普通值最快)

6.3 空数组:永远 pending

1
2
3
const p = myPromiseRace([]);
setTimeout(() => console.log('still pending...'), 1000);
// 1 秒后输出 still pending...,race 本身没有结果

7. 实战:用 race 做请求超时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function timeout(ms) {
return new Promise((_, reject) => {
setTimeout(() => reject(new Error(`Timeout after ${ms}ms`)), ms);
});
}

async function fetchWithTimeout(url, ms = 3000) {
return myPromiseRace([fetch(url), timeout(ms)]);
}

// 用法
fetchWithTimeout('https://example.com/api', 2000)
.then(res => res.json())
.then(data => console.log('data:', data))
.catch(err => console.error('error:', err.message));

8. 面试常问点(加分项)

  • Q:race 能取消其他 Promise 吗?
    不能。Promise 本身不支持取消,除非你用 AbortController(fetch)或自己设计可取消任务。

  • Q:race 里放普通值会怎样?
    会被 Promise.resolve 包装成 fulfilled Promise,通常会立刻“获胜”。

  • Q:race 和 any/allSettled 有什么区别?

    • race:谁先 settle 用谁
    • any:谁先 fulfilled 用谁(全部 rejected 才 rejected)
    • allSettled:等全部结束,返回每个结果状态

9. 总结

手写 Promise.race 的核心只有一句话:

遍历 iterable,把每个元素 Promise.resolve 后接上同一个 resolve/reject,谁先 settle 就定局。

在 JavaScript 中,new 运算符是面试和日常开发中都会频繁遇到的知识点。
很多人会用 new,但并不真正理解它背后到底发生了什么

本文将通过 手写 new 的实现,一步步拆解 new 运算符的底层逻辑,帮助你真正掌握 JavaScript 对象创建机制。

一、new 运算符到底做了什么?

当我们执行下面这行代码时:

1
const person = new Person('Alice', 25);

JavaScript 实际上在背后做了 4 件事情:

  1. 创建一个新的空对象
  2. 将新对象的原型指向构造函数的 prototype
  3. 执行构造函数,并将 this 绑定到新对象
  4. 如果构造函数返回的是对象,则返回该对象;否则返回新对象

理解这 4 步,是理解 new 的关键。

二、为什么要手写 new?

手写 new 的好处有三点:

  • 加深对 原型链this 指向 的理解
  • 几乎是 前端面试高频题
  • 有助于理解框架中对象创建的底层逻辑

接下来我们直接上代码。

三、手写 new 的完整实现

1
2
3
4
5
6
7
8
9
10
11
function myNew(constructor, ...args) {
// 1. 创建一个新对象,并将其原型指向构造函数的 prototype
const obj = Object.create(constructor.prototype);

// 2. 执行构造函数,将 this 绑定到新对象
const result = constructor.apply(obj, args);

// 3. 如果构造函数返回的是对象,则返回该对象
// 否则返回我们创建的新对象
return result instanceof Object ? result : obj;
}

这段代码完整复现了 new 运算符的核心行为。

四、逐行拆解实现逻辑

1. Object.create 的作用

1
const obj = Object.create(constructor.prototype);

这一步等价于:

1
obj.__proto__ === constructor.prototype

也就是说:

  • 新对象可以访问构造函数原型上的方法
  • 原型链关系得以建立

2. constructor.apply(obj, args)

1
const result = constructor.apply(obj, args);

这一步做了两件事:

  • 执行构造函数
  • 将构造函数内部的 this 指向新创建的对象

也就是:

1
2
this.name = name;
this.age = age;

实际上是在给 obj 添加属性。

3. 为什么要判断返回值?

1
return result instanceof Object ? result : obj;

这是很多人容易忽略的一点。

来看一个例子:

1
2
3
4
5
6
7
function Test() {
this.a = 1;
return { b: 2 };
}

const t = new Test();
console.log(t); // { b: 2 }

如果构造函数 显式返回一个对象new 会返回这个对象,而不是 this

所以我们在 myNew 中必须处理这种情况。

五、测试手写的 myNew

1
2
3
4
5
6
7
8
9
10
function Person(name, age) {
this.name = name;
this.age = age;
}

const p1 = myNew(Person, 'Alice', 25);

console.log(p1.name); // Alice
console.log(p1.age); // 25
console.log(p1 instanceof Person); // true

结果完全符合原生 new 的行为。

六、myNew 与 new 的对比总结

行为 new myNew
创建新对象
绑定原型
绑定 this
处理返回对象

可以说,myNew 已经实现了 90% 的 new 运算符行为,足以应付面试和深入理解 JS 原理。

七、常见面试追问

Q1:为什么不能直接 const obj = {}

因为 {} 创建的对象原型是 Object.prototype
new 创建的对象原型必须是 构造函数的 prototype

Q2:constructor.apply 能不能换成 call?

可以,只是参数形式不同:

1
constructor.call(obj, ...args);

Q3:instanceof 是如何工作的?

1
p1 instanceof Person

本质是沿着原型链查找:

1
Person.prototype 是否在 p1 的原型链上

八、总结

  • new 并不是魔法,而是一套明确的执行流程
  • 手写 new 是理解 原型链 + this + 构造函数 的最佳方式
  • 掌握这个知识点,对学习 Vue、React、Node.js 都有帮助

在 Web 开发中,XSS(Cross-Site Scripting,跨站脚本攻击) 是最常见、危害也非常大的安全漏洞之一。
一旦网站存在 XSS 漏洞,攻击者就可能在用户浏览器中执行恶意脚本,进而窃取 Cookie、劫持会话、篡改页面内容,甚至控制用户账号。

本文将从 原理 + 实战防御手段 的角度,系统讲解前端和全栈开发中 如何有效防御 XSS 攻击

一、什么是 XSS 攻击?

XSS 的本质是:

攻击者将恶意 JavaScript 注入到网页中,并在其他用户的浏览器里执行。

常见的 XSS 攻击类型包括:

  • 反射型 XSS:恶意脚本来自 URL 参数
  • 存储型 XSS:恶意脚本被存储在数据库中
  • DOM 型 XSS:前端 JS 逻辑直接操作不可信数据

不论是哪种形式,核心问题都在于:
👉 用户输入被当作代码执行了

二、输入验证和过滤(最基础也是最重要)

永远不要相信用户输入。

1. 前端层面的基本校验

在前端可以做一些基础校验,例如:

  • 限制输入长度
  • 限制输入格式(如邮箱、手机号)
  • 过滤明显的危险字符

示例(仅用于基础校验):

1
2
3
function sanitizeInput(input) {
return input.replace(/[<>]/g, '');
}

⚠️ 注意:
前端校验只能作为用户体验优化,不能作为最终安全防线。

2. 后端必须做严格校验(关键)

真正有效的输入验证 必须在后端完成

  • 校验数据类型
  • 校验长度
  • 拒绝不符合业务规则的输入
  • 对 HTML 内容进行转义或白名单过滤

推荐策略:

  • 默认拒绝
  • 白名单优先

三、对输出进行编码(比过滤更重要)

很多 XSS 漏洞并不是“输入有问题”,而是输出方式不安全

1. HTML 转义输出

在将用户输入输出到页面时,应进行 HTML 编码:

原字符 转义后
< &lt;
> &gt;
" &quot;
' &#39;

很多模板引擎(如 Vue、React、Handlebars)默认会做转义,不要轻易关闭。


2. 避免危险 API

尽量避免以下写法:

1
element.innerHTML = userInput;

推荐安全方式:

1
element.textContent = userInput;

或者:

1
2
3
const el = document.createElement('div');
el.textContent = userInput;
container.appendChild(el);

四、使用安全的 API 和框架机制

现代前端框架本身已经内置了大量 XSS 防护机制。

1. React / Vue 的默认保护

  • JSX / 模板语法默认转义
  • 明确标注危险行为(如 v-htmldangerouslySetInnerHTML

⚠️ 一旦使用这些 API,就必须 100% 确保内容可信

2. 避免在前端拼接 HTML 字符串

错误示例:

1
container.innerHTML = `<div>${userInput}</div>`;

正确做法:

1
2
3
const div = document.createElement('div');
div.textContent = userInput;
container.appendChild(div);

五、使用内容安全策略(CSP)

CSP(Content Security Policy) 是防御 XSS 的“终极武器”之一。

1. CSP 能做什么?

  • 禁止内联脚本执行
  • 限制脚本来源
  • 阻止恶意第三方资源加载

示例 HTTP 头:

1
2
3
4
Content-Security-Policy:
default-src 'self';
script-src 'self';
object-src 'none';

即使攻击者成功注入脚本,只要 CSP 生效,浏览器也会拒绝执行。

2. 为什么 CSP 非常重要?

因为它属于:

  • 浏览器层面的安全防护
  • 就算代码有疏漏,也能兜底

1. HttpOnly 的作用

设置 Cookie 时加上 HttpOnly

1
Set-Cookie: sessionId=xxx; HttpOnly;

这样可以:

  • 防止 JavaScript 读取 Cookie
  • 即使发生 XSS,也能降低会话被盗风险

2. 配合 Secure 和 SameSite

推荐同时使用:

  • Secure
  • SameSite=StrictLax

提升整体安全等级。

七、定期更新第三方库和依赖

现实中的 XSS 漏洞,大量来自第三方依赖

  • 旧版本 UI 组件
  • 不再维护的插件
  • 存在漏洞的 npm 包

建议:

  • 定期更新依赖
  • 使用 npm audit
  • 关注安全公告

八、良好的安全编程习惯

最后,防御 XSS 不是“某一个技巧”,而是一整套习惯:

  • 不信任任何外部输入
  • 不随意使用 innerHTML
  • 不忽略安全警告
  • 安全优先于“图方便”

九、总结

  • XSS 是最常见、也是最危险的 Web 安全问题之一
  • 真正有效的防御需要 前端 + 后端 + 浏览器策略 三层配合
  • 输入校验只是开始,安全输出和 CSP 才是关键
  • 养成良好的安全编程习惯,比任何补丁都重要

对于前端开发者来说,
懂得如何防御 XSS,是走向专业的重要一步。

centos8使用yum失败,切换镜像源,更改镜像源文件,尝试清除缓存并重新建立

1
2
3
4
5
6
wget -O /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-8.repo

sudo yum clean all

sudo yum makecache

Centos8已不维护,只能找

puppeteer报错下载依赖 参考链接:https://www.cnblogs.com/ilizhu/p/14504049.html
报错sandbox:

1
2
#添加启动参数
const browser = await puppeteer.launch({args: ['--no-sandbox', '--disable-setuid-sandbox']});

有个需求是根据一个表格的所有子表,同步到另个表格(包含子表)的数据,用google sheets里面的扩展程序里的apps 脚本功能,编写脚本。为什么要同步呢?因为原表默认是不可编辑的,打算同步过来后再进行编辑。sourceSpreadsheetId跟targetSpreadsheetId为google sheet 表格地址栏d后面的一串字符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
function synchronizeSheetsToAnotherFile() {
const sourceSpreadsheetId = "id1";
const targetSpreadsheetId = "id2"; # 比如:1NOtSSvDwXrca8vqyZW6OqQJwGaCICMVguSz1-i72oqg

const sourceSpreadsheet = SpreadsheetApp.openById(sourceSpreadsheetId);
const targetSpreadsheet = SpreadsheetApp.openById(targetSpreadsheetId);

const sourceSheets = sourceSpreadsheet.getSheets();

sourceSheets.forEach(sheet => {
const sheetName = sheet.getName();
let targetSheet = targetSpreadsheet.getSheetByName(sheetName);

if (!targetSheet) {
targetSheet = targetSpreadsheet.insertSheet(sheetName);
} else {
targetSheet.clear();
}

const range = sheet.getDataRange();
const displayValues = range.getDisplayValues();
const formats = range.getBackgrounds();
const fontWeights = range.getFontWeights();

const targetRange = targetSheet.getRange(1, 1, displayValues.length, displayValues[0].length);
targetRange.setValues(displayValues);
targetRange.setBackgrounds(formats);
targetRange.setFontWeights(fontWeights);


});
}




  • 通过Props和Props回调:用于父子组件的通信,子组件通过props接收父的数据,父组件将回调函数传给子组件,子组件调用这个回调函数,传数据给父组件。
  • 通过Context API:可以共享状态的API,不需要按层级去传递。
  • Redux或Mobx:对于复杂的应用,可以用状态库来实现组件通信。
  • 事件订阅模式:使用EventEmitter或者第三方库来实现跨组件的事件发布订阅。
  • Hooks:React的一些Hooks,比如useState,能实现不同组件的共享逻辑跟状态。

《if-elif条件语句》逐字稿

  同学们,老师刚才留意到班级上的值日表,周一到周五有好多勤劳的小蜜蜂呀。老师想问下周一值日的同学有哪些举下手?诶,老师看见了。那么同学们记住这些周一值日的同学了吗?是的,总共有六位。那如果以三个班为单位,给你一个名字,你能快速给出这名同学是星期几值日吗?你们有什么好办法吗?请你,对了,可以自己用绕口令等方式背下来。还有更好的办法吗?请你,对了,可以利用程序的方式输出出来。那该使用什么语句来表示多个条件呢?今天我们就一起来学习python中的另一种条件语句if-elif-else

  现在请大家同桌两人为一小组,先来分析if-elif-else怎么实现同学名字对应星期几值日的问题,时间是8分钟。开始吧,时间到了,刚才在巡视的过程中,大家都在认真的讨论,那哪个小组代表想来分享你们的成果,你来说,你说想要判断同学是哪天值日的,只要输入他的名字,根据名字找到对应星期几输出就行了。还有补充的吗?第五小组,你们来说,他说可以弄五个数组对应星期一到星期五,把每个人的名字放进去对应的数组里,然后判断输出。你补充的非常关键。请坐。非常好,请坐,大家分析的很正确,说明大家有充分预习本节课的内容。

  接下来我们就综合大家的分析,请同学们自主思考,并画出流程图,时间是5分钟,开始吧,时间到了,请这位同学来展示你的流程图,你说首先输入学生的名字,假如小花在星期一的数组里,那么输出星期一,依次类推。现在请大家尝试写出多分支结构的语句,老师提示一下,条件子句的如果需要用elif来表示。我们请这位同学将他的答案写到黑板上,请同学们来看跟你写的一样吗?哪些地方需要修改?你说将最后一个elif应该写成else,是表达都不满足条件的话,执行else后面的语句。你说要注意缩进和冒号。大家观察的很仔细,希望同学们写的时候一定要认真。请大家利用学到的if-elif语句去编写刚才你们设计的算法。我看到大家都能准确的写出来了,并且都避免了刚才的问题。

  语句我们已经写出来了,那他的执行过程是什么呢?现在请同学们以同桌两人为一小组,进行讨论,一会我们请一名小组代表展示讲解,时间是7分钟,开始吧,我看到有同学在小声的讨论,时间到了,第二小组,请你们讲一讲执行过程,他结合流程图告诉我们,执行过程是依次判断输入的同学姓名符合哪个条件,如符合则输出,若都不符合则输出错误。他用图文的形式讲解,非常的直观。那现在我们就可以就着大屏幕上的流程图,总结出if-elif语句的执行过程是从if行开始依次对条件进行判断,如果正确则执行改条件下面的语句块,然后跳出if语句,如果不正确则对下一个条件进行判断,如果所有条件都不成立,则执行else下面的语句块。

  新知识我们已经学完了,现在请大家编写完整程序,实现上述的执行过程,学有余力的同学,请自己思考生活中有哪些需要对多个条件进行检测的事情,自主进行编写。我们请这些同学来进行展示,他们中有的从数学出发,编写了比较三个数,求最大数问题,还有的是从生活出发,编写了出租车计费的问题。大家编写的语句和格式都很规范。

  这堂课已经接近尾声了,这节课你都有哪些收获呢?大家说学会了if-elif的格式和执行过程,看来这节课的知识同学们都掌握了。其实编程离咱们的生活和学习很近,并且也没有想象中的那么难,老师希望同学们以后都可以像今天一样在课堂上积极思考,表达想法。
  最后布置一个小作业,请同学们对比三种if语句特点,整理成表格。
  这节课就上到这里,同学们再见。

  • Fabric:同步渲染 UI,消除 Bridge 通信瓶颈
  • TurboModules:按需加载原生模块,提升启动速度
  • JSI(JavaScript Interface):直接调用原生代码,替代异步 Bridge, Bridge是异步的、单线程的,会带来额外的开销;而JSI是同步的,更低开销、并发的。

Next版本为7.8.0,找到 /themes/next/_config.yml 文件,搜索 font 关键词,这里注意一定要将 font 里面的 enable 设置为true, 因为默认为false,然后设置 family 为自己想要的,如果family在其他平台找的,就需要修改host。(只是修改了family, 没修改enable就会不生效,所以一定一定要记得)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
font:
enable: true

# Uri of fonts host, e.g. https://fonts.googleapis.com (Default).
host:

# Font options:
# `external: true` will load this font family from `host` above.
# `family: Times New Roman`. Without any quotes.
# `size: x.x`. Use `em` as unit. Default: 1 (16px)

# Global font settings used for all elements inside <body>.
global:
external: true
family: Noto Serif SC
size:

# Font settings for site title (.site-title).
title:
external: true
family: Noto Serif SC
size:

# Font settings for headlines (<h1> to <h6>).
headings:
external: true
family: Noto Serif SC
size:

# Font settings for posts (.post-body).
posts:
external: true
family: Noto Serif SC

# Font settings for <code> and code blocks.
codes:
external: true
family: Source Code Pro


代码可复制,还是上面同一个文件,找到以下代码段,将copy_button的enable设置为true,启用就可以啦。show_result设置true后,复制完会显示一个勾。style对应的copy_button不同的样式,看个人喜好来~

1
2
3
4
5
6
7
8
9
10
11
12
codeblock:
# Code Highlight theme
# Available values: normal | night | night eighties | night blue | night bright | solarized | solarized dark | galactic
# See: https://github.com/chriskempson/tomorrow-theme
highlight_theme: night eighties
# Add copy button on codeblock
copy_button:
enable: true
# Show text copy result.
show_result: true
# Available values: default | flat | mac
style: mac

前端脱离框架去开发,就是不使用任何前端框架(Vue、React等)的情况下,直接用js、html和css去开发web。这意味更能深入了解前端底层原理和机制,避免依赖框架。

在面对静态且不做过多功能的低项目下,其实脱离框架就已经足够了。框架编写的代码必须要进行编译才能在浏览器中渲染,所以便有了webpack。像Vue的出现就是为了解决一些痛点问题,比如双向数据流、自动响应式更新等,但我们又不得不遵守他们框架内的一些语法,随着vue的升级,我们也要不停学习更新的框架内容。框架本身是有利于开发的,但面对一些简单的问题时,不用框架反而是明智的。根据需求而决定去不去用框架进行下一步的开发。

当开发多个类似的网站,没有打包过的代码放上服务器,能直接在服务器改源代码。而使用框架后打包的源码放在服务器中,要修改却只能本地修改,反而更浪费时间。当熟悉纯js开发后,还能开发一些工具以及框架出来。