跳到主要内容

· 阅读需 2 分钟

SVG 格式的文件,是可缩放的矢量图像文件,它根据设定的一些参数和曲线直线等算法来渲染出来图片,所以它能很容易去自定义图片的样式。

当然在前端中,svg 运用到 html 上是更加的方便,它可以直接使用标签的形式来被渲染出来,标签中的一些属性等来决定渲染的 svg 的样式,那么根据这样的一个特点,我们就可以方便的对不同的 svg 设置不同的样式等。

1. 如何渲染自定义的 svg 图片来运用到 js 的渲染中

使用 btoa 来构建 base64 的 svg 图片。

下面来封装一个函数,简单的传入一个 svg 的 fill 属性支持的颜色字符串,来生成不同颜色的 svg 图片。

//genSVG.js
//这里的svg的iconfont中的
export default (color) => {
var svg = `
<svg fill="${color}" t="1692813506367" class="icon" viewBox="0 0 1064 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1440" width="200" height="200"><path d="M512 0c282.781538 0 512 229.218462 512 512S794.781538 1024 512 1024s-512-229.218462-512-512S229.218462 0 512 0z" p-id="1441"></path></svg>
`;

// 转换为data URI
var svgDataURI = "data:image/svg+xml;base64," + btoa(svg);

return svgDataURI;
};

2. 总结

很多时候会认为图片这种非代码的不能去动态的修改,其实可以换一个思路就是去构建 base64 编码来实现。

· 阅读需 6 分钟

根据上一篇博客,可以进行代码的提交,那么这里记录一下关于在 github 上面提交 pr 的时候遇到的一些问题等。

pr 提交

在我们需要对一个仓库进行代码贡献的时候,先 fork 代码到自己的仓库,然后在本地去 clone,这是一般的比较标准的流程。

那么在 clone 完之后,本地会自动初始化一个仓库,这个仓库和 github 管理的仓库的版本是一致的,由于 github 的远程仓库是刚从需要贡献代码的仓库 fork 过来的,那么它们 2 个仓库的版本也是一致的。

因为我们是提交 pr,所以实际上对贡献代码的版本管理是管理员去进行的,我们只需要管理我们自己的仓库就行了,那么我们要做的就是在这个版本的基础上面去修改代码,然后提交到远程仓库,这样在远程仓库中就有了一些提示,其中就有contribute等等(也可以到贡献仓库中去 new Pr,也可以识别到代码的更改)。 我们进行contribute之后就会生成一个prpr中展示了我们的仓库的一些提交记录,可以看到修改了哪些内容等等。 对于提交的内容,别人可以在代码出进行评论等,等待没有任何分歧之后,管理员可以去允许合并代码。

合并提交记录

这里合并提交记录用到变基,通过强制推送来达到远程仓库和本地仓库保持一致的干净提交历史记录,在使用的时候一定要把握代码的安全性,以防因为强制推送带来代码的冲突等导致代码损失。

pr 中提交多个代码,导致 pr 中的提示信息杂乱 —— 变基

比如我们提交了一段代码,但是有人说这个代码有些地方不好,于是我们在提交了第一次的版本上又进行了更新和推送,那么我们的远程仓库就比贡献仓库多了 2 个提交记录,在 pr 中也会有相应的提交记录提示。此时我们发现有个地方写错了,我们修改后又提交了一个记录。

根据上述的情形,已经提交了 3 条记录,其中第 1 条记录是我们的第一个修改思路,第 2,3 条记录是对第 1 个记录的优化,显然我们不需要将第二次优化分成 2,3 这两条记录,我们需要做的就是变基,将 2,3 条记录中的代码通过变基将提交记录合并到 2 中,远程仓库中以及 pr 中多余的提示信息都会相应的被合并。

首先我们要做的就是通过git log来查看 commit hash 值以及查看 3 条记录信息。

git log

输出的内容将会是这样

第三次提交内容 21j31239802173124h12h
第二次提交内容 128930218312j312mn3lk
第一次提交内容 ji123n21jk3kj21h312h3

接下来我们使用变基,通过指明一个我们想合并到的一个能让提交 log 信息变干净的一次提交记录的 hash 值,像下面这样

