diff --git a/.github/workflows/comment.yml b/.github/workflows/comment.yml new file mode 100644 index 0000000..fa2c305 --- /dev/null +++ b/.github/workflows/comment.yml @@ -0,0 +1,17 @@ +on: + issue_comment: + types: [created, edited] + +jobs: + issue: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Dump GitHub context + env: + GITHUB_CONTEXT: ${{ toJSON(github) }} + COMMENTHOST: ${{ secrets.COMMENTHOST }} + run: | + yarn + node ./script/comment.js diff --git a/.github/workflows/issue.yml b/.github/workflows/issue.yml new file mode 100644 index 0000000..2b664ba --- /dev/null +++ b/.github/workflows/issue.yml @@ -0,0 +1,17 @@ +on: + issues: + types: [opened, edited] + +jobs: + issue: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Dump GitHub context + env: + GITHUB_CONTEXT: ${{ toJSON(github) }} + ISSUEHOST: ${{ secrets.ISSUEHOST }} + run: | + yarn + node ./script/issue.js diff --git a/.github/workflows/markdown.yml b/.github/workflows/markdown.yml new file mode 100644 index 0000000..af1e919 --- /dev/null +++ b/.github/workflows/markdown.yml @@ -0,0 +1,29 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +# This workflow will install Deno then run Deno lint and test. +# For more information see: https://github.com/denoland/setup-deno + +name: markdown-autodocs + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: install + run: | + npm i -g prettier + prettier README.md -w + - uses: EndBug/add-and-commit@v7 # You can change this to use a specific version. + with: + add: './README.md' diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c16ef02 --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# compiled output +/dist +/node_modules + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# OS +.DS_Store + +# Tests +/coverage +/.nyc_output + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..10b731c --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ diff --git a/.idea/fucking-frontend.iml b/.idea/fucking-frontend.iml new file mode 100644 index 0000000..0c8867d --- /dev/null +++ b/.idea/fucking-frontend.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..c5f2385 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Answer/1 ~ 10/1.md b/Answer/1 ~ 10/1.md new file mode 100644 index 0000000..7b774b2 --- /dev/null +++ b/Answer/1 ~ 10/1.md @@ -0,0 +1,29 @@ +```js +try { + (async function() { a().b().c() })() +} catch (e) { + console.log(`执行出错:${e.message}`) +} +``` + +这道题目主要三个考点: + +1. 执行一个没有定义的函数会发生什么 +2. 在 `async` 内部发生报错会发生什么 +3. `try catch` 只能捕获同步代码的异常 + +因此答案就明了了。 + +因为我们执行了一个未定义的函数,所以会报错 `a is not defind`,又因为是在 `async` 中,所以报错信息会显示 `in promise`。最后 `try cathch` 只能捕获同步代码的抛错,因为是 `async`,所以走不到 `catch` 里面。 + +如果我们把代码这样改一下就可以了: + +```js +try { + await (async function() { a().b().c() })() +} catch (e) { + console.log(`执行出错:${e.message}`) +} +``` + +另外说个小知识点,最新的 Chrome 已经支持在全局作用域下使用 `await` 了。 diff --git a/Answer/1 ~ 10/2.md b/Answer/1 ~ 10/2.md new file mode 100644 index 0000000..1663ff4 --- /dev/null +++ b/Answer/1 ~ 10/2.md @@ -0,0 +1,32 @@ +```js +try { + let a = 0 + ;(async function() { + a += 1 + console.log('inner', a) + throw new Error('123') + // a() + })() + console.log('outer', a) +} catch(e) { + console.warn('Error', e) +} +``` + +这道题目基础不好的同学容易答错,很容易以为 `async` 的函数内部代码就是异步的,实际都还是同步的,把代码改成这样大家就明白了: + +```js +function a() { + return new Promise(function(resolve, reject) { + a += 1 + console.log('inner', a) + throw new Error('123') + }) +} +``` + +另外发现还有同学疑问为什么 `throw` 出去的错误被 `promise catch` 住了,这个我们直接看标准: + +[标准具体地址](https://tc39.es/ecma262/#sec-control-abstraction-objects) + +[这里也有一个不错的作答](https://github.com/KieSun/fucking-frontend/issues/2#issuecomment-793266365) diff --git a/Answer/1 ~ 10/3.md b/Answer/1 ~ 10/3.md new file mode 100644 index 0000000..1e56ec2 --- /dev/null +++ b/Answer/1 ~ 10/3.md @@ -0,0 +1,66 @@ +```js +// 请使用原生代码实现一个Events模块,可以实现自定义事件的订阅、触发、移除功能 +const fn1 = (... args)=>console.log('I want sleep1', ... args) +const fn2 = (... args)=>console.log('I want sleep2', ... args) +const event = new Events(); +event.on('sleep', fn1, 1, 2, 3); +event.on('sleep', fn2, 1, 2, 3); +event.fire('sleep', 4, 5, 6); +// I want sleep1 1 2 3 4 5 6 +// I want sleep2 1 2 3 4 5 6 +event.off('sleep', fn1); +event.once('sleep', () => console.log('I want sleep')); +event.fire('sleep'); +// I want sleep2 1 2 3 +// I want sleep +event.fire('sleep'); +// I want sleep2 1 2 3 +``` + +今天的题目算是一道常考题了,没有一个标准解法,输出正确就行。 + +但是如果你能用上一些 ES6 的语法以及处理好一些边界问题,面试官对你的评价会更好点。 + +```js +class Events { + constructor() { + this.events = new Map(); + } + + addEvent(key, fn, isOnce, ...args) { + const value = this.events.get(key) ? this.events.get(key) : this.events.set(key, new Map()).get(key) + value.set(fn, (...args1) => { + fn(...args, ...args1) + isOnce && this.off(key, fn) + }) + } + + on(key, fn, ...args) { + if (!fn) { + console.error(`没有传入回调函数`); + return + } + this.addEvent(key, fn, false, ...args) + } + + fire(key, ...args) { + if (!this.events.get(key)) { + console.warn(`没有 ${key} 事件`); + return; + } + for (let [, cb] of this.events.get(key).entries()) { + cb(...args); + } + } + + off(key, fn) { + if (this.events.get(key)) { + this.events.get(key).delete(fn); + } + } + + once(key, fn, ...args) { + this.addEvent(key, fn, true, ...args) + } +} +``` diff --git a/Answer/1 ~ 10/4.md b/Answer/1 ~ 10/4.md new file mode 100644 index 0000000..68a0693 --- /dev/null +++ b/Answer/1 ~ 10/4.md @@ -0,0 +1,46 @@ +页面上有三个按钮,分别为 A、B、C,点击各个按钮都会发送异步请求且互不影响,每次请求回来的数据都为按钮的名字。 + +请实现当用户依次点击 A、B、C、A、C、B 的时候,最终获取的数据为 ABCACB。 + +这道题目主要两个考点: + +1. 请求不能阻塞,但是输出可以阻塞。比如说 B 请求需要耗时 3 秒,其他请求耗时 1 秒,那么当用户点击 BAC 时,三个请求都应该发起,但是因为 B 请求回来的慢,所以得等着输出结果。 +2. 如何实现一个队列? + +其实我们无需自己去构建一个队列,直接利用 `promise.then` 方法就能实现队列的效果了。 + +答案摘自 [pandaomeng(https://github.com/KieSun/fucking-frontend/issues/5#issuecomment-796657041) + +```js +class Queue { + promise = Promise.resolve(); + + excute(promise) { + this.promise = this.promise.then(() => promise); + return this.promise; + } +} + +const queue = new Queue(); + +const delay = (params) => { + const time = Math.floor(Math.random() * 5); + return new Promise((resolve) => { + setTimeout(() => { + resolve(params); + }, time * 500); + }); +}; + +const handleClick = async (name) => { + const res = await queue.excute(delay(name)); + console.log(res); +}; + +handleClick('A'); +handleClick('B'); +handleClick('C'); +handleClick('A'); +handleClick('C'); +handleClick('B'); +``` diff --git a/Answer/1 ~ 10/5.md b/Answer/1 ~ 10/5.md new file mode 100644 index 0000000..74fa085 --- /dev/null +++ b/Answer/1 ~ 10/5.md @@ -0,0 +1,43 @@ +异步请求通过 `Promise.all` 处理,怎么让其中失败的所有请求重试。 + +```js +Promise.all([A, B, C, D]) +// 4 个请求完成后发现 AD 请求失败了,如果让 AD 请求重试 +``` + +这个题目其实很简单,因为 `Promise.all` 中一个 `promise` 挂了就挂了,所以我们直接在接口上处理 `catch` 就行了。 + +看了些答案,结果是对的,但是处理方式是错误的。比如说在 `resolve` 中去判断是否要重试。一般我们业务中请求都是封装过的函数,出现错误肯定直接 `reject` 了,不可能 `resolve` 出来。 + +答案摘自 [vandvassily](https://github.com/KieSun/fucking-frontend/issues/6#issuecomment-797237791) + +其他类似优秀答案:[yancongwen](https://github.com/KieSun/fucking-frontend/issues/6#issuecomment-797283287) + +```js +function request(name, count = 0) { + return new Promise((resolve, reject) => { + const isSuccess = Math.random() > 0.5; + console.log(`接口${name}: ${isSuccess}`); + setTimeout(() => { + isSuccess > 0.5 ? resolve(name) : reject(name); + }, Math.random() * 1000); + }).catch((err) => { + count++; + + if (count > 2) { + return Promise.reject(`后端大爷${name}接口写的666`); + } + return request(name, count); + }); +} + +let queue = [request('A'), request('B'), request('C'), request('D')]; + +Promise.all(queue) + .then((arr) => { + console.log(arr); + }) + .catch((err) => { + console.log(err); + }); +``` diff --git a/Answer/1 ~ 10/6.md b/Answer/1 ~ 10/6.md new file mode 100644 index 0000000..c395449 --- /dev/null +++ b/Answer/1 ~ 10/6.md @@ -0,0 +1,70 @@ +```js +/** + * @param input + * @param size + * @returns {Array} + */ +_.chunk(['a', 'b', 'c', 'd'], 2) +// => [['a', 'b'], ['c', 'd']] + +_.chunk(['a', 'b', 'c', 'd'], 3) +// => [['a', 'b', 'c'], ['d']] + +_.chunk(['a', 'b', 'c', 'd'], 5) +// => [['a', 'b', 'c', 'd']] + +_.chunk(['a', 'b', 'c', 'd'], 0) +// => [] +``` + +这道题目其实就是让大家实现一个 lodash 里的函数,这边我们需要注意的一个点是不能更改原数组,虽然题目没有提到,但是我们得想到这个。 + +笔者这里推荐几个答案,分别是不同的写法,难度从低到高吧。 + +首先是遍历的写法: + +[答案链接](https://github.com/KieSun/fucking-frontend/issues/8#issuecomment-799053150) + +```js +function chunk(arr, num) { + if (num === 0) return []; + if (Array.isArray(arr) && typeof num === "number") { + let result = []; + let i = 0; + while (i < arr.length) { + result.push(arr.slice(i, i + num)); + i += num; + } + return result; + } else { + console.log("params type error"); + } +} +``` + +我们也可以使用 `reduce` 来解题: + +[答案链接](https://github.com/KieSun/fucking-frontend/issues/8#issuecomment-799021398) + +```js +const chunk = (arr, len) => arr.reduce((pre, cur, index) => { + if (index % len === 0) { + pre.push([cur]) + return pre + } + const temp = pre[pre.length - 1] + temp && temp.push(cur) + return pre +}, []) +``` + +甚至直接优化到一行: + +[答案链接](https://github.com/KieSun/fucking-frontend/issues/8#issuecomment-799344324) + +```js +function chunk(arr, size){ + return Array.from({length: (size = Number.parseInt(size)) ? Math.ceil(arr.length/size) : 0}) + .map((a,i) => arr.slice(i*size, (i+1)*size)) +} +``` diff --git a/Answer/11-20/16.md b/Answer/11-20/16.md new file mode 100644 index 0000000..438f497 --- /dev/null +++ b/Answer/11-20/16.md @@ -0,0 +1,21 @@ +```js +let length = 10 +function fn() { + console.log(this.length); +} +let obj = { + length: 5, + method(fn) { + // 两次调用各输出什么 + fn() + arguments[0](0) + } +} +obj.method(fn, 1) +``` + +这道题目核心是在考 `this` 的指向,但是还有一些别的坑。 + +执行第一次 `fn` 的时候,这时候的 `this` 就是 `window`,但是 `window.length` 并不是指向用 `let` 声明的 `length` 上的,而是「返回当前窗口中包含的框架数量(框架包括frame和iframe两种元素)」,具体可以参考 [文档](https://developer.mozilla.org/zh-CN/docs/Web/API/Window/length)。 + +执行第二次 `fn` 的时候,此时 `this` 为 `arguments`,`arguments.length` 为实参的长度,也就是 2。 \ No newline at end of file diff --git a/Answer/11-20/17.md b/Answer/11-20/17.md new file mode 100644 index 0000000..4bcd3e3 --- /dev/null +++ b/Answer/11-20/17.md @@ -0,0 +1,14 @@ +```js +var a = 0 +var b = async () => { + a = a + await 10 + console.log('2', a) // -> ? +} +b() +a++ +console.log('1', a) // -> ? +``` + +这道题目大部分读者肯定会想到 `await` 左边是异步代码,因此会先把同步代码执行完,此时 `a` 已经变成 1,所以答案应该是 11。 + +其实 `a` 为 0 是因为加法运算法,先算左边再算右边,所以会把 0 固定下来。如果我们把题目改成 `await 10 + a` 的话,答案就是 11 了。 \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5c81beb --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 yck + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 7a15038..8f5a773 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,144 @@ -# 干爆前端 +# Fucking Frontend -仓库内容分为以下几块: +