git rebase -i 128930218312j312mn3lk

然后就会输出下面的信息。我们要做的就是把前面的 pick 改成 drop,这样的话,输出的这几条记录就会合并成 pick 的这一条记录。

- pick 第三次提交内容 21j31239802173124h12h
+ drop 第三次提交内容 21j31239802173124h12h
pick 第二次提交内容 128930218312j312mn3lk

完成之后,通过vim的命令!wq来保存退出,会输出一些完成 rebase 的内容。

我们再次通过git log来查看,发现前面的三条记录现在只剩下了两条。但是远程仓库中实际上还是三条提交记录,那么我们要做的是推送当前仓库的提交。

在保证没有任何的代码冲突以及远程代码的最新提交,保持好 rebase 不会带来所有损失的情况下,用git push --force ....来进行强制推送。在完成之后,远程仓库以及引用了第三次提交记录的信息都会被删除,这样也就保持了提交信息的干净。

· 阅读需 6 分钟

从下载 git 到创建 git 本地仓库

我们要用 github(或者 gitlab 等等)去管理代码,首先要做的是对本地的 git 仓库进行管理。那么第一点是下载 git 这个软件,在 git 的官网直接下载 git 的程序,下载完成之后对 git 进行环境变量的配置,这样我们就可以在任意的终端中使用 git 的所有命令了。

接下来我们要创建本地的 git 管理仓库,首先要将终端的路径选择到我们要创建 git 管理的文件夹地址,使用命令

git init

这样下来 git 帮我们初始化了仓库,你可以发现当前文件夹中自动创建了一个.git 的文件,依靠这个文件就可以通过 git 来管理仓库中的代码。 ls -a(linux/macos显示当前目录所有文件)

管理代码

所谓管理代码,就是对本地的代码进行记录,记录我们的修改,记录我们的提交历史等等。其中比较常用的命令也就是

git add . //添加所有的文件到暂存区
git commit -m "提交内容"
git log //查看提交历史

这些都是 git 的命令,为了更好的了解它们,应该到 git 的官网中详细的了解 git 的命令和规则。 这里我们的重点是如何通过本地代码来关联到 github 上面,实现 github 在线托管我们的代码。

关联 github 仓库(ssh)

问题现在就来了,在 github 创建了仓库,但是本地如何知道哪个 github 账号是我们的?

ssh 的连接是通过公钥和私钥的方式来连接的,所以我们要做的是在本地生成一些密钥,然后在 github 上面注册。

1. 为 git 配置全局的属性

这样的作用是为 git 的命令提供一些参数,如果遇到 user.name 的参数那么就会使用后面的值,那么我们要用到 github 所以就把它们设置成 github 账号相关的值。

git config --global user.name "name"
git config --global user.email "email"

2. 生成私钥和公钥

输入以下命令之后就会出现一些提示,根据提示来选择即可。通常都选是,(有 yes/no 的可能默认为 no,手动输入 yes)。提示生成了密钥的路径,找到 .pub 后缀的这个就是公钥

ssh-keygen -t rsa

3. github 绑定公钥

在 github 的设置里面,侧边菜单栏里面有一个 SSH 相关的选项,进去点击add key,那么把私钥的内容复制到下面的输入框,上面的标题填写自己想要的,点击添加提示添加成功那么说明 github 和本机已经配置好了 ssh 连接的一些内容,接下来通过 ssh 来连接即可。

4. 配置 ssh

找到 github 仓库,点击绿色的大按钮,下面就有 ssh 了,复制 ssh 的内容。

接下来,到本地的仓库文件夹下打开文件夹,来添加远程 git 仓库的源。

git remote add origin(origin可以自己取名字) main "git@github.com:......."

把复制的 ssh 的内容放在引号里面,这样回车确认之后如果没有提示错误应该就是配置好了。通过下列的命令来确认,会发现显示了一些 fetch 等等以及仓库地址等信息,可以检查是否是正确的。

git remote -v

5. 推送代码到远程分支

接下来我们要把相应的分支推送到远程 github 仓库对应的分支(如果远程没有这个分支,会自动的创建)

git branch //检查所有分支
git chekout branch_name //切换到branch_name分支

这里有一个需要注意的点就是,我们在没有做 add 和 commit 的时候,branch 不会显示任何内容。

通过上面的命令已经切换到了我们需要推送的分支,前面我们也通过git add . git commit ....把修改的代码提交了,接下来是要进行 push。

如果我们的本地仓库版本和远程不一样,那么会造成代码的冲突,我们在 push 之前务必进行 pull。

git pull origin main

git push -u origin main

以后我们只需要通过add commit pull push这一套流程,就可以完成代码的推送了。当然其中会遇到很多的坑,结合网络,会很容易解决的。

· 阅读需 5 分钟

关于 dom 优化的情况

页面中有大量 dom 需要优化的情况可能分为 2 种,一种是大量的 dom 刚好铺满一个屏幕,另外一种是大量的 dom 渲染到一个页面上并且超出了一个屏幕。

对于前者,举例一种情况就是需要渲染几百个图片在一个完整的页面上,可能看到几百个图片的 img 标签并不是像百万级那么夸张,但是对于这样的渲染仍然还是有很多值得优化的空间。

对于后者,在前者的基础上还会加入一个对滚动时的优化,因为 dom 非常的多导致滚动的时候会掉帧,那么需要进行滚动优化,虚拟化列表就是其中的一个解决方法。

对于滚动采用虚拟化列表即可解决了,这里只讲解如何优化 dom 的渲染性能

dom 的渲染优化

如果我们要渲染几百个页面,现在我们用原生的 js 来做演示,那么我们需要创建几百个 dom 之后 append 到父元素中。

const parent = document.getElementById("parent")

//循环800次
for(let i = 0; i < 800; i++){
const img = document.createElement("img")
img.src = "....jpg"
parent.appendChild("img")
}

这样的逻辑会创建 800 个 dom 并且渲染到这个 parent 容器中(parent 是一个 div 标签)。当我们执行的时候,如果机器性能不是及其顶尖,那么你会发现页面会处于一个较长时间的加载中,并且你会发现如果你放一个按钮在页面上,这个时候你鼠标点击按钮是不能相应的,因为主线程正在被占用来渲染 800 个 dom。到这里就出现了第一个优化的点异步加载

异步加载

异步加载我们就要使用到异步任务来进行 dom 的渲染,其中 setTimeout 这个定时器 api 就可以实现异步任务,setTimeout 将执行的函数放入到异步任务队列中,当主线程的任务执行完成之后就进入到异步任务来执行这个任务,所以我们的主线程始终是不会被阻塞的。

function createDOM(){
setTimeout(()=>{
const img = document.createElement("img")
img.src = "....jpg"
parent.appendChild("img")
},50)
}

经过上面的封装,我们把 createDOM 封装成了一个创建一个异步任务来渲染一次 dom 的逻辑,那么我们要渲染 800 个就需要调用 800 次 createDOM。其中设置定时器的时间间隔为 50 毫秒,那么每隔 50 毫秒就会创建一个 dom 并添加到父容器中,并且会发现它在渲染的时候我们的鼠标以及页面都不会被阻塞卡住,仍然可以触发一些事件。

提到了异步加载,还有一个比较重要的 api,requestAnimationFrame。这个 api 在动画中也很常用,它的作用是在一帧渲染之后执行函数,至于每秒渲染多少帧,通常根据屏幕目前刷新率来确定。在更多情况下使用这个 api 具有更大的优势。

const count = 800
let current = 0
function createDOM(){
if(current >= count) return

const img = document.createElement("img")
img.src = "....jpg"
parent.appendChild("img")

current++
requestAnimationFrame(createDOM)
}
requestAnimationFrame(createDOM)

requestAnimationFrame 的讲解

· 阅读需 10 分钟

在学习 webpack 的过程中,遇到了一个 webpack 的运行时代码所带来的问题。