+ Commit + Stars + Forks + GitHub + visitor badge +

-- 每日更新一道大厂原题,第二天公布答案 -- 前端学习路径及包括的各个知识点,每个知识点都有具体文章帮助大家融会贯通 -- 前端 15 万字面试资料 -- 前端进阶好文 -- 各类大厂内推 +干爆前端。一网打尽前端学习、进阶、面试等各类内容,帮助大家一年内拿到期望的 offer!大家可以关注【[公众号](#个人信息)】获取最新内容或者加我【[微信](#个人信息)】提意见(别忘了点个 Star 咯)。 + +**本仓库欢迎各位读者提交你认为不错的内容,直接开 PR 或者 Issue 都可。** + +## 这是什么? + +这是一份笔者从自学前端逐步成长为大厂基架组资深前端开发所沉淀下来的资料,其中包含了前端体系化学习、进阶、面试等内容。 + +**此资料适用于初中级进阶、高级查漏补缺。** + +## 如何使用 + +前端学习路径笔者会以计划的方式呈现,从基础到进阶内容都会涉及,并且会附带上笔者的一些解释以便读者们将知识连贯起来,从而建立自己的知识体系而不是单独记忆松散的知识点。 + +**推荐大家 Star 并 Fork 本项目(点击仓库顶部右上角按钮),然后 `git clone` 到本地后从上至下逐步完成学习计划。** + +当然除了前端学习路径,笔者还提供了热门技术原理解析、工程化体系学习、Github 上的优秀资料等内容供大家自行选择,读者们可以挑选感兴趣的内容自行学习。 ## 目录 -- [每日大厂原题](#每日大厂原题) -- [前端十五万字面试资料](#前端十五万字面试资料) -- [各类大厂内推](#各类大厂内推) +- [学习路径](#学习路径) + - [前端核心知识点](#前端核心知识点) +- [十五万字面试资料](#十五万字面试资料) +- [前端工程化文章](#前端工程化文章) +- [React 原理](#react-原理) +- [前端进阶文章](#前端进阶文章) +- [更多文章](#更多文章) +- [个人信息](#个人信息) +## 学习路径 -## 每日大厂原题 +前端知识点很多这是公认的事情,但是我们确实没必要把所有知识都去学习,那样只会贪多嚼不烂。我们能把核心知识点、热门技术以及工作中需要用到的知识学好就已经能打败大部分前端工程师了,剩下的大家可以自行选择学习内容。 -欢迎大家提供面试题,发送题目至邮箱:zx597813039@gmail.com。 +接下来的内容笔者会按照优先级高低来排布学习路径的目录,大家只需从上到下顺序学习即可。 -成功采纳后,如果你面试遇到其他问题,笔者可以提供免费解答服务。 +> 为了保证大家学到的知识是正确的,下文中的文档资料笔者都使用了英文版本。因为在审核资料的过程中笔者发现不少中文文档都存在翻译错误或者过时的情况。 +## 前端核心知识点 + +前端核心知识点分为三块,分别为 HTML、CSS、JS,其中最重要的当然是 JS 了。 + +如果你还是一位初学者,推荐先自行完整阅读一至二本书后再按照该计划学习。关于书籍笔者推荐以下两本: + +- [JavaScript 高级程序设计(第 4 版)](https://book.douban.com/subject/35175321/) +- [JavaScript 忍者秘籍(第 2 版)](https://book.douban.com/subject/30143702/) + +## JS + +### 数据类型 + +JS 数据类型分为两大类及八种数据类型,注意别漏了 ES6 新增的 `bigint`。 + +- [ ] 文档: + - [ ] [JavaScript 数据类型和数据结构](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures) + +### 类型判断 + +类型判断有好几种方式,分别为: + +- `typeof` +- `instanceof` +- `Object.prototype.toString` +- `isXXX`,比如 `isArray` + +- [ ] 文档 + - [ ] [typeof](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof) + - [ ] [instanceof](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof) + - [ ] [Object.prototype.toString](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString#using_tostring_to_detect_object_class) +- [ ] 推荐文章,Issue 也挺重要 + - [ ] [JavaScript 专题之类型判断(上)](https://github.com/mqyqingfeng/Blog/issues/28) + - [ ] [JavaScript 专题之类型判断(下)](https://github.com/mqyqingfeng/Blog/issues/30) + +### 类型转换 + +类型转换算是 JS 中情况繁杂且容易出错,但是开发中还经常会遇到的知识点。强行全部记忆容易遗忘,推荐记忆及练习开发中的常见情况。 + +- [ ] 文档 + - [ ] [ES 标准](https://tc39.es/ecma262/#sec-abstract-operations) + - 标准并不好读,如果读者英文水平欠佳,可以只阅读该小节内的表格内容。 + - [ ] [双等判断](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Equality) +- [ ] 推荐文章 + - [ ] [You Don't Know JS 书中关于类型转换的一章节](https://github.com/weiqinl/You-Dont-Know-JS-CN/blob/master/types%20%26%20grammar/ch4.md) + - [ ] [几种基本类型简单的类型转换](https://zh.javascript.info/type-conversions) + - [ ] [JavaScript 深入之头疼的类型转换(上)](https://github.com/mqyqingfeng/Blog/issues/159) + - [ ] [JavaScript 深入之头疼的类型转换(下)](https://github.com/mqyqingfeng/Blog/issues/164) + +### this + +`this` 算是不少初学者容易搞混的一个知识点,但是它很重要,务必掌握。 + +- [ ] 文档 + - [ ] [this](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) +- [ ] 推荐文章 + - [ ] [You Don't Know JS 书中关于 this 的第一章节](https://github.com/getify/You-Dont-Know-JS/blob/1st-ed/this%20%26%20object%20prototypes/ch1.md) + - [ ] [You Don't Know JS 书中关于 this 的第二章节](https://github.com/getify/You-Dont-Know-JS/blob/1st-ed/this%20%26%20object%20prototypes/ch2.md) + - [ ] [深入理解 js this 绑定 ( 无需死记硬背,尾部有总结和面试题解析 )](https://segmentfault.com/a/1190000011194676) + +> 务必自己总结规则! + +### 闭包 + +- [ ] 文档 + - [ ] [闭包](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures) +- [ ] 推荐文章 + - [ ] [You Don't Know JS 第二版中对于闭包的解释](https://github.com/getify/You-Dont-Know-JS/blob/2nd-ed/scope-closures/ch7.md),该版本暂无中文翻译,读者可用 [DeepL](https://www.deepl.com/translator) 进行翻译。 + - [ ] [JavaScript 的静态作用域链与“动态”闭包链](https://juejin.cn/post/6957913856488243237) + - [ ] [知乎中关于闭包的讨论](https://www.zhihu.com/question/34210214) + +### 作用域 + +作用域是指程序中定义变量的区域,该位置决定了变量的生命周期,也就是变量和函数的可访问范围。 +- [ ] 文档 + - [ ] [作用域](https://developer.mozilla.org/en-US/docs/Glossary/Scope) + - [ ] [块作用域](https://developer.mozilla.org/en-US/docs/Glossary/Block/Scripting) +- [ ] 推荐文章 + - [ ] [JavaScript 深入之词法作用域和动态作用域](https://github.com/mqyqingfeng/Blog/issues/3) + - [ ] [JavaScript 深入之作用域链](https://github.com/mqyqingfeng/Blog/issues/6) + - [ ] [Variable scope, closure](https://javascript.info/closure),另有 [中文翻译版](https://zh.javascript.info/closure) + - [ ] [You Don't Know JS Yet: Scope & Closures](https://github.com/getify/You-Dont-Know-JS/tree/2nd-ed/scope-closures) + - [ ] [The battle between function scope and block scope](https://www.deadcoderising.com/2017-04-11-es6-var-let-and-const-the-battle-between-function-scope-and-block-scope/) + +### new + +`new` 操作符可以帮助我们构建出一个实例,并且绑定上 `this`。 + +- [ ] 文档 + - [ ] [new](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new) +- [ ] 推荐文章 + - [ ] [JS 的 new 到底是干什么的?](https://zhuanlan.zhihu.com/p/23987456) + - [ ] [JavaScript 深入之 new 的模拟实现](https://github.com/mqyqingfeng/Blog/issues/13) + ### 变量提升&函数提升 > 变量提升(Hoisting)被认为是, Javascript中执行上下文 (特别是创建和执行阶段)工作方式的一种认识。例如,从概念的字面意义上说,“变量提升”意味着变量和函数的声明会在物理层面移动到代码的最前面,但这么说并不准确。实际上变量和函数声明在代码里的位置是不会动的,而是在编译阶段被放入内存中。 @@ -68,14 +186,121 @@ let yck='大佬'; - [ ] [JavaScript的执行上下文](https://www.huaweicloud.com/articles/b0e28ae85a6f6d4e4e28824a94d17b51.html) - [ ] [JavaScript Scoping and Hoisting](http://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html) -> 务必自己总结规则! +### Class(类) -## 前端十五万字面试资料 +`class` 只是原型链的语法糖,与其它语言中的类不是同一样东西。 + +- [ ] 文档 + - [ ] [Class](https://developer.mozilla.org/en-US/docs/Glossary/Class) +- [ ] 推荐文章 + - [ ] [理解 JavaScript 的类](https://www.digitalocean.com/community/tutorials/understanding-classes-in-javascript) + - [ ] [Babel 是如何编译 Class 上](https://github.com/mqyqingfeng/Blog/issues/105) + - [ ] [Babel 是如何编译 Class 下](https://github.com/mqyqingfeng/Blog/issues/106) + - [ ] [给 ES6 class 说句公道话](https://juejin.cn/post/6924108426125508616) + - [ ] [应该在 JavaScript 中使用 Class 吗?](https://zhuanlan.zhihu.com/p/158956514) + +### 事件循环 + +大家都知道 JS 是一门单线程的非阻塞的脚本语言。这也就意味着,代码在执行的任何时候只有一个主线程来处理所有的任务。所以弄懂事件循环机制对我们学习 JS 至关重要。 + +- [ ] 文档 + - [ ] [事件循环](https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop) +- [ ] 推荐文章 + - [ ] [这一次,彻底弄懂 JavaScript 执行机制](https://juejin.cn/post/6844903512845860872) + - [ ] [一次弄懂 Event Loop](https://juejin.cn/post/6844903764202094606) + - [ ] [JavaScript 的工作原理](https://blog.sessionstack.com/how-javascript-works-event-loop-and-the-rise-of-async-programming-5-ways-to-better-coding-with-2f077c4438b5),需自备梯子,另有 [中文翻译版](https://github.com/Troland/how-javascript-works/blob/master/event-loop.md)。 +- [ ] 事件循环可视化 + - [ ] [Loupe](http://latentflip.com/loupe/?code=JC5vbignYnV0dG9uJywgJ2NsaWNrJywgZnVuY3Rpb24gb25DbGljaygpIHsKICAgIHNldFRpbWVvdXQoZnVuY3Rpb24gdGltZXIoKSB7CiAgICAgICAgY29uc29sZS5sb2coJ1lvdSBjbGlja2VkIHRoZSBidXR0b24hJyk7ICAgIAogICAgfSwgMjAwMCk7Cn0pOwoKY29uc29sZS5sb2coIkhpISIpOwoKc2V0VGltZW91dChmdW5jdGlvbiB0aW1lb3V0KCkgewogICAgY29uc29sZS5sb2coIkNsaWNrIHRoZSBidXR0b24hIik7Cn0sIDUwMDApOwoKY29uc29sZS5sb2coIldlbGNvbWUgdG8gbG91cGUuIik7!!!PGJ1dHRvbj5DbGljayBtZSE8L2J1dHRvbj4%3D) + +### 继承 + +继承是面向对象语言(Object-Oriented Language)三大特征之一,在 JS 中也占有非常重要的地位。而想要实现继承有多种方式,它们都有各自的优缺点。 + +- [ ] 文档 + - [ ] [继承](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Inheritance) +- [ ] 推荐文章 + - [ ] [JavaScript 深入之继承的多种方式和优缺点](https://github.com/mqyqingfeng/Blog/issues/16) + - [ ] [JavaScript 中的继承](https://medium.com/@happymishra66/inheritance-in-javascript-21d2b82ffa6f),需自备梯子 + - [ ] [JS 类继承](https://javascript.info/class-inheritance),另有 [中文翻译版](https://zh.javascript.info/class-inheritance) + +## 跨端框架 + +跨端的最主要的含义就是一套代码多端运行,减少重复劳动 + +目前看来,国内比较流行的是小程序, H5, App 三端跨的框架, [uniapp](https://github.com/dcloudio/uni-app) 及 [taro](https://github.com/NervJS/taro) 是其中做的比较出彩的两个框架。当然在 taro3.0 之前(以下主要是说小程序),taro 跟 uniapp 都是使用编译时做更多的事情,编译成小程序能够运行的代码。 + +而在 taro3.0 之后变成了与 [remax](https://github.com/remaxjs/remax) 相同的思想,在运行时做更多的事情,保证了原框架代码能够完全使用,而不需要为了转换成其他小程序时做兼容。 + +- [awesome-remax](https://github.com/remaxjs/awesome-remax) +- [美团自研 react 跨端](https://tech.meituan.com/2021/06/10/react-native-hybrid-practice-dsl-in-meituan.html) +- [京东 taro](https://juejin.cn/post/6844904036743774216) + +## 微前端 + +是一种类似于微服务的架构,它将微服务的理念应用于浏览器端,即将单页面前端应用由单一的单体应用转变为多个小型前端应用聚合为一的应用。 + +优势: + +- 代码库更小,更内聚、可维护性更高 +- 松耦合、自治的团队可扩展性更好 +- 渐进地升级、更新甚至重写部分前端功能成为了可能 +- 独立开发部署,缩小变更范围,进而降低相关风险 + +框架: + +- [single-spa](https://single-spa.js.org/) +- [蚂蚁-乾坤](https://github.com/umijs/qiankun) +- [淘系-icestark](https://github.com/ice-lab/icestark) +- [字节-Garfish](https://github.com/modern-js-dev/garfish) +- [京东-micro-app](https://github.com/micro-zoe/micro-app) +- [Bit](https://bit.dev) + +推荐文章: + +- [从零到一实现企业级微前端框架,保姆级教学](https://juejin.cn/post/7004661323124441102) +- [What Are Micro Frontends?](https://livebook.manning.com/book/micro-frontends-in-action/chapter-1/v-4/) +- [Bifrost 微前端框架及其在美团闪购中的实践](https://tech.meituan.com/2019/12/26/meituan-bifrost.html) +- [每日优鲜供应链前端团队微前端改造](https://juejin.cn/post/6844903943873675271) +- [微前端在美团外卖的实践](https://tech.meituan.com/2020/02/27/meituan-waimai-micro-frontends-practice.html) +- [How We Build Micro Frontends](https://blog.bitsrc.io/how-we-build-micro-front-ends-d3eeeac0acfc) +- [Revolutionizing Micro Frontends with Webpack 5, Module Federation and Bit](https://blog.bitsrc.io/revolutionizing-micro-frontends-with-webpack-5-module-federation-and-bit-99ff81ceb0) + +## 十五万字面试资料 [在线阅读](https://yuchengkai.cn/home/) -## 各类大厂内推 +- [解决 90% 的大厂基础面试题](https://mp.weixin.qq.com/s?__biz=MzUyNDYxNDAyMg==&mid=2247488553&idx=1&sn=62d8a99b5554751221b45b3c411ad325) +- [解决 90% 的大厂前端网络面试题](https://mp.weixin.qq.com/s?__biz=MzUyNDYxNDAyMg==&mid=2247488796&idx=1&sn=dec4fdb3e596ad87b02419f69c174113) -数十家公司及岗位,数百个岗位 HC,具体内容查看 [表格](https://bitable.feishu.cn/appdL3vQNYWhT2hGnNRjlC9XoWD)。 +## 前端工程化文章 -另外欢迎有招人需求的朋友提供招聘信息,触达数万前端,内推奖拿到手软。发送招聘信息至邮箱:zx597813039@gmail.com。 +- [这才是你需要了解的性能优化呀!](https://mp.weixin.qq.com/s?__biz=MzUyNDYxNDAyMg==&mid=2247489161&idx=1&sn=f029ea01370364f49fe61451b2d247ff) +- [绝了,没想到一个 source map 居然涉及到那么多知识盲区](https://mp.weixin.qq.com/s?__biz=MzUyNDYxNDAyMg==&mid=2247488867&idx=1&sn=cac0ae95c560cca820658a8c3af3f8e5) +- [揭秘自动化部署系统的核心原理](https://mp.weixin.qq.com/s?__biz=MzUyNDYxNDAyMg==&mid=2247488166&idx=1&sn=f62669f6c9331c95badbef2815a8d1b5) +- [你知道 monorepo 居然有那么多坑么?](https://mp.weixin.qq.com/s?__biz=MzUyNDYxNDAyMg==&mid=2247488604&idx=1&sn=6ded0f2ddaedb71bd5d12890cf462726) +- [从零打造性能检测库](https://mp.weixin.qq.com/s?__biz=MzUyNDYxNDAyMg==&mid=2247487811&idx=2&sn=40d02e839fb5400c5b82858d422fb738) +- [还在看那些老掉牙的性能优化文章么?这些最新性能指标了解下](https://mp.weixin.qq.com/s?__biz=MzUyNDYxNDAyMg==&mid=2247486142&idx=1&sn=b75b87a109c28cc6c32046d0fb234aab) + +## React 原理 + +[在线阅读](https://github.com/KieSun/react-interpretation),另外笔者最近也在准备更新这部分内容,有兴趣的读者可以持续关注。 + +## 前端进阶文章 + +- [这些 JS 新语法有点东西啊!](https://mp.weixin.qq.com/s?__biz=MzUyNDYxNDAyMg==&mid=2247489249&idx=1&sn=9a3cedafd021afe8ed3ef81bcd0583c2) +- [这个 React 小细节居然很多人都写错了?](https://mp.weixin.qq.com/s?__biz=MzUyNDYxNDAyMg==&mid=2247489054&idx=1&sn=f025d5dfd81656e5cffd7eb598b0954b) +- [反直觉!大部分人并不知道浏览器到底是如何下载资源的](https://mp.weixin.qq.com/s?__biz=MzUyNDYxNDAyMg==&mid=2247488074&idx=1&sn=21dc9b18cf52d875dec5dc38b34900fe) +- [好家伙,这些写 CSS 的新姿势你还不知道?](https://mp.weixin.qq.com/s?__biz=MzUyNDYxNDAyMg==&mid=2247488007&idx=1&sn=8578ba4a4278de3286feeb18535c75a2) +- [这个异步问题你肯定遇到过,但是会解决的并不多](https://mp.weixin.qq.com/s?__biz=MzUyNDYxNDAyMg==&mid=2247487988&idx=1&sn=6b56888236e0cc8b59e7bf63ff882a21) +- [前端前沿观察,Cookie 居然可以这样整了](https://mp.weixin.qq.com/s?__biz=MzUyNDYxNDAyMg==&mid=2247486890&idx=1&sn=3f92d1290e62549731b9d840aed68cc9) +- [Promise 你可能真的还没用明白(续集进阶版)](https://mp.weixin.qq.com/s?__biz=MzUyNDYxNDAyMg==&mid=2247486584&idx=1&sn=26c5d27b34cc98f952715581b8c74027) + +## 更多文章 + +因为笔者文章实在太多,足足已经写了 150 篇+ 原创,更多文章欢迎大家到【[公众号](#个人信息)】里阅读。 + +## 个人信息 + +| 微信扫码关注公众号,订阅更多精彩内容 | 加笔者微信进群与大厂大佬讨论技术 | +| ---------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | +| | | diff --git a/deep/第一期.md b/deep/第一期.md new file mode 100644 index 0000000..7e2bd96 --- /dev/null +++ b/deep/第一期.md @@ -0,0 +1,30 @@ +本周精读的第一篇文章是 [Migrating a 150K LOC codebase to Vite and ESBuild](https://dev.to/noriste/migrating-a-150k-loc-codebase-to-vite-and-esbuild-why-part-1-3-2idj),其实这是一个系列文章了,主体内容在第二篇。 + +这篇文章主要讲了两块内容: + +1. Webpack 对比 Vite 的一些数据 +2. 迁移至 Vite 过程中踩得坑 + +踩得坑在第二篇文章,如果你近期想试试迁移的话可以读一下,当然其实他并没有写多少踩坑的内容,让我稍微对于他这个迁移那么大项目居然才那么点坑产生怀疑,或者说很多没写上来。 + +但是对于数据这块大家还是可以参考参考的,选型时候还是用得上。 + +第一块数据是 Webpack 和 Vite 在 `yarn start` 以及 `hmr` 层面的一些对比,可以看到在这两块内容上 Vite 是碾压胜出的,其它两个数据差别不大,可以略过。 + +![Y2f6el](https://yck-1254263422.file.myqcloud.com/uPic/Y2f6el.png) + +第二块数据是使用了 esbuild-loader 插件的 Webpack 和 Vite 的数据对比,基本上还是 Vite 胜出,但是可以发现当 Webpack 加上 ESBuild 以后,很多数据相比之前已经有了很大幅度的提升,无非限制于 Webpack 本身的一些打包机制导致了在一些数据上没什么起色。 + +![KGiSCa](https://yck-1254263422.file.myqcloud.com/uPic/KGiSCa.png) + +第三块数据是 Build 层面的数据分析,使用了 esbuild-loader 插件的 Webpack 胜出了 Vite。 + +![wZrWQX](https://yck-1254263422.file.myqcloud.com/uPic/wZrWQX.png) + +总的来说,从上述这些数据中我们可以看出 Vite 和 ESBuild 对于提效的加成是很大的,轻轻松松提速几倍。 + +但是这一切都是美好的,实际你如果要把这套东西在业务中真真正正用起来,会踩到很多坑,目前应该是没有一个大厂能把这套体系在很多业务里跑起来。虽然提效确实挺多,但是迁移的成本也是很大的。 + +综合来说,可以自己项目里玩玩,未来这个体系肯定是会普及的,但并不是现在。 + +另外对于原理有兴趣的可以看看云谦写的[玩具](https://github.com/sorrycc/toy-vite)。 \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..e3c9ba6 --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "fucking-frontend", + "version": "1.0.0", + "description": "仓库内容分为以下几块:", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/KieSun/fucking-frontend.git" + }, + "keywords": [], + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/KieSun/fucking-frontend/issues" + }, + "homepage": "https://github.com/KieSun/fucking-frontend#readme", + "dependencies": { + "axios": "^0.21.1", + "gitment": "^0.0.3" + } +} diff --git a/script/comment.js b/script/comment.js new file mode 100644 index 0000000..dda9298 --- /dev/null +++ b/script/comment.js @@ -0,0 +1,25 @@ +const axios = require("axios"); +const { + event: { + comment, + issue: { number }, + }, +} = JSON.parse(process.env.GITHUB_CONTEXT); +const { + body, + updated_at, + html_url, + id, + user: { avatar_url, login }, +} = comment; +const host = process.env.COMMENTHOST; + +axios.post(host, { + issueId: number, + commentId: id, + htmlUrl: html_url, + userName: login, + avatarUrl: avatar_url, + content: body, + updatedAt: updated_at, +}); diff --git a/script/issue.js b/script/issue.js new file mode 100644 index 0000000..b2683d3 --- /dev/null +++ b/script/issue.js @@ -0,0 +1,22 @@ +const axios = require("axios"); +const { + event: { issue, action }, +} = JSON.parse(process.env.GITHUB_CONTEXT); +const { author_association, body, title, number } = issue; +const host = process.env.ISSUEHOST; + +if (author_association === "OWNER") { + if (action === "edited") { + axios.put(host, { + name: title, + issueId: number, + content: body, + }); + } else if (action === "opened") { + axios.post(host, { + name: title, + issueId: number, + content: body, + }); + } +} diff --git a/weekly/第一期.md b/weekly/第一期.md new file mode 100644 index 0000000..1b0dd51 --- /dev/null +++ b/weekly/第一期.md @@ -0,0 +1,39 @@ +## 新闻 + +其实以下几个玩意更新的版本我基本没咋关心,看了眼就过了,反正短期用不到的。 + +\- [Chrome 91 发布](https://developer.chrome.com/blog/new-in-chrome-91/) + +\- [TypeScript 4.3 发布](https://devblogs.microsoft.com/typescript/announcing-typescript-4-3/) + +\- [Jest 27 发布](https://jestjs.io/blog/2021/05/25/jest-27) + +## 优秀文章 + +\- [Migrating a 150K LOC codebase to Vite and ESBuild](https://dev.to/noriste/migrating-a-150k-loc-codebase-to-vite-and-esbuild-why-part-1-3-2idj) + +一个系列文章,大概讲了讲 15w 行代码从 Webpack 迁移至 Vite 过程中遇到的坑。Vite 这玩意我周末也讲过它的优点,但是它的缺点其实我和一些大厂的大佬也聊过以后发现大家遇到的问题几乎一致。环境的问题(ESM)导致了太多坑的出现,接入一个业务需要花的成本是挺高的。大家可以通过这篇文章来了解具体的问题。 + + + +\- [跨域的一篇长文](https://ieftimov.com/post/deep-dive-cors-history-how-it-works-best-practices/) + +这篇文章我没看什么,但是它写了特别多内容,打算去看看到底写了些啥玩意,如果你也好奇的话可以了解下。 + + + +\- [防御型编程](https://github.com/Tencent/secguide/blob/main/JavaScript%E5%AE%89%E5%85%A8%E6%8C%87%E5%8D%97.md) + +这玩意前几天推荐过了,大家有兴趣的读一读吧。 + +## Code + +\- [preconstruct](https://github.com/preconstruct/preconstruct) + +monorepo 开发构建工具,我更倾向于把它当成源码学习。 + + + +\- [cosmiconfig](https://github.com/davidtheclark/cosmiconfig) + +一个能帮助寻找及加载各类配置文件的工具。比如说 `package.json` 各种 `rc` 文件等等,工具链产品中寻找配置文件是个很常见的需求。 \ No newline at end of file diff --git a/weekly/第三期.md b/weekly/第三期.md new file mode 100644 index 0000000..3da7a6c --- /dev/null +++ b/weekly/第三期.md @@ -0,0 +1 @@ +- [关于前端职业规划的一点思考](https://mp.weixin.qq.com/s/Pe-I8b60Gyo91wJDTTx50Q) diff --git a/weekly/第二期.md b/weekly/第二期.md new file mode 100644 index 0000000..814c151 --- /dev/null +++ b/weekly/第二期.md @@ -0,0 +1,46 @@ +## 新闻 + +- [What's New In DevTools (Chrome 92)](https://developer.chrome.com/blog/new-in-devtools-92/) + + 有挺多新功能的,但是我觉得里面最香的是这个:Support for `const` redeclarations in the Console。 + + ![qGZMZp](https://yck-1254263422.file.myqcloud.com/uPic/qGZMZp.jpg) + + 以后再也不需要换变量名了,特别香! + +- Node 12 岁生日了 +- [.at() 进入 Stage 3](https://github.com/tc39/proposal-relative-indexing-method) + + 这是个挺不错的新语法。其他有些语言是可以用 `arr[-1]` 来获取数组末尾的元素,但是对于 JS 来说这是实现不了的事情。因为 `[key]` 对于对象来说就是在获取 `key` 对应的值。数组也是对象,对于数组使用 `arr[-1]` 就是在获取 `key` 为 `-1` 的值。由于以上原因,我们想获取末尾元素就得这样写 `arr[arr.length - 1]`,以后有了 `at` 这个方法,我们就可以通过 `arr.at(-1)` 来拿末尾的元素了,另外同样适用类数组、字符串。 + + ```js + // Polyfill + function at(n) { + // ToInteger() abstract op + n = Math.trunc(n) || 0; + // Allow negative indexing from the end + if(n < 0) n += this.length; + // OOB access is guaranteed to return undefined + if(n < 0 || n >= this.length) return undefined; + // Otherwise, this is just normal property access + return this[n]; + } + ``` + +## 文章 + +- [居然有比 npm link 更好的调试?](https://mp.weixin.qq.com/s/I4hhrgI3-Y18HD8zw_9g9w) + + 用 `npm link` 调试包应该是基本操作了,但是这种方式在某些情况下还是会带来一些不便利性。比如说在使用 React Hooks 的情况下调试与 React 相关的包就会报错。有公共依赖冲突的情况下我们可以 [yalc](https://github.com/wclr/yalc) 这个库来解决问题。它会将包丢到全局,然后我们在项目里添加该依赖即可,公共依赖会从项目的 node_modules 里面拿,其它的就从全局拿,这样就可以解决依赖冲突带来的问题了。 + +- [CSS 对 Web Vitas 的影响](https://web.dev/css-web-vitals/) + + 如果你不了解什么是 Web Vitas,那么很推荐浏览下这网站里的内容。如果你了解什么是 Web Vitas,那么你可以来学习下哪些 CSS 属性会对指标产生影响。 + +- [尤雨溪:做技术哪有什么两全之策,都是取舍和平衡](https://mp.weixin.qq.com/s/_q_SnCbGyXrNnXA876tXbA) + + 没啥好说的。 + +- [当聊到前端性能优化时,我们会关注什么?](https://mp.weixin.qq.com/s/-yQ9PLzveEsXk4uSdlO36g) + + 性能优化老生常谈的话题了,每个团队根据业务场景的不同关注的点可能都不同,文章结合作者针对一个 toB 系统的性能优化实践去剖析一些大家可能共同关注的点。 diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..fb26875 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,27 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +axios@^0.21.1: + version "0.21.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" + integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA== + dependencies: + follow-redirects "^1.10.0" + +follow-redirects@^1.10.0: + version "1.13.3" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.3.tgz#e5598ad50174c1bc4e872301e82ac2cd97f90267" + integrity sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA== + +gitment@^0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/gitment/-/gitment-0.0.3.tgz#dbccbad7480b1345b656913bd70eff792a7b38b8" + integrity sha1-28y610gLE0W2VpE71w7/eSp7OLg= + dependencies: + mobx "^3.1.7" + +mobx@^3.1.7: + version "3.6.2" + resolved "https://registry.yarnpkg.com/mobx/-/mobx-3.6.2.tgz#fb9f5ff5090539a1ad54e75dc4c098b602693320" + integrity sha512-Dq3boJFLpZEvuh5a/MbHLUIyN9XobKWIb0dBfkNOJffNkE3vtuY0C9kSDVpfH8BB0BPkVw8g22qCv7d05LEhKg==