比如optimizationruntimeChunk的配置等,会出现带有 runtime 的文件,在我们使用output.dependOn 属性配置的时候,也会生成 runtime 标识的文件,那么这个 runtime 文件到底是什么?

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
mode: "development",
+ entry: {
+ index: {
+ import: "./src/index.js",
+ dependOn: "shared",
+ },
+ "other-bundle": {
+ import: "./src/other-bundle.js",
+ dependOn: "shared",
+ },
+ shared: "lodash",
+ },
output: {
filename: "[name].[contenthash].bundle.js",
path: path.resolve(__dirname, "dist"),
clean: true,
},
plugins: [
new HtmlWebpackPlugin({
title: "资源输出",
filename: "webpack.html",
}),
],
};

上面这是一个webpack.config.js的配置文件,其中的重点就在entry里面,看到其中配置了一个dependOn,然后依赖的是一个新的入口文件,那么理论上生成文件就会有至少 3 个,index.[hash].jsother-bundle.[hash].jsshared.[hash].js。现在没有什么问题,就是我们简单的配置了 3 个入口文件,然后其中 2 个依赖与另外一个 chunk 文件,其中包含的是lodash这个库,所以shared.[hash].js中存放的是 lodash 的代码。然后继续看下面的说明

optimization.runtimeChunk

现在我们引入一个新的配置,optimization.runtimeChunk。对于 runtimeChunk 设置为'single'的作用就是,为所有的 chunk 文件生成一个共享的 runtime 的文件,这样所有对模块引入或者初始化的代码都在这个共享文件中实现。设置成'true'(或者'multiple')就会为每一个 chunk 生成一个单独的 runtime 的文件。

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
mode: "development",
entry: {
index: {
import: "./src/index.js",
dependOn: "shared",
},
"other-bundle": {
import: "./src/other-bundle.js",
dependOn: "shared",
},
shared: "lodash",
},
+ optimization: {
+ runtimeChunk: true,//默认是false,true对应的是'multiple',还可以选择'single'。
+ },
output: {
filename: "[name].[contenthash].bundle.js",
path: path.resolve(__dirname, "dist"),
clean: true,
},
plugins: [
new HtmlWebpackPlugin({
title: "资源输出",
filename: "webpack.html",
}),
],
};

那么我们设置了这个属性之后进行编译,产生的文件会多出一个runtime~shared.....的文件。

runtime 文件的内容

我们现在要知道runtime~shared...是怎么和shared.....js文件联系起来的,这样就可以分析为什么能够生成出一个 runtime 文件来。

下面是shared.js生成的入口 js 文件的代码开头,看的出来是往一个数组self[....]中 push 了一些数组,通过注释和测试环境的代码,可以分析出来这个数组中设置了一个对象,是我们之前在shared.js中引入的 lodash 的代码。那么这个 self 显然是一个全局的对象,然后现在我们给他 push 了一些值。那么这个属性我们接下来再runtime文件中看一看。

shared.xxxxx.js

...
* or disable the default devtool with "devtool: false".
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
*/
(self["webpackChunkwebpack_learn"] = self["webpackChunkwebpack_learn"] || []).push([
["shared"],
{
/***/ "./node_modules/lodash/lodash.js":
/*!***************************************!*\
!*** ./node_modules/lodash/lodash.js ***!
\***************************************/
/***/ function (module, exports, __webpack_require__) {
eval( "/* module decorator */ module = __webpack_require__.nmd(module);\nvar __WEBPACK_AMD_DEFINE_RESULT__;/**\n * @license\n * Lodash <https://lodash.com/>\n * Copyright OpenJS Foundation and other contributors <https://openjsf.org/>\n * Released under MIT license <https://lodash.com/license>\n * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>\n * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors\n */\n;(function() {\n\n /** Used as a safe reference for `undefined` in pre-ES5 environments. */\n var undefined;\n\n /** Used as the semantic version number. */\n var VERSION = '4.17.21';\n\n /** Used as the size to enable large array optimizations. */\n var LARGE_ARRAY_SIZE = 200;\n\n /** Error message constants. */\n var CORE_ERROR_TEXT = 'Unsupported core-js use. Try https://npms.io/search?q=ponyfill.',\n FUNC_ERROR_TEXT = 'Expected a function',\n INVALID_TEMPL_VAR_ERROR_TEXT = 'Invalid `variable` option passed into `_.template`';\n\n /** Used to stand-in for `undefined` hash values. */\n var HASH_UNDEFINED = '__lodash_hash_undefined__';\n\n /** Used as the maximum memoize cache size. */\n var MAX_MEMOIZE_SIZE = 500;\n\n /** Used as the internal argument placeholder. */\n var PLACEHOLDER = '__lodash_placeholder__';\n\n /** Used to compose bitmasks for cloning. */\n var CLONE_DEEP_FLAG = 1,\n CLONE_FLAT_FLAG = 2,\n CL

...

下面是对应的 runtime 的代码,找到了这样的代码。其中对现在已有的 self 中的值进行调用webpackJsonpCallback(调用前执行了 bind 绑定,这个场景下是 null)。 然后重写了 push,将 push 重写成了 webpackJsonpCallback 中实现的方法,最后会移动到webpack_require.0 的逻辑中去。也就是说在此之后在 self['webpackChunkwebpack_learn']中的 push 操作,都会被以这里的逻辑来执行。显然 runtime 文件会优先于入口文件去引入到 html 中。

shared.runtime.xxxx.js

...
/******/ installedChunks[chunkId] = 0;
/******/ }
/******/ return __webpack_require__.O(result);
/******/ }
/******/
/******/ var chunkLoadingGlobal = self["webpackChunkwebpack_learn"] = self["webpackChunkwebpack_learn"] || [];
/******/ chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));
/******/ chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));
/******/ })();
/******/
/************************************************************************/
/******/
/******/
...

那么现在就知道了,我们生成的这个 runtime 的文件,实际上是对入口文件模块的处理。我们在入口文件中会将模块的所有加载进行执行,生成 runtime 文件之后,在入口文件中对模块的处理的逻辑就会被转移到这个 runtime 文件中来执行。

关于 runtime 代码的处理

那么,现在比如我们有 2 个 js 文件,其中都引入了 lodash 的库。在我们不设置任何对 runtime 有影响的 webpack 的配置的时候,打包后lodash的模块会被加入到这 2 个打包的 js 文件中去。当我们对这 2 个 js 文件设置一个dependOn属性,然后dependOn的模块依赖的是lodash,打包后会发现我们之前的 2 个 js 文件中的lodash的代码都被转移到了一个新的 chunk 文件中去。那么这就是我们第一个使用方法,就是对重复模块的抽离。

这里没有很好的体现我们对运行时代码的利用,但是在下面的一个例子很能体现。同样是 2 个 js 文件,还有一个count.js中导出了一个对象,其中的 value 属性为 0,我们在另外 2 个 js 文件中都实现的是对这个对象的值自增。

首先我们不对 runtime 进行任何的处理,显然这个时候count.js会分别存在于那 2 个 js 文件中,那么这个时候如果在 html 中来引入解析的话,结果就是独立的 2 个对 count 对象进行初始化的代码,形成的也是 2 个独立的作用域。

那么这个时候我们设置optimization.runtimeChunk='single',让所有一切的 chunk 都形成一个runtime,这样这 2 个 js 文件中所有的内容都会 push 到 runtime 文件中去(push 显然就是上面提到过的被重写的 push),那么最终所有的作用域都会在runtime文件中形成,通过 webpack 对重复 module 的过滤处理等,最终重复的模块全部都会在这个 runtime 中引入一次。

实际上,无论我们设不设置optimization这个参数,最终的模块都会通过 webpack 的运行时的函数去加载,第一个例子在优化前是lodash分别在 2 个运行时代码中加载,在我们设置dependOn之后就是将这 2 个运行时抽离成一个独立的 chunk 来通过运行时加载lodash

多 runtimeChunk

也就是设置optimization.runtimeChunk=true,这个时候,webpack 会对每一个 chunk 文件进行修改,让其中的逻辑转移到 runtime 中去执行。虽然每个 chunk 生成了单独的 runtime 的文件,但是一般情况下 runtime 文件中都是执行一样的逻辑,上面提到的对self中对象的引用也都是同一个,所以这个时候上面提到的count也还是只是被初始化一次的。但是在官网中代码分离板块中提到过,如果使用多入口的时候,还是要设置optimization.runtimeChunk=single,这样至少可以确保百分之不会造成模块的多次实例化等错误。